Merge branch 'master' into 4183_follow_pep440

This commit is contained in:
Takeshi KOMIYA 2018-01-13 13:12:32 +09:00 committed by GitHub
commit f5b1aff2d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 1085 additions and 407 deletions

View File

@ -6,7 +6,6 @@ cache: pip
env: env:
global: global:
- PYTHONFAULTHANDLER=x - PYTHONFAULTHANDLER=x
- PYTHONWARNINGS=all
- SKIP_LATEX_BUILD=1 - SKIP_LATEX_BUILD=1
matrix: matrix:

View File

@ -18,6 +18,7 @@ Other co-maintainers:
Other contributors, listed alphabetically, are: Other contributors, listed alphabetically, are:
* Alastair Houghton -- Apple Help builder * Alastair Houghton -- Apple Help builder
* Alexander Todorov -- inheritance_diagram tests and improvements
* Andi Albrecht -- agogo theme * Andi Albrecht -- agogo theme
* Jakob Lykke Andersen -- Rewritten C++ domain * Jakob Lykke Andersen -- Rewritten C++ domain
* Henrique Bastos -- SVG support for graphviz extension * Henrique Bastos -- SVG support for graphviz extension

36
CHANGES
View File

@ -19,6 +19,8 @@ Incompatible changes
* #4226: apidoc: Generate new style makefile (make-mode) * #4226: apidoc: Generate new style makefile (make-mode)
* #4274: sphinx-build returns 2 as an exit code on argument error * #4274: sphinx-build returns 2 as an exit code on argument error
* #4389: output directory will be created after loading extensions * #4389: output directory will be created after loading extensions
* autodoc does not generate warnings messages to the generated document even if
:confval:`keep_warnings` is True. They are only emitted to stderr.
Deprecated Deprecated
---------- ----------
@ -27,6 +29,12 @@ Deprecated
values will be accepted at 2.0. values will be accepted at 2.0.
* ``format_annotation()`` and ``formatargspec()`` is deprecated. Please use * ``format_annotation()`` and ``formatargspec()`` is deprecated. Please use
``sphinx.util.inspect.Signature`` instead. ``sphinx.util.inspect.Signature`` instead.
* ``sphinx.ext.autodoc.AutodocReporter`` is replaced by ``sphinx.util.docutils.
switch_source_input()`` and now deprecated. It will be removed in Sphinx-2.0.
* ``sphinx.ext.autodoc.add_documenter()`` and ``AutoDirective._register`` is now
deprecated. Please use ``app.add_autodocumenter()`` instead.
* ``AutoDirective._special_attrgetters`` is now deprecated. Please use
``app.add_autodoc_attrgetter()`` instead.
Features added Features added
-------------- --------------
@ -62,6 +70,8 @@ Features added
* #947: autodoc now supports ignore-module-all to ignore a module's ``__all__`` * #947: autodoc now supports ignore-module-all to ignore a module's ``__all__``
* #4332: Let LaTeX obey :confval:`math_numfig` for equation numbering * #4332: Let LaTeX obey :confval:`math_numfig` for equation numbering
* #4093: sphinx-build creates empty directories for unknown targets/builders * #4093: sphinx-build creates empty directories for unknown targets/builders
* Add ``top-classes`` option for the ``sphinx.ext.inheritance_diagram``
extension to limit the scope of inheritance graphs.
* #4183: doctest: ``:pyversion:`` option also follows PEP-440 specification * #4183: doctest: ``:pyversion:`` option also follows PEP-440 specification
Features removed Features removed
@ -108,13 +118,18 @@ Bugs fixed
one of figures and tables one of figures and tables
* #4330: PDF 'howto' documents have an incoherent default LaTeX tocdepth counter * #4330: PDF 'howto' documents have an incoherent default LaTeX tocdepth counter
setting setting
* #4198: autosummary emits multiple 'autodoc-process-docstring' event. Thanks
to Joel Nothman.
* #4081: Warnings and errors colored the same when building
* latex: Do not display 'Release' label if :confval:`release` is not set
Testing Testing
-------- --------
* Add support for docutils 0.14 * Add support for docutils 0.14
* Add tests for the ``sphinx.ext.inheritance_diagram`` extension.
Release 1.6.6 (in development) Release 1.6.7 (in development)
============================== ==============================
Dependencies Dependencies
@ -129,11 +144,27 @@ Deprecated
Features added Features added
-------------- --------------
Bugs fixed
----------
Testing
--------
Release 1.6.6 (released Jan 08, 2018)
=====================================
Features added
--------------
* #4181: autodoc: Sort dictionary keys when possible * #4181: autodoc: Sort dictionary keys when possible
* ``VerbatimHighlightColor`` is a new * ``VerbatimHighlightColor`` is a new
:ref:`LaTeX 'sphinxsetup' <latexsphinxsetup>` key (refs: #4285) :ref:`LaTeX 'sphinxsetup' <latexsphinxsetup>` key (refs: #4285)
* Easier customizability of LaTeX macros involved in rendering of code-blocks * Easier customizability of LaTeX macros involved in rendering of code-blocks
* Show traceback if conf.py raises an exception (refs: #4369) * Show traceback if conf.py raises an exception (refs: #4369)
* Add :confval:`smartquotes` to disable smart quotes through ``conf.py``
(refs: #3967)
* Add :confval:`smartquotes_action` and :confval:`smartquotes_excludes`
(refs: #4142, #4357)
Bugs fixed Bugs fixed
---------- ----------
@ -158,9 +189,6 @@ Bugs fixed
* Fix links to external option docs with intersphinx (refs: #3769) * Fix links to external option docs with intersphinx (refs: #3769)
* #4091: Private members not documented without :undoc-members: * #4091: Private members not documented without :undoc-members:
Testing
--------
Release 1.6.5 (released Oct 23, 2017) Release 1.6.5 (released Oct 23, 2017)
===================================== =====================================

View File

@ -93,7 +93,7 @@ Documentation using the classic theme
* simuPOP: http://simupop.sourceforge.net/manual_release/build/userGuide.html (customized) * simuPOP: http://simupop.sourceforge.net/manual_release/build/userGuide.html (customized)
* Sprox: http://sprox.org/ (customized) * Sprox: http://sprox.org/ (customized)
* SymPy: http://docs.sympy.org/ * SymPy: http://docs.sympy.org/
* TurboGears: https://turbogears.readthedocs.org/ (customized) * TurboGears: https://turbogears.readthedocs.io/ (customized)
* tvtk: http://docs.enthought.com/mayavi/tvtk/ * tvtk: http://docs.enthought.com/mayavi/tvtk/
* Varnish: https://www.varnish-cache.org/docs/ (customized, alabaster for index) * Varnish: https://www.varnish-cache.org/docs/ (customized, alabaster for index)
* Waf: https://waf.io/apidocs/ * Waf: https://waf.io/apidocs/
@ -259,7 +259,7 @@ Documentation using sphinx_bootstrap_theme
* Bootstrap Theme: https://ryan-roemer.github.io/sphinx-bootstrap-theme/ * Bootstrap Theme: https://ryan-roemer.github.io/sphinx-bootstrap-theme/
* C/C++ Software Development with Eclipse: http://eclipsebook.in/ * C/C++ Software Development with Eclipse: http://eclipsebook.in/
* Dataverse: http://guides.dataverse.org/ * Dataverse: http://guides.dataverse.org/
* e-cidadania: http://e-cidadania.readthedocs.org/ * e-cidadania: https://e-cidadania.readthedocs.io/
* Hangfire: http://docs.hangfire.io/ * Hangfire: http://docs.hangfire.io/
* Hedge: https://documen.tician.de/hedge/ * Hedge: https://documen.tician.de/hedge/
* ObsPy: https://docs.obspy.org/ * ObsPy: https://docs.obspy.org/

View File

@ -74,9 +74,9 @@
<p>{%trans%} <p>{%trans%}
You can also download PDF/EPUB versions of the Sphinx documentation: You can also download PDF/EPUB versions of the Sphinx documentation:
a <a href="http://readthedocs.org/projects/sphinx/downloads/pdf/stable/">PDF version</a> generated from a <a href="https://media.readthedocs.org/pdf/sphinx/stable/sphinx.pdf">PDF version</a> generated from
the LaTeX Sphinx produces, and the LaTeX Sphinx produces, and
a <a href="http://readthedocs.org/projects/sphinx/downloads/epub/stable/">EPUB version</a>. a <a href="https://media.readthedocs.org/epub/sphinx/stable/sphinx.epub">EPUB version</a>.
{%endtrans%} {%endtrans%}
</p> </p>
@ -106,7 +106,7 @@
<h2>{%trans%}Hosting{%endtrans%}</h2> <h2>{%trans%}Hosting{%endtrans%}</h2>
<p>{%trans%}Need a place to host your Sphinx docs? <p>{%trans%}Need a place to host your Sphinx docs?
<a href="http://readthedocs.org">readthedocs.org</a> hosts a lot of Sphinx docs <a href="https://readthedocs.org/">readthedocs.org</a> hosts a lot of Sphinx docs
already, and integrates well with projects' source control. It also features a already, and integrates well with projects' source control. It also features a
powerful built-in search that exceeds the possibilities of Sphinx' JavaScript-based powerful built-in search that exceeds the possibilities of Sphinx' JavaScript-based
offline search.{%endtrans%}</p> offline search.{%endtrans%}</p>

View File

@ -354,6 +354,63 @@ General configuration
The LaTeX builder obeys this setting (if :confval:`numfig` is set to The LaTeX builder obeys this setting (if :confval:`numfig` is set to
``True``). ``True``).
.. confval:: smartquotes
If true, the `Docutils Smart Quotes transform`__, originally based on
`SmartyPants`__ (limited to English) and currently applying to many
languages, will be used to convert quotes and dashes to typographically
correct entities. Default: ``True``.
__ http://docutils.sourceforge.net/docs/user/smartquotes.html
__ https://daringfireball.net/projects/smartypants/
.. versionadded:: 1.6.6
It replaces deprecated :confval:`html_use_smartypants`.
It applies by default to all builders except ``man`` and ``text``
(see :confval:`smartquotes_excludes`.)
A `docutils.conf`__ file located in the configuration directory (or a
global :file:`~/.docutils` file) is obeyed unconditionally if it
*deactivates* smart quotes via the corresponding `Docutils option`__. But
if it *activates* them, then :confval:`smartquotes` does prevail.
__ http://docutils.sourceforge.net/docs/user/config.html
__ http://docutils.sourceforge.net/docs/user/config.html#smart-quotes
.. confval:: smartquotes_action
This string, for use with Docutils ``0.14`` or later, customizes the Smart
Quotes transform. See the file :file:`smartquotes.py` at the `Docutils
repository`__ for details. The default ``'qDe'`` educates normal **q**\
uote characters ``"``, ``'``, em- and en-**D**\ ashes ``---``, ``--``, and
**e**\ llipses ``...``.
.. versionadded:: 1.6.6
__ https://sourceforge.net/p/docutils/code/HEAD/tree/trunk/docutils/
.. confval:: smartquotes_excludes
This is a ``dict`` whose default is::
{'languages': ['ja'], 'builders': ['man', 'text']}
Each entry gives a sufficient condition to ignore the
:confval:`smartquotes` setting and deactivate the Smart Quotes transform.
Accepted keys are as above ``'builders'`` or ``'languages'``.
The values are lists.
.. note:: Currently, in case of invocation of :program:`make` with multiple
targets, the first target name is the only one which is tested against
the ``'builders'`` entry and it decides for all. Also, a ``make text``
following ``make html`` needs to be issued in the form ``make text
O="-E"`` to force re-parsing of source files, as the cached ones are
already transformed. On the other hand the issue does not arise with
direct usage of :program:`sphinx-build` as it caches
(in its default usage) the parsed source files in per builder locations.
.. versionadded:: 1.6.6
.. confval:: tls_verify .. confval:: tls_verify
If true, Sphinx verifies server certifications. Default is ``True``. If true, Sphinx verifies server certifications. Default is ``True``.
@ -785,15 +842,11 @@ that use Sphinx's HTMLWriter class.
.. confval:: html_use_smartypants .. confval:: html_use_smartypants
If true, `SmartyPants <https://daringfireball.net/projects/smartypants/>`_ If true, quotes and dashes are converted to typographically correct
will be used to convert quotes and dashes to typographically correct
entities. Default: ``True``. entities. Default: ``True``.
.. deprecated:: 1.6 .. deprecated:: 1.6
To disable or customize smart quotes, use the Docutils configuration file To disable smart quotes, use rather :confval:`smartquotes`.
(``docutils.conf``) instead to set there its `smart_quotes option`_.
.. _`smart_quotes option`: http://docutils.sourceforge.net/docs/user/config.html#smart-quotes
.. confval:: html_add_permalinks .. confval:: html_add_permalinks

View File

@ -138,7 +138,7 @@ own extensions.
.. _cmakedomain: https://bitbucket.org/klorenz/sphinxcontrib-cmakedomain .. _cmakedomain: https://bitbucket.org/klorenz/sphinxcontrib-cmakedomain
.. _GNU Make: http://www.gnu.org/software/make/ .. _GNU Make: http://www.gnu.org/software/make/
.. _makedomain: https://bitbucket.org/klorenz/sphinxcontrib-makedomain .. _makedomain: https://bitbucket.org/klorenz/sphinxcontrib-makedomain
.. _inlinesyntaxhighlight: http://sphinxcontrib-inlinesyntaxhighlight.readthedocs.org .. _inlinesyntaxhighlight: https://sphinxcontrib-inlinesyntaxhighlight.readthedocs.io/
.. _CMake: https://cmake.org .. _CMake: https://cmake.org
.. _domaintools: https://bitbucket.org/klorenz/sphinxcontrib-domaintools .. _domaintools: https://bitbucket.org/klorenz/sphinxcontrib-domaintools
.. _restbuilder: https://pypi.python.org/pypi/sphinxcontrib-restbuilder .. _restbuilder: https://pypi.python.org/pypi/sphinxcontrib-restbuilder

View File

@ -42,6 +42,54 @@ It adds this directive:
.. versionchanged:: 1.5 .. versionchanged:: 1.5
Added ``caption`` option Added ``caption`` option
It also supports a ``top-classes`` option which requires one or more class
names separated by comma. If specified inheritance traversal will stop at the
specified class names. Given the following Python module::
"""
A
/ \
B C
/ \ / \
E D F
"""
class A(object):
pass
class B(A):
pass
class C(A):
pass
class D(B, C):
pass
class E(B):
pass
class F(C):
pass
If you have specified a module in the inheritance diagram like this::
.. inheritance-diagram:: dummy.test
:top-classes: dummy.test.B, dummy.test.C
any base classes which are ancestors to ``top-classes`` and are also defined
in the same module will be rendered as stand alone nodes. In this example
class A will be rendered as stand alone node in the graph. This is a known
issue due to how this extension works internally.
If you don't want class A (or any other ancestors) to be visible then specify
only the classes you would like to generate the diagram for like this::
.. inheritance-diagram:: dummy.test.D dummy.test.E dummy.test.F
:top-classes: dummy.test.B, dummy.test.C
.. versionchanged:: 1.7
Added ``top-classes`` option to limit the scope of inheritance graphs.
New config values are: New config values are:

View File

@ -6,7 +6,7 @@ repository. It is open for anyone who wants to maintain an extension
publicly; just send a short message asking for write permissions. publicly; just send a short message asking for write permissions.
There are also several extensions hosted elsewhere. The `Sphinx extension There are also several extensions hosted elsewhere. The `Sphinx extension
survey <http://sphinxext-survey.readthedocs.org/en/latest/>`__ contains a survey <https://sphinxext-survey.readthedocs.io/>`__ contains a
comprehensive list. comprehensive list.
If you write an extension that you think others will find useful or you think If you write an extension that you think others will find useful or you think

View File

@ -117,12 +117,30 @@ Both APIs parse the content into a given node. They are used like this::
node = docutils.nodes.paragraph() node = docutils.nodes.paragraph()
# either # either
from sphinx.ext.autodoc import AutodocReporter
self.state.memo.reporter = AutodocReporter(self.result, self.state.memo.reporter) # override reporter to avoid errors from "include" directive
nested_parse_with_titles(self.state, self.result, node) nested_parse_with_titles(self.state, self.result, node)
# or # or
self.state.nested_parse(self.result, 0, node) self.state.nested_parse(self.result, 0, node)
.. note::
``sphinx.util.docutils.switch_source_input()`` allows to change a target file
during nested_parse. It is useful to mixed contents. For example, ``sphinx.
ext.autodoc`` uses it to parse docstrings::
from sphinx.util.docutils import switch_source_input
# Switch source_input between parsing content.
# Inside this context, all parsing errors and warnings are reported as
# happened in new source_input (in this case, ``self.result``).
with switch_source_input(self.state, self.result):
node = docutils.nodes.paragraph()
self.state.nested_parse(self.result, 0, node)
.. deprecated:: 1.7
Until Sphinx-1.6, ``sphinx.ext.autodoc.AutodocReporter`` is used for this purpose.
For now, it is replaced by ``switch_source_input()``.
If you don't need the wrapping node, you can use any concrete node type and If you don't need the wrapping node, you can use any concrete node type and
return ``node.children`` from the Directive. return ``node.children`` from the Directive.

View File

@ -58,7 +58,7 @@ Read the Docs
Sphinx. They will host sphinx documentation, along with supporting a number Sphinx. They will host sphinx documentation, along with supporting a number
of other features including version support, PDF generation, and more. The of other features including version support, PDF generation, and more. The
`Getting Started `Getting Started
<http://read-the-docs.readthedocs.org/en/latest/getting_started.html>`_ <https://read-the-docs.readthedocs.io/en/latest/getting_started.html>`_
guide is a good place to start. guide is a good place to start.
Epydoc Epydoc

View File

@ -3,7 +3,7 @@ Introduction
This is the documentation for the Sphinx documentation builder. Sphinx is a This is the documentation for the Sphinx documentation builder. Sphinx is a
tool that translates a set of reStructuredText_ source files into various output tool that translates a set of reStructuredText_ source files into various output
formats, automatically producing cross-references, indices etc. That is, if formats, automatically producing cross-references, indices, etc. That is, if
you have a directory containing a bunch of reST-formatted documents (and you have a directory containing a bunch of reST-formatted documents (and
possibly subdirectories of docs in there as well), Sphinx can generate a possibly subdirectories of docs in there as well), Sphinx can generate a
nicely-organized arrangement of HTML files (in some other directory) for easy nicely-organized arrangement of HTML files (in some other directory) for easy
@ -17,7 +17,7 @@ docs have a look at `Epydoc <http://epydoc.sourceforge.net/>`_, which also
understands reST. understands reST.
For a great "introduction" to writing docs in general -- the whys and hows, see For a great "introduction" to writing docs in general -- the whys and hows, see
also `Write the docs <http://write-the-docs.readthedocs.org/>`_, written by Eric also `Write the docs <https://write-the-docs.readthedocs.io/>`_, written by Eric
Holscher. Holscher.
.. _rinohtype: https://github.com/brechtm/rinohtype .. _rinohtype: https://github.com/brechtm/rinohtype
@ -38,7 +38,7 @@ to reStructuredText/Sphinx from other documentation systems.
code to convert Python-doc-style LaTeX markup to Sphinx reST. code to convert Python-doc-style LaTeX markup to Sphinx reST.
* Marcin Wojdyr has written a script to convert Docbook to reST with Sphinx * Marcin Wojdyr has written a script to convert Docbook to reST with Sphinx
markup; it is at `Google Code <https://github.com/wojdyr/db2rst>`_. markup; it is at `GitHub <https://github.com/wojdyr/db2rst>`_.
* Christophe de Vienne wrote a tool to convert from Open/LibreOffice documents * Christophe de Vienne wrote a tool to convert from Open/LibreOffice documents
to Sphinx: `odt2sphinx <https://pypi.python.org/pypi/odt2sphinx/>`_. to Sphinx: `odt2sphinx <https://pypi.python.org/pypi/odt2sphinx/>`_.

View File

@ -642,15 +642,14 @@ class Sphinx(object):
def add_autodocumenter(self, cls): def add_autodocumenter(self, cls):
# type: (Any) -> None # type: (Any) -> None
logger.debug('[app] adding autodocumenter: %r', cls) logger.debug('[app] adding autodocumenter: %r', cls)
from sphinx.ext import autodoc from sphinx.ext.autodoc.directive import AutodocDirective
autodoc.add_documenter(cls) self.registry.add_documenter(cls.objtype, cls)
self.add_directive('auto' + cls.objtype, autodoc.AutoDirective) self.add_directive('auto' + cls.objtype, AutodocDirective)
def add_autodoc_attrgetter(self, type, getter): def add_autodoc_attrgetter(self, typ, getter):
# type: (Any, Callable) -> None # type: (Type, Callable[[Any, unicode, Any], Any]) -> None
logger.debug('[app] adding autodoc attrgetter: %r', (type, getter)) logger.debug('[app] adding autodoc attrgetter: %r', (typ, getter))
from sphinx.ext import autodoc self.registry.add_autodoc_attrgetter(typ, getter)
autodoc.AutoDirective._special_attrgetters[type] = getter
def add_search_language(self, cls): def add_search_language(self, cls):
# type: (Any) -> None # type: (Any) -> None

View File

@ -137,6 +137,11 @@ class Config(object):
tls_verify = (True, 'env'), tls_verify = (True, 'env'),
tls_cacerts = (None, 'env'), tls_cacerts = (None, 'env'),
smartquotes = (True, 'env'),
smartquotes_action = ('qDe', 'env'),
smartquotes_excludes = ({'languages': ['ja'],
'builders': ['man', 'text']},
'env'),
) # type: Dict[unicode, Tuple] ) # type: Dict[unicode, Tuple]
def __init__(self, dirname, filename, overrides, tags): def __init__(self, dirname, filename, overrides, tags):

View File

@ -19,8 +19,9 @@ import warnings
from os import path from os import path
from copy import copy from copy import copy
from collections import defaultdict from collections import defaultdict
from contextlib import contextmanager
from six import BytesIO, itervalues, class_types, next from six import BytesIO, itervalues, class_types, next, iteritems
from six.moves import cPickle as pickle from six.moves import cPickle as pickle
from docutils.utils import Reporter, get_source_line, normalize_language_tag from docutils.utils import Reporter, get_source_line, normalize_language_tag
@ -40,14 +41,14 @@ from sphinx.util.matching import compile_matchers
from sphinx.util.parallel import ParallelTasks, parallel_available, make_chunks from sphinx.util.parallel import ParallelTasks, parallel_available, make_chunks
from sphinx.util.websupport import is_commentable from sphinx.util.websupport import is_commentable
from sphinx.errors import SphinxError, ExtensionError from sphinx.errors import SphinxError, ExtensionError
from sphinx.transforms import SphinxTransformer from sphinx.transforms import SphinxTransformer, SphinxSmartQuotes
from sphinx.deprecation import RemovedInSphinx20Warning from sphinx.deprecation import RemovedInSphinx20Warning
from sphinx.environment.adapters.indexentries import IndexEntries from sphinx.environment.adapters.indexentries import IndexEntries
from sphinx.environment.adapters.toctree import TocTree from sphinx.environment.adapters.toctree import TocTree
if False: if False:
# For type annotation # For type annotation
from typing import Any, Callable, Dict, IO, Iterator, List, Pattern, Set, Tuple, Type, Union # NOQA from typing import Any, Callable, Dict, IO, Iterator, List, Pattern, Set, Tuple, Type, Union, Generator # NOQA
from docutils import nodes # NOQA from docutils import nodes # NOQA
from sphinx.application import Sphinx # NOQA from sphinx.application import Sphinx # NOQA
from sphinx.builders import Builder # NOQA from sphinx.builders import Builder # NOQA
@ -66,6 +67,7 @@ default_settings = {
'sectsubtitle_xform': False, 'sectsubtitle_xform': False,
'halt_level': 5, 'halt_level': 5,
'file_insertion_enabled': True, 'file_insertion_enabled': True,
'smartquotes_locales': [],
} }
# This is increased every time an environment attribute is added # This is increased every time an environment attribute is added
@ -82,6 +84,22 @@ versioning_conditions = {
} # type: Dict[unicode, Union[bool, Callable]] } # type: Dict[unicode, Union[bool, Callable]]
@contextmanager
def sphinx_smartquotes_action(env):
# type: (BuildEnvironment) -> Generator
if not hasattr(SphinxSmartQuotes, 'smartquotes_action'):
# less than docutils-0.14
yield
else:
# docutils-0.14 or above
try:
original = SphinxSmartQuotes.smartquotes_action
SphinxSmartQuotes.smartquotes_action = env.config.smartquotes_action
yield
finally:
SphinxSmartQuotes.smartquotes_action = original
class NoUri(Exception): class NoUri(Exception):
"""Raised by get_relative_uri if there is no URI available.""" """Raised by get_relative_uri if there is no URI available."""
pass pass
@ -584,7 +602,8 @@ class BuildEnvironment(object):
# remove all inventory entries for that file # remove all inventory entries for that file
app.emit('env-purge-doc', self, docname) app.emit('env-purge-doc', self, docname)
self.clear_doc(docname) self.clear_doc(docname)
self.read_doc(docname, app) with sphinx_smartquotes_action(self):
self.read_doc(docname, app)
def _read_parallel(self, docnames, app, nproc): def _read_parallel(self, docnames, app, nproc):
# type: (List[unicode], Sphinx, int) -> None # type: (List[unicode], Sphinx, int) -> None
@ -596,8 +615,9 @@ class BuildEnvironment(object):
def read_process(docs): def read_process(docs):
# type: (List[unicode]) -> unicode # type: (List[unicode]) -> unicode
self.app = app self.app = app
for docname in docs: with sphinx_smartquotes_action(self):
self.read_doc(docname, app) for docname in docs:
self.read_doc(docname, app)
# allow pickling self to send it back # allow pickling self to send it back
return BuildEnvironment.dumps(self) return BuildEnvironment.dumps(self)
@ -645,7 +665,19 @@ class BuildEnvironment(object):
language = self.config.language or 'en' language = self.config.language or 'en'
self.settings['language_code'] = language self.settings['language_code'] = language
if 'smart_quotes' not in self.settings: if 'smart_quotes' not in self.settings:
self.settings['smart_quotes'] = True self.settings['smart_quotes'] = self.config.smartquotes
# some conditions exclude smart quotes, overriding smart_quotes
for valname, vallist in iteritems(self.config.smartquotes_excludes):
if valname == 'builders':
# this will work only for checking first build target
if self.app.builder.name in vallist:
self.settings['smart_quotes'] = False
break
elif valname == 'languages':
if self.config.language in vallist:
self.settings['smart_quotes'] = False
break
# confirm selected language supports smart_quotes or not # confirm selected language supports smart_quotes or not
for tag in normalize_language_tag(language): for tag in normalize_language_tag(language):

View File

@ -14,17 +14,15 @@
import re import re
import sys import sys
import inspect import inspect
import traceback import warnings
from six import PY2, iterkeys, iteritems, itervalues, text_type, class_types, string_types from six import iteritems, itervalues, text_type, class_types, string_types
from docutils import nodes
from docutils.utils import assemble_option_dict
from docutils.parsers.rst import Directive
from docutils.statemachine import ViewList from docutils.statemachine import ViewList
import sphinx import sphinx
from sphinx.ext.autodoc.importer import mock, import_module from sphinx.deprecation import RemovedInSphinx20Warning
from sphinx.ext.autodoc.importer import mock, import_object, get_object_members
from sphinx.ext.autodoc.importer import _MockImporter # to keep compatibility # NOQA from sphinx.ext.autodoc.importer import _MockImporter # to keep compatibility # NOQA
from sphinx.ext.autodoc.inspector import format_annotation, formatargspec # to keep compatibility # NOQA from sphinx.ext.autodoc.inspector import format_annotation, formatargspec # to keep compatibility # NOQA
from sphinx.util import rpartition, force_decode from sphinx.util import rpartition, force_decode
@ -32,21 +30,23 @@ from sphinx.locale import _
from sphinx.pycode import ModuleAnalyzer, PycodeError from sphinx.pycode import ModuleAnalyzer, PycodeError
from sphinx.application import ExtensionError from sphinx.application import ExtensionError
from sphinx.util import logging from sphinx.util import logging
from sphinx.util.nodes import nested_parse_with_titles
from sphinx.util.inspect import Signature, isdescriptor, safe_getmembers, \ from sphinx.util.inspect import Signature, isdescriptor, safe_getmembers, \
safe_getattr, object_description, is_builtin_class_method, \ safe_getattr, object_description, is_builtin_class_method, \
isenumclass, isenumattribute, getdoc isenumattribute, getdoc
from sphinx.util.docstrings import prepare_docstring from sphinx.util.docstrings import prepare_docstring
if False: if False:
# For type annotation # For type annotation
from types import ModuleType # NOQA from types import ModuleType # NOQA
from typing import Any, Callable, Dict, Iterator, List, Sequence, Set, Tuple, Type, Union # NOQA from typing import Any, Callable, Dict, Iterator, List, Sequence, Set, Tuple, Type, Union # NOQA
from docutils import nodes # NOQA
from docutils.utils import Reporter # NOQA from docutils.utils import Reporter # NOQA
from sphinx.application import Sphinx # NOQA from sphinx.application import Sphinx # NOQA
from sphinx.ext.autodoc.directive import DocumenterBridge # NOQA
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# This type isn't exposed directly in any modules, but can be found # This type isn't exposed directly in any modules, but can be found
# here in most Python versions # here in most Python versions
MethodDescriptorType = type(type.__subclasses__) MethodDescriptorType = type(type.__subclasses__)
@ -63,42 +63,11 @@ py_ext_sig_re = re.compile(
''', re.VERBOSE) ''', re.VERBOSE)
class DefDict(dict):
"""A dict that returns a default on nonexisting keys."""
def __init__(self, default):
# type: (Any) -> None
dict.__init__(self)
self.default = default
def __getitem__(self, key):
# type: (Any) -> Any
try:
return dict.__getitem__(self, key)
except KeyError:
return self.default
def __bool__(self):
# type: () -> bool
# docutils check "if option_spec"
return True
__nonzero__ = __bool__ # for python2 compatibility
def identity(x): def identity(x):
# type: (Any) -> Any # type: (Any) -> Any
return x return x
class Options(dict):
"""A dict/attribute hybrid that returns None on nonexisting keys."""
def __getattr__(self, name):
# type: (unicode) -> Any
try:
return self[name.replace('_', '-')]
except KeyError:
return None
ALL = object() ALL = object()
INSTANCEATTR = object() INSTANCEATTR = object()
@ -146,6 +115,9 @@ class AutodocReporter(object):
""" """
def __init__(self, viewlist, reporter): def __init__(self, viewlist, reporter):
# type: (ViewList, Reporter) -> None # type: (ViewList, Reporter) -> None
warnings.warn('AutodocReporter is now deprecated. '
'Use sphinx.util.docutils.switch_source_input() instead.',
RemovedInSphinx20Warning)
self.viewlist = viewlist self.viewlist = viewlist
self.reporter = reporter self.reporter = reporter
@ -284,14 +256,10 @@ class Documenter(object):
option_spec = {'noindex': bool_option} # type: Dict[unicode, Callable] option_spec = {'noindex': bool_option} # type: Dict[unicode, Callable]
@staticmethod def get_attr(self, obj, name, *defargs):
def get_attr(obj, name, *defargs):
# type: (Any, unicode, Any) -> Any # type: (Any, unicode, Any) -> Any
"""getattr() override for types such as Zope interfaces.""" """getattr() override for types such as Zope interfaces."""
for typ, func in iteritems(AutoDirective._special_attrgetters): return autodoc_attrgetter(self.env.app, obj, name, *defargs)
if isinstance(obj, typ):
return func(obj, name, *defargs)
return safe_getattr(obj, name, *defargs)
@classmethod @classmethod
def can_document_member(cls, member, membername, isattr, parent): def can_document_member(cls, member, membername, isattr, parent):
@ -300,7 +268,7 @@ class Documenter(object):
raise NotImplementedError('must be implemented in subclasses') raise NotImplementedError('must be implemented in subclasses')
def __init__(self, directive, name, indent=u''): def __init__(self, directive, name, indent=u''):
# type: (Directive, unicode, unicode) -> None # type: (DocumenterBridge, unicode, unicode) -> None
self.directive = directive self.directive = directive
self.env = directive.env self.env = directive.env
self.options = directive.genopt self.options = directive.genopt
@ -324,6 +292,12 @@ class Documenter(object):
# the module analyzer to get at attribute docs, or None # the module analyzer to get at attribute docs, or None
self.analyzer = None # type: Any self.analyzer = None # type: Any
@property
def documenters(self):
# type: () -> Dict[unicode, Type[Documenter]]
"""Returns registered Documenter classes"""
return get_documenters(self.env.app)
def add_line(self, line, source, *lineno): def add_line(self, line, source, *lineno):
# type: (unicode, unicode, int) -> None # type: (unicode, unicode, int) -> None
"""Append one line of generated reST to the output.""" """Append one line of generated reST to the output."""
@ -354,8 +328,7 @@ class Documenter(object):
explicit_modname, path, base, args, retann = \ explicit_modname, path, base, args, retann = \
py_ext_sig_re.match(self.name).groups() # type: ignore py_ext_sig_re.match(self.name).groups() # type: ignore
except AttributeError: except AttributeError:
self.directive.warn('invalid signature for auto%s (%r)' % logger.warning('invalid signature for auto%s (%r)' % (self.objtype, self.name))
(self.objtype, self.name))
return False return False
# support explicit module and class name separation via :: # support explicit module and class name separation via ::
@ -384,56 +357,15 @@ class Documenter(object):
Returns True if successful, False if an error occurred. Returns True if successful, False if an error occurred.
""" """
if self.objpath:
logger.debug('[autodoc] from %s import %s',
self.modname, '.'.join(self.objpath))
# always enable mock import hook
# it will do nothing if autodoc_mock_imports is empty
with mock(self.env.config.autodoc_mock_imports): with mock(self.env.config.autodoc_mock_imports):
try: try:
logger.debug('[autodoc] import %s', self.modname) ret = import_object(self.modname, self.objpath, self.objtype,
obj = import_module(self.modname, self.env.config.autodoc_warningiserror) attrgetter=self.get_attr,
parent = None warningiserror=self.env.config.autodoc_warningiserror)
self.module = obj self.module, self.parent, self.object_name, self.object = ret
logger.debug('[autodoc] => %r', obj)
for part in self.objpath:
parent = obj
logger.debug('[autodoc] getattr(_, %r)', part)
obj = self.get_attr(obj, part)
logger.debug('[autodoc] => %r', obj)
self.object_name = part
self.parent = parent
self.object = obj
return True return True
except (AttributeError, ImportError) as exc: except ImportError as exc:
if self.objpath: logger.warning(exc.args[0])
errmsg = 'autodoc: failed to import %s %r from module %r' % \
(self.objtype, '.'.join(self.objpath), self.modname)
else:
errmsg = 'autodoc: failed to import %s %r' % \
(self.objtype, self.fullname)
if isinstance(exc, ImportError):
# import_module() raises ImportError having real exception obj and
# traceback
real_exc, traceback_msg = exc.args
if isinstance(real_exc, SystemExit):
errmsg += ('; the module executes module level statement ' +
'and it might call sys.exit().')
elif isinstance(real_exc, ImportError):
errmsg += ('; the following exception was raised:\n%s' %
real_exc.args[0])
else:
errmsg += ('; the following exception was raised:\n%s' %
traceback_msg)
else:
errmsg += ('; the following exception was raised:\n%s' %
traceback.format_exc())
if PY2:
errmsg = errmsg.decode('utf-8') # type: ignore
logger.debug(errmsg)
self.directive.warn(errmsg)
self.env.note_reread() self.env.note_reread()
return False return False
@ -493,8 +425,8 @@ class Documenter(object):
try: try:
args = self.format_args() args = self.format_args()
except Exception as err: except Exception as err:
self.directive.warn('error while formatting arguments for ' logger.warning('error while formatting arguments for %s: %s' %
'%s: %s' % (self.fullname, err)) (self.fullname, err))
args = None args = None
retann = self.retann retann = self.retann
@ -606,57 +538,24 @@ class Documenter(object):
If *want_all* is True, return all members. Else, only return those If *want_all* is True, return all members. Else, only return those
members given by *self.options.members* (which may also be none). members given by *self.options.members* (which may also be none).
""" """
analyzed_member_names = set() members = get_object_members(self.object, self.objpath, self.get_attr, self.analyzer)
if self.analyzer:
attr_docs = self.analyzer.find_attr_docs()
namespace = '.'.join(self.objpath)
for item in iteritems(attr_docs):
if item[0][0] == namespace:
analyzed_member_names.add(item[0][1])
if not want_all: if not want_all:
if not self.options.members: if not self.options.members:
return False, [] return False, []
# specific members given # specific members given
members = [] selected = []
for mname in self.options.members: for name in self.options.members:
try: if name in members:
members.append((mname, self.get_attr(self.object, mname))) selected.append((name, members[name].value))
except AttributeError: else:
if mname not in analyzed_member_names: logger.warning('missing attribute %s in object %s' %
self.directive.warn('missing attribute %s in object %s' (name, self.fullname))
% (mname, self.fullname)) return False, sorted(selected)
elif self.options.inherited_members: elif self.options.inherited_members:
# safe_getmembers() uses dir() which pulls in members from all return False, sorted((m.name, m.value) for m in itervalues(members))
# base classes
members = safe_getmembers(self.object, attr_getter=self.get_attr)
else: else:
# __dict__ contains only the members directly defined in return False, sorted((m.name, m.value) for m in itervalues(members)
# the class (but get them via getattr anyway, to e.g. get if m.directly_defined)
# unbound method objects instead of function objects);
# using list(iterkeys()) because apparently there are objects for which
# __dict__ changes while getting attributes
try:
obj_dict = self.get_attr(self.object, '__dict__')
except AttributeError:
members = []
else:
members = [(mname, self.get_attr(self.object, mname, None))
for mname in list(iterkeys(obj_dict))]
# Py34 doesn't have enum members in __dict__.
if isenumclass(self.object):
members.extend(
item for item in self.object.__members__.items()
if item not in members
)
membernames = set(m[0] for m in members)
# add instance attributes from the analyzer
for aname in analyzed_member_names:
if aname not in membernames and \
(want_all or aname in self.options.members):
members.append((aname, INSTANCEATTR))
return False, sorted(members)
def filter_members(self, members, want_all): def filter_members(self, members, want_all):
# type: (List[Tuple[unicode, Any]], bool) -> List[Tuple[unicode, Any, bool]] # type: (List[Tuple[unicode, Any]], bool) -> List[Tuple[unicode, Any, bool]]
@ -768,7 +667,7 @@ class Documenter(object):
# document non-skipped members # document non-skipped members
memberdocumenters = [] # type: List[Tuple[Documenter, bool]] memberdocumenters = [] # type: List[Tuple[Documenter, bool]]
for (mname, member, isattr) in self.filter_members(members, want_all): for (mname, member, isattr) in self.filter_members(members, want_all):
classes = [cls for cls in itervalues(AutoDirective._registry) classes = [cls for cls in itervalues(self.documenters)
if cls.can_document_member(member, mname, isattr, self)] if cls.can_document_member(member, mname, isattr, self)]
if not classes: if not classes:
# don't know how to document this member # don't know how to document this member
@ -819,11 +718,11 @@ class Documenter(object):
""" """
if not self.parse_name(): if not self.parse_name():
# need a module to import # need a module to import
self.directive.warn( logger.warning(
'don\'t know which module to import for autodocumenting ' 'don\'t know which module to import for autodocumenting '
'%r (try placing a "module" or "currentmodule" directive ' '%r (try placing a "module" or "currentmodule" directive '
'in the document, or giving an explicit module name)' 'in the document, or giving an explicit module name)' %
% self.name) self.name)
return return
# now, import the module and get object to document # now, import the module and get object to document
@ -909,15 +808,15 @@ class ModuleDocumenter(Documenter):
def resolve_name(self, modname, parents, path, base): def resolve_name(self, modname, parents, path, base):
# type: (str, Any, str, Any) -> Tuple[str, List[unicode]] # type: (str, Any, str, Any) -> Tuple[str, List[unicode]]
if modname is not None: if modname is not None:
self.directive.warn('"::" in automodule name doesn\'t make sense') logger.warning('"::" in automodule name doesn\'t make sense')
return (path or '') + base, [] return (path or '') + base, []
def parse_name(self): def parse_name(self):
# type: () -> bool # type: () -> bool
ret = Documenter.parse_name(self) ret = Documenter.parse_name(self)
if self.args or self.retann: if self.args or self.retann:
self.directive.warn('signature arguments or return annotation ' logger.warning('signature arguments or return annotation '
'given for automodule %s' % self.fullname) 'given for automodule %s' % self.fullname)
return ret return ret
def add_directive_header(self, sig): def add_directive_header(self, sig):
@ -949,7 +848,7 @@ class ModuleDocumenter(Documenter):
# Sometimes __all__ is broken... # Sometimes __all__ is broken...
if not isinstance(memberlist, (list, tuple)) or not \ if not isinstance(memberlist, (list, tuple)) or not \
all(isinstance(entry, string_types) for entry in memberlist): all(isinstance(entry, string_types) for entry in memberlist):
self.directive.warn( logger.warning(
'__all__ should be a list of strings, not %r ' '__all__ should be a list of strings, not %r '
'(in module %s) -- ignoring __all__' % '(in module %s) -- ignoring __all__' %
(memberlist, self.fullname)) (memberlist, self.fullname))
@ -962,10 +861,10 @@ class ModuleDocumenter(Documenter):
try: try:
ret.append((mname, safe_getattr(self.object, mname))) ret.append((mname, safe_getattr(self.object, mname)))
except AttributeError: except AttributeError:
self.directive.warn( logger.warning(
'missing attribute mentioned in :members: or __all__: ' 'missing attribute mentioned in :members: or __all__: '
'module %s, attribute %s' % ( 'module %s, attribute %s' %
safe_getattr(self.object, '__name__', '???'), mname)) (safe_getattr(self.object, '__name__', '???'), mname))
return False, ret return False, ret
@ -1504,118 +1403,56 @@ class InstanceAttributeDocumenter(AttributeDocumenter):
AttributeDocumenter.add_content(self, more_content, no_docstring=True) AttributeDocumenter.add_content(self, more_content, no_docstring=True)
class AutoDirective(Directive): class DeprecatedDict(dict):
""" def __init__(self, message):
The AutoDirective class is used for all autodoc directives. It dispatches self.message = message
most of the work to one of the Documenters, which it selects through its super(DeprecatedDict, self).__init__()
*_registry* dictionary.
The *_special_attrgetters* attribute is used to customize ``getattr()`` def __setitem__(self, key, value):
calls that the Documenters make; its entries are of the form ``type: warnings.warn(self.message, RemovedInSphinx20Warning)
getattr_function``. super(DeprecatedDict, self).__setitem__(key, value)
def setdefault(self, key, default=None):
warnings.warn(self.message, RemovedInSphinx20Warning)
super(DeprecatedDict, self).setdefault(key, default)
def update(self, other=None):
warnings.warn(self.message, RemovedInSphinx20Warning)
super(DeprecatedDict, self).update(other)
class AutodocRegistry(object):
"""
A registry of Documenters and attrgetters.
Note: When importing an object, all items along the import chain are Note: When importing an object, all items along the import chain are
accessed using the descendant's *_special_attrgetters*, thus this accessed using the descendant's *_special_attrgetters*, thus this
dictionary should include all necessary functions for accessing dictionary should include all necessary functions for accessing
attributes of the parents. attributes of the parents.
""" """
# a registry of objtype -> documenter class # a registry of objtype -> documenter class (Deprecated)
_registry = {} # type: Dict[unicode, Type[Documenter]] _registry = DeprecatedDict(
'AutoDirective._registry has been deprecated. '
'Please use app.add_autodocumenter() instead.'
) # type: Dict[unicode, Type[Documenter]]
# a registry of type -> getattr function # a registry of type -> getattr function
_special_attrgetters = {} # type: Dict[Type, Callable] _special_attrgetters = DeprecatedDict(
'AutoDirective._special_attrgetters has been deprecated. '
'Please use app.add_autodoc_attrgetter() instead.'
) # type: Dict[Type, Callable]
# flags that can be given in autodoc_default_flags
_default_flags = set([
'members', 'undoc-members', 'inherited-members', 'show-inheritance',
'private-members', 'special-members', 'ignore-module-all'
])
# standard docutils directive settings AutoDirective = AutodocRegistry # for backward compatibility
has_content = True
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = True
# allow any options to be passed; the options are parsed further
# by the selected Documenter
option_spec = DefDict(identity)
def warn(self, msg):
# type: (unicode) -> None
self.warnings.append(self.reporter.warning(msg, line=self.lineno))
def run(self):
# type: () -> List[nodes.Node]
self.filename_set = set() # type: Set[unicode]
# a set of dependent filenames
self.reporter = self.state.document.reporter
self.env = self.state.document.settings.env
self.warnings = [] # type: List[unicode]
self.result = ViewList()
try:
source, lineno = self.reporter.get_source_and_line(self.lineno)
except AttributeError:
source = lineno = None
logger.debug('[autodoc] %s:%s: input:\n%s',
source, lineno, self.block_text)
# find out what documenter to call
objtype = self.name[4:]
doc_class = self._registry[objtype]
# add default flags
for flag in self._default_flags:
if flag not in doc_class.option_spec:
continue
negated = self.options.pop('no-' + flag, 'not given') is None
if flag in self.env.config.autodoc_default_flags and \
not negated:
self.options[flag] = None
# process the options with the selected documenter's option_spec
try:
self.genopt = Options(assemble_option_dict(
self.options.items(), doc_class.option_spec))
except (KeyError, ValueError, TypeError) as err:
# an option is either unknown or has a wrong type
msg = self.reporter.error('An option to %s is either unknown or '
'has an invalid value: %s' % (self.name, err),
line=self.lineno)
return [msg]
# generate the output
documenter = doc_class(self, self.arguments[0])
documenter.generate(more_content=self.content)
if not self.result:
return self.warnings
logger.debug('[autodoc] output:\n%s', '\n'.join(self.result))
# record all filenames as dependencies -- this will at least
# partially make automatic invalidation possible
for fn in self.filename_set:
self.state.document.settings.record_dependencies.add(fn)
# use a custom reporter that correctly assigns lines to source
# filename/description and lineno
old_reporter = self.state.memo.reporter
self.state.memo.reporter = AutodocReporter(self.result,
self.state.memo.reporter)
if documenter.titles_allowed:
node = nodes.section()
# necessary so that the child nodes get the right source/line set
node.document = self.state.document
nested_parse_with_titles(self.state, self.result, node)
else:
node = nodes.paragraph()
node.document = self.state.document
self.state.nested_parse(self.result, 0, node)
self.state.memo.reporter = old_reporter
return self.warnings + node.children
def add_documenter(cls): def add_documenter(cls):
# type: (Type[Documenter]) -> None # type: (Type[Documenter]) -> None
"""Register a new Documenter.""" """Register a new Documenter."""
warnings.warn('sphinx.ext.autodoc.add_documenter() has been deprecated. '
'Please use app.add_autodocumenter() instead.',
RemovedInSphinx20Warning)
if not issubclass(cls, Documenter): if not issubclass(cls, Documenter):
raise ExtensionError('autodoc documenter %r must be a subclass ' raise ExtensionError('autodoc documenter %r must be a subclass '
'of Documenter' % cls) 'of Documenter' % cls)
@ -1626,6 +1463,29 @@ def add_documenter(cls):
AutoDirective._registry[cls.objtype] = cls AutoDirective._registry[cls.objtype] = cls
def get_documenters(app):
# type: (Sphinx) -> Dict[unicode, Type[Documenter]]
"""Returns registered Documenter classes"""
classes = dict(AutoDirective._registry) # registered directly
if app:
classes.update(app.registry.documenters) # registered by API
return classes
def autodoc_attrgetter(app, obj, name, *defargs):
# type: (Sphinx, Any, unicode, Any) -> Any
"""Alternative getattr() for types"""
candidates = dict(AutoDirective._special_attrgetters)
if app:
candidates.update(app.registry.autodoc_attrgettrs)
for typ, func in iteritems(candidates):
if isinstance(obj, typ):
return func(obj, name, *defargs)
return safe_getattr(obj, name, *defargs)
def setup(app): def setup(app):
# type: (Sphinx) -> Dict[unicode, Any] # type: (Sphinx) -> Dict[unicode, Any]
app.add_autodocumenter(ModuleDocumenter) app.add_autodocumenter(ModuleDocumenter)

View File

@ -0,0 +1,155 @@
# -*- coding: utf-8 -*-
"""
sphinx.ext.autodoc.directive
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
from docutils import nodes
from docutils.parsers.rst import Directive
from docutils.statemachine import ViewList
from docutils.utils import assemble_option_dict
from sphinx.ext.autodoc import get_documenters
from sphinx.util import logging
from sphinx.util.docutils import switch_source_input
from sphinx.util.nodes import nested_parse_with_titles
if False:
# For type annotation
from typing import Any, Dict, List, Set, Type # NOQA
from docutils.statemachine import State, StateMachine, StringList # NOQA
from docutils.utils import Reporter # NOQA
from sphinx.config import Config # NOQA
from sphinx.environment import BuildEnvironment # NOQA
from sphinx.ext.autodoc import Documenter # NOQA
logger = logging.getLogger(__name__)
# common option names for autodoc directives
AUTODOC_DEFAULT_OPTIONS = ['members', 'undoc-members', 'inherited-members',
'show-inheritance', 'private-members', 'special-members',
'ignore-module-all']
class DummyOptionSpec(object):
"""An option_spec allows any options."""
def __getitem__(self, key):
# type: (Any) -> Any
return lambda x: x
class Options(dict):
"""A dict/attribute hybrid that returns None on nonexisting keys."""
def __getattr__(self, name):
# type: (unicode) -> Any
try:
return self[name.replace('_', '-')]
except KeyError:
return None
class DocumenterBridge(object):
"""A parameters container for Documenters."""
def __init__(self, env, reporter, options, lineno):
# type: (BuildEnvironment, Reporter, Options, int) -> None
self.env = env
self.reporter = reporter
self.genopt = options
self.lineno = lineno
self.filename_set = set() # type: Set[unicode]
self.result = ViewList()
def warn(self, msg):
# type: (unicode) -> None
logger.warning(msg, line=self.lineno)
def process_documenter_options(documenter, config, options):
# type: (Type[Documenter], Config, Dict) -> Options
"""Recognize options of Documenter from user input."""
for name in AUTODOC_DEFAULT_OPTIONS:
if name not in documenter.option_spec:
continue
else:
negated = options.pop('no-' + name, True) is None
if name in config.autodoc_default_flags and not negated:
options[name] = None
return Options(assemble_option_dict(options.items(), documenter.option_spec))
def parse_generated_content(state, content, documenter):
# type: (State, StringList, Documenter) -> List[nodes.Node]
"""Parse a generated content by Documenter."""
with switch_source_input(state, content):
if documenter.titles_allowed:
node = nodes.section()
# necessary so that the child nodes get the right source/line set
node.document = state.document
nested_parse_with_titles(state, content, node)
else:
node = nodes.paragraph()
node.document = state.document
state.nested_parse(content, 0, node)
return node.children
class AutodocDirective(Directive):
"""A directive class for all autodoc directives. It works as a dispatcher of Documenters.
It invokes a Documenter on running. After the processing, it parses and returns
the generated content by Documenter.
"""
option_spec = DummyOptionSpec()
has_content = True
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = True
def run(self):
# type: () -> List[nodes.Node]
env = self.state.document.settings.env
reporter = self.state.document.reporter
try:
source, lineno = reporter.get_source_and_line(self.lineno)
except AttributeError:
source, lineno = (None, None)
logger.debug('[autodoc] %s:%s: input:\n%s', source, lineno, self.block_text)
# look up target Documenter
objtype = self.name[4:] # strip prefix (auto-).
doccls = get_documenters(env.app)[objtype]
# process the options with the selected documenter's option_spec
try:
documenter_options = process_documenter_options(doccls, env.config, self.options)
except (KeyError, ValueError, TypeError) as exc:
# an option is either unknown or has a wrong type
logger.error('An option to %s is either unknown or has an invalid value: %s' %
(self.name, exc), line=lineno)
return []
# generate the output
params = DocumenterBridge(env, reporter, documenter_options, lineno)
documenter = doccls(params, self.arguments[0])
documenter.generate(more_content=self.content)
if not params.result:
return []
logger.debug('[autodoc] output:\n%s', '\n'.join(params.result))
# record all filenames as dependencies -- this will at least
# partially make automatic invalidation possible
for fn in params.filename_set:
self.state.document.settings.record_dependencies.add(fn)
result = parse_generated_content(self.state, params.result, documenter)
return result

View File

@ -13,13 +13,17 @@ import sys
import warnings import warnings
import traceback import traceback
import contextlib import contextlib
from collections import namedtuple
from types import FunctionType, MethodType, ModuleType from types import FunctionType, MethodType, ModuleType
from six import PY2
from sphinx.util import logging from sphinx.util import logging
from sphinx.util.inspect import isenumclass, safe_getattr
if False: if False:
# For type annotation # For type annotation
from typing import Any, Generator, List, Set # NOQA from typing import Any, Callable, Dict, Generator, List, Optional, Set # NOQA
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -144,3 +148,86 @@ def import_module(modname, warningiserror=False):
# Importing modules may cause any side effects, including # Importing modules may cause any side effects, including
# SystemExit, so we need to catch all errors. # SystemExit, so we need to catch all errors.
raise ImportError(exc, traceback.format_exc()) raise ImportError(exc, traceback.format_exc())
def import_object(modname, objpath, objtype='', attrgetter=safe_getattr, warningiserror=False):
# type: (str, List[unicode], str, Callable[[Any, unicode], Any], bool) -> Any
if objpath:
logger.debug('[autodoc] from %s import %s', modname, '.'.join(objpath))
else:
logger.debug('[autodoc] import %s', modname)
try:
module = import_module(modname, warningiserror=warningiserror)
logger.debug('[autodoc] => %r', module)
obj = module
parent = None
object_name = None
for attrname in objpath:
parent = obj
logger.debug('[autodoc] getattr(_, %r)', attrname)
obj = attrgetter(obj, attrname)
logger.debug('[autodoc] => %r', obj)
object_name = attrname
return [module, parent, object_name, obj]
except (AttributeError, ImportError) as exc:
if objpath:
errmsg = ('autodoc: failed to import %s %r from module %r' %
(objtype, '.'.join(objpath), modname))
else:
errmsg = 'autodoc: failed to import %s %r' % (objtype, modname)
if isinstance(exc, ImportError):
# import_module() raises ImportError having real exception obj and
# traceback
real_exc, traceback_msg = exc.args
if isinstance(real_exc, SystemExit):
errmsg += ('; the module executes module level statement '
'and it might call sys.exit().')
elif isinstance(real_exc, ImportError):
errmsg += '; the following exception was raised:\n%s' % real_exc.args[0]
else:
errmsg += '; the following exception was raised:\n%s' % traceback_msg
else:
errmsg += '; the following exception was raised:\n%s' % traceback.format_exc()
if PY2:
errmsg = errmsg.decode('utf-8') # type: ignore
logger.debug(errmsg)
raise ImportError(errmsg)
Attribute = namedtuple('Attribute', ['name', 'directly_defined', 'value'])
def get_object_members(subject, objpath, attrgetter, analyzer=None):
# type: (Any, List[unicode], Callable, Any) -> Dict[str, Attribute] # NOQA
"""Get members and attributes of target object."""
# the members directly defined in the class
obj_dict = attrgetter(subject, '__dict__', {})
# Py34 doesn't have enum members in __dict__.
if sys.version_info[:2] == (3, 4) and isenumclass(subject):
obj_dict = dict(obj_dict)
for name, value in subject.__members__.items():
obj_dict[name] = value
members = {}
for name in dir(subject):
try:
value = attrgetter(subject, name)
directly_defined = name in obj_dict
members[name] = Attribute(name, directly_defined, value)
except AttributeError:
continue
if analyzer:
# append instance attributes (cf. self.attr1) if analyzer knows
from sphinx.ext.autodoc import INSTANCEATTR
namespace = '.'.join(objpath)
for (ns, name) in analyzer.find_attr_docs():
if namespace == ns and name not in members:
members[name] = Attribute(name, True, INSTANCEATTR)
return members

View File

@ -72,7 +72,8 @@ from sphinx import addnodes
from sphinx.environment.adapters.toctree import TocTree from sphinx.environment.adapters.toctree import TocTree
from sphinx.util import import_object, rst, logging from sphinx.util import import_object, rst, logging
from sphinx.pycode import ModuleAnalyzer, PycodeError from sphinx.pycode import ModuleAnalyzer, PycodeError
from sphinx.ext.autodoc import Options from sphinx.ext.autodoc import get_documenters
from sphinx.ext.autodoc.directive import DocumenterBridge, Options
from sphinx.ext.autodoc.importer import import_module from sphinx.ext.autodoc.importer import import_module
if False: if False:
@ -153,13 +154,13 @@ def autosummary_table_visit_html(self, node):
# -- autodoc integration ------------------------------------------------------- # -- autodoc integration -------------------------------------------------------
class FakeDirective(object): class FakeDirective(DocumenterBridge):
env = {} # type: Dict def __init__(self):
genopt = Options() super(FakeDirective, self).__init__({}, None, Options(), 0) # type: ignore
def get_documenter(obj, parent): def get_documenter(app, obj, parent):
# type: (Any, Any) -> Type[Documenter] # type: (Sphinx, Any, Any) -> Type[Documenter]
"""Get an autodoc.Documenter class suitable for documenting the given """Get an autodoc.Documenter class suitable for documenting the given
object. object.
@ -167,8 +168,7 @@ def get_documenter(obj, parent):
another Python object (e.g. a module or a class) to which *obj* another Python object (e.g. a module or a class) to which *obj*
belongs to. belongs to.
""" """
from sphinx.ext.autodoc import AutoDirective, DataDocumenter, \ from sphinx.ext.autodoc import DataDocumenter, ModuleDocumenter
ModuleDocumenter
if inspect.ismodule(obj): if inspect.ismodule(obj):
# ModuleDocumenter.can_document_member always returns False # ModuleDocumenter.can_document_member always returns False
@ -176,7 +176,7 @@ def get_documenter(obj, parent):
# Construct a fake documenter for *parent* # Construct a fake documenter for *parent*
if parent is not None: if parent is not None:
parent_doc_cls = get_documenter(parent, None) parent_doc_cls = get_documenter(app, parent, None)
else: else:
parent_doc_cls = ModuleDocumenter parent_doc_cls = ModuleDocumenter
@ -186,7 +186,7 @@ def get_documenter(obj, parent):
parent_doc = parent_doc_cls(FakeDirective(), "") parent_doc = parent_doc_cls(FakeDirective(), "")
# Get the corrent documenter class for *obj* # Get the corrent documenter class for *obj*
classes = [cls for cls in AutoDirective._registry.values() classes = [cls for cls in get_documenters(app).values()
if cls.can_document_member(obj, '', False, parent_doc)] if cls.can_document_member(obj, '', False, parent_doc)]
if classes: if classes:
classes.sort(key=lambda cls: cls.priority) classes.sort(key=lambda cls: cls.priority)
@ -289,7 +289,7 @@ class Autosummary(Directive):
full_name = modname + '::' + full_name[len(modname) + 1:] full_name = modname + '::' + full_name[len(modname) + 1:]
# NB. using full_name here is important, since Documenters # NB. using full_name here is important, since Documenters
# handle module prefixes slightly differently # handle module prefixes slightly differently
documenter = get_documenter(obj, parent)(self, full_name) documenter = get_documenter(self.env.app, obj, parent)(self, full_name)
if not documenter.parse_name(): if not documenter.parse_name():
self.warn('failed to parse name %s' % real_name) self.warn('failed to parse name %s' % real_name)
items.append((display_name, '', '', real_name)) items.append((display_name, '', '', real_name))
@ -325,7 +325,7 @@ class Autosummary(Directive):
# -- Grab the summary # -- Grab the summary
documenter.add_content(None) documenter.add_content(None)
doc = list(documenter.process_doc([self.result.data])) doc = self.result.data
while doc and not doc[0].strip(): while doc and not doc[0].strip():
doc.pop(0) doc.pop(0)
@ -615,7 +615,8 @@ def process_generate_options(app):
generate_autosummary_docs(genfiles, builder=app.builder, generate_autosummary_docs(genfiles, builder=app.builder,
warn=logger.warning, info=logger.info, warn=logger.warning, info=logger.info,
suffix=suffix, base_path=app.srcdir) suffix=suffix, base_path=app.srcdir,
app=app)
def setup(app): def setup(app):

View File

@ -33,24 +33,11 @@ from sphinx import __display_version__
from sphinx import package_dir from sphinx import package_dir
from sphinx.ext.autosummary import import_by_name, get_documenter from sphinx.ext.autosummary import import_by_name, get_documenter
from sphinx.jinja2glue import BuiltinTemplateLoader from sphinx.jinja2glue import BuiltinTemplateLoader
from sphinx.registry import SphinxComponentRegistry
from sphinx.util.osutil import ensuredir from sphinx.util.osutil import ensuredir
from sphinx.util.inspect import safe_getattr from sphinx.util.inspect import safe_getattr
from sphinx.util.rst import escape as rst_escape from sphinx.util.rst import escape as rst_escape
# Add documenters to AutoDirective registry
from sphinx.ext.autodoc import add_documenter, \
ModuleDocumenter, ClassDocumenter, ExceptionDocumenter, DataDocumenter, \
FunctionDocumenter, MethodDocumenter, AttributeDocumenter, \
InstanceAttributeDocumenter
add_documenter(ModuleDocumenter)
add_documenter(ClassDocumenter)
add_documenter(ExceptionDocumenter)
add_documenter(DataDocumenter)
add_documenter(FunctionDocumenter)
add_documenter(MethodDocumenter)
add_documenter(AttributeDocumenter)
add_documenter(InstanceAttributeDocumenter)
if False: if False:
# For type annotation # For type annotation
from typing import Any, Callable, Dict, Tuple, List # NOQA from typing import Any, Callable, Dict, Tuple, List # NOQA
@ -60,6 +47,30 @@ if False:
from sphinx.environment import BuildEnvironment # NOQA from sphinx.environment import BuildEnvironment # NOQA
class DummyApplication(object):
"""Dummy Application class for sphinx-autogen command."""
def __init__(self):
# type: () -> None
self.registry = SphinxComponentRegistry()
def setup_documenters(app):
# type: (Any) -> None
from sphinx.ext.autodoc import (
ModuleDocumenter, ClassDocumenter, ExceptionDocumenter, DataDocumenter,
FunctionDocumenter, MethodDocumenter, AttributeDocumenter,
InstanceAttributeDocumenter
)
documenters = [
ModuleDocumenter, ClassDocumenter, ExceptionDocumenter, DataDocumenter,
FunctionDocumenter, MethodDocumenter, AttributeDocumenter,
InstanceAttributeDocumenter
]
for documenter in documenters:
app.registry.add_documenter(documenter.objtype, documenter)
def _simple_info(msg): def _simple_info(msg):
# type: (unicode) -> None # type: (unicode) -> None
print(msg) print(msg)
@ -81,8 +92,8 @@ def _underline(title, line='='):
def generate_autosummary_docs(sources, output_dir=None, suffix='.rst', def generate_autosummary_docs(sources, output_dir=None, suffix='.rst',
warn=_simple_warn, info=_simple_info, warn=_simple_warn, info=_simple_info,
base_path=None, builder=None, template_dir=None, base_path=None, builder=None, template_dir=None,
imported_members=False): imported_members=False, app=None):
# type: (List[unicode], unicode, unicode, Callable, Callable, unicode, Builder, unicode, bool) -> None # NOQA # type: (List[unicode], unicode, unicode, Callable, Callable, unicode, Builder, unicode, bool, Any) -> None # NOQA
showed_sources = list(sorted(sources)) showed_sources = list(sorted(sources))
if len(showed_sources) > 20: if len(showed_sources) > 20:
@ -148,7 +159,7 @@ def generate_autosummary_docs(sources, output_dir=None, suffix='.rst',
new_files.append(fn) new_files.append(fn)
with open(fn, 'w') as f: with open(fn, 'w') as f:
doc = get_documenter(obj, parent) doc = get_documenter(app, obj, parent)
if template_name is not None: if template_name is not None:
template = template_env.get_template(template_name) template = template_env.get_template(template_name)
@ -167,7 +178,7 @@ def generate_autosummary_docs(sources, output_dir=None, suffix='.rst',
value = safe_getattr(obj, name) value = safe_getattr(obj, name)
except AttributeError: except AttributeError:
continue continue
documenter = get_documenter(value, obj) documenter = get_documenter(app, value, obj)
if documenter.objtype == typ: if documenter.objtype == typ:
if typ == 'method': if typ == 'method':
items.append(name) items.append(name)
@ -392,11 +403,14 @@ The format of the autosummary directive is documented in the
def main(argv=sys.argv[1:]): def main(argv=sys.argv[1:]):
# type: (List[str]) -> None # type: (List[str]) -> None
app = DummyApplication()
setup_documenters(app)
args = get_parser().parse_args(argv) args = get_parser().parse_args(argv)
generate_autosummary_docs(args.source_file, args.output_dir, generate_autosummary_docs(args.source_file, args.output_dir,
'.' + args.suffix, '.' + args.suffix,
template_dir=args.templates, template_dir=args.templates,
imported_members=args.imported_members) imported_members=args.imported_members,
app=app)
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -130,8 +130,8 @@ class InheritanceGraph(object):
graphviz dot graph from them. graphviz dot graph from them.
""" """
def __init__(self, class_names, currmodule, show_builtins=False, def __init__(self, class_names, currmodule, show_builtins=False,
private_bases=False, parts=0, aliases=None): private_bases=False, parts=0, aliases=None, top_classes=[]):
# type: (unicode, str, bool, bool, int, Optional[Dict[unicode, unicode]]) -> None # type: (unicode, str, bool, bool, int, Optional[Dict[unicode, unicode]], List[Any]) -> None # NOQA
"""*class_names* is a list of child classes to show bases from. """*class_names* is a list of child classes to show bases from.
If *show_builtins* is True, then Python builtins will be shown If *show_builtins* is True, then Python builtins will be shown
@ -140,7 +140,7 @@ class InheritanceGraph(object):
self.class_names = class_names self.class_names = class_names
classes = self._import_classes(class_names, currmodule) classes = self._import_classes(class_names, currmodule)
self.class_info = self._class_info(classes, show_builtins, self.class_info = self._class_info(classes, show_builtins,
private_bases, parts, aliases) private_bases, parts, aliases, top_classes)
if not self.class_info: if not self.class_info:
raise InheritanceException('No classes found for ' raise InheritanceException('No classes found for '
'inheritance diagram') 'inheritance diagram')
@ -153,13 +153,16 @@ class InheritanceGraph(object):
classes.extend(import_classes(name, currmodule)) classes.extend(import_classes(name, currmodule))
return classes return classes
def _class_info(self, classes, show_builtins, private_bases, parts, aliases): def _class_info(self, classes, show_builtins, private_bases, parts, aliases, top_classes):
# type: (List[Any], bool, bool, int, Optional[Dict[unicode, unicode]]) -> List[Tuple[unicode, unicode, List[unicode], unicode]] # NOQA # type: (List[Any], bool, bool, int, Optional[Dict[unicode, unicode]], List[Any]) -> List[Tuple[unicode, unicode, List[unicode], unicode]] # NOQA
"""Return name and bases for all classes that are ancestors of """Return name and bases for all classes that are ancestors of
*classes*. *classes*.
*parts* gives the number of dotted name parts that is removed from the *parts* gives the number of dotted name parts that is removed from the
displayed node names. displayed node names.
*top_classes* gives the name(s) of the top most ancestor class to traverse
to. Multiple names can be specified separated by comma.
""" """
all_classes = {} all_classes = {}
py_builtins = vars(builtins).values() py_builtins = vars(builtins).values()
@ -189,6 +192,10 @@ class InheritanceGraph(object):
baselist = [] # type: List[unicode] baselist = [] # type: List[unicode]
all_classes[cls] = (nodename, fullname, baselist, tooltip) all_classes[cls] = (nodename, fullname, baselist, tooltip)
if fullname in top_classes:
return
for base in cls.__bases__: for base in cls.__bases__:
if not show_builtins and base in py_builtins: if not show_builtins and base in py_builtins:
continue continue
@ -322,6 +329,7 @@ class InheritanceDiagram(Directive):
'parts': directives.nonnegative_int, 'parts': directives.nonnegative_int,
'private-bases': directives.flag, 'private-bases': directives.flag,
'caption': directives.unchanged, 'caption': directives.unchanged,
'top-classes': directives.unchanged_required,
} }
def run(self): def run(self):
@ -334,6 +342,11 @@ class InheritanceDiagram(Directive):
# Store the original content for use as a hash # Store the original content for use as a hash
node['parts'] = self.options.get('parts', 0) node['parts'] = self.options.get('parts', 0)
node['content'] = ', '.join(class_names) node['content'] = ', '.join(class_names)
node['top-classes'] = []
for cls in self.options.get('top-classes', '').split(','):
cls = cls.strip()
if cls:
node['top-classes'].append(cls)
# Create a graph starting with the list of classes # Create a graph starting with the list of classes
try: try:
@ -341,7 +354,8 @@ class InheritanceDiagram(Directive):
class_names, env.ref_context.get('py:module'), class_names, env.ref_context.get('py:module'),
parts=node['parts'], parts=node['parts'],
private_bases='private-bases' in self.options, private_bases='private-bases' in self.options,
aliases=env.config.inheritance_alias) aliases=env.config.inheritance_alias,
top_classes=node['top-classes'])
except InheritanceException as err: except InheritanceException as err:
return [node.document.reporter.warning(err.args[0], return [node.document.reporter.warning(err.args[0],
line=self.lineno)] line=self.lineno)]

View File

@ -71,12 +71,12 @@ def doctree_read(app, doctree):
code = analyzer.code.decode(analyzer.encoding) code = analyzer.code.decode(analyzer.encoding)
else: else:
code = analyzer.code code = analyzer.code
if entry is None or entry[0] != code: if entry is False:
return
elif entry is None or entry[0] != code:
analyzer.find_tags() analyzer.find_tags()
entry = code, analyzer.tags, {}, refname entry = code, analyzer.tags, {}, refname
env._viewcode_modules[modname] = entry # type: ignore env._viewcode_modules[modname] = entry # type: ignore
elif entry is False:
return
_, tags, used, _ = entry _, tags, used, _ = entry
if fullname in tags: if fullname in tags:
used[fullname] = docname used[fullname] = docname

View File

@ -23,7 +23,7 @@ from sphinx.transforms import (
ApplySourceWorkaround, ExtraTranslatableNodes, CitationReferences, ApplySourceWorkaround, ExtraTranslatableNodes, CitationReferences,
DefaultSubstitutions, MoveModuleTargets, HandleCodeBlocks, SortIds, DefaultSubstitutions, MoveModuleTargets, HandleCodeBlocks, SortIds,
AutoNumbering, AutoIndexUpgrader, FilterSystemMessages, AutoNumbering, AutoIndexUpgrader, FilterSystemMessages,
UnreferencedFootnotesDetector UnreferencedFootnotesDetector, SphinxSmartQuotes
) )
from sphinx.transforms.compact_bullet_list import RefOnlyBulletListTransform from sphinx.transforms.compact_bullet_list import RefOnlyBulletListTransform
from sphinx.transforms.i18n import ( from sphinx.transforms.i18n import (
@ -86,8 +86,15 @@ class SphinxStandaloneReader(SphinxBaseReader):
def __init__(self, app, *args, **kwargs): def __init__(self, app, *args, **kwargs):
# type: (Sphinx, Any, Any) -> None # type: (Sphinx, Any, Any) -> None
self.transforms = self.transforms + app.registry.get_transforms() self.transforms = self.transforms + app.registry.get_transforms()
self.smart_quotes = app.env.settings['smart_quotes']
SphinxBaseReader.__init__(self, *args, **kwargs) # type: ignore SphinxBaseReader.__init__(self, *args, **kwargs) # type: ignore
def get_transforms(self):
transforms = SphinxBaseReader.get_transforms(self)
if self.smart_quotes:
transforms.append(SphinxSmartQuotes)
return transforms
class SphinxI18nReader(SphinxBaseReader): class SphinxI18nReader(SphinxBaseReader):
""" """

View File

@ -15,8 +15,6 @@ from docutils.parsers.rst import states
from docutils.statemachine import StringList from docutils.statemachine import StringList
from docutils.transforms.universal import SmartQuotes from docutils.transforms.universal import SmartQuotes
from sphinx.transforms import SphinxSmartQuotes
if False: if False:
# For type annotation # For type annotation
from typing import Any, Dict, List, Type # NOQA from typing import Any, Dict, List, Type # NOQA
@ -63,10 +61,11 @@ class RSTParser(docutils.parsers.rst.Parser):
def get_transforms(self): def get_transforms(self):
# type: () -> List[Type[Transform]] # type: () -> List[Type[Transform]]
"""Sphinx's reST parser replaces a transform class for smart-quotes by own's""" """Sphinx's reST parser replaces a transform class for smart-quotes by own's
refs: sphinx.io.SphinxStandaloneReader"""
transforms = docutils.parsers.rst.Parser.get_transforms(self) transforms = docutils.parsers.rst.Parser.get_transforms(self)
transforms.remove(SmartQuotes) transforms.remove(SmartQuotes)
transforms.append(SphinxSmartQuotes)
return transforms return transforms
def parse(self, inputstring, document): def parse(self, inputstring, document):

View File

@ -38,6 +38,7 @@ if False:
from sphinx.builders import Builder # NOQA from sphinx.builders import Builder # NOQA
from sphinx.domains import Domain, Index # NOQA from sphinx.domains import Domain, Index # NOQA
from sphinx.environment import BuildEnvironment # NOQA from sphinx.environment import BuildEnvironment # NOQA
from sphinx.ext.autodoc import Documenter # NOQA
from sphinx.util.typing import RoleFunction # NOQA from sphinx.util.typing import RoleFunction # NOQA
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -51,7 +52,9 @@ EXTENSION_BLACKLIST = {
class SphinxComponentRegistry(object): class SphinxComponentRegistry(object):
def __init__(self): def __init__(self):
self.autodoc_attrgettrs = {} # type: Dict[Type, Callable[[Any, unicode, Any], Any]]
self.builders = {} # type: Dict[unicode, Type[Builder]] self.builders = {} # type: Dict[unicode, Type[Builder]]
self.documenters = {} # type: Dict[unicode, Type[Documenter]]
self.domains = {} # type: Dict[unicode, Type[Domain]] self.domains = {} # type: Dict[unicode, Type[Domain]]
self.domain_directives = {} # type: Dict[unicode, Dict[unicode, Any]] self.domain_directives = {} # type: Dict[unicode, Dict[unicode, Any]]
self.domain_indices = {} # type: Dict[unicode, List[Type[Index]]] self.domain_indices = {} # type: Dict[unicode, List[Type[Index]]]
@ -284,6 +287,14 @@ class SphinxComponentRegistry(object):
# type: () -> List[Type[Transform]] # type: () -> List[Type[Transform]]
return self.post_transforms return self.post_transforms
def add_documenter(self, objtype, documenter):
# type: (unicode, Type[Documenter]) -> None
self.documenters[objtype] = documenter
def add_autodoc_attrgetter(self, typ, attrgetter):
# type: (Type, Callable[[Any, unicode, Any], Any]) -> None
self.autodoc_attrgettrs[typ] = attrgetter
def load_extension(self, app, extname): def load_extension(self, app, extname):
# type: (Sphinx, unicode) -> None # type: (Sphinx, unicode) -> None
"""Load a Sphinx extension.""" """Load a Sphinx extension."""

View File

@ -18,7 +18,7 @@ from contextlib import contextmanager
import docutils import docutils
from docutils.languages import get_language from docutils.languages import get_language
from docutils.statemachine import ViewList from docutils.statemachine import StateMachine, ViewList
from docutils.parsers.rst import directives, roles, convert_directive_function from docutils.parsers.rst import directives, roles, convert_directive_function
from docutils.utils import Reporter from docutils.utils import Reporter
@ -31,8 +31,9 @@ report_re = re.compile('^(.+?:(?:\\d+)?): \\((DEBUG|INFO|WARNING|ERROR|SEVERE)/(
if False: if False:
# For type annotation # For type annotation
from typing import Any, Callable, Iterator, List, Tuple # NOQA from typing import Any, Callable, Generator, Iterator, List, Tuple # NOQA
from docutils import nodes # NOQA from docutils import nodes # NOQA
from docutils.statemachine import State # NOQA
from sphinx.environment import BuildEnvironment # NOQA from sphinx.environment import BuildEnvironment # NOQA
from sphinx.io import SphinxFileInput # NOQA from sphinx.io import SphinxFileInput # NOQA
@ -216,3 +217,22 @@ def directive_helper(obj, has_content=None, argument_spec=None, **option_spec):
raise ExtensionError(__('when adding directive classes, no ' raise ExtensionError(__('when adding directive classes, no '
'additional arguments may be given')) 'additional arguments may be given'))
return obj return obj
@contextmanager
def switch_source_input(state, content):
# type: (State, ViewList) -> Generator
"""Switch current source input of state temporarily."""
try:
# remember the original ``get_source_and_line()`` method
get_source_and_line = state.memo.reporter.get_source_and_line
# replace it by new one
state_machine = StateMachine([], None)
state_machine.input_lines = content
state.memo.reporter.get_source_and_line = state_machine.get_source_and_line
yield
finally:
# restore the method
state.memo.reporter.get_source_and_line = get_source_and_line

View File

@ -53,7 +53,7 @@ VERBOSITY_MAP.update({
COLOR_MAP = defaultdict(lambda: 'blue') # type: Dict[int, unicode] COLOR_MAP = defaultdict(lambda: 'blue') # type: Dict[int, unicode]
COLOR_MAP.update({ COLOR_MAP.update({
logging.ERROR: 'darkred', logging.ERROR: 'darkred',
logging.WARNING: 'darkred', logging.WARNING: 'red',
logging.DEBUG: 'darkgray', logging.DEBUG: 'darkgray',
}) })

View File

@ -549,7 +549,7 @@ class LaTeXTranslator(nodes.NodeVisitor):
'author': document.settings.author, # treat as a raw LaTeX code 'author': document.settings.author, # treat as a raw LaTeX code
'indexname': _('Index'), 'indexname': _('Index'),
}) })
if not self.elements['releasename']: if not self.elements['releasename'] and self.elements['release']:
self.elements.update({ self.elements.update({
'releasename': _('Release'), 'releasename': _('Release'),
}) })

View File

@ -35,14 +35,6 @@ def pytest_report_header(config):
sys.version.split()[0]) sys.version.split()[0])
def _filter_warnings():
def ignore(**kwargs): warnings.filterwarnings('ignore', **kwargs)
ignore(category=DeprecationWarning, module='site') # virtualenv
ignore(category=PendingDeprecationWarning, module=r'_pytest\..*')
ignore(category=ImportWarning, module='pkgutil')
def _initialize_test_directory(session): def _initialize_test_directory(session):
testroot = os.path.join(str(session.config.rootdir), 'tests') testroot = os.path.join(str(session.config.rootdir), 'tests')
tempdir = os.path.abspath(os.getenv('SPHINX_TEST_TEMPDIR', tempdir = os.path.abspath(os.getenv('SPHINX_TEST_TEMPDIR',
@ -58,5 +50,4 @@ def _initialize_test_directory(session):
def pytest_sessionstart(session): def pytest_sessionstart(session):
_filter_warnings()
_initialize_test_directory(session) _initialize_test_directory(session)

View File

@ -21,6 +21,7 @@ from docutils.statemachine import ViewList
from sphinx.ext.autodoc import AutoDirective, add_documenter, \ from sphinx.ext.autodoc import AutoDirective, add_documenter, \
ModuleLevelDocumenter, FunctionDocumenter, cut_lines, between, ALL ModuleLevelDocumenter, FunctionDocumenter, cut_lines, between, ALL
from sphinx.util import logging
app = None app = None
@ -30,7 +31,7 @@ def setup_module(rootdir, sphinx_test_tempdir):
global app global app
srcdir = sphinx_test_tempdir / 'autodoc-root' srcdir = sphinx_test_tempdir / 'autodoc-root'
if not srcdir.exists(): if not srcdir.exists():
(rootdir/'test-root').copytree(srcdir) (rootdir / 'test-root').copytree(srcdir)
app = SphinxTestApp(srcdir=srcdir) app = SphinxTestApp(srcdir=srcdir)
app.builder.env.app = app app.builder.env.app = app
app.builder.env.temp_data['docname'] = 'dummy' app.builder.env.temp_data['docname'] = 'dummy'
@ -47,7 +48,7 @@ directive = options = None
@pytest.fixture @pytest.fixture
def setup_test(): def setup_test():
global options, directive global options, directive
global processed_docstrings, processed_signatures, _warnings global processed_docstrings, processed_signatures
options = Struct( options = Struct(
inherited_members = False, inherited_members = False,
@ -70,24 +71,17 @@ def setup_test():
env = app.builder.env, env = app.builder.env,
genopt = options, genopt = options,
result = ViewList(), result = ViewList(),
warn = warnfunc,
filename_set = set(), filename_set = set(),
) )
processed_docstrings = [] processed_docstrings = []
processed_signatures = [] processed_signatures = []
_warnings = []
_warnings = []
processed_docstrings = [] processed_docstrings = []
processed_signatures = [] processed_signatures = []
def warnfunc(msg):
_warnings.append(msg)
def process_docstring(app, what, name, obj, options, lines): def process_docstring(app, what, name, obj, options, lines):
processed_docstrings.append((what, name)) processed_docstrings.append((what, name))
if name == 'bar': if name == 'bar':
@ -111,20 +105,21 @@ def skip_member(app, what, name, obj, skip, options):
@pytest.mark.usefixtures('setup_test') @pytest.mark.usefixtures('setup_test')
def test_generate(): def test_generate():
logging.setup(app, app._status, app._warning)
def assert_warns(warn_str, objtype, name, **kw): def assert_warns(warn_str, objtype, name, **kw):
inst = AutoDirective._registry[objtype](directive, name) inst = app.registry.documenters[objtype](directive, name)
inst.generate(**kw) inst.generate(**kw)
assert len(directive.result) == 0, directive.result assert len(directive.result) == 0, directive.result
assert len(_warnings) == 1, _warnings assert warn_str in app._warning.getvalue()
assert warn_str in _warnings[0], _warnings app._warning.truncate(0)
del _warnings[:]
def assert_works(objtype, name, **kw): def assert_works(objtype, name, **kw):
inst = AutoDirective._registry[objtype](directive, name) inst = app.registry.documenters[objtype](directive, name)
inst.generate(**kw) inst.generate(**kw)
assert directive.result assert directive.result
# print '\n'.join(directive.result) # print '\n'.join(directive.result)
assert len(_warnings) == 0, _warnings assert app._warning.getvalue() == ''
del directive.result[:] del directive.result[:]
def assert_processes(items, objtype, name, **kw): def assert_processes(items, objtype, name, **kw):
@ -134,18 +129,18 @@ def test_generate():
assert set(processed_docstrings) | set(processed_signatures) == set(items) assert set(processed_docstrings) | set(processed_signatures) == set(items)
def assert_result_contains(item, objtype, name, **kw): def assert_result_contains(item, objtype, name, **kw):
inst = AutoDirective._registry[objtype](directive, name) inst = app.registry.documenters[objtype](directive, name)
inst.generate(**kw) inst.generate(**kw)
# print '\n'.join(directive.result) # print '\n'.join(directive.result)
assert len(_warnings) == 0, _warnings assert app._warning.getvalue() == ''
assert item in directive.result assert item in directive.result
del directive.result[:] del directive.result[:]
def assert_order(items, objtype, name, member_order, **kw): def assert_order(items, objtype, name, member_order, **kw):
inst = AutoDirective._registry[objtype](directive, name) inst = app.registry.documenters[objtype](directive, name)
inst.options.member_order = member_order inst.options.member_order = member_order
inst.generate(**kw) inst.generate(**kw)
assert len(_warnings) == 0, _warnings assert app._warning.getvalue() == ''
items = list(reversed(items)) items = list(reversed(items))
lineiter = iter(directive.result) lineiter = iter(directive.result)
# for line in directive.result: # for line in directive.result:

View File

@ -0,0 +1,5 @@
Basic Diagram
==============
.. inheritance-diagram::
dummy.test

View File

@ -0,0 +1,6 @@
import sys, os
sys.path.insert(0, os.path.abspath('.'))
extensions = ['sphinx.ext.inheritance_diagram']
source_suffix = '.rst'

View File

@ -0,0 +1,4 @@
.. toctree::
:glob:
*

View File

@ -0,0 +1,6 @@
Diagram using module with 2 top classes
=======================================
.. inheritance-diagram::
dummy.test
:top-classes: dummy.test.B, dummy.test.C

View File

@ -0,0 +1,7 @@
Diagram using 1 top class
=========================
.. inheritance-diagram::
dummy.test
:top-classes: dummy.test.B

View File

@ -0,0 +1,9 @@
Diagram using 2 top classes
===========================
.. inheritance-diagram::
dummy.test.F
dummy.test.D
dummy.test.E
:top-classes: dummy.test.B, dummy.test.C

View File

@ -0,0 +1,7 @@
Diagram using the parts option
==============================
.. inheritance-diagram::
dummy.test
:parts: 1

View File

@ -0,0 +1,30 @@
"""
Test with a class diagram like this::
A
/ \
B C
/ \ / \
E D F
"""
class A(object):
pass
class B(A):
pass
class C(A):
pass
class D(B, C):
pass
class E(B):
pass
class F(C):
pass

View File

@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
master_doc = 'index'
latex_documents = [
(master_doc, 'test.tex', 'The basic Sphinx documentation for testing', 'Sphinx', 'report')
]

View File

@ -0,0 +1,4 @@
test-smartquotes
================
-- "Sphinx" is a tool that makes it easy ...

View File

@ -20,6 +20,7 @@ from docutils.statemachine import ViewList
from sphinx.ext.autodoc import AutoDirective, add_documenter, \ from sphinx.ext.autodoc import AutoDirective, add_documenter, \
ModuleLevelDocumenter, FunctionDocumenter, cut_lines, between, ALL ModuleLevelDocumenter, FunctionDocumenter, cut_lines, between, ALL
from sphinx.util import logging
app = None app = None
@ -51,7 +52,7 @@ directive = options = None
@pytest.fixture @pytest.fixture
def setup_test(): def setup_test():
global options, directive global options, directive
global processed_docstrings, processed_signatures, _warnings global processed_docstrings, processed_signatures
options = Struct( options = Struct(
inherited_members = False, inherited_members = False,
@ -75,28 +76,24 @@ def setup_test():
env = app.builder.env, env = app.builder.env,
genopt = options, genopt = options,
result = ViewList(), result = ViewList(),
warn = warnfunc,
filename_set = set(), filename_set = set(),
) )
processed_docstrings = [] processed_docstrings = []
processed_signatures = [] processed_signatures = []
_warnings = []
app._status.truncate(0)
app._warning.truncate(0)
yield yield
AutoDirective._special_attrgetters.clear() AutoDirective._special_attrgetters.clear()
_warnings = []
processed_docstrings = [] processed_docstrings = []
processed_signatures = [] processed_signatures = []
def warnfunc(msg):
_warnings.append(msg)
def process_docstring(app, what, name, obj, options, lines): def process_docstring(app, what, name, obj, options, lines):
processed_docstrings.append((what, name)) processed_docstrings.append((what, name))
if name == 'bar': if name == 'bar':
@ -120,8 +117,10 @@ def skip_member(app, what, name, obj, skip, options):
@pytest.mark.usefixtures('setup_test') @pytest.mark.usefixtures('setup_test')
def test_parse_name(): def test_parse_name():
logging.setup(app, app._status, app._warning)
def verify(objtype, name, result): def verify(objtype, name, result):
inst = AutoDirective._registry[objtype](directive, name) inst = app.registry.documenters[objtype](directive, name)
assert inst.parse_name() assert inst.parse_name()
assert (inst.modname, inst.objpath, inst.args, inst.retann) == result assert (inst.modname, inst.objpath, inst.args, inst.retann) == result
@ -129,8 +128,7 @@ def test_parse_name():
verify('module', 'test_autodoc', ('test_autodoc', [], None, None)) verify('module', 'test_autodoc', ('test_autodoc', [], None, None))
verify('module', 'test.test_autodoc', ('test.test_autodoc', [], None, None)) verify('module', 'test.test_autodoc', ('test.test_autodoc', [], None, None))
verify('module', 'test(arg)', ('test', [], 'arg', None)) verify('module', 'test(arg)', ('test', [], 'arg', None))
assert 'signature arguments' in _warnings[0] assert 'signature arguments' in app._warning.getvalue()
del _warnings[:]
# for functions/classes # for functions/classes
verify('function', 'test_autodoc.raises', verify('function', 'test_autodoc.raises',
@ -164,7 +162,7 @@ def test_parse_name():
@pytest.mark.usefixtures('setup_test') @pytest.mark.usefixtures('setup_test')
def test_format_signature(): def test_format_signature():
def formatsig(objtype, name, obj, args, retann): def formatsig(objtype, name, obj, args, retann):
inst = AutoDirective._registry[objtype](directive, name) inst = app.registry.documenters[objtype](directive, name)
inst.fullname = name inst.fullname = name
inst.doc_as_attr = False # for class objtype inst.doc_as_attr = False # for class objtype
inst.object = obj inst.object = obj
@ -246,7 +244,6 @@ def test_format_signature():
# test exception handling (exception is caught and args is '') # test exception handling (exception is caught and args is '')
directive.env.config.autodoc_docstring_signature = False directive.env.config.autodoc_docstring_signature = False
assert formatsig('function', 'int', int, None, None) == '' assert formatsig('function', 'int', int, None, None) == ''
del _warnings[:]
# test processing by event handler # test processing by event handler
assert formatsig('method', 'bar', H.foo1, None, None) == '42' assert formatsig('method', 'bar', H.foo1, None, None) == '42'
@ -270,7 +267,7 @@ def test_format_signature():
@pytest.mark.usefixtures('setup_test') @pytest.mark.usefixtures('setup_test')
def test_get_doc(): def test_get_doc():
def getdocl(objtype, obj, encoding=None): def getdocl(objtype, obj, encoding=None):
inst = AutoDirective._registry[objtype](directive, 'tmp') inst = app.registry.documenters[objtype](directive, 'tmp')
inst.object = obj inst.object = obj
inst.objpath = [obj.__name__] inst.objpath = [obj.__name__]
inst.doc_as_attr = False inst.doc_as_attr = False
@ -449,7 +446,7 @@ def test_get_doc():
@pytest.mark.usefixtures('setup_test') @pytest.mark.usefixtures('setup_test')
def test_docstring_processing(): def test_docstring_processing():
def process(objtype, name, obj): def process(objtype, name, obj):
inst = AutoDirective._registry[objtype](directive, name) inst = app.registry.documenters[objtype](directive, name)
inst.object = obj inst.object = obj
inst.fullname = name inst.fullname = name
return list(inst.process_doc(inst.get_doc())) return list(inst.process_doc(inst.get_doc()))
@ -506,7 +503,7 @@ def test_docstring_property_processing():
def genarate_docstring(objtype, name, **kw): def genarate_docstring(objtype, name, **kw):
del processed_docstrings[:] del processed_docstrings[:]
del processed_signatures[:] del processed_signatures[:]
inst = AutoDirective._registry[objtype](directive, name) inst = app.registry.documenters[objtype](directive, name)
inst.generate(**kw) inst.generate(**kw)
results = list(directive.result) results = list(directive.result)
docstrings = inst.get_doc()[0] docstrings = inst.get_doc()[0]
@ -540,6 +537,8 @@ def test_docstring_property_processing():
@pytest.mark.usefixtures('setup_test') @pytest.mark.usefixtures('setup_test')
def test_new_documenter(): def test_new_documenter():
logging.setup(app, app._status, app._warning)
class MyDocumenter(ModuleLevelDocumenter): class MyDocumenter(ModuleLevelDocumenter):
objtype = 'integer' objtype = 'integer'
directivetype = 'data' directivetype = 'data'
@ -555,10 +554,11 @@ def test_new_documenter():
add_documenter(MyDocumenter) add_documenter(MyDocumenter)
def assert_result_contains(item, objtype, name, **kw): def assert_result_contains(item, objtype, name, **kw):
inst = AutoDirective._registry[objtype](directive, name) app._warning.truncate(0)
inst = app.registry.documenters[objtype](directive, name)
inst.generate(**kw) inst.generate(**kw)
# print '\n'.join(directive.result) # print '\n'.join(directive.result)
assert len(_warnings) == 0, _warnings assert app._warning.getvalue() == ''
assert item in directive.result assert item in directive.result
del directive.result[:] del directive.result[:]
@ -581,7 +581,7 @@ def test_attrgetter_using():
AutoDirective._special_attrgetters[type] = special_getattr AutoDirective._special_attrgetters[type] = special_getattr
del getattr_spy[:] del getattr_spy[:]
inst = AutoDirective._registry[objtype](directive, name) inst = app.registry.documenters[objtype](directive, name)
inst.generate(**kw) inst.generate(**kw)
hooked_members = [s[1] for s in getattr_spy] hooked_members = [s[1] for s in getattr_spy]
@ -602,20 +602,22 @@ def test_attrgetter_using():
@pytest.mark.usefixtures('setup_test') @pytest.mark.usefixtures('setup_test')
def test_generate(): def test_generate():
logging.setup(app, app._status, app._warning)
def assert_warns(warn_str, objtype, name, **kw): def assert_warns(warn_str, objtype, name, **kw):
inst = AutoDirective._registry[objtype](directive, name) inst = app.registry.documenters[objtype](directive, name)
inst.generate(**kw) inst.generate(**kw)
assert len(directive.result) == 0, directive.result assert len(directive.result) == 0, directive.result
assert len(_warnings) == 1, _warnings
assert warn_str in _warnings[0], _warnings assert warn_str in app._warning.getvalue()
del _warnings[:] app._warning.truncate(0)
def assert_works(objtype, name, **kw): def assert_works(objtype, name, **kw):
inst = AutoDirective._registry[objtype](directive, name) inst = app.registry.documenters[objtype](directive, name)
inst.generate(**kw) inst.generate(**kw)
assert directive.result assert directive.result
# print '\n'.join(directive.result) # print '\n'.join(directive.result)
assert len(_warnings) == 0, _warnings assert app._warning.getvalue() == ''
del directive.result[:] del directive.result[:]
def assert_processes(items, objtype, name, **kw): def assert_processes(items, objtype, name, **kw):
@ -625,18 +627,18 @@ def test_generate():
assert set(processed_docstrings) | set(processed_signatures) == set(items) assert set(processed_docstrings) | set(processed_signatures) == set(items)
def assert_result_contains(item, objtype, name, **kw): def assert_result_contains(item, objtype, name, **kw):
inst = AutoDirective._registry[objtype](directive, name) inst = app.registry.documenters[objtype](directive, name)
inst.generate(**kw) inst.generate(**kw)
# print '\n'.join(directive.result) # print '\n'.join(directive.result)
assert len(_warnings) == 0, _warnings assert app._warning.getvalue() == ''
assert item in directive.result assert item in directive.result
del directive.result[:] del directive.result[:]
def assert_order(items, objtype, name, member_order, **kw): def assert_order(items, objtype, name, member_order, **kw):
inst = AutoDirective._registry[objtype](directive, name) inst = app.registry.documenters[objtype](directive, name)
inst.options.member_order = member_order inst.options.member_order = member_order
inst.generate(**kw) inst.generate(**kw)
assert len(_warnings) == 0, _warnings assert app._warning.getvalue() == ''
items = list(reversed(items)) items = list(reversed(items))
lineiter = iter(directive.result) lineiter = iter(directive.result)
# for line in directive.result: # for line in directive.result:

View File

@ -165,13 +165,15 @@ def test_latex_warnings(app, status, warning):
@pytest.mark.sphinx('latex', testroot='basic') @pytest.mark.sphinx('latex', testroot='basic')
def test_latex_title(app, status, warning): def test_latex_basic(app, status, warning):
app.builder.build_all() app.builder.build_all()
result = (app.outdir / 'test.tex').text(encoding='utf8') result = (app.outdir / 'test.tex').text(encoding='utf8')
print(result) print(result)
print(status.getvalue()) print(status.getvalue())
print(warning.getvalue()) print(warning.getvalue())
assert '\\title{The basic Sphinx documentation for testing}' in result assert r'\title{The basic Sphinx documentation for testing}' in result
assert r'\release{}' in result
assert r'\renewcommand{\releasename}{}' in result
@pytest.mark.sphinx('latex', testroot='latex-title') @pytest.mark.sphinx('latex', testroot='latex-title')
@ -184,6 +186,18 @@ def test_latex_title_after_admonitions(app, status, warning):
assert '\\title{test-latex-title}' in result assert '\\title{test-latex-title}' in result
@pytest.mark.sphinx('latex', testroot='basic',
confoverrides={'release': '1.0'})
def test_latex_release(app, status, warning):
app.builder.build_all()
result = (app.outdir / 'test.tex').text(encoding='utf8')
print(result)
print(status.getvalue())
print(warning.getvalue())
assert r'\release{1.0}' in result
assert r'\renewcommand{\releasename}{Release}' in result
@pytest.mark.sphinx('latex', testroot='numfig', @pytest.mark.sphinx('latex', testroot='numfig',
confoverrides={'numfig': True}) confoverrides={'numfig': True})
def test_numref(app, status, warning): def test_numref(app, status, warning):

View File

@ -57,10 +57,14 @@ def test_mangle_signature():
@pytest.mark.sphinx('dummy', **default_kw) @pytest.mark.sphinx('dummy', **default_kw)
def test_get_items_summary(app, status, warning): def test_get_items_summary(make_app, app_params):
import sphinx.ext.autosummary
import sphinx.ext.autosummary.generate
args, kwargs = app_params
app = make_app(*args, **kwargs)
sphinx.ext.autosummary.generate.setup_documenters(app)
# monkey-patch Autosummary.get_items so we can easily get access to it's # monkey-patch Autosummary.get_items so we can easily get access to it's
# results.. # results..
import sphinx.ext.autosummary
orig_get_items = sphinx.ext.autosummary.Autosummary.get_items orig_get_items = sphinx.ext.autosummary.Autosummary.get_items
autosummary_items = {} autosummary_items = {}
@ -73,6 +77,10 @@ def test_get_items_summary(app, status, warning):
def handler(app, what, name, obj, options, lines): def handler(app, what, name, obj, options, lines):
assert isinstance(lines, list) assert isinstance(lines, list)
# ensure no docstring is processed twice:
assert 'THIS HAS BEEN HANDLED' not in lines
lines.append('THIS HAS BEEN HANDLED')
app.connect('autodoc-process-docstring', handler) app.connect('autodoc-process-docstring', handler)
sphinx.ext.autosummary.Autosummary.get_items = new_get_items sphinx.ext.autosummary.Autosummary.get_items = new_get_items
@ -81,7 +89,7 @@ def test_get_items_summary(app, status, warning):
finally: finally:
sphinx.ext.autosummary.Autosummary.get_items = orig_get_items sphinx.ext.autosummary.Autosummary.get_items = orig_get_items
html_warnings = warning.getvalue() html_warnings = app._warning.getvalue()
assert html_warnings == '' assert html_warnings == ''
expected_values = { expected_values = {

View File

@ -0,0 +1,133 @@
# -*- coding: utf-8 -*-
"""
test_inheritance
~~~~~~~~~~~~~~~~
Tests for :mod:`sphinx.ext.inheritance_diagram` module.
:copyright: Copyright 2015 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import os
import pytest
from sphinx.ext.inheritance_diagram import InheritanceDiagram
@pytest.mark.sphinx(buildername="html", testroot="inheritance")
@pytest.mark.usefixtures('if_graphviz_found')
def test_inheritance_diagram(app, status, warning):
# monkey-patch InheritaceDiagram.run() so we can get access to its
# results.
orig_run = InheritanceDiagram.run
graphs = {}
def new_run(self):
result = orig_run(self)
node = result[0]
source = os.path.basename(node.document.current_source).replace(".rst", "")
graphs[source] = node['graph']
return result
InheritanceDiagram.run = new_run
try:
app.builder.build_all()
finally:
InheritanceDiagram.run = orig_run
assert app.statuscode == 0
html_warnings = warning.getvalue()
assert html_warnings == ""
# note: it is better to split these asserts into separate test functions
# but I can't figure out how to build only a specific .rst file
# basic inheritance diagram showing all classes
for cls in graphs['basic_diagram'].class_info:
# use in b/c traversing order is different sometimes
assert cls in [
('dummy.test.A', 'dummy.test.A', [], None),
('dummy.test.F', 'dummy.test.F', ['dummy.test.C'], None),
('dummy.test.C', 'dummy.test.C', ['dummy.test.A'], None),
('dummy.test.E', 'dummy.test.E', ['dummy.test.B'], None),
('dummy.test.D', 'dummy.test.D',
['dummy.test.B', 'dummy.test.C'], None),
('dummy.test.B', 'dummy.test.B', ['dummy.test.A'], None)
]
# inheritance diagram using :parts: 1 option
for cls in graphs['diagram_w_parts'].class_info:
assert cls in [
('A', 'dummy.test.A', [], None),
('F', 'dummy.test.F', ['C'], None),
('C', 'dummy.test.C', ['A'], None),
('E', 'dummy.test.E', ['B'], None),
('D', 'dummy.test.D', ['B', 'C'], None),
('B', 'dummy.test.B', ['A'], None)
]
# inheritance diagram with 1 top class
# :top-classes: dummy.test.B
# rendering should be
# A
# \
# B C
# / \ / \
# E D F
#
for cls in graphs['diagram_w_1_top_class'].class_info:
assert cls in [
('dummy.test.A', 'dummy.test.A', [], None),
('dummy.test.F', 'dummy.test.F', ['dummy.test.C'], None),
('dummy.test.C', 'dummy.test.C', ['dummy.test.A'], None),
('dummy.test.E', 'dummy.test.E', ['dummy.test.B'], None),
('dummy.test.D', 'dummy.test.D',
['dummy.test.B', 'dummy.test.C'], None),
('dummy.test.B', 'dummy.test.B', [], None)
]
# inheritance diagram with 2 top classes
# :top-classes: dummy.test.B, dummy.test.C
# Note: we're specifying separate classes, not the entire module here
# rendering should be
#
# B C
# / \ / \
# E D F
#
for cls in graphs['diagram_w_2_top_classes'].class_info:
assert cls in [
('dummy.test.F', 'dummy.test.F', ['dummy.test.C'], None),
('dummy.test.C', 'dummy.test.C', [], None),
('dummy.test.E', 'dummy.test.E', ['dummy.test.B'], None),
('dummy.test.D', 'dummy.test.D',
['dummy.test.B', 'dummy.test.C'], None),
('dummy.test.B', 'dummy.test.B', [], None)
]
# inheritance diagram with 2 top classes and specifiying the entire module
# rendering should be
#
# A
# B C
# / \ / \
# E D F
#
# Note: dummy.test.A is included in the graph before its descendants are even processed
# b/c we've specified to load the entire module. The way InheritanceGraph works it is very
# hard to exclude parent classes once after they have been included in the graph.
# If you'd like to not show class A in the graph don't specify the entire module.
# this is a known issue.
for cls in graphs['diagram_module_w_2_top_classes'].class_info:
assert cls in [
('dummy.test.F', 'dummy.test.F', ['dummy.test.C'], None),
('dummy.test.C', 'dummy.test.C', [], None),
('dummy.test.E', 'dummy.test.E', ['dummy.test.B'], None),
('dummy.test.D', 'dummy.test.D',
['dummy.test.B', 'dummy.test.C'], None),
('dummy.test.B', 'dummy.test.B', [], None),
('dummy.test.A', 'dummy.test.A', [], None),
]

92
tests/test_smartquotes.py Normal file
View File

@ -0,0 +1,92 @@
# -*- coding: utf-8 -*-
"""
test_smartquotes
~~~~~~~~~~~~~~~~
Test smart quotes.
:copyright: Copyright 2007-2018 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import pytest
from sphinx.util import docutils
@pytest.mark.sphinx(buildername='html', testroot='smartquotes', freshenv=True)
def test_basic(app, status, warning):
app.build()
content = (app.outdir / 'index.html').text()
assert u'<p> “Sphinx” is a tool that makes it easy …</p>' in content
@pytest.mark.sphinx(buildername='text', testroot='smartquotes', freshenv=True)
def test_text_builder(app, status, warning):
app.build()
content = (app.outdir / 'index.txt').text()
assert u'-- "Sphinx" is a tool that makes it easy ...' in content
@pytest.mark.sphinx(buildername='man', testroot='smartquotes', freshenv=True)
def test_man_builder(app, status, warning):
app.build()
content = (app.outdir / 'python.1').text()
assert u'\\-\\- "Sphinx" is a tool that makes it easy ...' in content
@pytest.mark.sphinx(buildername='latex', testroot='smartquotes', freshenv=True)
def test_latex_builder(app, status, warning):
app.build()
content = (app.outdir / 'test.tex').text()
assert u'\\textendash{} “Sphinx” is a tool that makes it easy …' in content
@pytest.mark.sphinx(buildername='html', testroot='smartquotes', freshenv=True,
confoverrides={'language': 'ja'})
def test_ja_html_builder(app, status, warning):
app.build()
content = (app.outdir / 'index.html').text()
assert u'<p>-- &quot;Sphinx&quot; is a tool that makes it easy ...</p>' in content
@pytest.mark.sphinx(buildername='html', testroot='smartquotes', freshenv=True,
confoverrides={'smartquotes': False})
def test_smartquotes_disabled(app, status, warning):
app.build()
content = (app.outdir / 'index.html').text()
assert u'<p>-- &quot;Sphinx&quot; is a tool that makes it easy ...</p>' in content
@pytest.mark.skipif(docutils.__version_info__ < (0, 14),
reason='docutils-0.14 or above is required')
@pytest.mark.sphinx(buildername='html', testroot='smartquotes', freshenv=True,
confoverrides={'smartquotes_action': 'q'})
def test_smartquotes_action(app, status, warning):
app.build()
content = (app.outdir / 'index.html').text()
assert u'<p>-- “Sphinx” is a tool that makes it easy ...</p>' in content
@pytest.mark.sphinx(buildername='html', testroot='smartquotes', freshenv=True,
confoverrides={'language': 'ja', 'smartquotes_excludes': {}})
def test_smartquotes_excludes_language(app, status, warning):
app.build()
content = (app.outdir / 'index.html').text()
assert u'<p> 「Sphinx」 is a tool that makes it easy …</p>' in content
@pytest.mark.sphinx(buildername='man', testroot='smartquotes', freshenv=True,
confoverrides={'smartquotes_excludes': {}})
def test_smartquotes_excludes_builders(app, status, warning):
app.build()
content = (app.outdir / 'python.1').text()
assert u' “Sphinx” is a tool that makes it easy …' in content

View File

@ -10,10 +10,14 @@
""" """
import pytest import pytest
from sphinx.ext.autosummary.generate import setup_documenters
@pytest.mark.sphinx('html', testroot='templating') @pytest.mark.sphinx('html', testroot='templating')
def test_layout_overloading(app, status, warning): def test_layout_overloading(make_app, app_params):
args, kwargs = app_params
app = make_app(*args, **kwargs)
setup_documenters(app)
app.builder.build_update() app.builder.build_update()
result = (app.outdir / 'contents.html').text(encoding='utf-8') result = (app.outdir / 'contents.html').text(encoding='utf-8')
@ -22,7 +26,10 @@ def test_layout_overloading(app, status, warning):
@pytest.mark.sphinx('html', testroot='templating') @pytest.mark.sphinx('html', testroot='templating')
def test_autosummary_class_template_overloading(app, status, warning): def test_autosummary_class_template_overloading(make_app, app_params):
args, kwargs = app_params
app = make_app(*args, **kwargs)
setup_documenters(app)
app.builder.build_update() app.builder.build_update()
result = (app.outdir / 'generated' / 'sphinx.application.TemplateBridge.html').text( result = (app.outdir / 'generated' / 'sphinx.application.TemplateBridge.html').text(

View File

@ -183,7 +183,7 @@ def test_warning_location(app, status, warning):
assert 'index.txt:10: WARNING: message2' in warning.getvalue() assert 'index.txt:10: WARNING: message2' in warning.getvalue()
logger.warning('message3', location=None) logger.warning('message3', location=None)
assert colorize('darkred', 'WARNING: message3') in warning.getvalue() assert colorize('red', 'WARNING: message3') in warning.getvalue()
node = nodes.Node() node = nodes.Node()
node.source, node.line = ('index.txt', 10) node.source, node.line = ('index.txt', 10)
@ -200,7 +200,7 @@ def test_warning_location(app, status, warning):
node.source, node.line = (None, None) node.source, node.line = (None, None)
logger.warning('message7', location=node) logger.warning('message7', location=node)
assert colorize('darkred', 'WARNING: message7') in warning.getvalue() assert colorize('red', 'WARNING: message7') in warning.getvalue()
def test_pending_warnings(app, status, warning): def test_pending_warnings(app, status, warning):
@ -236,7 +236,7 @@ def test_colored_logs(app, status, warning):
assert colorize('darkgray', 'message1') in status.getvalue() assert colorize('darkgray', 'message1') in status.getvalue()
assert 'message2\n' in status.getvalue() # not colored assert 'message2\n' in status.getvalue() # not colored
assert 'message3\n' in status.getvalue() # not colored assert 'message3\n' in status.getvalue() # not colored
assert colorize('darkred', 'WARNING: message4') in warning.getvalue() assert colorize('red', 'WARNING: message4') in warning.getvalue()
assert 'WARNING: message5\n' in warning.getvalue() # not colored assert 'WARNING: message5\n' in warning.getvalue() # not colored
assert colorize('darkred', 'WARNING: message6') in warning.getvalue() assert colorize('darkred', 'WARNING: message6') in warning.getvalue()

View File

@ -21,7 +21,7 @@ deps =
du13: docutils==0.13.1 du13: docutils==0.13.1
du14: docutils==0.14 du14: docutils==0.14
setenv = setenv =
PYTHONWARNINGS = all,ignore::ImportWarning:pkgutil PYTHONWARNINGS = all,ignore::ImportWarning:pkgutil,ignore::ImportWarning:importlib._bootstrap,ignore::ImportWarning:importlib._bootstrap_external,ignore::ImportWarning:pytest_cov.plugin,ignore::DeprecationWarning:site,ignore::DeprecationWarning:_pytest.assertion.rewrite,ignore::DeprecationWarning:_pytest.fixtures
SPHINX_TEST_TEMPDIR = {envdir}/testbuild SPHINX_TEST_TEMPDIR = {envdir}/testbuild
commands= commands=
pytest -Wall --durations 25 {posargs} pytest -Wall --durations 25 {posargs}