mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge branch '2.0' into refactor_py_domain2
This commit is contained in:
commit
a285220778
9
CHANGES
9
CHANGES
@ -44,6 +44,9 @@ Deprecated
|
||||
* ``sphinx.ext.autodoc.importer.MockLoader``
|
||||
* ``sphinx.ext.autodoc.importer.mock()``
|
||||
* ``sphinx.ext.autosummary.autolink_role()``
|
||||
* ``sphinx.ext.imgmath.DOC_BODY``
|
||||
* ``sphinx.ext.imgmath.DOC_BODY_PREVIEW``
|
||||
* ``sphinx.ext.imgmath.DOC_HEAD``
|
||||
* ``sphinx.transforms.CitationReferences``
|
||||
* ``sphinx.transforms.SmartQuotesSkipper``
|
||||
* ``sphinx.util.docfields.DocFieldTransformer.preprocess_fieldtypes()``
|
||||
@ -68,8 +71,13 @@ Features added
|
||||
* ``math`` directive now supports ``:class:`` option
|
||||
* todo: ``todo`` directive now supports ``:name:`` option
|
||||
* #6232: Enable CLI override of Makefile variables
|
||||
* #6287: autodoc: Unable to document bound instance methods exported as module
|
||||
functions
|
||||
* #6289: autodoc: :confval:`autodoc_default_options` now supports
|
||||
``imported-members`` option
|
||||
* #6212 autosummary: Add :confval:`autosummary_imported_members` to display
|
||||
imported members on autosummary
|
||||
* #6271: ``make clean`` is catastrophically broken if building into '.'
|
||||
* Add ``:classmethod:`` and ``:staticmethod:`` options to :rst:dir:`py:method`
|
||||
directive
|
||||
|
||||
@ -80,6 +88,7 @@ Bugs fixed
|
||||
is consisted by non-ASCII characters
|
||||
* #6213: ifconfig: contents after headings are not shown
|
||||
* commented term in glossary directive is wrongly recognized
|
||||
* #6299: rst domain: rst:directive directive generates waste space
|
||||
|
||||
Testing
|
||||
--------
|
||||
|
@ -147,7 +147,7 @@ Sphinx core events
|
||||
------------------
|
||||
|
||||
These events are known to the core. The arguments shown are given to the
|
||||
registered event handlers. Use :meth:`.connect` in an extension's ``setup``
|
||||
registered event handlers. Use :meth:`.Sphinx.connect` in an extension's ``setup``
|
||||
function (note that ``conf.py`` can also have a ``setup`` function) to connect
|
||||
handlers to the events. Example:
|
||||
|
||||
|
@ -38,3 +38,8 @@ Builder API
|
||||
.. automethod:: write_doc
|
||||
.. automethod:: finish
|
||||
|
||||
**Attributes**
|
||||
|
||||
.. attribute:: events
|
||||
|
||||
An :class:`.EventManager` object.
|
||||
|
@ -177,6 +177,21 @@ The following is a list of deprecated interfaces.
|
||||
- 4.0
|
||||
- ``sphinx.ext.autosummary.AutoLink``
|
||||
|
||||
* - ``sphinx.ext.imgmath.DOC_BODY``
|
||||
- 2.1
|
||||
- 4.0
|
||||
- N/A
|
||||
|
||||
* - ``sphinx.ext.imgmath.DOC_BODY_PREVIEW``
|
||||
- 2.1
|
||||
- 4.0
|
||||
- N/A
|
||||
|
||||
* - ``sphinx.ext.imgmath.DOC_HEAD``
|
||||
- 2.1
|
||||
- 4.0
|
||||
- N/A
|
||||
|
||||
* - ``sphinx.transforms.CitationReferences``
|
||||
- 2.1
|
||||
- 4.0
|
||||
|
@ -27,6 +27,10 @@ Build environment API
|
||||
|
||||
Directory for storing pickled doctrees.
|
||||
|
||||
.. attribute:: events
|
||||
|
||||
An :class:`.EventManager` object.
|
||||
|
||||
.. attribute:: found_docs
|
||||
|
||||
A set of all existing docnames.
|
||||
|
@ -29,3 +29,9 @@ components (e.g. :class:`.Config`, :class:`.BuildEnvironment` and so on) easily.
|
||||
|
||||
.. autoclass:: sphinx.transforms.post_transforms.images.ImageConverter
|
||||
:members:
|
||||
|
||||
Utility components
|
||||
------------------
|
||||
|
||||
.. autoclass:: sphinx.events.EventManager
|
||||
:members:
|
||||
|
@ -387,14 +387,17 @@ There are also config values that you can set:
|
||||
|
||||
The supported options are ``'members'``, ``'member-order'``,
|
||||
``'undoc-members'``, ``'private-members'``, ``'special-members'``,
|
||||
``'inherited-members'``, ``'show-inheritance'``, ``'ignore-module-all'`` and
|
||||
``'exclude-members'``.
|
||||
``'inherited-members'``, ``'show-inheritance'``, ``'ignore-module-all'``,
|
||||
``'imported-members'`` and ``'exclude-members'``.
|
||||
|
||||
.. versionadded:: 1.8
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
Accepts ``True`` as a value.
|
||||
|
||||
.. versionchanged:: 2.1
|
||||
Added ``'imported-members'``.
|
||||
|
||||
.. confval:: autodoc_docstring_signature
|
||||
|
||||
Functions imported from C modules cannot be introspected, and therefore the
|
||||
|
@ -187,7 +187,7 @@ class Sphinx:
|
||||
self.warningiserror = warningiserror
|
||||
logging.setup(self, self._status, self._warning)
|
||||
|
||||
self.events = EventManager()
|
||||
self.events = EventManager(self)
|
||||
|
||||
# keep last few messages for traceback
|
||||
# This will be filled by sphinx.util.logging.LastMessagesWriter
|
||||
@ -254,7 +254,7 @@ class Sphinx:
|
||||
|
||||
# now that we know all config values, collect them from conf.py
|
||||
self.config.init_values()
|
||||
self.emit('config-inited', self.config)
|
||||
self.events.emit('config-inited', self.config)
|
||||
|
||||
# create the project
|
||||
self.project = Project(self.srcdir, self.config.source_suffix)
|
||||
@ -324,7 +324,7 @@ class Sphinx:
|
||||
# type: () -> None
|
||||
self.builder.set_environment(self.env)
|
||||
self.builder.init()
|
||||
self.emit('builder-inited')
|
||||
self.events.emit('builder-inited')
|
||||
|
||||
# ---- main "build" method -------------------------------------------------
|
||||
|
||||
@ -365,10 +365,10 @@ class Sphinx:
|
||||
envfile = path.join(self.doctreedir, ENV_PICKLE_FILENAME)
|
||||
if path.isfile(envfile):
|
||||
os.unlink(envfile)
|
||||
self.emit('build-finished', err)
|
||||
self.events.emit('build-finished', err)
|
||||
raise
|
||||
else:
|
||||
self.emit('build-finished', None)
|
||||
self.events.emit('build-finished', None)
|
||||
self.builder.cleanup()
|
||||
|
||||
# ---- general extensibility interface -------------------------------------
|
||||
@ -437,13 +437,7 @@ class Sphinx:
|
||||
Return the return values of all callbacks as a list. Do not emit core
|
||||
Sphinx events in extensions!
|
||||
"""
|
||||
try:
|
||||
logger.debug('[app] emitting event: %r%s', event, repr(args)[:100])
|
||||
except Exception:
|
||||
# not every object likes to be repr()'d (think
|
||||
# random stuff coming via autodoc)
|
||||
pass
|
||||
return self.events.emit(event, self, *args)
|
||||
return self.events.emit(event, *args)
|
||||
|
||||
def emit_firstresult(self, event, *args):
|
||||
# type: (str, Any) -> Any
|
||||
@ -453,7 +447,7 @@ class Sphinx:
|
||||
|
||||
.. versionadded:: 0.5
|
||||
"""
|
||||
return self.events.emit_firstresult(event, self, *args)
|
||||
return self.events.emit_firstresult(event, *args)
|
||||
|
||||
# registering addon parts
|
||||
|
||||
|
@ -43,6 +43,7 @@ if False:
|
||||
from sphinx.application import Sphinx # NOQA
|
||||
from sphinx.config import Config # NOQA
|
||||
from sphinx.environment import BuildEnvironment # NOQA
|
||||
from sphinx.events import EventManager # NOQA
|
||||
from sphinx.util.i18n import CatalogInfo # NOQA
|
||||
from sphinx.util.tags import Tags # NOQA
|
||||
|
||||
@ -93,6 +94,7 @@ class Builder:
|
||||
|
||||
self.app = app # type: Sphinx
|
||||
self.env = None # type: BuildEnvironment
|
||||
self.events = app.events # type: EventManager
|
||||
self.config = app.config # type: Config
|
||||
self.tags = app.tags # type: Tags
|
||||
self.tags.add(self.format)
|
||||
@ -399,7 +401,7 @@ class Builder:
|
||||
added, changed, removed = self.env.get_outdated_files(updated)
|
||||
|
||||
# allow user intervention as well
|
||||
for docs in self.app.emit('env-get-outdated', self, added, changed, removed):
|
||||
for docs in self.events.emit('env-get-outdated', self, added, changed, removed):
|
||||
changed.update(set(docs) & self.env.found_docs)
|
||||
|
||||
# if files were added or removed, all documents with globbed toctrees
|
||||
@ -416,13 +418,13 @@ class Builder:
|
||||
|
||||
# clear all files no longer present
|
||||
for docname in removed:
|
||||
self.app.emit('env-purge-doc', self.env, docname)
|
||||
self.events.emit('env-purge-doc', self.env, docname)
|
||||
self.env.clear_doc(docname)
|
||||
|
||||
# read all new and changed files
|
||||
docnames = sorted(added | changed)
|
||||
# allow changing and reordering the list of docs to read
|
||||
self.app.emit('env-before-read-docs', self.env, docnames)
|
||||
self.events.emit('env-before-read-docs', self.env, docnames)
|
||||
|
||||
# check if we should do parallel or serial read
|
||||
if parallel_available and len(docnames) > 5 and self.app.parallel > 1:
|
||||
@ -439,7 +441,7 @@ class Builder:
|
||||
raise SphinxError('master file %s not found' %
|
||||
self.env.doc2path(self.config.master_doc))
|
||||
|
||||
for retval in self.app.emit('env-updated', self.env):
|
||||
for retval in self.events.emit('env-updated', self.env):
|
||||
if retval is not None:
|
||||
docnames.extend(retval)
|
||||
|
||||
@ -453,7 +455,7 @@ class Builder:
|
||||
for docname in status_iterator(docnames, __('reading sources... '), "purple",
|
||||
len(docnames), self.app.verbosity):
|
||||
# remove all inventory entries for that file
|
||||
self.app.emit('env-purge-doc', self.env, docname)
|
||||
self.events.emit('env-purge-doc', self.env, docname)
|
||||
self.env.clear_doc(docname)
|
||||
self.read_doc(docname)
|
||||
|
||||
@ -461,7 +463,7 @@ class Builder:
|
||||
# type: (List[str], int) -> None
|
||||
# clear all outdated docs at once
|
||||
for docname in docnames:
|
||||
self.app.emit('env-purge-doc', self.env, docname)
|
||||
self.events.emit('env-purge-doc', self.env, docname)
|
||||
self.env.clear_doc(docname)
|
||||
|
||||
def read_process(docs):
|
||||
|
@ -686,7 +686,7 @@ class StandaloneHTMLBuilder(Builder):
|
||||
def gen_additional_pages(self):
|
||||
# type: () -> None
|
||||
# pages from extensions
|
||||
for pagelist in self.app.emit('html-collect-pages'):
|
||||
for pagelist in self.events.emit('html-collect-pages'):
|
||||
for pagename, context, template in pagelist:
|
||||
self.handle_page(pagename, context, template)
|
||||
|
||||
|
@ -72,11 +72,19 @@ class Make:
|
||||
|
||||
def build_clean(self):
|
||||
# type: () -> int
|
||||
srcdir = path.abspath(self.srcdir)
|
||||
builddir = path.abspath(self.builddir)
|
||||
if not path.exists(self.builddir):
|
||||
return 0
|
||||
elif not path.isdir(self.builddir):
|
||||
print("Error: %r is not a directory!" % self.builddir)
|
||||
return 1
|
||||
elif srcdir == builddir:
|
||||
print("Error: %r is same as source directory!" % self.builddir)
|
||||
return 1
|
||||
elif path.commonpath([srcdir, builddir]) == builddir:
|
||||
print("Error: %r directory contains source directory!" % self.builddir)
|
||||
return 1
|
||||
print("Removing everything under %r..." % self.builddir)
|
||||
for item in os.listdir(self.builddir):
|
||||
rmtree(self.builddir_join(item))
|
||||
|
@ -9,12 +9,14 @@
|
||||
"""
|
||||
|
||||
import re
|
||||
from typing import cast
|
||||
|
||||
from sphinx import addnodes
|
||||
from sphinx.directives import ObjectDescription
|
||||
from sphinx.domains import Domain, ObjType
|
||||
from sphinx.locale import _
|
||||
from sphinx.locale import _, __
|
||||
from sphinx.roles import XRefRole
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.nodes import make_refnode
|
||||
|
||||
if False:
|
||||
@ -26,6 +28,8 @@ if False:
|
||||
from sphinx.environment import BuildEnvironment # NOQA
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
dir_sig_re = re.compile(r'\.\. (.+?)::(.*)$')
|
||||
|
||||
|
||||
@ -43,14 +47,9 @@ class ReSTMarkup(ObjectDescription):
|
||||
signode['first'] = (not self.names)
|
||||
self.state.document.note_explicit_target(signode)
|
||||
|
||||
objects = self.env.domaindata['rst']['objects']
|
||||
key = (self.objtype, name)
|
||||
if key in objects:
|
||||
self.state_machine.reporter.warning(
|
||||
'duplicate description of %s %s, ' % (self.objtype, name) +
|
||||
'other instance in ' + self.env.doc2path(objects[key]),
|
||||
line=self.lineno)
|
||||
objects[key] = self.env.docname
|
||||
domain = cast(ReSTDomain, self.env.get_domain('rst'))
|
||||
domain.note_object(self.objtype, name, location=(self.env.docname, self.lineno))
|
||||
|
||||
indextext = self.get_index_text(self.objtype, name)
|
||||
if indextext:
|
||||
self.indexnode['entries'].append(('single', indextext,
|
||||
@ -58,10 +57,6 @@ class ReSTMarkup(ObjectDescription):
|
||||
|
||||
def get_index_text(self, objectname, name):
|
||||
# type: (str, str) -> str
|
||||
if self.objtype == 'directive':
|
||||
return _('%s (directive)') % name
|
||||
elif self.objtype == 'role':
|
||||
return _('%s (role)') % name
|
||||
return ''
|
||||
|
||||
|
||||
@ -80,7 +75,10 @@ def parse_directive(d):
|
||||
if not m:
|
||||
return (dir, '')
|
||||
parsed_dir, parsed_args = m.groups()
|
||||
return (parsed_dir.strip(), ' ' + parsed_args.strip())
|
||||
if parsed_args.strip():
|
||||
return (parsed_dir.strip(), ' ' + parsed_args.strip())
|
||||
else:
|
||||
return (parsed_dir.strip(), '')
|
||||
|
||||
|
||||
class ReSTDirective(ReSTMarkup):
|
||||
@ -96,6 +94,10 @@ class ReSTDirective(ReSTMarkup):
|
||||
signode += addnodes.desc_addname(args, args)
|
||||
return name
|
||||
|
||||
def get_index_text(self, objectname, name):
|
||||
# type: (str, str) -> str
|
||||
return _('%s (directive)') % name
|
||||
|
||||
|
||||
class ReSTRole(ReSTMarkup):
|
||||
"""
|
||||
@ -106,6 +108,10 @@ class ReSTRole(ReSTMarkup):
|
||||
signode += addnodes.desc_name(':%s:' % sig, ':%s:' % sig)
|
||||
return sig
|
||||
|
||||
def get_index_text(self, objectname, name):
|
||||
# type: (str, str) -> str
|
||||
return _('%s (role)') % name
|
||||
|
||||
|
||||
class ReSTDomain(Domain):
|
||||
"""ReStructuredText domain."""
|
||||
@ -126,42 +132,54 @@ class ReSTDomain(Domain):
|
||||
}
|
||||
initial_data = {
|
||||
'objects': {}, # fullname -> docname, objtype
|
||||
} # type: Dict[str, Dict[str, Tuple[str, ObjType]]]
|
||||
} # type: Dict[str, Dict[Tuple[str, str], str]]
|
||||
|
||||
@property
|
||||
def objects(self):
|
||||
# type: () -> Dict[Tuple[str, str], str]
|
||||
return self.data.setdefault('objects', {}) # (objtype, fullname) -> docname
|
||||
|
||||
def note_object(self, objtype, name, location=None):
|
||||
# type: (str, str, Any) -> None
|
||||
if (objtype, name) in self.objects:
|
||||
docname = self.objects[objtype, name]
|
||||
logger.warning(__('duplicate description of %s %s, other instance in %s') %
|
||||
(objtype, name, docname), location=location)
|
||||
|
||||
self.objects[objtype, name] = self.env.docname
|
||||
|
||||
def clear_doc(self, docname):
|
||||
# type: (str) -> None
|
||||
for (typ, name), doc in list(self.data['objects'].items()):
|
||||
for (typ, name), doc in list(self.objects.items()):
|
||||
if doc == docname:
|
||||
del self.data['objects'][typ, name]
|
||||
del self.objects[typ, name]
|
||||
|
||||
def merge_domaindata(self, docnames, otherdata):
|
||||
# type: (List[str], Dict) -> None
|
||||
# XXX check duplicates
|
||||
for (typ, name), doc in otherdata['objects'].items():
|
||||
if doc in docnames:
|
||||
self.data['objects'][typ, name] = doc
|
||||
self.objects[typ, name] = doc
|
||||
|
||||
def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode):
|
||||
# type: (BuildEnvironment, str, Builder, str, str, addnodes.pending_xref, nodes.Element) -> nodes.Element # NOQA
|
||||
objects = self.data['objects']
|
||||
objtypes = self.objtypes_for_role(typ)
|
||||
for objtype in objtypes:
|
||||
if (objtype, target) in objects:
|
||||
return make_refnode(builder, fromdocname,
|
||||
objects[objtype, target],
|
||||
todocname = self.objects.get((objtype, target))
|
||||
if todocname:
|
||||
return make_refnode(builder, fromdocname, todocname,
|
||||
objtype + '-' + target,
|
||||
contnode, target + ' ' + objtype)
|
||||
return None
|
||||
|
||||
def resolve_any_xref(self, env, fromdocname, builder, target, node, contnode):
|
||||
# type: (BuildEnvironment, str, Builder, str, addnodes.pending_xref, nodes.Element) -> List[Tuple[str, nodes.Element]] # NOQA
|
||||
objects = self.data['objects']
|
||||
results = [] # type: List[Tuple[str, nodes.Element]]
|
||||
for objtype in self.object_types:
|
||||
if (objtype, target) in self.data['objects']:
|
||||
todocname = self.objects.get((objtype, target))
|
||||
if todocname:
|
||||
results.append(('rst:' + self.role_for_objtype(objtype),
|
||||
make_refnode(builder, fromdocname,
|
||||
objects[objtype, target],
|
||||
make_refnode(builder, fromdocname, todocname,
|
||||
objtype + '-' + target,
|
||||
contnode, target + ' ' + objtype)))
|
||||
return results
|
||||
|
@ -37,6 +37,7 @@ if False:
|
||||
from sphinx.application import Sphinx # NOQA
|
||||
from sphinx.builders import Builder # NOQA
|
||||
from sphinx.config import Config # NOQA
|
||||
from sphinx.event import EventManager # NOQA
|
||||
from sphinx.domains import Domain # NOQA
|
||||
from sphinx.project import Project # NOQA
|
||||
|
||||
@ -98,6 +99,7 @@ class BuildEnvironment:
|
||||
self.srcdir = None # type: str
|
||||
self.config = None # type: Config
|
||||
self.config_status = None # type: int
|
||||
self.events = None # type: EventManager
|
||||
self.project = None # type: Project
|
||||
self.version = None # type: Dict[str, str]
|
||||
|
||||
@ -193,7 +195,7 @@ class BuildEnvironment:
|
||||
# type: () -> Dict
|
||||
"""Obtains serializable data for pickling."""
|
||||
__dict__ = self.__dict__.copy()
|
||||
__dict__.update(app=None, domains={}) # clear unpickable attributes
|
||||
__dict__.update(app=None, domains={}, events=None) # clear unpickable attributes
|
||||
return __dict__
|
||||
|
||||
def __setstate__(self, state):
|
||||
@ -213,6 +215,7 @@ class BuildEnvironment:
|
||||
|
||||
self.app = app
|
||||
self.doctreedir = app.doctreedir
|
||||
self.events = app.events
|
||||
self.srcdir = app.srcdir
|
||||
self.project = app.project
|
||||
self.version = app.registry.get_envversion(app)
|
||||
@ -310,7 +313,7 @@ class BuildEnvironment:
|
||||
|
||||
for domainname, domain in self.domains.items():
|
||||
domain.merge_domaindata(docnames, other.domaindata[domainname])
|
||||
app.emit('env-merge-info', self, docnames, other)
|
||||
self.events.emit('env-merge-info', self, docnames, other)
|
||||
|
||||
def path2doc(self, filename):
|
||||
# type: (str) -> Optional[str]
|
||||
@ -452,7 +455,7 @@ class BuildEnvironment:
|
||||
def check_dependents(self, app, already):
|
||||
# type: (Sphinx, Set[str]) -> Iterator[str]
|
||||
to_rewrite = [] # type: List[str]
|
||||
for docnames in app.emit('env-get-updated', self):
|
||||
for docnames in self.events.emit('env-get-updated', self):
|
||||
to_rewrite.extend(docnames)
|
||||
for docname in set(to_rewrite):
|
||||
if docname not in already:
|
||||
@ -600,7 +603,7 @@ class BuildEnvironment:
|
||||
self.temp_data = backup
|
||||
|
||||
# allow custom references to be resolved
|
||||
self.app.emit('doctree-resolved', doctree, docname)
|
||||
self.events.emit('doctree-resolved', doctree, docname)
|
||||
|
||||
def collect_relations(self):
|
||||
# type: () -> Dict[str, List[str]]
|
||||
@ -656,7 +659,7 @@ class BuildEnvironment:
|
||||
# call check-consistency for all extensions
|
||||
for domain in self.domains.values():
|
||||
domain.check_consistency()
|
||||
self.app.emit('env-check-consistency', self)
|
||||
self.events.emit('env-check-consistency', self)
|
||||
|
||||
# --------- METHODS FOR COMPATIBILITY --------------------------------------
|
||||
|
||||
|
@ -10,14 +10,20 @@
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
import warnings
|
||||
from collections import OrderedDict, defaultdict
|
||||
|
||||
from sphinx.deprecation import RemovedInSphinx40Warning
|
||||
from sphinx.errors import ExtensionError
|
||||
from sphinx.locale import __
|
||||
from sphinx.util import logging
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any, Callable, Dict, List # NOQA
|
||||
from sphinx.application import Sphinx # NOQA
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# List of all known core events. Maps name to arguments description.
|
||||
@ -42,20 +48,28 @@ core_events = {
|
||||
|
||||
|
||||
class EventManager:
|
||||
def __init__(self):
|
||||
# type: () -> None
|
||||
"""Event manager for Sphinx."""
|
||||
|
||||
def __init__(self, app=None):
|
||||
# type: (Sphinx) -> None
|
||||
if app is None:
|
||||
warnings.warn('app argument is required for EventManager.',
|
||||
RemovedInSphinx40Warning)
|
||||
self.app = app
|
||||
self.events = core_events.copy()
|
||||
self.listeners = defaultdict(OrderedDict) # type: Dict[str, Dict[int, Callable]]
|
||||
self.next_listener_id = 0
|
||||
|
||||
def add(self, name):
|
||||
# type: (str) -> None
|
||||
"""Register a custom Sphinx event."""
|
||||
if name in self.events:
|
||||
raise ExtensionError(__('Event %r already present') % name)
|
||||
self.events[name] = ''
|
||||
|
||||
def connect(self, name, callback):
|
||||
# type: (str, Callable) -> int
|
||||
"""Connect a handler to specific event."""
|
||||
if name not in self.events:
|
||||
raise ExtensionError(__('Unknown event name: %s') % name)
|
||||
|
||||
@ -66,18 +80,35 @@ class EventManager:
|
||||
|
||||
def disconnect(self, listener_id):
|
||||
# type: (int) -> None
|
||||
"""Disconnect a handler."""
|
||||
for event in self.listeners.values():
|
||||
event.pop(listener_id, None)
|
||||
|
||||
def emit(self, name, *args):
|
||||
# type: (str, Any) -> List
|
||||
"""Emit a Sphinx event."""
|
||||
try:
|
||||
logger.debug('[app] emitting event: %r%s', name, repr(args)[:100])
|
||||
except Exception:
|
||||
# not every object likes to be repr()'d (think
|
||||
# random stuff coming via autodoc)
|
||||
pass
|
||||
|
||||
results = []
|
||||
for callback in self.listeners[name].values():
|
||||
results.append(callback(*args))
|
||||
if self.app is None:
|
||||
# for compatibility; RemovedInSphinx40Warning
|
||||
results.append(callback(*args))
|
||||
else:
|
||||
results.append(callback(self.app, *args))
|
||||
return results
|
||||
|
||||
def emit_firstresult(self, name, *args):
|
||||
# type: (str, Any) -> Any
|
||||
"""Emit a Sphinx event and returns first result.
|
||||
|
||||
This returns the result of the first handler that doesn't return ``None``.
|
||||
"""
|
||||
for result in self.emit(name, *args):
|
||||
if result is not None:
|
||||
return result
|
||||
|
@ -405,9 +405,9 @@ class Documenter:
|
||||
|
||||
retann = self.retann
|
||||
|
||||
result = self.env.app.emit_firstresult(
|
||||
'autodoc-process-signature', self.objtype, self.fullname,
|
||||
self.object, self.options, args, retann)
|
||||
result = self.env.events.emit_firstresult('autodoc-process-signature',
|
||||
self.objtype, self.fullname,
|
||||
self.object, self.options, args, retann)
|
||||
if result:
|
||||
args, retann = result
|
||||
|
||||
@ -993,7 +993,9 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
|
||||
@classmethod
|
||||
def can_document_member(cls, member, membername, isattr, parent):
|
||||
# type: (Any, str, bool, Any) -> bool
|
||||
return inspect.isfunction(member) or inspect.isbuiltin(member)
|
||||
# supports functions, builtins and bound methods exported at the module level
|
||||
return (inspect.isfunction(member) or inspect.isbuiltin(member) or
|
||||
(inspect.isroutine(member) and isinstance(parent, ModuleDocumenter)))
|
||||
|
||||
def format_args(self):
|
||||
# type: () -> str
|
||||
@ -1347,17 +1349,14 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter):
|
||||
@classmethod
|
||||
def can_document_member(cls, member, membername, isattr, parent):
|
||||
# type: (Any, str, bool, Any) -> bool
|
||||
non_attr_types = (type, MethodDescriptorType)
|
||||
isdatadesc = inspect.isdescriptor(member) and not \
|
||||
cls.is_function_or_method(member) and not \
|
||||
isinstance(member, non_attr_types) and not \
|
||||
type(member).__name__ == "instancemethod"
|
||||
# That last condition addresses an obscure case of C-defined
|
||||
# methods using a deprecated type in Python 3, that is not otherwise
|
||||
# exported anywhere by Python
|
||||
return isdatadesc or (not isinstance(parent, ModuleDocumenter) and
|
||||
not inspect.isroutine(member) and
|
||||
not isinstance(member, type))
|
||||
if inspect.isattributedescriptor(member):
|
||||
return True
|
||||
elif (not isinstance(parent, ModuleDocumenter) and
|
||||
not inspect.isroutine(member) and
|
||||
not isinstance(member, type)):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def document_members(self, all_members=False):
|
||||
# type: (bool) -> None
|
||||
@ -1368,8 +1367,7 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter):
|
||||
ret = super().import_object()
|
||||
if inspect.isenumattribute(self.object):
|
||||
self.object = self.object.value
|
||||
if inspect.isdescriptor(self.object) and \
|
||||
not self.is_function_or_method(self.object):
|
||||
if inspect.isattributedescriptor(self.object):
|
||||
self._datadescriptor = True
|
||||
else:
|
||||
# if it's not a data descriptor
|
||||
|
@ -30,7 +30,8 @@ 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', 'exclude-members', 'member-order']
|
||||
'ignore-module-all', 'exclude-members', 'member-order',
|
||||
'imported-members']
|
||||
|
||||
|
||||
class DummyOptionSpec(dict):
|
||||
|
@ -21,12 +21,15 @@ from subprocess import CalledProcessError, PIPE
|
||||
from docutils import nodes
|
||||
|
||||
import sphinx
|
||||
from sphinx import package_dir
|
||||
from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias
|
||||
from sphinx.errors import SphinxError
|
||||
from sphinx.locale import _, __
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.math import get_node_equation_number, wrap_displaymath
|
||||
from sphinx.util.osutil import ensuredir
|
||||
from sphinx.util.png import read_png_depth, write_png_depth
|
||||
from sphinx.util.template import LaTeXRenderer
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
@ -38,6 +41,8 @@ if False:
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
templates_path = path.join(package_dir, 'templates', 'imgmath')
|
||||
|
||||
|
||||
class MathExtError(SphinxError):
|
||||
category = 'Math extension error'
|
||||
@ -87,19 +92,27 @@ DOC_BODY_PREVIEW = r'''
|
||||
depth_re = re.compile(br'\[\d+ depth=(-?\d+)\]')
|
||||
|
||||
|
||||
def generate_latex_macro(math, config):
|
||||
# type: (str, Config) -> str
|
||||
def generate_latex_macro(math, config, confdir=''):
|
||||
# type: (str, Config, str) -> str
|
||||
"""Generate LaTeX macro."""
|
||||
fontsize = config.imgmath_font_size
|
||||
baselineskip = int(round(fontsize * 1.2))
|
||||
variables = {
|
||||
'fontsize': config.imgmath_font_size,
|
||||
'baselineskip': int(round(config.imgmath_font_size * 1.2)),
|
||||
'preamble': config.imgmath_latex_preamble,
|
||||
'math': math
|
||||
}
|
||||
|
||||
latex = DOC_HEAD + config.imgmath_latex_preamble
|
||||
if config.imgmath_use_preview:
|
||||
latex += DOC_BODY_PREVIEW % (fontsize, baselineskip, math)
|
||||
template_name = 'preview.tex_t'
|
||||
else:
|
||||
latex += DOC_BODY % (fontsize, baselineskip, math)
|
||||
template_name = 'template.tex_t'
|
||||
|
||||
return latex
|
||||
for template_dir in config.templates_path:
|
||||
template = path.join(confdir, template_dir, template_name)
|
||||
if path.exists(template):
|
||||
return LaTeXRenderer().render(template, variables)
|
||||
|
||||
return LaTeXRenderer(templates_path).render(template_name, variables)
|
||||
|
||||
|
||||
def ensure_tempdir(builder):
|
||||
@ -220,7 +233,7 @@ def render_math(self, math):
|
||||
if image_format not in SUPPORT_FORMAT:
|
||||
raise MathExtError('imgmath_image_format must be either "png" or "svg"')
|
||||
|
||||
latex = generate_latex_macro(math, self.builder.config)
|
||||
latex = generate_latex_macro(math, self.builder.config, self.builder.confdir)
|
||||
|
||||
filename = "%s.%s" % (sha1(latex.encode()).hexdigest(), image_format)
|
||||
relfn = posixpath.join(self.builder.imgpath, 'math', filename)
|
||||
@ -332,6 +345,15 @@ def html_visit_displaymath(self, node):
|
||||
raise nodes.SkipNode
|
||||
|
||||
|
||||
deprecated_alias('sphinx.ext.imgmath',
|
||||
{
|
||||
'DOC_BODY': DOC_BODY,
|
||||
'DOC_BODY_PREVIEW': DOC_BODY_PREVIEW,
|
||||
'DOC_HEAD': DOC_HEAD,
|
||||
},
|
||||
RemovedInSphinx40Warning)
|
||||
|
||||
|
||||
def setup(app):
|
||||
# type: (Sphinx) -> Dict[str, Any]
|
||||
app.add_html_math_renderer('imgmath',
|
||||
|
@ -86,7 +86,7 @@ def process_todos(app, doctree):
|
||||
if not hasattr(env, 'todo_all_todos'):
|
||||
env.todo_all_todos = [] # type: ignore
|
||||
for node in doctree.traverse(todo_node):
|
||||
app.emit('todo-defined', node)
|
||||
app.events.emit('todo-defined', node)
|
||||
|
||||
newnode = node.deepcopy()
|
||||
newnode['ids'] = []
|
||||
|
18
sphinx/templates/imgmath/preview.tex_t
Normal file
18
sphinx/templates/imgmath/preview.tex_t
Normal file
@ -0,0 +1,18 @@
|
||||
\documentclass[12pt]{article}
|
||||
\usepackage[utf8x]{inputenc}
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amsthm}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{amsfonts}
|
||||
\usepackage{anyfontsize}
|
||||
\usepackage{bm}
|
||||
\pagestyle{empty}
|
||||
<%= preamble %>
|
||||
|
||||
\usepackage[active]{preview}
|
||||
|
||||
\begin{document}
|
||||
\begin{preview}
|
||||
\fontsize{<%= fontsize %>}{<%= baselineskip %}}\selectfont <%= math %>
|
||||
\end{preview}
|
||||
\end{document}
|
14
sphinx/templates/imgmath/template.tex_t
Normal file
14
sphinx/templates/imgmath/template.tex_t
Normal file
@ -0,0 +1,14 @@
|
||||
\documentclass[12pt]{article}
|
||||
\usepackage[utf8x]{inputenc}
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amsthm}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{amsfonts}
|
||||
\usepackage{anyfontsize}
|
||||
\usepackage{bm}
|
||||
\pagestyle{empty}
|
||||
<%= preamble %>
|
||||
|
||||
\begin{document}
|
||||
\fontsize{<%= fontsize %>}{<%= baselineskip %>}\selectfont <%= math %>
|
||||
\end{document}
|
@ -29,7 +29,7 @@ def deprecate_source_parsers(app, config):
|
||||
# type: (Sphinx, Config) -> None
|
||||
if config.source_parsers:
|
||||
warnings.warn('The config variable "source_parsers" is deprecated. '
|
||||
'Please use app.add_source_parser() API instead.',
|
||||
'Please update your extension for the parser and remove the setting.',
|
||||
RemovedInSphinx30Warning)
|
||||
for suffix, parser in config.source_parsers.items():
|
||||
if isinstance(parser, str):
|
||||
|
@ -29,6 +29,17 @@ if False:
|
||||
# For type annotation
|
||||
from typing import Any, Callable, Mapping, List, Tuple, Type # NOQA
|
||||
|
||||
if sys.version_info > (3, 7):
|
||||
from types import (
|
||||
ClassMethodDescriptorType,
|
||||
MethodDescriptorType,
|
||||
WrapperDescriptorType
|
||||
)
|
||||
else:
|
||||
ClassMethodDescriptorType = type(object.__init__)
|
||||
MethodDescriptorType = type(str.join)
|
||||
WrapperDescriptorType = type(dict.__dict__['fromkeys'])
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
memory_address_re = re.compile(r' at 0x[0-9a-f]{8,16}(?=>)', re.IGNORECASE)
|
||||
@ -161,6 +172,34 @@ def isdescriptor(x):
|
||||
return False
|
||||
|
||||
|
||||
def isattributedescriptor(obj):
|
||||
# type: (Any) -> bool
|
||||
"""Check if the object is an attribute like descriptor."""
|
||||
if inspect.isdatadescriptor(object):
|
||||
# data descriptor is kind of attribute
|
||||
return True
|
||||
elif isdescriptor(obj):
|
||||
# non data descriptor
|
||||
if isfunction(obj) or isbuiltin(obj) or inspect.ismethod(obj):
|
||||
# attribute must not be either function, builtin and method
|
||||
return False
|
||||
elif inspect.isclass(obj):
|
||||
# attribute must not be a class
|
||||
return False
|
||||
elif isinstance(obj, (ClassMethodDescriptorType,
|
||||
MethodDescriptorType,
|
||||
WrapperDescriptorType)):
|
||||
# attribute must not be a method descriptor
|
||||
return False
|
||||
elif type(obj).__name__ == "instancemethod":
|
||||
# attribute must not be an instancemethod (C-API)
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def isfunction(obj):
|
||||
# type: (Any) -> bool
|
||||
"""Check if the object is function."""
|
||||
|
@ -67,9 +67,10 @@ class SphinxRenderer(FileRenderer):
|
||||
|
||||
|
||||
class LaTeXRenderer(SphinxRenderer):
|
||||
def __init__(self):
|
||||
# type: () -> None
|
||||
template_path = os.path.join(package_dir, 'templates', 'latex')
|
||||
def __init__(self, template_path=None):
|
||||
# type: (str) -> None
|
||||
if template_path is None:
|
||||
template_path = os.path.join(package_dir, 'templates', 'latex')
|
||||
super().__init__(template_path)
|
||||
|
||||
# use texescape as escape filter
|
||||
|
7
tests/roots/test-ext-autodoc/target/bound_method.py
Normal file
7
tests/roots/test-ext-autodoc/target/bound_method.py
Normal file
@ -0,0 +1,7 @@
|
||||
class Cls:
|
||||
def method(self):
|
||||
"""Method docstring"""
|
||||
pass
|
||||
|
||||
|
||||
bound_method = Cls().method
|
@ -259,6 +259,11 @@ def test_format_signature():
|
||||
assert formatsig('method', 'H.foo', H.foo2, None, None) == '(*c)'
|
||||
assert formatsig('method', 'H.foo', H.foo3, None, None) == r"(d='\\n')"
|
||||
|
||||
# test bound methods interpreted as functions
|
||||
assert formatsig('function', 'foo', H().foo1, None, None) == '(b, *c)'
|
||||
assert formatsig('function', 'foo', H().foo2, None, None) == '(*c)'
|
||||
assert formatsig('function', 'foo', H().foo3, None, None) == r"(d='\\n')"
|
||||
|
||||
# test exception handling (exception is caught and args is '')
|
||||
directive.env.config.autodoc_docstring_signature = False
|
||||
assert formatsig('function', 'int', int, None, None) == ''
|
||||
@ -451,6 +456,14 @@ def test_get_doc():
|
||||
directive.env.config.autoclass_content = 'both'
|
||||
assert getdocl('class', I) == ['Class docstring', '', 'New docstring']
|
||||
|
||||
# verify that method docstrings get extracted in both normal case
|
||||
# and in case of bound method posing as a function
|
||||
class J: # NOQA
|
||||
def foo(self):
|
||||
"""Method docstring"""
|
||||
assert getdocl('method', J.foo) == ['Method docstring']
|
||||
assert getdocl('function', J().foo) == ['Method docstring']
|
||||
|
||||
from target import Base, Derived
|
||||
|
||||
# NOTE: inspect.getdoc seems not to work with locally defined classes
|
||||
@ -1491,6 +1504,23 @@ def test_partialfunction():
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('setup_test')
|
||||
def test_bound_method():
|
||||
options = {"members": None}
|
||||
actual = do_autodoc(app, 'module', 'target.bound_method', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:module:: target.bound_method',
|
||||
'',
|
||||
'',
|
||||
'.. py:function:: bound_method()',
|
||||
' :module: target.bound_method',
|
||||
'',
|
||||
' Method docstring',
|
||||
' ',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('setup_test')
|
||||
def test_coroutine():
|
||||
options = {"members": None}
|
||||
@ -1579,6 +1609,8 @@ def test_autodoc_default_options(app):
|
||||
assert ' .. py:attribute:: EnumCls.val4' not in actual
|
||||
actual = do_autodoc(app, 'class', 'target.CustomIter')
|
||||
assert ' .. py:method:: target.CustomIter' not in actual
|
||||
actual = do_autodoc(app, 'module', 'target')
|
||||
assert '.. py:function:: save_traceback(app)' not in actual
|
||||
|
||||
# with :members:
|
||||
app.config.autodoc_default_options = {'members': None}
|
||||
@ -1642,6 +1674,15 @@ def test_autodoc_default_options(app):
|
||||
assert ' .. py:method:: CustomIter.snafucate()' in actual
|
||||
assert ' Makes this snafucated.' in actual
|
||||
|
||||
# with :imported-members:
|
||||
app.config.autodoc_default_options = {
|
||||
'members': None,
|
||||
'imported-members': None,
|
||||
'ignore-module-all': None,
|
||||
}
|
||||
actual = do_autodoc(app, 'module', 'target')
|
||||
assert '.. py:function:: save_traceback(app)' in actual
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autodoc_default_options_with_values(app):
|
||||
|
@ -8,7 +8,14 @@
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
from sphinx import addnodes
|
||||
from sphinx.addnodes import (
|
||||
desc, desc_addname, desc_content, desc_name, desc_optional, desc_parameter,
|
||||
desc_parameterlist, desc_returns, desc_signature
|
||||
)
|
||||
from sphinx.domains.rst import parse_directive
|
||||
from sphinx.testing import restructuredtext
|
||||
from sphinx.testing.util import assert_node
|
||||
|
||||
|
||||
def test_parse_directive():
|
||||
@ -16,10 +23,59 @@ def test_parse_directive():
|
||||
assert s == ('foö', '')
|
||||
|
||||
s = parse_directive(' .. foö :: ')
|
||||
assert s == ('foö', ' ')
|
||||
assert s == ('foö', '')
|
||||
|
||||
s = parse_directive('.. foö:: args1 args2')
|
||||
assert s == ('foö', ' args1 args2')
|
||||
|
||||
s = parse_directive('.. :: bar')
|
||||
assert s == ('.. :: bar', '')
|
||||
|
||||
|
||||
def test_rst_directive(app):
|
||||
# bare
|
||||
text = ".. rst:directive:: toctree"
|
||||
doctree = restructuredtext.parse(app, text)
|
||||
assert_node(doctree, (addnodes.index,
|
||||
[desc, ([desc_signature, desc_name, ".. toctree::"],
|
||||
[desc_content, ()])]))
|
||||
assert_node(doctree[0],
|
||||
entries=[("single", "toctree (directive)", "directive-toctree", "", None)])
|
||||
assert_node(doctree[1], addnodes.desc, desctype="directive",
|
||||
domain="rst", objtype="directive", noindex=False)
|
||||
|
||||
# decorated
|
||||
text = ".. rst:directive:: .. toctree::"
|
||||
doctree = restructuredtext.parse(app, text)
|
||||
assert_node(doctree, (addnodes.index,
|
||||
[desc, ([desc_signature, desc_name, ".. toctree::"],
|
||||
[desc_content, ()])]))
|
||||
assert_node(doctree[0],
|
||||
entries=[("single", "toctree (directive)", "directive-toctree", "", None)])
|
||||
assert_node(doctree[1], addnodes.desc, desctype="directive",
|
||||
domain="rst", objtype="directive", noindex=False)
|
||||
|
||||
|
||||
def test_rst_directive_with_argument(app):
|
||||
text = ".. rst:directive:: .. toctree:: foo bar baz"
|
||||
doctree = restructuredtext.parse(app, text)
|
||||
assert_node(doctree, (addnodes.index,
|
||||
[desc, ([desc_signature, ([desc_name, ".. toctree::"],
|
||||
[desc_addname, " foo bar baz"])],
|
||||
[desc_content, ()])]))
|
||||
assert_node(doctree[0],
|
||||
entries=[("single", "toctree (directive)", "directive-toctree", "", None)])
|
||||
assert_node(doctree[1], addnodes.desc, desctype="directive",
|
||||
domain="rst", objtype="directive", noindex=False)
|
||||
|
||||
|
||||
def test_rst_role(app):
|
||||
text = ".. rst:role:: ref"
|
||||
doctree = restructuredtext.parse(app, text)
|
||||
assert_node(doctree, (addnodes.index,
|
||||
[desc, ([desc_signature, desc_name, ":ref:"],
|
||||
[desc_content, ()])]))
|
||||
assert_node(doctree[0],
|
||||
entries=[("single", "ref (role)", "role-ref", "", None)])
|
||||
assert_node(doctree[1], addnodes.desc, desctype="role",
|
||||
domain="rst", objtype="role", noindex=False)
|
||||
|
@ -7,8 +7,12 @@
|
||||
:copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
import _testcapi
|
||||
import datetime
|
||||
import functools
|
||||
import sys
|
||||
import types
|
||||
from textwrap import dedent
|
||||
|
||||
import pytest
|
||||
@ -432,3 +436,26 @@ def test_isdescriptor(app):
|
||||
assert inspect.isdescriptor(Base.meth) is True # method of class
|
||||
assert inspect.isdescriptor(Base().meth) is True # method of instance
|
||||
assert inspect.isdescriptor(func) is True # function
|
||||
|
||||
|
||||
@pytest.mark.sphinx(testroot='ext-autodoc')
|
||||
def test_isattributedescriptor(app):
|
||||
from target.methods import Base
|
||||
|
||||
class Descriptor:
|
||||
def __get__(self, obj, typ=None):
|
||||
pass
|
||||
|
||||
testinstancemethod = _testcapi.instancemethod(str.__repr__)
|
||||
|
||||
assert inspect.isattributedescriptor(Base.prop) is True # property
|
||||
assert inspect.isattributedescriptor(Base.meth) is False # method
|
||||
assert inspect.isattributedescriptor(Base.staticmeth) is False # staticmethod
|
||||
assert inspect.isattributedescriptor(Base.classmeth) is False # classmetho
|
||||
assert inspect.isattributedescriptor(Descriptor) is False # custom descriptor class # NOQA
|
||||
assert inspect.isattributedescriptor(str.join) is False # MethodDescriptorType # NOQA
|
||||
assert inspect.isattributedescriptor(object.__init__) is False # WrapperDescriptorType # NOQA
|
||||
assert inspect.isattributedescriptor(dict.__dict__['fromkeys']) is False # ClassMethodDescriptorType # NOQA
|
||||
assert inspect.isattributedescriptor(types.FrameType.f_locals) is True # GetSetDescriptorType # NOQA
|
||||
assert inspect.isattributedescriptor(datetime.timedelta.days) is True # MemberDescriptorType # NOQA
|
||||
assert inspect.isattributedescriptor(testinstancemethod) is False # instancemethod (C-API) # NOQA
|
||||
|
Loading…
Reference in New Issue
Block a user