mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge branch 'master' into 1618-make-search-results-reader-friendly
This commit is contained in:
2
CHANGES
2
CHANGES
@@ -21,6 +21,8 @@ Features added
|
|||||||
|
|
||||||
__ https://github.com/sphinx-contrib/sphinx-pretty-searchresults
|
__ https://github.com/sphinx-contrib/sphinx-pretty-searchresults
|
||||||
|
|
||||||
|
* #4182: autodoc: Support :confval:`suppress_warnings`
|
||||||
|
|
||||||
Bugs fixed
|
Bugs fixed
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,10 @@ docstrings to correct reStructuredText before :mod:`autodoc` processes them.
|
|||||||
.. _NumPy:
|
.. _NumPy:
|
||||||
https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt
|
https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt
|
||||||
|
|
||||||
|
|
||||||
|
Directives
|
||||||
|
----------
|
||||||
|
|
||||||
:mod:`autodoc` provides several directives that are versions of the usual
|
:mod:`autodoc` provides several directives that are versions of the usual
|
||||||
:rst:dir:`py:module`, :rst:dir:`py:class` and so forth. On parsing time, they
|
:rst:dir:`py:module`, :rst:dir:`py:class` and so forth. On parsing time, they
|
||||||
import the corresponding module and extract the docstring of the given objects,
|
import the corresponding module and extract the docstring of the given objects,
|
||||||
@@ -306,6 +310,9 @@ inserting them into the page source under a suitable :rst:dir:`py:module`,
|
|||||||
well-behaved decorating functions.
|
well-behaved decorating functions.
|
||||||
|
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
-------------
|
||||||
|
|
||||||
There are also new config values that you can set:
|
There are also new config values that you can set:
|
||||||
|
|
||||||
.. confval:: autoclass_content
|
.. confval:: autoclass_content
|
||||||
@@ -432,6 +439,16 @@ There are also new config values that you can set:
|
|||||||
|
|
||||||
.. versionadded:: 1.7
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
|
.. confval:: suppress_warnings
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
:mod:`autodoc` supports to suppress warning messages via
|
||||||
|
:confval:`suppress_warnings`. It allows following warnings types in
|
||||||
|
addition:
|
||||||
|
|
||||||
|
* autodoc
|
||||||
|
* autodoc.import_object
|
||||||
|
|
||||||
|
|
||||||
Docstring preprocessing
|
Docstring preprocessing
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ from __future__ import absolute_import
|
|||||||
import gzip
|
import gzip
|
||||||
import re
|
import re
|
||||||
from os import path
|
from os import path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from docutils import nodes
|
from docutils import nodes
|
||||||
|
|
||||||
@@ -23,6 +24,7 @@ from sphinx.builders.html import StandaloneHTMLBuilder
|
|||||||
from sphinx.environment.adapters.indexentries import IndexEntries
|
from sphinx.environment.adapters.indexentries import IndexEntries
|
||||||
from sphinx.locale import __
|
from sphinx.locale import __
|
||||||
from sphinx.util import logging
|
from sphinx.util import logging
|
||||||
|
from sphinx.util.nodes import NodeMatcher
|
||||||
from sphinx.util.osutil import make_filename
|
from sphinx.util.osutil import make_filename
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -32,7 +34,7 @@ except ImportError:
|
|||||||
|
|
||||||
if False:
|
if False:
|
||||||
# For type annotation
|
# For type annotation
|
||||||
from typing import Any, Dict, List # NOQA
|
from typing import Dict, List # NOQA
|
||||||
from sphinx.application import Sphinx # NOQA
|
from sphinx.application import Sphinx # NOQA
|
||||||
|
|
||||||
|
|
||||||
@@ -100,12 +102,8 @@ class DevhelpBuilder(StandaloneHTMLBuilder):
|
|||||||
parent.attrib['link'] = node['refuri']
|
parent.attrib['link'] = node['refuri']
|
||||||
parent.attrib['name'] = node.astext()
|
parent.attrib['name'] = node.astext()
|
||||||
|
|
||||||
def istoctree(node):
|
matcher = NodeMatcher(addnodes.compact_paragraph, toctree=Any)
|
||||||
# type: (nodes.Node) -> bool
|
for node in tocdoc.traverse(matcher):
|
||||||
return isinstance(node, addnodes.compact_paragraph) and \
|
|
||||||
'toctree' in node
|
|
||||||
|
|
||||||
for node in tocdoc.traverse(istoctree):
|
|
||||||
write_toc(node, chapters)
|
write_toc(node, chapters)
|
||||||
|
|
||||||
# Index
|
# Index
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ from sphinx.builders.latex.nodes import (
|
|||||||
captioned_literal_block, footnotemark, footnotetext, math_reference, thebibliography
|
captioned_literal_block, footnotemark, footnotetext, math_reference, thebibliography
|
||||||
)
|
)
|
||||||
from sphinx.transforms import SphinxTransform
|
from sphinx.transforms import SphinxTransform
|
||||||
|
from sphinx.util.nodes import NodeMatcher
|
||||||
|
|
||||||
if False:
|
if False:
|
||||||
# For type annotation
|
# For type annotation
|
||||||
@@ -30,7 +31,7 @@ class FootnoteDocnameUpdater(SphinxTransform):
|
|||||||
TARGET_NODES = (nodes.footnote, nodes.footnote_reference)
|
TARGET_NODES = (nodes.footnote, nodes.footnote_reference)
|
||||||
|
|
||||||
def apply(self):
|
def apply(self):
|
||||||
for node in self.document.traverse(lambda n: isinstance(n, self.TARGET_NODES)):
|
for node in self.document.traverse(NodeMatcher(*self.TARGET_NODES)):
|
||||||
node['docname'] = self.env.docname
|
node['docname'] = self.env.docname
|
||||||
|
|
||||||
|
|
||||||
@@ -536,9 +537,9 @@ class CitationReferenceTransform(SphinxTransform):
|
|||||||
if self.app.builder.name != 'latex':
|
if self.app.builder.name != 'latex':
|
||||||
return
|
return
|
||||||
|
|
||||||
|
matcher = NodeMatcher(addnodes.pending_xref, refdomain='std', reftype='citation')
|
||||||
citations = self.env.get_domain('std').data['citations']
|
citations = self.env.get_domain('std').data['citations']
|
||||||
for node in self.document.traverse(addnodes.pending_xref):
|
for node in self.document.traverse(matcher):
|
||||||
if node['refdomain'] == 'std' and node['reftype'] == 'citation':
|
|
||||||
docname, labelid, _ = citations.get(node['reftarget'], ('', '', 0))
|
docname, labelid, _ = citations.get(node['reftarget'], ('', '', 0))
|
||||||
if docname:
|
if docname:
|
||||||
citation_ref = nodes.citation_reference('', *node.children,
|
citation_ref = nodes.citation_reference('', *node.children,
|
||||||
@@ -577,8 +578,8 @@ class LiteralBlockTransform(SphinxTransform):
|
|||||||
if self.app.builder.name != 'latex':
|
if self.app.builder.name != 'latex':
|
||||||
return
|
return
|
||||||
|
|
||||||
for node in self.document.traverse(nodes.container):
|
matcher = NodeMatcher(nodes.container, literal_block=True)
|
||||||
if node.get('literal_block') is True:
|
for node in self.document.traverse(matcher):
|
||||||
newnode = captioned_literal_block('', *node.children, **node.attributes)
|
newnode = captioned_literal_block('', *node.children, **node.attributes)
|
||||||
node.replace_self(newnode)
|
node.replace_self(newnode)
|
||||||
|
|
||||||
|
|||||||
@@ -336,9 +336,6 @@ class BuildEnvironment(object):
|
|||||||
if docname in other.reread_always:
|
if docname in other.reread_always:
|
||||||
self.reread_always.add(docname)
|
self.reread_always.add(docname)
|
||||||
|
|
||||||
for docname in other.included:
|
|
||||||
self.included.add(docname)
|
|
||||||
|
|
||||||
for version, changes in other.versionchanges.items():
|
for version, changes in other.versionchanges.items():
|
||||||
self.versionchanges.setdefault(version, []).extend(
|
self.versionchanges.setdefault(version, []).extend(
|
||||||
change for change in changes if change[1] in docnames)
|
change for change in changes if change[1] in docnames)
|
||||||
|
|||||||
@@ -342,7 +342,8 @@ class Documenter(object):
|
|||||||
explicit_modname, path, base, args, retann = \
|
explicit_modname, path, base, args, retann = \
|
||||||
py_ext_sig_re.match(self.name).groups() # type: ignore
|
py_ext_sig_re.match(self.name).groups() # type: ignore
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
logger.warning(__('invalid signature for auto%s (%r)') % (self.objtype, self.name))
|
logger.warning(__('invalid signature for auto%s (%r)') % (self.objtype, self.name),
|
||||||
|
type='autodoc')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# support explicit module and class name separation via ::
|
# support explicit module and class name separation via ::
|
||||||
@@ -379,7 +380,7 @@ class Documenter(object):
|
|||||||
self.module, self.parent, self.object_name, self.object = ret
|
self.module, self.parent, self.object_name, self.object = ret
|
||||||
return True
|
return True
|
||||||
except ImportError as exc:
|
except ImportError as exc:
|
||||||
logger.warning(exc.args[0])
|
logger.warning(exc.args[0], type='autodoc', subtype='import_object')
|
||||||
self.env.note_reread()
|
self.env.note_reread()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -442,7 +443,7 @@ class Documenter(object):
|
|||||||
args = self.format_args()
|
args = self.format_args()
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logger.warning(__('error while formatting arguments for %s: %s') %
|
logger.warning(__('error while formatting arguments for %s: %s') %
|
||||||
(self.fullname, err))
|
(self.fullname, err), type='autodoc')
|
||||||
args = None
|
args = None
|
||||||
|
|
||||||
retann = self.retann
|
retann = self.retann
|
||||||
@@ -564,7 +565,7 @@ class Documenter(object):
|
|||||||
selected.append((name, members[name].value))
|
selected.append((name, members[name].value))
|
||||||
else:
|
else:
|
||||||
logger.warning(__('missing attribute %s in object %s') %
|
logger.warning(__('missing attribute %s in object %s') %
|
||||||
(name, self.fullname))
|
(name, self.fullname), type='autodoc')
|
||||||
return False, sorted(selected)
|
return False, sorted(selected)
|
||||||
elif self.options.inherited_members:
|
elif self.options.inherited_members:
|
||||||
return False, sorted((m.name, m.value) for m in itervalues(members))
|
return False, sorted((m.name, m.value) for m in itervalues(members))
|
||||||
@@ -653,7 +654,7 @@ class Documenter(object):
|
|||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.warning(__('autodoc: failed to determine %r to be documented.'
|
logger.warning(__('autodoc: failed to determine %r to be documented.'
|
||||||
'the following exception was raised:\n%s'),
|
'the following exception was raised:\n%s'),
|
||||||
member, exc)
|
member, exc, type='autodoc')
|
||||||
keep = False
|
keep = False
|
||||||
|
|
||||||
if keep:
|
if keep:
|
||||||
@@ -746,7 +747,7 @@ class Documenter(object):
|
|||||||
__('don\'t know which module to import for autodocumenting '
|
__('don\'t know which module to import for autodocumenting '
|
||||||
'%r (try placing a "module" or "currentmodule" directive '
|
'%r (try placing a "module" or "currentmodule" directive '
|
||||||
'in the document, or giving an explicit module name)') %
|
'in the document, or giving an explicit module name)') %
|
||||||
self.name)
|
self.name, type='autodoc')
|
||||||
return
|
return
|
||||||
|
|
||||||
# now, import the module and get object to document
|
# now, import the module and get object to document
|
||||||
@@ -832,7 +833,8 @@ class ModuleDocumenter(Documenter):
|
|||||||
def resolve_name(self, modname, parents, path, base):
|
def resolve_name(self, modname, parents, path, base):
|
||||||
# type: (str, Any, str, Any) -> Tuple[str, List[unicode]]
|
# type: (str, Any, str, Any) -> Tuple[str, List[unicode]]
|
||||||
if modname is not None:
|
if modname is not None:
|
||||||
logger.warning(__('"::" in automodule name doesn\'t make sense'))
|
logger.warning(__('"::" in automodule name doesn\'t make sense'),
|
||||||
|
type='autodoc')
|
||||||
return (path or '') + base, []
|
return (path or '') + base, []
|
||||||
|
|
||||||
def parse_name(self):
|
def parse_name(self):
|
||||||
@@ -840,7 +842,8 @@ class ModuleDocumenter(Documenter):
|
|||||||
ret = Documenter.parse_name(self)
|
ret = Documenter.parse_name(self)
|
||||||
if self.args or self.retann:
|
if self.args or self.retann:
|
||||||
logger.warning(__('signature arguments or return annotation '
|
logger.warning(__('signature arguments or return annotation '
|
||||||
'given for automodule %s') % self.fullname)
|
'given for automodule %s') % self.fullname,
|
||||||
|
type='autodoc')
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def add_directive_header(self, sig):
|
def add_directive_header(self, sig):
|
||||||
@@ -875,7 +878,9 @@ class ModuleDocumenter(Documenter):
|
|||||||
logger.warning(
|
logger.warning(
|
||||||
__('__all__ should be a list of strings, not %r '
|
__('__all__ should be a list of strings, not %r '
|
||||||
'(in module %s) -- ignoring __all__') %
|
'(in module %s) -- ignoring __all__') %
|
||||||
(memberlist, self.fullname))
|
(memberlist, self.fullname),
|
||||||
|
type='autodoc'
|
||||||
|
)
|
||||||
# fall back to all members
|
# fall back to all members
|
||||||
return True, safe_getmembers(self.object)
|
return True, safe_getmembers(self.object)
|
||||||
else:
|
else:
|
||||||
@@ -888,7 +893,9 @@ class ModuleDocumenter(Documenter):
|
|||||||
logger.warning(
|
logger.warning(
|
||||||
__('missing attribute mentioned in :members: or __all__: '
|
__('missing attribute mentioned in :members: or __all__: '
|
||||||
'module %s, attribute %s') %
|
'module %s, attribute %s') %
|
||||||
(safe_getattr(self.object, '__name__', '???'), mname))
|
(safe_getattr(self.object, '__name__', '???'), mname),
|
||||||
|
type='autodoc'
|
||||||
|
)
|
||||||
return False, ret
|
return False, ret
|
||||||
|
|
||||||
|
|
||||||
@@ -1539,7 +1546,8 @@ def merge_autodoc_default_flags(app, config):
|
|||||||
return
|
return
|
||||||
|
|
||||||
logger.warning(__('autodoc_default_flags is now deprecated. '
|
logger.warning(__('autodoc_default_flags is now deprecated. '
|
||||||
'Please use autodoc_default_options instead.'))
|
'Please use autodoc_default_options instead.'),
|
||||||
|
type='autodoc')
|
||||||
|
|
||||||
for option in config.autodoc_default_flags:
|
for option in config.autodoc_default_flags:
|
||||||
if isinstance(option, string_types):
|
if isinstance(option, string_types):
|
||||||
@@ -1547,7 +1555,7 @@ def merge_autodoc_default_flags(app, config):
|
|||||||
else:
|
else:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
__("Ignoring invalid option in autodoc_default_flags: %r"),
|
__("Ignoring invalid option in autodoc_default_flags: %r"),
|
||||||
option
|
option, type='autodoc'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from os import path
|
from os import path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from docutils import nodes
|
from docutils import nodes
|
||||||
from docutils.io import StringInput
|
from docutils.io import StringInput
|
||||||
@@ -22,14 +23,14 @@ from sphinx.transforms import SphinxTransform
|
|||||||
from sphinx.util import split_index_msg, logging
|
from sphinx.util import split_index_msg, logging
|
||||||
from sphinx.util.i18n import find_catalog
|
from sphinx.util.i18n import find_catalog
|
||||||
from sphinx.util.nodes import (
|
from sphinx.util.nodes import (
|
||||||
LITERAL_TYPE_NODES, IMAGE_TYPE_NODES,
|
LITERAL_TYPE_NODES, IMAGE_TYPE_NODES, NodeMatcher,
|
||||||
extract_messages, is_pending_meta, traverse_translatable_index,
|
extract_messages, is_pending_meta, traverse_translatable_index,
|
||||||
)
|
)
|
||||||
from sphinx.util.pycompat import indent
|
from sphinx.util.pycompat import indent
|
||||||
|
|
||||||
if False:
|
if False:
|
||||||
# For type annotation
|
# For type annotation
|
||||||
from typing import Any, Dict, List, Tuple # NOQA
|
from typing import Dict, List, Tuple # NOQA
|
||||||
from sphinx.application import Sphinx # NOQA
|
from sphinx.application import Sphinx # NOQA
|
||||||
from sphinx.config import Config # NOQA
|
from sphinx.config import Config # NOQA
|
||||||
|
|
||||||
@@ -183,11 +184,8 @@ class Locale(SphinxTransform):
|
|||||||
self.document.note_implicit_target(section_node)
|
self.document.note_implicit_target(section_node)
|
||||||
|
|
||||||
# replace target's refname to new target name
|
# replace target's refname to new target name
|
||||||
def is_named_target(node):
|
matcher = NodeMatcher(nodes.target, refname=old_name)
|
||||||
# type: (nodes.Node) -> bool
|
for old_target in self.document.traverse(matcher):
|
||||||
return isinstance(node, nodes.target) and \
|
|
||||||
node.get('refname') == old_name
|
|
||||||
for old_target in self.document.traverse(is_named_target):
|
|
||||||
old_target['refname'] = new_name
|
old_target['refname'] = new_name
|
||||||
|
|
||||||
processed = True
|
processed = True
|
||||||
@@ -276,16 +274,14 @@ class Locale(SphinxTransform):
|
|||||||
continue # skip
|
continue # skip
|
||||||
|
|
||||||
# auto-numbered foot note reference should use original 'ids'.
|
# auto-numbered foot note reference should use original 'ids'.
|
||||||
def is_autofootnote_ref(node):
|
|
||||||
# type: (nodes.Node) -> bool
|
|
||||||
return isinstance(node, nodes.footnote_reference) and node.get('auto')
|
|
||||||
|
|
||||||
def list_replace_or_append(lst, old, new):
|
def list_replace_or_append(lst, old, new):
|
||||||
# type: (List, Any, Any) -> None
|
# type: (List, Any, Any) -> None
|
||||||
if old in lst:
|
if old in lst:
|
||||||
lst[lst.index(old)] = new
|
lst[lst.index(old)] = new
|
||||||
else:
|
else:
|
||||||
lst.append(new)
|
lst.append(new)
|
||||||
|
|
||||||
|
is_autofootnote_ref = NodeMatcher(nodes.footnote_reference, auto=Any)
|
||||||
old_foot_refs = node.traverse(is_autofootnote_ref)
|
old_foot_refs = node.traverse(is_autofootnote_ref)
|
||||||
new_foot_refs = patch.traverse(is_autofootnote_ref)
|
new_foot_refs = patch.traverse(is_autofootnote_ref)
|
||||||
if len(old_foot_refs) != len(new_foot_refs):
|
if len(old_foot_refs) != len(new_foot_refs):
|
||||||
@@ -328,10 +324,7 @@ class Locale(SphinxTransform):
|
|||||||
# * reference target ".. _Python: ..." is not translatable.
|
# * reference target ".. _Python: ..." is not translatable.
|
||||||
# * use translated refname for section refname.
|
# * use translated refname for section refname.
|
||||||
# * inline reference "`Python <...>`_" has no 'refname'.
|
# * inline reference "`Python <...>`_" has no 'refname'.
|
||||||
def is_refnamed_ref(node):
|
is_refnamed_ref = NodeMatcher(nodes.reference, refname=Any)
|
||||||
# type: (nodes.Node) -> bool
|
|
||||||
return isinstance(node, nodes.reference) and \
|
|
||||||
'refname' in node
|
|
||||||
old_refs = node.traverse(is_refnamed_ref)
|
old_refs = node.traverse(is_refnamed_ref)
|
||||||
new_refs = patch.traverse(is_refnamed_ref)
|
new_refs = patch.traverse(is_refnamed_ref)
|
||||||
if len(old_refs) != len(new_refs):
|
if len(old_refs) != len(new_refs):
|
||||||
@@ -358,10 +351,7 @@ class Locale(SphinxTransform):
|
|||||||
self.document.note_refname(new)
|
self.document.note_refname(new)
|
||||||
|
|
||||||
# refnamed footnote should use original 'ids'.
|
# refnamed footnote should use original 'ids'.
|
||||||
def is_refnamed_footnote_ref(node):
|
is_refnamed_footnote_ref = NodeMatcher(nodes.footnote_reference, refname=Any)
|
||||||
# type: (nodes.Node) -> bool
|
|
||||||
return isinstance(node, nodes.footnote_reference) and \
|
|
||||||
'refname' in node
|
|
||||||
old_foot_refs = node.traverse(is_refnamed_footnote_ref)
|
old_foot_refs = node.traverse(is_refnamed_footnote_ref)
|
||||||
new_foot_refs = patch.traverse(is_refnamed_footnote_ref)
|
new_foot_refs = patch.traverse(is_refnamed_footnote_ref)
|
||||||
refname_ids_map = {}
|
refname_ids_map = {}
|
||||||
@@ -380,10 +370,7 @@ class Locale(SphinxTransform):
|
|||||||
new["ids"] = refname_ids_map[refname]
|
new["ids"] = refname_ids_map[refname]
|
||||||
|
|
||||||
# citation should use original 'ids'.
|
# citation should use original 'ids'.
|
||||||
def is_citation_ref(node):
|
is_citation_ref = NodeMatcher(nodes.citation_reference, refname=Any)
|
||||||
# type: (nodes.Node) -> bool
|
|
||||||
return isinstance(node, nodes.citation_reference) and \
|
|
||||||
'refname' in node
|
|
||||||
old_cite_refs = node.traverse(is_citation_ref)
|
old_cite_refs = node.traverse(is_citation_ref)
|
||||||
new_cite_refs = patch.traverse(is_citation_ref)
|
new_cite_refs = patch.traverse(is_citation_ref)
|
||||||
refname_ids_map = {}
|
refname_ids_map = {}
|
||||||
@@ -474,10 +461,7 @@ class Locale(SphinxTransform):
|
|||||||
node['entries'] = new_entries
|
node['entries'] = new_entries
|
||||||
|
|
||||||
# remove translated attribute that is used for avoiding double translation.
|
# remove translated attribute that is used for avoiding double translation.
|
||||||
def has_translatable(node):
|
for node in self.document.traverse(NodeMatcher(translated=Any)):
|
||||||
# type: (nodes.Node) -> bool
|
|
||||||
return isinstance(node, nodes.Element) and 'translated' in node
|
|
||||||
for node in self.document.traverse(has_translatable):
|
|
||||||
node.delattr('translated')
|
node.delattr('translated')
|
||||||
|
|
||||||
|
|
||||||
@@ -492,7 +476,8 @@ class RemoveTranslatableInline(SphinxTransform):
|
|||||||
from sphinx.builders.gettext import MessageCatalogBuilder
|
from sphinx.builders.gettext import MessageCatalogBuilder
|
||||||
if isinstance(self.app.builder, MessageCatalogBuilder):
|
if isinstance(self.app.builder, MessageCatalogBuilder):
|
||||||
return
|
return
|
||||||
for inline in self.document.traverse(nodes.inline):
|
|
||||||
if 'translatable' in inline:
|
matcher = NodeMatcher(nodes.inline, translatable=Any)
|
||||||
|
for inline in self.document.traverse(matcher):
|
||||||
inline.parent.remove(inline)
|
inline.parent.remove(inline)
|
||||||
inline.parent += inline.children
|
inline.parent += inline.children
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from docutils import nodes
|
from docutils import nodes
|
||||||
from six import text_type
|
from six import text_type
|
||||||
@@ -33,6 +34,57 @@ explicit_title_re = re.compile(r'^(.+?)\s*(?<!\x00)<(.*?)>$', re.DOTALL)
|
|||||||
caption_ref_re = explicit_title_re # b/w compat alias
|
caption_ref_re = explicit_title_re # b/w compat alias
|
||||||
|
|
||||||
|
|
||||||
|
class NodeMatcher(object):
|
||||||
|
"""A helper class for Node.traverse().
|
||||||
|
|
||||||
|
It checks that given node is an instance of specified node-classes and it has
|
||||||
|
specified node-attributes.
|
||||||
|
|
||||||
|
For example, following example searches ``reference`` node having ``refdomain``
|
||||||
|
and ``reftype`` attributes::
|
||||||
|
|
||||||
|
matcher = NodeMatcher(nodes.reference, refdomain='std', reftype='citation')
|
||||||
|
doctree.traverse(matcher)
|
||||||
|
# => [<reference ...>, <reference ...>, ...]
|
||||||
|
|
||||||
|
A special value ``typing.Any`` matches any kind of node-attributes. For example,
|
||||||
|
following example searches ``reference`` node having ``refdomain`` attributes::
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
matcher = NodeMatcher(nodes.reference, refdomain=Any)
|
||||||
|
doctree.traverse(matcher)
|
||||||
|
# => [<reference ...>, <reference ...>, ...]
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *classes, **attrs):
|
||||||
|
# type: (nodes.Node, Any) -> None
|
||||||
|
self.classes = classes
|
||||||
|
self.attrs = attrs
|
||||||
|
|
||||||
|
def match(self, node):
|
||||||
|
# type: (nodes.Node) -> bool
|
||||||
|
try:
|
||||||
|
if self.classes and not isinstance(node, self.classes):
|
||||||
|
return False
|
||||||
|
|
||||||
|
for key, value in self.attrs.items():
|
||||||
|
if key not in node:
|
||||||
|
return False
|
||||||
|
elif value is Any:
|
||||||
|
continue
|
||||||
|
elif node.get(key) != value:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
# for non-Element nodes
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __call__(self, node):
|
||||||
|
# type: (nodes.Node) -> bool
|
||||||
|
return self.match(node)
|
||||||
|
|
||||||
|
|
||||||
def get_full_module_name(node):
|
def get_full_module_name(node):
|
||||||
# type: (nodes.Node) -> str
|
# type: (nodes.Node) -> str
|
||||||
"""
|
"""
|
||||||
@@ -241,11 +293,7 @@ def traverse_parent(node, cls=None):
|
|||||||
def traverse_translatable_index(doctree):
|
def traverse_translatable_index(doctree):
|
||||||
# type: (nodes.Node) -> Iterable[Tuple[nodes.Node, List[unicode]]]
|
# type: (nodes.Node) -> Iterable[Tuple[nodes.Node, List[unicode]]]
|
||||||
"""Traverse translatable index node from a document tree."""
|
"""Traverse translatable index node from a document tree."""
|
||||||
def is_block_index(node):
|
for node in doctree.traverse(NodeMatcher(addnodes.index, inline=False)):
|
||||||
# type: (nodes.Node) -> bool
|
|
||||||
return isinstance(node, addnodes.index) and \
|
|
||||||
node.get('inline') is False
|
|
||||||
for node in doctree.traverse(is_block_index):
|
|
||||||
if 'raw_entries' in node:
|
if 'raw_entries' in node:
|
||||||
entries = node['raw_entries']
|
entries = node['raw_entries']
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ from sphinx import addnodes
|
|||||||
from sphinx.locale import admonitionlabels, _
|
from sphinx.locale import admonitionlabels, _
|
||||||
from sphinx.util import logging
|
from sphinx.util import logging
|
||||||
from sphinx.util.i18n import format_date
|
from sphinx.util.i18n import format_date
|
||||||
|
from sphinx.util.nodes import NodeMatcher
|
||||||
|
|
||||||
if False:
|
if False:
|
||||||
# For type annotation
|
# For type annotation
|
||||||
@@ -63,16 +64,13 @@ class NestedInlineTransform(object):
|
|||||||
|
|
||||||
def apply(self):
|
def apply(self):
|
||||||
# type: () -> None
|
# type: () -> None
|
||||||
def is_inline(node):
|
matcher = NodeMatcher(nodes.literal, nodes.emphasis, nodes.strong)
|
||||||
# type: (nodes.Node) -> bool
|
for node in self.document.traverse(matcher):
|
||||||
return isinstance(node, (nodes.literal, nodes.emphasis, nodes.strong))
|
if any(matcher(subnode) for subnode in node):
|
||||||
|
|
||||||
for node in self.document.traverse(is_inline):
|
|
||||||
if any(is_inline(subnode) for subnode in node):
|
|
||||||
pos = node.parent.index(node)
|
pos = node.parent.index(node)
|
||||||
for subnode in reversed(node[1:]):
|
for subnode in reversed(node[1:]):
|
||||||
node.remove(subnode)
|
node.remove(subnode)
|
||||||
if is_inline(subnode):
|
if matcher(subnode):
|
||||||
node.parent.insert(pos + 1, subnode)
|
node.parent.insert(pos + 1, subnode)
|
||||||
else:
|
else:
|
||||||
newnode = node.__class__('', subnode, **node.attributes)
|
newnode = node.__class__('', subnode, **node.attributes)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
:license: BSD, see LICENSE for details.
|
:license: BSD, see LICENSE for details.
|
||||||
"""
|
"""
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from docutils import frontend
|
from docutils import frontend
|
||||||
@@ -17,7 +18,7 @@ from docutils.parsers import rst
|
|||||||
from docutils.utils import new_document
|
from docutils.utils import new_document
|
||||||
|
|
||||||
from sphinx.transforms import ApplySourceWorkaround
|
from sphinx.transforms import ApplySourceWorkaround
|
||||||
from sphinx.util.nodes import extract_messages, clean_astext
|
from sphinx.util.nodes import NodeMatcher, extract_messages, clean_astext
|
||||||
|
|
||||||
|
|
||||||
def _transform(doctree):
|
def _transform(doctree):
|
||||||
@@ -50,6 +51,38 @@ def assert_node_count(messages, node_type, expect_count):
|
|||||||
% (node_type, node_list, count, expect_count))
|
% (node_type, node_list, count, expect_count))
|
||||||
|
|
||||||
|
|
||||||
|
def test_NodeMatcher():
|
||||||
|
doctree = nodes.document(None, None)
|
||||||
|
doctree += nodes.paragraph('', 'Hello')
|
||||||
|
doctree += nodes.paragraph('', 'Sphinx', block=1)
|
||||||
|
doctree += nodes.paragraph('', 'World', block=2)
|
||||||
|
doctree += nodes.literal_block('', 'blah blah blah', block=3)
|
||||||
|
|
||||||
|
# search by node class
|
||||||
|
matcher = NodeMatcher(nodes.paragraph)
|
||||||
|
assert len(doctree.traverse(matcher)) == 3
|
||||||
|
|
||||||
|
# search by multiple node classes
|
||||||
|
matcher = NodeMatcher(nodes.paragraph, nodes.literal_block)
|
||||||
|
assert len(doctree.traverse(matcher)) == 4
|
||||||
|
|
||||||
|
# search by node attribute
|
||||||
|
matcher = NodeMatcher(block=1)
|
||||||
|
assert len(doctree.traverse(matcher)) == 1
|
||||||
|
|
||||||
|
# search by node attribute (Any)
|
||||||
|
matcher = NodeMatcher(block=Any)
|
||||||
|
assert len(doctree.traverse(matcher)) == 3
|
||||||
|
|
||||||
|
# search by both class and attribute
|
||||||
|
matcher = NodeMatcher(nodes.paragraph, block=Any)
|
||||||
|
assert len(doctree.traverse(matcher)) == 2
|
||||||
|
|
||||||
|
# mismatched
|
||||||
|
matcher = NodeMatcher(nodes.title)
|
||||||
|
assert len(doctree.traverse(matcher)) == 0
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'rst,node_cls,count',
|
'rst,node_cls,count',
|
||||||
[
|
[
|
||||||
|
|||||||
Reference in New Issue
Block a user