mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge branch 'master' into 4183_follow_pep440
This commit is contained in:
commit
f5b1aff2d1
@ -6,7 +6,6 @@ cache: pip
|
||||
env:
|
||||
global:
|
||||
- PYTHONFAULTHANDLER=x
|
||||
- PYTHONWARNINGS=all
|
||||
- SKIP_LATEX_BUILD=1
|
||||
|
||||
matrix:
|
||||
|
1
AUTHORS
1
AUTHORS
@ -18,6 +18,7 @@ Other co-maintainers:
|
||||
Other contributors, listed alphabetically, are:
|
||||
|
||||
* Alastair Houghton -- Apple Help builder
|
||||
* Alexander Todorov -- inheritance_diagram tests and improvements
|
||||
* Andi Albrecht -- agogo theme
|
||||
* Jakob Lykke Andersen -- Rewritten C++ domain
|
||||
* Henrique Bastos -- SVG support for graphviz extension
|
||||
|
36
CHANGES
36
CHANGES
@ -19,6 +19,8 @@ Incompatible changes
|
||||
* #4226: apidoc: Generate new style makefile (make-mode)
|
||||
* #4274: sphinx-build returns 2 as an exit code on argument error
|
||||
* #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
|
||||
----------
|
||||
@ -27,6 +29,12 @@ Deprecated
|
||||
values will be accepted at 2.0.
|
||||
* ``format_annotation()`` and ``formatargspec()`` is deprecated. Please use
|
||||
``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
|
||||
--------------
|
||||
@ -62,6 +70,8 @@ Features added
|
||||
* #947: autodoc now supports ignore-module-all to ignore a module's ``__all__``
|
||||
* #4332: Let LaTeX obey :confval:`math_numfig` for equation numbering
|
||||
* #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
|
||||
|
||||
Features removed
|
||||
@ -108,13 +118,18 @@ Bugs fixed
|
||||
one of figures and tables
|
||||
* #4330: PDF 'howto' documents have an incoherent default LaTeX tocdepth counter
|
||||
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
|
||||
--------
|
||||
|
||||
* 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
|
||||
@ -129,11 +144,27 @@ Deprecated
|
||||
Features added
|
||||
--------------
|
||||
|
||||
Bugs fixed
|
||||
----------
|
||||
|
||||
Testing
|
||||
--------
|
||||
|
||||
Release 1.6.6 (released Jan 08, 2018)
|
||||
=====================================
|
||||
|
||||
Features added
|
||||
--------------
|
||||
|
||||
* #4181: autodoc: Sort dictionary keys when possible
|
||||
* ``VerbatimHighlightColor`` is a new
|
||||
:ref:`LaTeX 'sphinxsetup' <latexsphinxsetup>` key (refs: #4285)
|
||||
* Easier customizability of LaTeX macros involved in rendering of code-blocks
|
||||
* 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
|
||||
----------
|
||||
@ -158,9 +189,6 @@ Bugs fixed
|
||||
* Fix links to external option docs with intersphinx (refs: #3769)
|
||||
* #4091: Private members not documented without :undoc-members:
|
||||
|
||||
Testing
|
||||
--------
|
||||
|
||||
Release 1.6.5 (released Oct 23, 2017)
|
||||
=====================================
|
||||
|
||||
|
4
EXAMPLES
4
EXAMPLES
@ -93,7 +93,7 @@ Documentation using the classic theme
|
||||
* simuPOP: http://simupop.sourceforge.net/manual_release/build/userGuide.html (customized)
|
||||
* Sprox: http://sprox.org/ (customized)
|
||||
* SymPy: http://docs.sympy.org/
|
||||
* TurboGears: https://turbogears.readthedocs.org/ (customized)
|
||||
* TurboGears: https://turbogears.readthedocs.io/ (customized)
|
||||
* tvtk: http://docs.enthought.com/mayavi/tvtk/
|
||||
* Varnish: https://www.varnish-cache.org/docs/ (customized, alabaster for index)
|
||||
* Waf: https://waf.io/apidocs/
|
||||
@ -259,7 +259,7 @@ Documentation using sphinx_bootstrap_theme
|
||||
* Bootstrap Theme: https://ryan-roemer.github.io/sphinx-bootstrap-theme/
|
||||
* C/C++ Software Development with Eclipse: http://eclipsebook.in/
|
||||
* Dataverse: http://guides.dataverse.org/
|
||||
* e-cidadania: http://e-cidadania.readthedocs.org/
|
||||
* e-cidadania: https://e-cidadania.readthedocs.io/
|
||||
* Hangfire: http://docs.hangfire.io/
|
||||
* Hedge: https://documen.tician.de/hedge/
|
||||
* ObsPy: https://docs.obspy.org/
|
||||
|
6
doc/_templates/index.html
vendored
6
doc/_templates/index.html
vendored
@ -74,9 +74,9 @@
|
||||
|
||||
<p>{%trans%}
|
||||
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
|
||||
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%}
|
||||
</p>
|
||||
|
||||
@ -106,7 +106,7 @@
|
||||
<h2>{%trans%}Hosting{%endtrans%}</h2>
|
||||
|
||||
<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
|
||||
powerful built-in search that exceeds the possibilities of Sphinx' JavaScript-based
|
||||
offline search.{%endtrans%}</p>
|
||||
|
@ -354,6 +354,63 @@ General configuration
|
||||
The LaTeX builder obeys this setting (if :confval:`numfig` is set to
|
||||
``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
|
||||
|
||||
If true, Sphinx verifies server certifications. Default is ``True``.
|
||||
@ -785,15 +842,11 @@ that use Sphinx's HTMLWriter class.
|
||||
|
||||
.. confval:: html_use_smartypants
|
||||
|
||||
If true, `SmartyPants <https://daringfireball.net/projects/smartypants/>`_
|
||||
will be used to convert quotes and dashes to typographically correct
|
||||
If true, quotes and dashes are converted to typographically correct
|
||||
entities. Default: ``True``.
|
||||
|
||||
.. deprecated:: 1.6
|
||||
To disable or customize smart quotes, use the Docutils configuration file
|
||||
(``docutils.conf``) instead to set there its `smart_quotes option`_.
|
||||
|
||||
.. _`smart_quotes option`: http://docutils.sourceforge.net/docs/user/config.html#smart-quotes
|
||||
To disable smart quotes, use rather :confval:`smartquotes`.
|
||||
|
||||
.. confval:: html_add_permalinks
|
||||
|
||||
|
@ -138,7 +138,7 @@ own extensions.
|
||||
.. _cmakedomain: https://bitbucket.org/klorenz/sphinxcontrib-cmakedomain
|
||||
.. _GNU Make: http://www.gnu.org/software/make/
|
||||
.. _makedomain: https://bitbucket.org/klorenz/sphinxcontrib-makedomain
|
||||
.. _inlinesyntaxhighlight: http://sphinxcontrib-inlinesyntaxhighlight.readthedocs.org
|
||||
.. _inlinesyntaxhighlight: https://sphinxcontrib-inlinesyntaxhighlight.readthedocs.io/
|
||||
.. _CMake: https://cmake.org
|
||||
.. _domaintools: https://bitbucket.org/klorenz/sphinxcontrib-domaintools
|
||||
.. _restbuilder: https://pypi.python.org/pypi/sphinxcontrib-restbuilder
|
||||
|
@ -42,6 +42,54 @@ It adds this directive:
|
||||
.. versionchanged:: 1.5
|
||||
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:
|
||||
|
||||
|
@ -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.
|
||||
|
||||
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.
|
||||
|
||||
If you write an extension that you think others will find useful or you think
|
||||
|
@ -117,12 +117,30 @@ Both APIs parse the content into a given node. They are used like this::
|
||||
|
||||
node = docutils.nodes.paragraph()
|
||||
# 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)
|
||||
# or
|
||||
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
|
||||
return ``node.children`` from the Directive.
|
||||
|
||||
|
@ -58,7 +58,7 @@ Read the Docs
|
||||
Sphinx. They will host sphinx documentation, along with supporting a number
|
||||
of other features including version support, PDF generation, and more. The
|
||||
`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.
|
||||
|
||||
Epydoc
|
||||
|
@ -3,7 +3,7 @@ Introduction
|
||||
|
||||
This is the documentation for the Sphinx documentation builder. Sphinx is a
|
||||
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
|
||||
possibly subdirectories of docs in there as well), Sphinx can generate a
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
.. _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.
|
||||
|
||||
* 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
|
||||
to Sphinx: `odt2sphinx <https://pypi.python.org/pypi/odt2sphinx/>`_.
|
||||
|
@ -642,15 +642,14 @@ class Sphinx(object):
|
||||
def add_autodocumenter(self, cls):
|
||||
# type: (Any) -> None
|
||||
logger.debug('[app] adding autodocumenter: %r', cls)
|
||||
from sphinx.ext import autodoc
|
||||
autodoc.add_documenter(cls)
|
||||
self.add_directive('auto' + cls.objtype, autodoc.AutoDirective)
|
||||
from sphinx.ext.autodoc.directive import AutodocDirective
|
||||
self.registry.add_documenter(cls.objtype, cls)
|
||||
self.add_directive('auto' + cls.objtype, AutodocDirective)
|
||||
|
||||
def add_autodoc_attrgetter(self, type, getter):
|
||||
# type: (Any, Callable) -> None
|
||||
logger.debug('[app] adding autodoc attrgetter: %r', (type, getter))
|
||||
from sphinx.ext import autodoc
|
||||
autodoc.AutoDirective._special_attrgetters[type] = getter
|
||||
def add_autodoc_attrgetter(self, typ, getter):
|
||||
# type: (Type, Callable[[Any, unicode, Any], Any]) -> None
|
||||
logger.debug('[app] adding autodoc attrgetter: %r', (typ, getter))
|
||||
self.registry.add_autodoc_attrgetter(typ, getter)
|
||||
|
||||
def add_search_language(self, cls):
|
||||
# type: (Any) -> None
|
||||
|
@ -137,6 +137,11 @@ class Config(object):
|
||||
|
||||
tls_verify = (True, 'env'),
|
||||
tls_cacerts = (None, 'env'),
|
||||
smartquotes = (True, 'env'),
|
||||
smartquotes_action = ('qDe', 'env'),
|
||||
smartquotes_excludes = ({'languages': ['ja'],
|
||||
'builders': ['man', 'text']},
|
||||
'env'),
|
||||
) # type: Dict[unicode, Tuple]
|
||||
|
||||
def __init__(self, dirname, filename, overrides, tags):
|
||||
|
@ -19,8 +19,9 @@ import warnings
|
||||
from os import path
|
||||
from copy import copy
|
||||
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 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.websupport import is_commentable
|
||||
from sphinx.errors import SphinxError, ExtensionError
|
||||
from sphinx.transforms import SphinxTransformer
|
||||
from sphinx.transforms import SphinxTransformer, SphinxSmartQuotes
|
||||
from sphinx.deprecation import RemovedInSphinx20Warning
|
||||
from sphinx.environment.adapters.indexentries import IndexEntries
|
||||
from sphinx.environment.adapters.toctree import TocTree
|
||||
|
||||
if False:
|
||||
# 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 sphinx.application import Sphinx # NOQA
|
||||
from sphinx.builders import Builder # NOQA
|
||||
@ -66,6 +67,7 @@ default_settings = {
|
||||
'sectsubtitle_xform': False,
|
||||
'halt_level': 5,
|
||||
'file_insertion_enabled': True,
|
||||
'smartquotes_locales': [],
|
||||
}
|
||||
|
||||
# This is increased every time an environment attribute is added
|
||||
@ -82,6 +84,22 @@ versioning_conditions = {
|
||||
} # 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):
|
||||
"""Raised by get_relative_uri if there is no URI available."""
|
||||
pass
|
||||
@ -584,7 +602,8 @@ class BuildEnvironment(object):
|
||||
# remove all inventory entries for that file
|
||||
app.emit('env-purge-doc', self, 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):
|
||||
# type: (List[unicode], Sphinx, int) -> None
|
||||
@ -596,8 +615,9 @@ class BuildEnvironment(object):
|
||||
def read_process(docs):
|
||||
# type: (List[unicode]) -> unicode
|
||||
self.app = app
|
||||
for docname in docs:
|
||||
self.read_doc(docname, app)
|
||||
with sphinx_smartquotes_action(self):
|
||||
for docname in docs:
|
||||
self.read_doc(docname, app)
|
||||
# allow pickling self to send it back
|
||||
return BuildEnvironment.dumps(self)
|
||||
|
||||
@ -645,7 +665,19 @@ class BuildEnvironment(object):
|
||||
language = self.config.language or 'en'
|
||||
self.settings['language_code'] = language
|
||||
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
|
||||
for tag in normalize_language_tag(language):
|
||||
|
@ -14,17 +14,15 @@
|
||||
import re
|
||||
import sys
|
||||
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
|
||||
|
||||
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.inspector import format_annotation, formatargspec # to keep compatibility # NOQA
|
||||
from sphinx.util import rpartition, force_decode
|
||||
@ -32,21 +30,23 @@ from sphinx.locale import _
|
||||
from sphinx.pycode import ModuleAnalyzer, PycodeError
|
||||
from sphinx.application import ExtensionError
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.nodes import nested_parse_with_titles
|
||||
from sphinx.util.inspect import Signature, isdescriptor, safe_getmembers, \
|
||||
safe_getattr, object_description, is_builtin_class_method, \
|
||||
isenumclass, isenumattribute, getdoc
|
||||
isenumattribute, getdoc
|
||||
from sphinx.util.docstrings import prepare_docstring
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from types import ModuleType # 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 sphinx.application import Sphinx # NOQA
|
||||
from sphinx.ext.autodoc.directive import DocumenterBridge # NOQA
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# This type isn't exposed directly in any modules, but can be found
|
||||
# here in most Python versions
|
||||
MethodDescriptorType = type(type.__subclasses__)
|
||||
@ -63,42 +63,11 @@ py_ext_sig_re = re.compile(
|
||||
''', 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):
|
||||
# type: (Any) -> Any
|
||||
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()
|
||||
INSTANCEATTR = object()
|
||||
|
||||
@ -146,6 +115,9 @@ class AutodocReporter(object):
|
||||
"""
|
||||
def __init__(self, viewlist, reporter):
|
||||
# type: (ViewList, Reporter) -> None
|
||||
warnings.warn('AutodocReporter is now deprecated. '
|
||||
'Use sphinx.util.docutils.switch_source_input() instead.',
|
||||
RemovedInSphinx20Warning)
|
||||
self.viewlist = viewlist
|
||||
self.reporter = reporter
|
||||
|
||||
@ -284,14 +256,10 @@ class Documenter(object):
|
||||
|
||||
option_spec = {'noindex': bool_option} # type: Dict[unicode, Callable]
|
||||
|
||||
@staticmethod
|
||||
def get_attr(obj, name, *defargs):
|
||||
def get_attr(self, obj, name, *defargs):
|
||||
# type: (Any, unicode, Any) -> Any
|
||||
"""getattr() override for types such as Zope interfaces."""
|
||||
for typ, func in iteritems(AutoDirective._special_attrgetters):
|
||||
if isinstance(obj, typ):
|
||||
return func(obj, name, *defargs)
|
||||
return safe_getattr(obj, name, *defargs)
|
||||
return autodoc_attrgetter(self.env.app, obj, name, *defargs)
|
||||
|
||||
@classmethod
|
||||
def can_document_member(cls, member, membername, isattr, parent):
|
||||
@ -300,7 +268,7 @@ class Documenter(object):
|
||||
raise NotImplementedError('must be implemented in subclasses')
|
||||
|
||||
def __init__(self, directive, name, indent=u''):
|
||||
# type: (Directive, unicode, unicode) -> None
|
||||
# type: (DocumenterBridge, unicode, unicode) -> None
|
||||
self.directive = directive
|
||||
self.env = directive.env
|
||||
self.options = directive.genopt
|
||||
@ -324,6 +292,12 @@ class Documenter(object):
|
||||
# the module analyzer to get at attribute docs, or None
|
||||
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):
|
||||
# type: (unicode, unicode, int) -> None
|
||||
"""Append one line of generated reST to the output."""
|
||||
@ -354,8 +328,7 @@ class Documenter(object):
|
||||
explicit_modname, path, base, args, retann = \
|
||||
py_ext_sig_re.match(self.name).groups() # type: ignore
|
||||
except AttributeError:
|
||||
self.directive.warn('invalid signature for auto%s (%r)' %
|
||||
(self.objtype, self.name))
|
||||
logger.warning('invalid signature for auto%s (%r)' % (self.objtype, self.name))
|
||||
return False
|
||||
|
||||
# support explicit module and class name separation via ::
|
||||
@ -384,56 +357,15 @@ class Documenter(object):
|
||||
|
||||
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):
|
||||
try:
|
||||
logger.debug('[autodoc] import %s', self.modname)
|
||||
obj = import_module(self.modname, self.env.config.autodoc_warningiserror)
|
||||
parent = None
|
||||
self.module = obj
|
||||
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
|
||||
ret = import_object(self.modname, self.objpath, self.objtype,
|
||||
attrgetter=self.get_attr,
|
||||
warningiserror=self.env.config.autodoc_warningiserror)
|
||||
self.module, self.parent, self.object_name, self.object = ret
|
||||
return True
|
||||
except (AttributeError, ImportError) as exc:
|
||||
if self.objpath:
|
||||
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)
|
||||
except ImportError as exc:
|
||||
logger.warning(exc.args[0])
|
||||
self.env.note_reread()
|
||||
return False
|
||||
|
||||
@ -493,8 +425,8 @@ class Documenter(object):
|
||||
try:
|
||||
args = self.format_args()
|
||||
except Exception as err:
|
||||
self.directive.warn('error while formatting arguments for '
|
||||
'%s: %s' % (self.fullname, err))
|
||||
logger.warning('error while formatting arguments for %s: %s' %
|
||||
(self.fullname, err))
|
||||
args = None
|
||||
|
||||
retann = self.retann
|
||||
@ -606,57 +538,24 @@ class Documenter(object):
|
||||
If *want_all* is True, return all members. Else, only return those
|
||||
members given by *self.options.members* (which may also be none).
|
||||
"""
|
||||
analyzed_member_names = set()
|
||||
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])
|
||||
members = get_object_members(self.object, self.objpath, self.get_attr, self.analyzer)
|
||||
if not want_all:
|
||||
if not self.options.members:
|
||||
return False, []
|
||||
# specific members given
|
||||
members = []
|
||||
for mname in self.options.members:
|
||||
try:
|
||||
members.append((mname, self.get_attr(self.object, mname)))
|
||||
except AttributeError:
|
||||
if mname not in analyzed_member_names:
|
||||
self.directive.warn('missing attribute %s in object %s'
|
||||
% (mname, self.fullname))
|
||||
selected = []
|
||||
for name in self.options.members:
|
||||
if name in members:
|
||||
selected.append((name, members[name].value))
|
||||
else:
|
||||
logger.warning('missing attribute %s in object %s' %
|
||||
(name, self.fullname))
|
||||
return False, sorted(selected)
|
||||
elif self.options.inherited_members:
|
||||
# safe_getmembers() uses dir() which pulls in members from all
|
||||
# base classes
|
||||
members = safe_getmembers(self.object, attr_getter=self.get_attr)
|
||||
return False, sorted((m.name, m.value) for m in itervalues(members))
|
||||
else:
|
||||
# __dict__ contains only the members directly defined in
|
||||
# the class (but get them via getattr anyway, to e.g. get
|
||||
# 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)
|
||||
return False, sorted((m.name, m.value) for m in itervalues(members)
|
||||
if m.directly_defined)
|
||||
|
||||
def filter_members(self, members, want_all):
|
||||
# type: (List[Tuple[unicode, Any]], bool) -> List[Tuple[unicode, Any, bool]]
|
||||
@ -768,7 +667,7 @@ class Documenter(object):
|
||||
# document non-skipped members
|
||||
memberdocumenters = [] # type: List[Tuple[Documenter, bool]]
|
||||
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 not classes:
|
||||
# don't know how to document this member
|
||||
@ -819,11 +718,11 @@ class Documenter(object):
|
||||
"""
|
||||
if not self.parse_name():
|
||||
# need a module to import
|
||||
self.directive.warn(
|
||||
logger.warning(
|
||||
'don\'t know which module to import for autodocumenting '
|
||||
'%r (try placing a "module" or "currentmodule" directive '
|
||||
'in the document, or giving an explicit module name)'
|
||||
% self.name)
|
||||
'in the document, or giving an explicit module name)' %
|
||||
self.name)
|
||||
return
|
||||
|
||||
# now, import the module and get object to document
|
||||
@ -909,15 +808,15 @@ class ModuleDocumenter(Documenter):
|
||||
def resolve_name(self, modname, parents, path, base):
|
||||
# type: (str, Any, str, Any) -> Tuple[str, List[unicode]]
|
||||
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, []
|
||||
|
||||
def parse_name(self):
|
||||
# type: () -> bool
|
||||
ret = Documenter.parse_name(self)
|
||||
if self.args or self.retann:
|
||||
self.directive.warn('signature arguments or return annotation '
|
||||
'given for automodule %s' % self.fullname)
|
||||
logger.warning('signature arguments or return annotation '
|
||||
'given for automodule %s' % self.fullname)
|
||||
return ret
|
||||
|
||||
def add_directive_header(self, sig):
|
||||
@ -949,7 +848,7 @@ class ModuleDocumenter(Documenter):
|
||||
# Sometimes __all__ is broken...
|
||||
if not isinstance(memberlist, (list, tuple)) or not \
|
||||
all(isinstance(entry, string_types) for entry in memberlist):
|
||||
self.directive.warn(
|
||||
logger.warning(
|
||||
'__all__ should be a list of strings, not %r '
|
||||
'(in module %s) -- ignoring __all__' %
|
||||
(memberlist, self.fullname))
|
||||
@ -962,10 +861,10 @@ class ModuleDocumenter(Documenter):
|
||||
try:
|
||||
ret.append((mname, safe_getattr(self.object, mname)))
|
||||
except AttributeError:
|
||||
self.directive.warn(
|
||||
logger.warning(
|
||||
'missing attribute mentioned in :members: or __all__: '
|
||||
'module %s, attribute %s' % (
|
||||
safe_getattr(self.object, '__name__', '???'), mname))
|
||||
'module %s, attribute %s' %
|
||||
(safe_getattr(self.object, '__name__', '???'), mname))
|
||||
return False, ret
|
||||
|
||||
|
||||
@ -1504,118 +1403,56 @@ class InstanceAttributeDocumenter(AttributeDocumenter):
|
||||
AttributeDocumenter.add_content(self, more_content, no_docstring=True)
|
||||
|
||||
|
||||
class AutoDirective(Directive):
|
||||
"""
|
||||
The AutoDirective class is used for all autodoc directives. It dispatches
|
||||
most of the work to one of the Documenters, which it selects through its
|
||||
*_registry* dictionary.
|
||||
class DeprecatedDict(dict):
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
super(DeprecatedDict, self).__init__()
|
||||
|
||||
The *_special_attrgetters* attribute is used to customize ``getattr()``
|
||||
calls that the Documenters make; its entries are of the form ``type:
|
||||
getattr_function``.
|
||||
def __setitem__(self, key, value):
|
||||
warnings.warn(self.message, RemovedInSphinx20Warning)
|
||||
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
|
||||
accessed using the descendant's *_special_attrgetters*, thus this
|
||||
dictionary should include all necessary functions for accessing
|
||||
attributes of the parents.
|
||||
"""
|
||||
# a registry of objtype -> documenter class
|
||||
_registry = {} # type: Dict[unicode, Type[Documenter]]
|
||||
# a registry of objtype -> documenter class (Deprecated)
|
||||
_registry = DeprecatedDict(
|
||||
'AutoDirective._registry has been deprecated. '
|
||||
'Please use app.add_autodocumenter() instead.'
|
||||
) # type: Dict[unicode, Type[Documenter]]
|
||||
|
||||
# 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
|
||||
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
|
||||
AutoDirective = AutodocRegistry # for backward compatibility
|
||||
|
||||
|
||||
def add_documenter(cls):
|
||||
# type: (Type[Documenter]) -> None
|
||||
"""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):
|
||||
raise ExtensionError('autodoc documenter %r must be a subclass '
|
||||
'of Documenter' % cls)
|
||||
@ -1626,6 +1463,29 @@ def add_documenter(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):
|
||||
# type: (Sphinx) -> Dict[unicode, Any]
|
||||
app.add_autodocumenter(ModuleDocumenter)
|
||||
|
155
sphinx/ext/autodoc/directive.py
Normal file
155
sphinx/ext/autodoc/directive.py
Normal 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
|
@ -13,13 +13,17 @@ import sys
|
||||
import warnings
|
||||
import traceback
|
||||
import contextlib
|
||||
from collections import namedtuple
|
||||
from types import FunctionType, MethodType, ModuleType
|
||||
|
||||
from six import PY2
|
||||
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.inspect import isenumclass, safe_getattr
|
||||
|
||||
if False:
|
||||
# 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__)
|
||||
|
||||
@ -144,3 +148,86 @@ def import_module(modname, warningiserror=False):
|
||||
# Importing modules may cause any side effects, including
|
||||
# SystemExit, so we need to catch all errors.
|
||||
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
|
||||
|
@ -72,7 +72,8 @@ from sphinx import addnodes
|
||||
from sphinx.environment.adapters.toctree import TocTree
|
||||
from sphinx.util import import_object, rst, logging
|
||||
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
|
||||
|
||||
if False:
|
||||
@ -153,13 +154,13 @@ def autosummary_table_visit_html(self, node):
|
||||
|
||||
# -- autodoc integration -------------------------------------------------------
|
||||
|
||||
class FakeDirective(object):
|
||||
env = {} # type: Dict
|
||||
genopt = Options()
|
||||
class FakeDirective(DocumenterBridge):
|
||||
def __init__(self):
|
||||
super(FakeDirective, self).__init__({}, None, Options(), 0) # type: ignore
|
||||
|
||||
|
||||
def get_documenter(obj, parent):
|
||||
# type: (Any, Any) -> Type[Documenter]
|
||||
def get_documenter(app, obj, parent):
|
||||
# type: (Sphinx, Any, Any) -> Type[Documenter]
|
||||
"""Get an autodoc.Documenter class suitable for documenting the given
|
||||
object.
|
||||
|
||||
@ -167,8 +168,7 @@ def get_documenter(obj, parent):
|
||||
another Python object (e.g. a module or a class) to which *obj*
|
||||
belongs to.
|
||||
"""
|
||||
from sphinx.ext.autodoc import AutoDirective, DataDocumenter, \
|
||||
ModuleDocumenter
|
||||
from sphinx.ext.autodoc import DataDocumenter, ModuleDocumenter
|
||||
|
||||
if inspect.ismodule(obj):
|
||||
# ModuleDocumenter.can_document_member always returns False
|
||||
@ -176,7 +176,7 @@ def get_documenter(obj, parent):
|
||||
|
||||
# Construct a fake documenter for *parent*
|
||||
if parent is not None:
|
||||
parent_doc_cls = get_documenter(parent, None)
|
||||
parent_doc_cls = get_documenter(app, parent, None)
|
||||
else:
|
||||
parent_doc_cls = ModuleDocumenter
|
||||
|
||||
@ -186,7 +186,7 @@ def get_documenter(obj, parent):
|
||||
parent_doc = parent_doc_cls(FakeDirective(), "")
|
||||
|
||||
# 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 classes:
|
||||
classes.sort(key=lambda cls: cls.priority)
|
||||
@ -289,7 +289,7 @@ class Autosummary(Directive):
|
||||
full_name = modname + '::' + full_name[len(modname) + 1:]
|
||||
# NB. using full_name here is important, since Documenters
|
||||
# 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():
|
||||
self.warn('failed to parse name %s' % real_name)
|
||||
items.append((display_name, '', '', real_name))
|
||||
@ -325,7 +325,7 @@ class Autosummary(Directive):
|
||||
# -- Grab the summary
|
||||
|
||||
documenter.add_content(None)
|
||||
doc = list(documenter.process_doc([self.result.data]))
|
||||
doc = self.result.data
|
||||
|
||||
while doc and not doc[0].strip():
|
||||
doc.pop(0)
|
||||
@ -615,7 +615,8 @@ def process_generate_options(app):
|
||||
|
||||
generate_autosummary_docs(genfiles, builder=app.builder,
|
||||
warn=logger.warning, info=logger.info,
|
||||
suffix=suffix, base_path=app.srcdir)
|
||||
suffix=suffix, base_path=app.srcdir,
|
||||
app=app)
|
||||
|
||||
|
||||
def setup(app):
|
||||
|
@ -33,24 +33,11 @@ from sphinx import __display_version__
|
||||
from sphinx import package_dir
|
||||
from sphinx.ext.autosummary import import_by_name, get_documenter
|
||||
from sphinx.jinja2glue import BuiltinTemplateLoader
|
||||
from sphinx.registry import SphinxComponentRegistry
|
||||
from sphinx.util.osutil import ensuredir
|
||||
from sphinx.util.inspect import safe_getattr
|
||||
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:
|
||||
# For type annotation
|
||||
from typing import Any, Callable, Dict, Tuple, List # NOQA
|
||||
@ -60,6 +47,30 @@ if False:
|
||||
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):
|
||||
# type: (unicode) -> None
|
||||
print(msg)
|
||||
@ -81,8 +92,8 @@ def _underline(title, line='='):
|
||||
def generate_autosummary_docs(sources, output_dir=None, suffix='.rst',
|
||||
warn=_simple_warn, info=_simple_info,
|
||||
base_path=None, builder=None, template_dir=None,
|
||||
imported_members=False):
|
||||
# type: (List[unicode], unicode, unicode, Callable, Callable, unicode, Builder, unicode, bool) -> None # NOQA
|
||||
imported_members=False, app=None):
|
||||
# type: (List[unicode], unicode, unicode, Callable, Callable, unicode, Builder, unicode, bool, Any) -> None # NOQA
|
||||
|
||||
showed_sources = list(sorted(sources))
|
||||
if len(showed_sources) > 20:
|
||||
@ -148,7 +159,7 @@ def generate_autosummary_docs(sources, output_dir=None, suffix='.rst',
|
||||
new_files.append(fn)
|
||||
|
||||
with open(fn, 'w') as f:
|
||||
doc = get_documenter(obj, parent)
|
||||
doc = get_documenter(app, obj, parent)
|
||||
|
||||
if template_name is not None:
|
||||
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)
|
||||
except AttributeError:
|
||||
continue
|
||||
documenter = get_documenter(value, obj)
|
||||
documenter = get_documenter(app, value, obj)
|
||||
if documenter.objtype == typ:
|
||||
if typ == 'method':
|
||||
items.append(name)
|
||||
@ -392,11 +403,14 @@ The format of the autosummary directive is documented in the
|
||||
|
||||
def main(argv=sys.argv[1:]):
|
||||
# type: (List[str]) -> None
|
||||
app = DummyApplication()
|
||||
setup_documenters(app)
|
||||
args = get_parser().parse_args(argv)
|
||||
generate_autosummary_docs(args.source_file, args.output_dir,
|
||||
'.' + args.suffix,
|
||||
template_dir=args.templates,
|
||||
imported_members=args.imported_members)
|
||||
imported_members=args.imported_members,
|
||||
app=app)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@ -130,8 +130,8 @@ class InheritanceGraph(object):
|
||||
graphviz dot graph from them.
|
||||
"""
|
||||
def __init__(self, class_names, currmodule, show_builtins=False,
|
||||
private_bases=False, parts=0, aliases=None):
|
||||
# type: (unicode, str, bool, bool, int, Optional[Dict[unicode, unicode]]) -> None
|
||||
private_bases=False, parts=0, aliases=None, top_classes=[]):
|
||||
# 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.
|
||||
|
||||
If *show_builtins* is True, then Python builtins will be shown
|
||||
@ -140,7 +140,7 @@ class InheritanceGraph(object):
|
||||
self.class_names = class_names
|
||||
classes = self._import_classes(class_names, currmodule)
|
||||
self.class_info = self._class_info(classes, show_builtins,
|
||||
private_bases, parts, aliases)
|
||||
private_bases, parts, aliases, top_classes)
|
||||
if not self.class_info:
|
||||
raise InheritanceException('No classes found for '
|
||||
'inheritance diagram')
|
||||
@ -153,13 +153,16 @@ class InheritanceGraph(object):
|
||||
classes.extend(import_classes(name, currmodule))
|
||||
return classes
|
||||
|
||||
def _class_info(self, classes, show_builtins, private_bases, parts, aliases):
|
||||
# type: (List[Any], bool, bool, int, Optional[Dict[unicode, unicode]]) -> List[Tuple[unicode, unicode, List[unicode], unicode]] # NOQA
|
||||
def _class_info(self, classes, show_builtins, private_bases, parts, aliases, top_classes):
|
||||
# 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
|
||||
*classes*.
|
||||
|
||||
*parts* gives the number of dotted name parts that is removed from the
|
||||
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 = {}
|
||||
py_builtins = vars(builtins).values()
|
||||
@ -189,6 +192,10 @@ class InheritanceGraph(object):
|
||||
|
||||
baselist = [] # type: List[unicode]
|
||||
all_classes[cls] = (nodename, fullname, baselist, tooltip)
|
||||
|
||||
if fullname in top_classes:
|
||||
return
|
||||
|
||||
for base in cls.__bases__:
|
||||
if not show_builtins and base in py_builtins:
|
||||
continue
|
||||
@ -322,6 +329,7 @@ class InheritanceDiagram(Directive):
|
||||
'parts': directives.nonnegative_int,
|
||||
'private-bases': directives.flag,
|
||||
'caption': directives.unchanged,
|
||||
'top-classes': directives.unchanged_required,
|
||||
}
|
||||
|
||||
def run(self):
|
||||
@ -334,6 +342,11 @@ class InheritanceDiagram(Directive):
|
||||
# Store the original content for use as a hash
|
||||
node['parts'] = self.options.get('parts', 0)
|
||||
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
|
||||
try:
|
||||
@ -341,7 +354,8 @@ class InheritanceDiagram(Directive):
|
||||
class_names, env.ref_context.get('py:module'),
|
||||
parts=node['parts'],
|
||||
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:
|
||||
return [node.document.reporter.warning(err.args[0],
|
||||
line=self.lineno)]
|
||||
|
@ -71,12 +71,12 @@ def doctree_read(app, doctree):
|
||||
code = analyzer.code.decode(analyzer.encoding)
|
||||
else:
|
||||
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()
|
||||
entry = code, analyzer.tags, {}, refname
|
||||
env._viewcode_modules[modname] = entry # type: ignore
|
||||
elif entry is False:
|
||||
return
|
||||
_, tags, used, _ = entry
|
||||
if fullname in tags:
|
||||
used[fullname] = docname
|
||||
|
@ -23,7 +23,7 @@ from sphinx.transforms import (
|
||||
ApplySourceWorkaround, ExtraTranslatableNodes, CitationReferences,
|
||||
DefaultSubstitutions, MoveModuleTargets, HandleCodeBlocks, SortIds,
|
||||
AutoNumbering, AutoIndexUpgrader, FilterSystemMessages,
|
||||
UnreferencedFootnotesDetector
|
||||
UnreferencedFootnotesDetector, SphinxSmartQuotes
|
||||
)
|
||||
from sphinx.transforms.compact_bullet_list import RefOnlyBulletListTransform
|
||||
from sphinx.transforms.i18n import (
|
||||
@ -86,8 +86,15 @@ class SphinxStandaloneReader(SphinxBaseReader):
|
||||
def __init__(self, app, *args, **kwargs):
|
||||
# type: (Sphinx, Any, Any) -> None
|
||||
self.transforms = self.transforms + app.registry.get_transforms()
|
||||
self.smart_quotes = app.env.settings['smart_quotes']
|
||||
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):
|
||||
"""
|
||||
|
@ -15,8 +15,6 @@ from docutils.parsers.rst import states
|
||||
from docutils.statemachine import StringList
|
||||
from docutils.transforms.universal import SmartQuotes
|
||||
|
||||
from sphinx.transforms import SphinxSmartQuotes
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any, Dict, List, Type # NOQA
|
||||
@ -63,10 +61,11 @@ class RSTParser(docutils.parsers.rst.Parser):
|
||||
|
||||
def get_transforms(self):
|
||||
# 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.remove(SmartQuotes)
|
||||
transforms.append(SphinxSmartQuotes)
|
||||
return transforms
|
||||
|
||||
def parse(self, inputstring, document):
|
||||
|
@ -38,6 +38,7 @@ if False:
|
||||
from sphinx.builders import Builder # NOQA
|
||||
from sphinx.domains import Domain, Index # NOQA
|
||||
from sphinx.environment import BuildEnvironment # NOQA
|
||||
from sphinx.ext.autodoc import Documenter # NOQA
|
||||
from sphinx.util.typing import RoleFunction # NOQA
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -51,7 +52,9 @@ EXTENSION_BLACKLIST = {
|
||||
|
||||
class SphinxComponentRegistry(object):
|
||||
def __init__(self):
|
||||
self.autodoc_attrgettrs = {} # type: Dict[Type, Callable[[Any, unicode, Any], Any]]
|
||||
self.builders = {} # type: Dict[unicode, Type[Builder]]
|
||||
self.documenters = {} # type: Dict[unicode, Type[Documenter]]
|
||||
self.domains = {} # type: Dict[unicode, Type[Domain]]
|
||||
self.domain_directives = {} # type: Dict[unicode, Dict[unicode, Any]]
|
||||
self.domain_indices = {} # type: Dict[unicode, List[Type[Index]]]
|
||||
@ -284,6 +287,14 @@ class SphinxComponentRegistry(object):
|
||||
# type: () -> List[Type[Transform]]
|
||||
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):
|
||||
# type: (Sphinx, unicode) -> None
|
||||
"""Load a Sphinx extension."""
|
||||
|
@ -18,7 +18,7 @@ from contextlib import contextmanager
|
||||
|
||||
import docutils
|
||||
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.utils import Reporter
|
||||
|
||||
@ -31,8 +31,9 @@ report_re = re.compile('^(.+?:(?:\\d+)?): \\((DEBUG|INFO|WARNING|ERROR|SEVERE)/(
|
||||
|
||||
if False:
|
||||
# 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.statemachine import State # NOQA
|
||||
from sphinx.environment import BuildEnvironment # 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 '
|
||||
'additional arguments may be given'))
|
||||
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
|
||||
|
@ -53,7 +53,7 @@ VERBOSITY_MAP.update({
|
||||
COLOR_MAP = defaultdict(lambda: 'blue') # type: Dict[int, unicode]
|
||||
COLOR_MAP.update({
|
||||
logging.ERROR: 'darkred',
|
||||
logging.WARNING: 'darkred',
|
||||
logging.WARNING: 'red',
|
||||
logging.DEBUG: 'darkgray',
|
||||
})
|
||||
|
||||
|
@ -549,7 +549,7 @@ class LaTeXTranslator(nodes.NodeVisitor):
|
||||
'author': document.settings.author, # treat as a raw LaTeX code
|
||||
'indexname': _('Index'),
|
||||
})
|
||||
if not self.elements['releasename']:
|
||||
if not self.elements['releasename'] and self.elements['release']:
|
||||
self.elements.update({
|
||||
'releasename': _('Release'),
|
||||
})
|
||||
|
@ -35,14 +35,6 @@ def pytest_report_header(config):
|
||||
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):
|
||||
testroot = os.path.join(str(session.config.rootdir), 'tests')
|
||||
tempdir = os.path.abspath(os.getenv('SPHINX_TEST_TEMPDIR',
|
||||
@ -58,5 +50,4 @@ def _initialize_test_directory(session):
|
||||
|
||||
|
||||
def pytest_sessionstart(session):
|
||||
_filter_warnings()
|
||||
_initialize_test_directory(session)
|
||||
|
@ -21,6 +21,7 @@ from docutils.statemachine import ViewList
|
||||
|
||||
from sphinx.ext.autodoc import AutoDirective, add_documenter, \
|
||||
ModuleLevelDocumenter, FunctionDocumenter, cut_lines, between, ALL
|
||||
from sphinx.util import logging
|
||||
|
||||
app = None
|
||||
|
||||
@ -30,7 +31,7 @@ def setup_module(rootdir, sphinx_test_tempdir):
|
||||
global app
|
||||
srcdir = sphinx_test_tempdir / 'autodoc-root'
|
||||
if not srcdir.exists():
|
||||
(rootdir/'test-root').copytree(srcdir)
|
||||
(rootdir / 'test-root').copytree(srcdir)
|
||||
app = SphinxTestApp(srcdir=srcdir)
|
||||
app.builder.env.app = app
|
||||
app.builder.env.temp_data['docname'] = 'dummy'
|
||||
@ -47,7 +48,7 @@ directive = options = None
|
||||
@pytest.fixture
|
||||
def setup_test():
|
||||
global options, directive
|
||||
global processed_docstrings, processed_signatures, _warnings
|
||||
global processed_docstrings, processed_signatures
|
||||
|
||||
options = Struct(
|
||||
inherited_members = False,
|
||||
@ -70,24 +71,17 @@ def setup_test():
|
||||
env = app.builder.env,
|
||||
genopt = options,
|
||||
result = ViewList(),
|
||||
warn = warnfunc,
|
||||
filename_set = set(),
|
||||
)
|
||||
|
||||
processed_docstrings = []
|
||||
processed_signatures = []
|
||||
_warnings = []
|
||||
|
||||
|
||||
_warnings = []
|
||||
processed_docstrings = []
|
||||
processed_signatures = []
|
||||
|
||||
|
||||
def warnfunc(msg):
|
||||
_warnings.append(msg)
|
||||
|
||||
|
||||
def process_docstring(app, what, name, obj, options, lines):
|
||||
processed_docstrings.append((what, name))
|
||||
if name == 'bar':
|
||||
@ -111,20 +105,21 @@ def skip_member(app, what, name, obj, skip, options):
|
||||
|
||||
@pytest.mark.usefixtures('setup_test')
|
||||
def test_generate():
|
||||
logging.setup(app, app._status, app._warning)
|
||||
|
||||
def assert_warns(warn_str, objtype, name, **kw):
|
||||
inst = AutoDirective._registry[objtype](directive, name)
|
||||
inst = app.registry.documenters[objtype](directive, name)
|
||||
inst.generate(**kw)
|
||||
assert len(directive.result) == 0, directive.result
|
||||
assert len(_warnings) == 1, _warnings
|
||||
assert warn_str in _warnings[0], _warnings
|
||||
del _warnings[:]
|
||||
assert warn_str in app._warning.getvalue()
|
||||
app._warning.truncate(0)
|
||||
|
||||
def assert_works(objtype, name, **kw):
|
||||
inst = AutoDirective._registry[objtype](directive, name)
|
||||
inst = app.registry.documenters[objtype](directive, name)
|
||||
inst.generate(**kw)
|
||||
assert directive.result
|
||||
# print '\n'.join(directive.result)
|
||||
assert len(_warnings) == 0, _warnings
|
||||
assert app._warning.getvalue() == ''
|
||||
del directive.result[:]
|
||||
|
||||
def assert_processes(items, objtype, name, **kw):
|
||||
@ -134,18 +129,18 @@ def test_generate():
|
||||
assert set(processed_docstrings) | set(processed_signatures) == set(items)
|
||||
|
||||
def assert_result_contains(item, objtype, name, **kw):
|
||||
inst = AutoDirective._registry[objtype](directive, name)
|
||||
inst = app.registry.documenters[objtype](directive, name)
|
||||
inst.generate(**kw)
|
||||
# print '\n'.join(directive.result)
|
||||
assert len(_warnings) == 0, _warnings
|
||||
assert app._warning.getvalue() == ''
|
||||
assert item in directive.result
|
||||
del directive.result[:]
|
||||
|
||||
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.generate(**kw)
|
||||
assert len(_warnings) == 0, _warnings
|
||||
assert app._warning.getvalue() == ''
|
||||
items = list(reversed(items))
|
||||
lineiter = iter(directive.result)
|
||||
# for line in directive.result:
|
||||
|
5
tests/roots/test-inheritance/basic_diagram.rst
Normal file
5
tests/roots/test-inheritance/basic_diagram.rst
Normal file
@ -0,0 +1,5 @@
|
||||
Basic Diagram
|
||||
==============
|
||||
|
||||
.. inheritance-diagram::
|
||||
dummy.test
|
6
tests/roots/test-inheritance/conf.py
Normal file
6
tests/roots/test-inheritance/conf.py
Normal file
@ -0,0 +1,6 @@
|
||||
import sys, os
|
||||
|
||||
sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
extensions = ['sphinx.ext.inheritance_diagram']
|
||||
source_suffix = '.rst'
|
4
tests/roots/test-inheritance/contents.rst
Normal file
4
tests/roots/test-inheritance/contents.rst
Normal file
@ -0,0 +1,4 @@
|
||||
.. toctree::
|
||||
:glob:
|
||||
|
||||
*
|
@ -0,0 +1,6 @@
|
||||
Diagram using module with 2 top classes
|
||||
=======================================
|
||||
|
||||
.. inheritance-diagram::
|
||||
dummy.test
|
||||
:top-classes: dummy.test.B, dummy.test.C
|
7
tests/roots/test-inheritance/diagram_w_1_top_class.rst
Normal file
7
tests/roots/test-inheritance/diagram_w_1_top_class.rst
Normal file
@ -0,0 +1,7 @@
|
||||
Diagram using 1 top class
|
||||
=========================
|
||||
|
||||
.. inheritance-diagram::
|
||||
dummy.test
|
||||
:top-classes: dummy.test.B
|
||||
|
9
tests/roots/test-inheritance/diagram_w_2_top_classes.rst
Normal file
9
tests/roots/test-inheritance/diagram_w_2_top_classes.rst
Normal 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
|
||||
|
7
tests/roots/test-inheritance/diagram_w_parts.rst
Normal file
7
tests/roots/test-inheritance/diagram_w_parts.rst
Normal file
@ -0,0 +1,7 @@
|
||||
Diagram using the parts option
|
||||
==============================
|
||||
|
||||
.. inheritance-diagram::
|
||||
dummy.test
|
||||
:parts: 1
|
||||
|
0
tests/roots/test-inheritance/dummy/__init__.py
Normal file
0
tests/roots/test-inheritance/dummy/__init__.py
Normal file
30
tests/roots/test-inheritance/dummy/test.py
Normal file
30
tests/roots/test-inheritance/dummy/test.py
Normal 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
|
||||
|
7
tests/roots/test-smartquotes/conf.py
Normal file
7
tests/roots/test-smartquotes/conf.py
Normal 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')
|
||||
]
|
4
tests/roots/test-smartquotes/index.rst
Normal file
4
tests/roots/test-smartquotes/index.rst
Normal file
@ -0,0 +1,4 @@
|
||||
test-smartquotes
|
||||
================
|
||||
|
||||
-- "Sphinx" is a tool that makes it easy ...
|
@ -20,6 +20,7 @@ from docutils.statemachine import ViewList
|
||||
|
||||
from sphinx.ext.autodoc import AutoDirective, add_documenter, \
|
||||
ModuleLevelDocumenter, FunctionDocumenter, cut_lines, between, ALL
|
||||
from sphinx.util import logging
|
||||
|
||||
app = None
|
||||
|
||||
@ -51,7 +52,7 @@ directive = options = None
|
||||
@pytest.fixture
|
||||
def setup_test():
|
||||
global options, directive
|
||||
global processed_docstrings, processed_signatures, _warnings
|
||||
global processed_docstrings, processed_signatures
|
||||
|
||||
options = Struct(
|
||||
inherited_members = False,
|
||||
@ -75,28 +76,24 @@ def setup_test():
|
||||
env = app.builder.env,
|
||||
genopt = options,
|
||||
result = ViewList(),
|
||||
warn = warnfunc,
|
||||
filename_set = set(),
|
||||
)
|
||||
|
||||
processed_docstrings = []
|
||||
processed_signatures = []
|
||||
_warnings = []
|
||||
|
||||
app._status.truncate(0)
|
||||
app._warning.truncate(0)
|
||||
|
||||
yield
|
||||
|
||||
AutoDirective._special_attrgetters.clear()
|
||||
|
||||
|
||||
_warnings = []
|
||||
processed_docstrings = []
|
||||
processed_signatures = []
|
||||
|
||||
|
||||
def warnfunc(msg):
|
||||
_warnings.append(msg)
|
||||
|
||||
|
||||
def process_docstring(app, what, name, obj, options, lines):
|
||||
processed_docstrings.append((what, name))
|
||||
if name == 'bar':
|
||||
@ -120,8 +117,10 @@ def skip_member(app, what, name, obj, skip, options):
|
||||
|
||||
@pytest.mark.usefixtures('setup_test')
|
||||
def test_parse_name():
|
||||
logging.setup(app, app._status, app._warning)
|
||||
|
||||
def verify(objtype, name, result):
|
||||
inst = AutoDirective._registry[objtype](directive, name)
|
||||
inst = app.registry.documenters[objtype](directive, name)
|
||||
assert inst.parse_name()
|
||||
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.test_autodoc', ('test.test_autodoc', [], None, None))
|
||||
verify('module', 'test(arg)', ('test', [], 'arg', None))
|
||||
assert 'signature arguments' in _warnings[0]
|
||||
del _warnings[:]
|
||||
assert 'signature arguments' in app._warning.getvalue()
|
||||
|
||||
# for functions/classes
|
||||
verify('function', 'test_autodoc.raises',
|
||||
@ -164,7 +162,7 @@ def test_parse_name():
|
||||
@pytest.mark.usefixtures('setup_test')
|
||||
def test_format_signature():
|
||||
def formatsig(objtype, name, obj, args, retann):
|
||||
inst = AutoDirective._registry[objtype](directive, name)
|
||||
inst = app.registry.documenters[objtype](directive, name)
|
||||
inst.fullname = name
|
||||
inst.doc_as_attr = False # for class objtype
|
||||
inst.object = obj
|
||||
@ -246,7 +244,6 @@ def test_format_signature():
|
||||
# test exception handling (exception is caught and args is '')
|
||||
directive.env.config.autodoc_docstring_signature = False
|
||||
assert formatsig('function', 'int', int, None, None) == ''
|
||||
del _warnings[:]
|
||||
|
||||
# test processing by event handler
|
||||
assert formatsig('method', 'bar', H.foo1, None, None) == '42'
|
||||
@ -270,7 +267,7 @@ def test_format_signature():
|
||||
@pytest.mark.usefixtures('setup_test')
|
||||
def test_get_doc():
|
||||
def getdocl(objtype, obj, encoding=None):
|
||||
inst = AutoDirective._registry[objtype](directive, 'tmp')
|
||||
inst = app.registry.documenters[objtype](directive, 'tmp')
|
||||
inst.object = obj
|
||||
inst.objpath = [obj.__name__]
|
||||
inst.doc_as_attr = False
|
||||
@ -449,7 +446,7 @@ def test_get_doc():
|
||||
@pytest.mark.usefixtures('setup_test')
|
||||
def test_docstring_processing():
|
||||
def process(objtype, name, obj):
|
||||
inst = AutoDirective._registry[objtype](directive, name)
|
||||
inst = app.registry.documenters[objtype](directive, name)
|
||||
inst.object = obj
|
||||
inst.fullname = name
|
||||
return list(inst.process_doc(inst.get_doc()))
|
||||
@ -506,7 +503,7 @@ def test_docstring_property_processing():
|
||||
def genarate_docstring(objtype, name, **kw):
|
||||
del processed_docstrings[:]
|
||||
del processed_signatures[:]
|
||||
inst = AutoDirective._registry[objtype](directive, name)
|
||||
inst = app.registry.documenters[objtype](directive, name)
|
||||
inst.generate(**kw)
|
||||
results = list(directive.result)
|
||||
docstrings = inst.get_doc()[0]
|
||||
@ -540,6 +537,8 @@ def test_docstring_property_processing():
|
||||
|
||||
@pytest.mark.usefixtures('setup_test')
|
||||
def test_new_documenter():
|
||||
logging.setup(app, app._status, app._warning)
|
||||
|
||||
class MyDocumenter(ModuleLevelDocumenter):
|
||||
objtype = 'integer'
|
||||
directivetype = 'data'
|
||||
@ -555,10 +554,11 @@ def test_new_documenter():
|
||||
add_documenter(MyDocumenter)
|
||||
|
||||
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)
|
||||
# print '\n'.join(directive.result)
|
||||
assert len(_warnings) == 0, _warnings
|
||||
assert app._warning.getvalue() == ''
|
||||
assert item in directive.result
|
||||
del directive.result[:]
|
||||
|
||||
@ -581,7 +581,7 @@ def test_attrgetter_using():
|
||||
AutoDirective._special_attrgetters[type] = special_getattr
|
||||
|
||||
del getattr_spy[:]
|
||||
inst = AutoDirective._registry[objtype](directive, name)
|
||||
inst = app.registry.documenters[objtype](directive, name)
|
||||
inst.generate(**kw)
|
||||
|
||||
hooked_members = [s[1] for s in getattr_spy]
|
||||
@ -602,20 +602,22 @@ def test_attrgetter_using():
|
||||
|
||||
@pytest.mark.usefixtures('setup_test')
|
||||
def test_generate():
|
||||
logging.setup(app, app._status, app._warning)
|
||||
|
||||
def assert_warns(warn_str, objtype, name, **kw):
|
||||
inst = AutoDirective._registry[objtype](directive, name)
|
||||
inst = app.registry.documenters[objtype](directive, name)
|
||||
inst.generate(**kw)
|
||||
assert len(directive.result) == 0, directive.result
|
||||
assert len(_warnings) == 1, _warnings
|
||||
assert warn_str in _warnings[0], _warnings
|
||||
del _warnings[:]
|
||||
|
||||
assert warn_str in app._warning.getvalue()
|
||||
app._warning.truncate(0)
|
||||
|
||||
def assert_works(objtype, name, **kw):
|
||||
inst = AutoDirective._registry[objtype](directive, name)
|
||||
inst = app.registry.documenters[objtype](directive, name)
|
||||
inst.generate(**kw)
|
||||
assert directive.result
|
||||
# print '\n'.join(directive.result)
|
||||
assert len(_warnings) == 0, _warnings
|
||||
assert app._warning.getvalue() == ''
|
||||
del directive.result[:]
|
||||
|
||||
def assert_processes(items, objtype, name, **kw):
|
||||
@ -625,18 +627,18 @@ def test_generate():
|
||||
assert set(processed_docstrings) | set(processed_signatures) == set(items)
|
||||
|
||||
def assert_result_contains(item, objtype, name, **kw):
|
||||
inst = AutoDirective._registry[objtype](directive, name)
|
||||
inst = app.registry.documenters[objtype](directive, name)
|
||||
inst.generate(**kw)
|
||||
# print '\n'.join(directive.result)
|
||||
assert len(_warnings) == 0, _warnings
|
||||
assert app._warning.getvalue() == ''
|
||||
assert item in directive.result
|
||||
del directive.result[:]
|
||||
|
||||
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.generate(**kw)
|
||||
assert len(_warnings) == 0, _warnings
|
||||
assert app._warning.getvalue() == ''
|
||||
items = list(reversed(items))
|
||||
lineiter = iter(directive.result)
|
||||
# for line in directive.result:
|
||||
|
@ -165,13 +165,15 @@ def test_latex_warnings(app, status, warning):
|
||||
|
||||
|
||||
@pytest.mark.sphinx('latex', testroot='basic')
|
||||
def test_latex_title(app, status, warning):
|
||||
def test_latex_basic(app, status, warning):
|
||||
app.builder.build_all()
|
||||
result = (app.outdir / 'test.tex').text(encoding='utf8')
|
||||
print(result)
|
||||
print(status.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')
|
||||
@ -184,6 +186,18 @@ def test_latex_title_after_admonitions(app, status, warning):
|
||||
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',
|
||||
confoverrides={'numfig': True})
|
||||
def test_numref(app, status, warning):
|
||||
|
@ -57,10 +57,14 @@ def test_mangle_signature():
|
||||
|
||||
|
||||
@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
|
||||
# results..
|
||||
import sphinx.ext.autosummary
|
||||
orig_get_items = sphinx.ext.autosummary.Autosummary.get_items
|
||||
|
||||
autosummary_items = {}
|
||||
@ -73,6 +77,10 @@ def test_get_items_summary(app, status, warning):
|
||||
|
||||
def handler(app, what, name, obj, options, lines):
|
||||
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)
|
||||
|
||||
sphinx.ext.autosummary.Autosummary.get_items = new_get_items
|
||||
@ -81,7 +89,7 @@ def test_get_items_summary(app, status, warning):
|
||||
finally:
|
||||
sphinx.ext.autosummary.Autosummary.get_items = orig_get_items
|
||||
|
||||
html_warnings = warning.getvalue()
|
||||
html_warnings = app._warning.getvalue()
|
||||
assert html_warnings == ''
|
||||
|
||||
expected_values = {
|
||||
|
133
tests/test_ext_inheritance.py
Normal file
133
tests/test_ext_inheritance.py
Normal 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
92
tests/test_smartquotes.py
Normal 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>-- "Sphinx" 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>-- "Sphinx" 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
|
@ -10,10 +10,14 @@
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from sphinx.ext.autosummary.generate import setup_documenters
|
||||
|
||||
|
||||
@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()
|
||||
|
||||
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')
|
||||
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()
|
||||
|
||||
result = (app.outdir / 'generated' / 'sphinx.application.TemplateBridge.html').text(
|
||||
|
@ -183,7 +183,7 @@ def test_warning_location(app, status, warning):
|
||||
assert 'index.txt:10: WARNING: message2' in warning.getvalue()
|
||||
|
||||
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.source, node.line = ('index.txt', 10)
|
||||
@ -200,7 +200,7 @@ def test_warning_location(app, status, warning):
|
||||
|
||||
node.source, node.line = (None, None)
|
||||
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):
|
||||
@ -236,7 +236,7 @@ def test_colored_logs(app, status, warning):
|
||||
assert colorize('darkgray', 'message1') in status.getvalue()
|
||||
assert 'message2\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 colorize('darkred', 'WARNING: message6') in warning.getvalue()
|
||||
|
||||
|
2
tox.ini
2
tox.ini
@ -21,7 +21,7 @@ deps =
|
||||
du13: docutils==0.13.1
|
||||
du14: docutils==0.14
|
||||
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
|
||||
commands=
|
||||
pytest -Wall --durations 25 {posargs}
|
||||
|
Loading…
Reference in New Issue
Block a user