Add ReferenceResolver as a post-transform

This commit is contained in:
Takeshi KOMIYA 2017-03-06 13:06:30 +09:00
parent 7117206b2a
commit eb40a36aa4
4 changed files with 109 additions and 32 deletions

View File

@ -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',

View File

@ -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)

View File

@ -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.

View File

@ -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,
}