Add CitationDomain for citation processing

This commit is contained in:
Takeshi KOMIYA 2019-03-24 13:32:46 +09:00
parent 87c6335b46
commit 885d35e374
5 changed files with 121 additions and 60 deletions

View File

@ -31,6 +31,7 @@ Deprecated
* ``sphinx.directives.TabularColumns`` * ``sphinx.directives.TabularColumns``
* ``sphinx.directives.TocTree`` * ``sphinx.directives.TocTree``
* ``sphinx.directives.VersionChange`` * ``sphinx.directives.VersionChange``
* ``sphinx.domains.std.StandardDomain._resolve_citation_xref()``
* ``sphinx.environment.NoUri`` * ``sphinx.environment.NoUri``
* ``sphinx.ext.autodoc.importer.MockFinder`` * ``sphinx.ext.autodoc.importer.MockFinder``
* ``sphinx.ext.autodoc.importer.MockLoader`` * ``sphinx.ext.autodoc.importer.MockLoader``

View File

@ -116,6 +116,11 @@ The following is a list of deprecated interfaces.
- 4.0 - 4.0
- ``sphinx.directives.other.VersionChange`` - ``sphinx.directives.other.VersionChange``
* - ``sphinx.domains.std.StandardDomain._resolve_citation_xref()``
- 2.1
- 4.0
- ``sphinx.domains.citation.CitationDomain.resolve_xref()``
* - ``sphinx.environment.NoUri`` * - ``sphinx.environment.NoUri``
- 2.1 - 2.1
- 4.0 - 4.0

View File

@ -16,6 +16,7 @@ from sphinx import addnodes
from sphinx.builders.latex.nodes import ( from sphinx.builders.latex.nodes import (
captioned_literal_block, footnotemark, footnotetext, math_reference, thebibliography captioned_literal_block, footnotemark, footnotetext, math_reference, thebibliography
) )
from sphinx.domains.citation import CitationDomain
from sphinx.transforms import SphinxTransform from sphinx.transforms import SphinxTransform
from sphinx.transforms.post_transforms import SphinxPostTransform from sphinx.transforms.post_transforms import SphinxPostTransform
from sphinx.util.nodes import NodeMatcher from sphinx.util.nodes import NodeMatcher
@ -545,10 +546,10 @@ class CitationReferenceTransform(SphinxPostTransform):
def run(self, **kwargs): def run(self, **kwargs):
# type: (Any) -> None # type: (Any) -> None
matcher = NodeMatcher(addnodes.pending_xref, refdomain='std', reftype='citation') domain = cast(CitationDomain, self.env.get_domain('citation'))
citations = self.env.get_domain('std').data['citations'] matcher = NodeMatcher(addnodes.pending_xref, refdomain='citation', reftype='ref')
for node in self.document.traverse(matcher): # type: addnodes.pending_xref for node in self.document.traverse(matcher): # type: addnodes.pending_xref
docname, labelid, _ = citations.get(node['reftarget'], ('', '', 0)) docname, labelid, _ = domain.citations.get(node['reftarget'], ('', '', 0))
if docname: if docname:
citation_ref = nodes.citation_reference('', '', *node.children, citation_ref = nodes.citation_reference('', '', *node.children,
docname=docname, refname=labelid) docname=docname, refname=labelid)

View File

@ -13,13 +13,102 @@ from typing import cast
from docutils import nodes from docutils import nodes
from sphinx import addnodes from sphinx import addnodes
from sphinx.domains import Domain
from sphinx.locale import __
from sphinx.transforms import SphinxTransform from sphinx.transforms import SphinxTransform
from sphinx.util.nodes import copy_source_info from sphinx.util import logging
from sphinx.util.nodes import copy_source_info, make_refnode
if False: if False:
# For type annotation # For type annotation
from typing import Any, Dict # NOQA from typing import Any, Dict, List, Set, Tuple, Union # NOQA
from sphinx.application import Sphinx # NOQA from sphinx.application import Sphinx # NOQA
from sphinx.builders import Builder # NOQA
from sphinx.environment import BuildEnvironment # NOQA
logger = logging.getLogger(__name__)
class CitationDomain(Domain):
"""Domain for citations."""
name = 'citation'
label = 'citation'
dangling_warnings = {
'ref': 'citation not found: %(target)s',
}
@property
def citations(self):
# type: () -> Dict[str, Tuple[str, str, int]]
return self.data.setdefault('citations', {})
@property
def citation_refs(self):
# type: () -> Dict[str, Set[str]]
return self.data.setdefault('citation_refs', {})
def clear_doc(self, docname):
# type: (str) -> None
for key, (fn, _l, lineno) in list(self.citations.items()):
if fn == docname:
del self.citations[key]
for key, docnames in list(self.citation_refs.items()):
if docnames == {docname}:
del self.citation_refs[key]
elif docname in docnames:
docnames.remove(docname)
def merge_domaindata(self, docnames, otherdata):
# type: (List[str], Dict) -> None
# XXX duplicates?
for key, data in otherdata['citations'].items():
if data[0] in docnames:
self.citations[key] = data
for key, data in otherdata['citation_refs'].items():
citation_refs = self.citation_refs.setdefault(key, set())
for docname in data:
if docname in docnames:
citation_refs.add(docname)
def note_citation(self, node):
# type: (nodes.citation) -> None
label = node[0].astext()
if label in self.citations:
path = self.env.doc2path(self.citations[label][0])
logger.warning(__('duplicate citation %s, other instance in %s'), label, path,
location=node, type='ref', subtype='citation')
self.citations[label] = (node['docname'], node['ids'][0], node.line)
def note_citation_reference(self, node):
# type: (addnodes.pending_xref) -> None
docnames = self.citation_refs.setdefault(node['reftarget'], set())
docnames.add(self.env.docname)
def check_consistency(self):
# type: () -> None
for name, (docname, labelid, lineno) in self.citations.items():
if name not in self.citation_refs:
logger.warning(__('Citation [%s] is not referenced.'), name,
type='ref', subtype='citation', location=(docname, lineno))
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
docname, labelid, lineno = self.citations.get(target, ('', '', 0))
if not docname:
return None
return make_refnode(builder, fromdocname, docname,
labelid, contnode)
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
refnode = self.resolve_xref(env, fromdocname, builder, 'ref', target, node, contnode)
if refnode is None:
return []
else:
return [('ref', refnode)]
class CitationDefinitionTransform(SphinxTransform): class CitationDefinitionTransform(SphinxTransform):
@ -28,7 +117,13 @@ class CitationDefinitionTransform(SphinxTransform):
def apply(self, **kwargs): def apply(self, **kwargs):
# type: (Any) -> None # type: (Any) -> None
domain = cast(CitationDomain, self.env.get_domain('citation'))
for node in self.document.traverse(nodes.citation): for node in self.document.traverse(nodes.citation):
# register citation node to domain
node['docname'] = self.env.docname
domain.note_citation(node)
# mark citation labels as not smartquoted
label = cast(nodes.label, node[0]) label = cast(nodes.label, node[0])
label['support_smartquotes'] = False label['support_smartquotes'] = False
@ -42,9 +137,10 @@ class CitationReferenceTransform(SphinxTransform):
def apply(self, **kwargs): def apply(self, **kwargs):
# type: (Any) -> None # type: (Any) -> None
domain = cast(CitationDomain, self.env.get_domain('citation'))
for node in self.document.traverse(nodes.citation_reference): for node in self.document.traverse(nodes.citation_reference):
target = node.astext() target = node.astext()
ref = addnodes.pending_xref(target, refdomain='std', reftype='citation', ref = addnodes.pending_xref(target, refdomain='citation', reftype='ref',
reftarget=target, refwarn=True, reftarget=target, refwarn=True,
support_smartquotes=False, support_smartquotes=False,
ids=node["ids"], ids=node["ids"],
@ -53,14 +149,19 @@ class CitationReferenceTransform(SphinxTransform):
copy_source_info(node, ref) copy_source_info(node, ref)
node.replace_self(ref) node.replace_self(ref)
# register reference node to domain
domain.note_citation_reference(ref)
def setup(app): def setup(app):
# type: (Sphinx) -> Dict[str, Any] # type: (Sphinx) -> Dict[str, Any]
app.add_domain(CitationDomain)
app.add_transform(CitationDefinitionTransform) app.add_transform(CitationDefinitionTransform)
app.add_transform(CitationReferenceTransform) app.add_transform(CitationReferenceTransform)
return { return {
'version': 'builtin', 'version': 'builtin',
'env_version': 1,
'parallel_read_safe': True, 'parallel_read_safe': True,
'parallel_write_safe': True, 'parallel_write_safe': True,
} }

View File

@ -19,7 +19,7 @@ from docutils.parsers.rst import directives
from docutils.statemachine import StringList from docutils.statemachine import StringList
from sphinx import addnodes from sphinx import addnodes
from sphinx.deprecation import RemovedInSphinx30Warning from sphinx.deprecation import RemovedInSphinx30Warning, RemovedInSphinx40Warning
from sphinx.directives import ObjectDescription from sphinx.directives import ObjectDescription
from sphinx.domains import Domain, ObjType from sphinx.domains import Domain, ObjType
from sphinx.errors import NoUri from sphinx.errors import NoUri
@ -496,8 +496,6 @@ class StandardDomain(Domain):
initial_data = { initial_data = {
'progoptions': {}, # (program, name) -> docname, labelid 'progoptions': {}, # (program, name) -> docname, labelid
'objects': {}, # (type, name) -> docname, labelid 'objects': {}, # (type, name) -> docname, labelid
'citations': {}, # citation_name -> docname, labelid, lineno
'citation_refs': {}, # citation_name -> list of docnames
'labels': { # labelname -> docname, labelid, sectionname 'labels': { # labelname -> docname, labelid, sectionname
'genindex': ('genindex', '', _('Index')), 'genindex': ('genindex', '', _('Index')),
'modindex': ('py-modindex', '', _('Module Index')), 'modindex': ('py-modindex', '', _('Module Index')),
@ -518,7 +516,6 @@ class StandardDomain(Domain):
'keyword': 'unknown keyword: %(target)s', 'keyword': 'unknown keyword: %(target)s',
'doc': 'unknown document: %(target)s', 'doc': 'unknown document: %(target)s',
'option': 'unknown option: %(target)s', 'option': 'unknown option: %(target)s',
'citation': 'citation not found: %(target)s',
} }
enumerable_nodes = { # node_class -> (figtype, title_getter) enumerable_nodes = { # node_class -> (figtype, title_getter)
@ -544,14 +541,6 @@ class StandardDomain(Domain):
for key, (fn, _l) in list(self.data['objects'].items()): for key, (fn, _l) in list(self.data['objects'].items()):
if fn == docname: if fn == docname:
del self.data['objects'][key] del self.data['objects'][key]
for key, (fn, _l, lineno) in list(self.data['citations'].items()):
if fn == docname:
del self.data['citations'][key]
for key, docnames in list(self.data['citation_refs'].items()):
if docnames == [docname]:
del self.data['citation_refs'][key]
elif docname in docnames:
docnames.remove(docname)
for key, (fn, _l, _l) in list(self.data['labels'].items()): for key, (fn, _l, _l) in list(self.data['labels'].items()):
if fn == docname: if fn == docname:
del self.data['labels'][key] del self.data['labels'][key]
@ -568,14 +557,6 @@ class StandardDomain(Domain):
for key, data in otherdata['objects'].items(): for key, data in otherdata['objects'].items():
if data[0] in docnames: if data[0] in docnames:
self.data['objects'][key] = data self.data['objects'][key] = data
for key, data in otherdata['citations'].items():
if data[0] in docnames:
self.data['citations'][key] = data
for key, data in otherdata['citation_refs'].items():
citation_refs = self.data['citation_refs'].setdefault(key, [])
for docname in data:
if docname in docnames:
citation_refs.append(docname)
for key, data in otherdata['labels'].items(): for key, data in otherdata['labels'].items():
if data[0] in docnames: if data[0] in docnames:
self.data['labels'][key] = data self.data['labels'][key] = data
@ -584,31 +565,6 @@ class StandardDomain(Domain):
self.data['anonlabels'][key] = data self.data['anonlabels'][key] = data
def process_doc(self, env, docname, document): def process_doc(self, env, docname, document):
# type: (BuildEnvironment, str, nodes.document) -> None
self.note_citations(env, docname, document)
self.note_citation_refs(env, docname, document)
self.note_labels(env, docname, document)
def note_citations(self, env, docname, document):
# type: (BuildEnvironment, str, nodes.document) -> None
for node in document.traverse(nodes.citation):
node['docname'] = docname
label = cast(nodes.label, node[0]).astext()
if label in self.data['citations']:
path = env.doc2path(self.data['citations'][label][0])
logger.warning(__('duplicate citation %s, other instance in %s'), label, path,
location=node, type='ref', subtype='citation')
self.data['citations'][label] = (docname, node['ids'][0], node.line)
def note_citation_refs(self, env, docname, document):
# type: (BuildEnvironment, str, nodes.document) -> None
for node in document.traverse(addnodes.pending_xref):
if node['refdomain'] == 'std' and node['reftype'] == 'citation':
label = node['reftarget']
citation_refs = self.data['citation_refs'].setdefault(label, [])
citation_refs.append(docname)
def note_labels(self, env, docname, document):
# type: (BuildEnvironment, str, nodes.document) -> None # type: (BuildEnvironment, str, nodes.document) -> None
labels, anonlabels = self.data['labels'], self.data['anonlabels'] labels, anonlabels = self.data['labels'], self.data['anonlabels']
for name, explicit in document.nametypes.items(): for name, explicit in document.nametypes.items():
@ -659,14 +615,6 @@ class StandardDomain(Domain):
# type: (str, str, str, str) -> None # type: (str, str, str, str) -> None
self.data['progoptions'][program, name] = (docname, labelid) self.data['progoptions'][program, name] = (docname, labelid)
def check_consistency(self):
# type: () -> None
for name, (docname, labelid, lineno) in self.data['citations'].items():
if name not in self.data['citation_refs']:
logger.warning(__('Citation [%s] is not referenced.'), name,
type='ref', subtype='citation',
location=(docname, lineno))
def build_reference_node(self, fromdocname, builder, docname, labelid, def build_reference_node(self, fromdocname, builder, docname, labelid,
sectname, rolename, **options): sectname, rolename, **options):
# type: (str, Builder, str, str, str, str, Any) -> nodes.Element # type: (str, Builder, str, str, str, str, Any) -> nodes.Element
@ -705,7 +653,10 @@ class StandardDomain(Domain):
elif typ == 'option': elif typ == 'option':
resolver = self._resolve_option_xref resolver = self._resolve_option_xref
elif typ == 'citation': elif typ == 'citation':
resolver = self._resolve_citation_xref warnings.warn('pending_xref(domain=std, type=citation) is deprecated: %r' % node,
RemovedInSphinx40Warning)
domain = env.get_domain('citation')
return domain.resolve_xref(env, fromdocname, builder, typ, target, node, contnode)
else: else:
resolver = self._resolve_obj_xref resolver = self._resolve_obj_xref
@ -839,6 +790,8 @@ class StandardDomain(Domain):
def _resolve_citation_xref(self, env, fromdocname, builder, typ, target, node, contnode): def _resolve_citation_xref(self, env, fromdocname, builder, typ, target, node, contnode):
# type: (BuildEnvironment, str, Builder, str, str, addnodes.pending_xref, nodes.Element) -> nodes.Element # NOQA # type: (BuildEnvironment, str, Builder, str, str, addnodes.pending_xref, nodes.Element) -> nodes.Element # NOQA
warnings.warn('StandardDomain._resolve_citation_xref() is deprecated.',
RemovedInSphinx30Warning)
docname, labelid, lineno = self.data['citations'].get(target, ('', '', 0)) docname, labelid, lineno = self.data['citations'].get(target, ('', '', 0))
if not docname: if not docname:
if 'ids' in node: if 'ids' in node: