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.TocTree``
* ``sphinx.directives.VersionChange``
* ``sphinx.domains.std.StandardDomain._resolve_citation_xref()``
* ``sphinx.environment.NoUri``
* ``sphinx.ext.autodoc.importer.MockFinder``
* ``sphinx.ext.autodoc.importer.MockLoader``

View File

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

View File

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

View File

@ -13,13 +13,102 @@ from typing import cast
from docutils import nodes
from sphinx import addnodes
from sphinx.domains import Domain
from sphinx.locale import __
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:
# 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.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):
@ -28,7 +117,13 @@ class CitationDefinitionTransform(SphinxTransform):
def apply(self, **kwargs):
# type: (Any) -> None
domain = cast(CitationDomain, self.env.get_domain('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['support_smartquotes'] = False
@ -42,9 +137,10 @@ class CitationReferenceTransform(SphinxTransform):
def apply(self, **kwargs):
# type: (Any) -> None
domain = cast(CitationDomain, self.env.get_domain('citation'))
for node in self.document.traverse(nodes.citation_reference):
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,
support_smartquotes=False,
ids=node["ids"],
@ -53,14 +149,19 @@ class CitationReferenceTransform(SphinxTransform):
copy_source_info(node, ref)
node.replace_self(ref)
# register reference node to domain
domain.note_citation_reference(ref)
def setup(app):
# type: (Sphinx) -> Dict[str, Any]
app.add_domain(CitationDomain)
app.add_transform(CitationDefinitionTransform)
app.add_transform(CitationReferenceTransform)
return {
'version': 'builtin',
'env_version': 1,
'parallel_read_safe': True,
'parallel_write_safe': True,
}

View File

@ -19,7 +19,7 @@ from docutils.parsers.rst import directives
from docutils.statemachine import StringList
from sphinx import addnodes
from sphinx.deprecation import RemovedInSphinx30Warning
from sphinx.deprecation import RemovedInSphinx30Warning, RemovedInSphinx40Warning
from sphinx.directives import ObjectDescription
from sphinx.domains import Domain, ObjType
from sphinx.errors import NoUri
@ -496,8 +496,6 @@ class StandardDomain(Domain):
initial_data = {
'progoptions': {}, # (program, 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
'genindex': ('genindex', '', _('Index')),
'modindex': ('py-modindex', '', _('Module Index')),
@ -518,7 +516,6 @@ class StandardDomain(Domain):
'keyword': 'unknown keyword: %(target)s',
'doc': 'unknown document: %(target)s',
'option': 'unknown option: %(target)s',
'citation': 'citation not found: %(target)s',
}
enumerable_nodes = { # node_class -> (figtype, title_getter)
@ -544,14 +541,6 @@ class StandardDomain(Domain):
for key, (fn, _l) in list(self.data['objects'].items()):
if fn == docname:
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()):
if fn == docname:
del self.data['labels'][key]
@ -568,14 +557,6 @@ class StandardDomain(Domain):
for key, data in otherdata['objects'].items():
if data[0] in docnames:
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():
if data[0] in docnames:
self.data['labels'][key] = data
@ -584,31 +565,6 @@ class StandardDomain(Domain):
self.data['anonlabels'][key] = data
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
labels, anonlabels = self.data['labels'], self.data['anonlabels']
for name, explicit in document.nametypes.items():
@ -659,14 +615,6 @@ class StandardDomain(Domain):
# type: (str, str, str, str) -> None
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,
sectname, rolename, **options):
# type: (str, Builder, str, str, str, str, Any) -> nodes.Element
@ -705,7 +653,10 @@ class StandardDomain(Domain):
elif typ == 'option':
resolver = self._resolve_option_xref
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:
resolver = self._resolve_obj_xref
@ -839,6 +790,8 @@ class StandardDomain(Domain):
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
warnings.warn('StandardDomain._resolve_citation_xref() is deprecated.',
RemovedInSphinx30Warning)
docname, labelid, lineno = self.data['citations'].get(target, ('', '', 0))
if not docname:
if 'ids' in node: