refactor XRefRole using ReferenceRole class

This commit is contained in:
Takeshi KOMIYA 2019-02-16 21:20:30 +09:00
parent 61f1477942
commit c23835ef06

View File

@ -15,7 +15,6 @@ from docutils import nodes, utils
from sphinx import addnodes from sphinx import addnodes
from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.deprecation import RemovedInSphinx40Warning
from sphinx.errors import SphinxError
from sphinx.locale import _ from sphinx.locale import _
from sphinx.util import ws_re from sphinx.util import ws_re
from sphinx.util.docutils import ReferenceRole, SphinxRole from sphinx.util.docutils import ReferenceRole, SphinxRole
@ -47,7 +46,7 @@ generic_docroles = {
# -- generic cross-reference role ---------------------------------------------- # -- generic cross-reference role ----------------------------------------------
class XRefRole: class XRefRole(ReferenceRole):
""" """
A generic cross-referencing role. To create a callable that can be used as A generic cross-referencing role. To create a callable that can be used as
a role function, create an instance of this class. a role function, create an instance of this class.
@ -85,8 +84,12 @@ class XRefRole:
if innernodeclass is not None: if innernodeclass is not None:
self.innernodeclass = innernodeclass self.innernodeclass = innernodeclass
super().__init__()
def _fix_parens(self, env, has_explicit_title, title, target): def _fix_parens(self, env, has_explicit_title, title, target):
# type: (BuildEnvironment, bool, str, str) -> Tuple[str, str] # type: (BuildEnvironment, bool, str, str) -> Tuple[str, str]
warnings.warn('XRefRole._fix_parens() is deprecated.',
RemovedInSphinx40Warning, stacklevel=2)
if not has_explicit_title: if not has_explicit_title:
if title.endswith('()'): if title.endswith('()'):
# remove parentheses # remove parentheses
@ -99,55 +102,70 @@ class XRefRole:
target = target[:-2] target = target[:-2]
return title, target return title, target
def __call__(self, typ, rawtext, text, lineno, inliner, def update_title_and_target(self, title, target):
options={}, content=[]): # type: (str, str) -> Tuple[str, str]
# type: (str, str, str, int, Inliner, Dict, List[str]) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA if not self.has_explicit_title:
env = inliner.document.settings.env if title.endswith('()'):
if not typ: # remove parentheses
typ = env.temp_data.get('default_role') title = title[:-2]
if not typ: if self.config.add_function_parentheses:
typ = env.config.default_role # add them back to all occurrences if configured
if not typ: title += '()'
raise SphinxError('cannot determine default role!') # remove parentheses from the target too
if target.endswith('()'):
target = target[:-2]
return title, target
def run(self):
# type: () -> Tuple[List[nodes.Node], List[nodes.system_message]]
if ':' not in self.name:
self.refdomain, self.reftype = '', self.name
self.classes = ['xref', self.reftype]
else: else:
typ = typ.lower() self.refdomain, self.reftype = self.name.split(':', 1)
if ':' not in typ: self.classes = ['xref', self.refdomain, '%s-%s' % (self.refdomain, self.reftype)]
domain, role = '', typ
classes = ['xref', role] if self.text.startswith('!'):
# if the first character is a bang, don't cross-reference at all
return self.create_non_xref_node()
else: else:
domain, role = typ.split(':', 1) return self.create_xref_node()
classes = ['xref', domain, '%s-%s' % (domain, role)]
# if the first character is a bang, don't cross-reference at all def create_non_xref_node(self):
if text[0:1] == '!': # type: () -> Tuple[List[nodes.Node], List[nodes.system_message]]
text = utils.unescape(text)[1:] text = utils.unescape(self.text[1:])
if self.fix_parens: if self.fix_parens:
text, tgt = self._fix_parens(env, False, text, "") self.has_explicit_title = False # treat as implicit
innernode = self.innernodeclass(rawtext, text, classes=classes) text, target = self.update_title_and_target(text, "")
return self.result_nodes(inliner.document, env, innernode, is_ref=False)
# split title and target in role content node = self.innernodeclass(self.rawtext, text, classes=self.classes)
has_explicit_title, title, target = split_explicit_title(text) return self.result_nodes(self.inliner.document, self.env, node, is_ref=False)
title = utils.unescape(title)
target = utils.unescape(target) def create_xref_node(self):
# fix-up title and target # type: () -> Tuple[List[nodes.Node], List[nodes.system_message]]
target = self.target
title = self.title
if self.lowercase: if self.lowercase:
target = target.lower() target = target.lower()
if self.fix_parens: if self.fix_parens:
title, target = self._fix_parens( title, target = self.update_title_and_target(title, target)
env, has_explicit_title, title, target)
# create the reference node # create the reference node
refnode = self.nodeclass(rawtext, reftype=role, refdomain=domain, options = {'refdoc': self.env.docname,
refexplicit=has_explicit_title) 'refdomain': self.refdomain,
# we may need the line number for warnings 'reftype': self.reftype,
set_role_source_info(inliner, lineno, refnode) 'refexplicit': self.has_explicit_title,
title, target = self.process_link(env, refnode, has_explicit_title, title, target) 'refwarn': self.warn_dangling}
# now that the target and title are finally determined, set them refnode = self.nodeclass(self.rawtext, **options)
self.set_source_info(refnode)
# determine the target and title for the class
title, target = self.process_link(self.env, refnode, self.has_explicit_title,
title, target)
refnode['reftarget'] = target refnode['reftarget'] = target
refnode += self.innernodeclass(rawtext, title, classes=classes) refnode += self.innernodeclass(self.rawtext, title, classes=self.classes)
# we also need the source document
refnode['refdoc'] = env.docname return self.result_nodes(self.inliner.document, self.env, refnode, is_ref=True)
refnode['refwarn'] = self.warn_dangling
# result_nodes allow further modification of return values
return self.result_nodes(inliner.document, env, refnode, is_ref=True)
# methods that can be overwritten # methods that can be overwritten