From eb40a36aa45549a6759297c6d0181c55ea76b063 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Mon, 6 Mar 2017 13:06:30 +0900 Subject: [PATCH] Add ReferenceResolver as a post-transform --- sphinx/application.py | 1 + sphinx/environment/__init__.py | 41 ++++----------- sphinx/transforms/__init__.py | 25 +++++++++- sphinx/transforms/post_transforms.py | 74 ++++++++++++++++++++++++++++ 4 files changed, 109 insertions(+), 32 deletions(-) create mode 100644 sphinx/transforms/post_transforms.py diff --git a/sphinx/application.py b/sphinx/application.py index 97339c62b..c09b738bf 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -105,6 +105,7 @@ builtin_extensions = ( 'sphinx.directives.other', 'sphinx.directives.patches', 'sphinx.roles', + 'sphinx.transforms.post_transforms', # collectors should be loaded by specific order 'sphinx.environment.collectors.dependencies', 'sphinx.environment.collectors.asset', diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py index c46ec7db2..8b08e4915 100644 --- a/sphinx/environment/__init__.py +++ b/sphinx/environment/__init__.py @@ -44,6 +44,7 @@ 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.versioning import add_uids, merge_doctrees from sphinx.deprecation import RemovedInSphinx20Warning from sphinx.environment.adapters.indexentries import IndexEntries @@ -920,38 +921,16 @@ class BuildEnvironment(object): def resolve_references(self, doctree, fromdocname, builder): # type: (nodes.Node, unicode, Builder) -> None - for node in doctree.traverse(addnodes.pending_xref): - contnode = node[0].deepcopy() - newnode = None + # apply all post-transforms + try: + # set env.docname during applying post-transforms + self.temp_data['docname'] = fromdocname - typ = node['reftype'] - target = node['reftarget'] - refdoc = node.get('refdoc', fromdocname) - domain = None - - try: - if 'refdomain' in node and node['refdomain']: - # let the domain try to resolve the reference - try: - domain = self.domains[node['refdomain']] - except KeyError: - raise NoUri - newnode = domain.resolve_xref(self, refdoc, builder, - typ, target, node, contnode) - # really hardwired reference types - elif typ == 'any': - newnode = self._resolve_any_reference(builder, refdoc, node, contnode) - # no new node found? try the missing-reference event - if newnode is None: - newnode = builder.app.emit_firstresult( - 'missing-reference', self, node, contnode) - # still not found? warn if node wishes to be warned about or - # we are in nit-picky mode - if newnode is None: - self._warn_missing_reference(refdoc, typ, target, node, domain) - except NoUri: - newnode = contnode - node.replace_self(newnode or contnode) + transformer = SphinxTransformer(doctree) + transformer.add_transforms(self.app.post_transforms) + transformer.apply_transforms() + finally: + self.temp_data.clear() # remove only-nodes that do not belong to our builder process_only_nodes(doctree, builder.tags) diff --git a/sphinx/transforms/__init__.py b/sphinx/transforms/__init__.py index f6fc0f18f..7c35e78a1 100644 --- a/sphinx/transforms/__init__.py +++ b/sphinx/transforms/__init__.py @@ -10,8 +10,9 @@ """ from docutils import nodes -from docutils.transforms import Transform +from docutils.transforms import Transform, Transformer from docutils.transforms.parts import ContentsFilter +from docutils.utils import new_document from sphinx import addnodes from sphinx.locale import _ @@ -69,6 +70,28 @@ class SphinxTransform(Transform): return self.document.settings.env.config +class SphinxTransformer(Transformer): + """ + A transformer for Sphinx. + """ + + document = None # type: nodes.Node + + def apply_transforms(self): + # type: () -> None + if isinstance(self.document, nodes.document): + Transformer.apply_transforms(self) + else: + # wrap the target node by document node during transforming + try: + document = new_document('') + document += self.document + self.document = document + Transformer.apply_transforms(self) + finally: + self.document = self.document[0] + + class DefaultSubstitutions(SphinxTransform): """ Replace some substitutions if they aren't defined in the document. diff --git a/sphinx/transforms/post_transforms.py b/sphinx/transforms/post_transforms.py new file mode 100644 index 000000000..9177b0308 --- /dev/null +++ b/sphinx/transforms/post_transforms.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +""" + sphinx.transforms.post_transforms + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Docutils transforms used by Sphinx. + + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +from sphinx import addnodes +from sphinx.environment import NoUri +from sphinx.transforms import SphinxTransform + +if False: + # For type annotation + from typing import Any, Dict # NOQA + from sphinx.application import Sphinx # NOQA + + +class ReferencesResolver(SphinxTransform): + """ + Resolves cross-references on doctrees. + """ + + default_priority = 10 + + def apply(self): + # type: () -> None + for node in self.document.traverse(addnodes.pending_xref): + contnode = node[0].deepcopy() + newnode = None + + typ = node['reftype'] + target = node['reftarget'] + refdoc = node.get('refdoc', self.env.docname) + domain = None + + try: + if 'refdomain' in node and node['refdomain']: + # let the domain try to resolve the reference + try: + domain = self.env.domains[node['refdomain']] + except KeyError: + raise NoUri + newnode = domain.resolve_xref(self.env, refdoc, self.app.builder, + typ, target, node, contnode) + # really hardwired reference types + elif typ == 'any': + newnode = self.env._resolve_any_reference(self.app.builder, refdoc, + node, contnode) + # no new node found? try the missing-reference event + if newnode is None: + newnode = self.app.emit_firstresult('missing-reference', self.env, + node, contnode) + # still not found? warn if node wishes to be warned about or + # we are in nit-picky mode + if newnode is None: + self.env._warn_missing_reference(refdoc, typ, target, node, domain) + except NoUri: + newnode = contnode + node.replace_self(newnode or contnode) + + +def setup(app): + # type: (Sphinx) -> Dict[unicode, Any] + app.add_post_transform(ReferencesResolver) + + return { + 'version': 'builtin', + 'parallel_read_safe': True, + 'parallel_write_safe': True, + }