fields, call roles instead of making a pending_xref

Except for py which has mixins that assumes a single pending_xref
This commit is contained in:
Jakob Lykke Andersen
2021-05-01 13:46:51 +02:00
parent 69cbf7aa92
commit 4534d2d1a5
2 changed files with 73 additions and 32 deletions

View File

@@ -20,6 +20,7 @@ from typing import Any, Dict, Iterable, Iterator, List, NamedTuple, Optional, Tu
from docutils import nodes
from docutils.nodes import Element, Node
from docutils.parsers.rst import directives
from docutils.parsers.rst.states import Inliner
from sphinx import addnodes
from sphinx.addnodes import desc_signature, pending_xref, pending_xref_condition
@@ -284,9 +285,13 @@ def _pseudo_parse_arglist(signode: desc_signature, arglist: str) -> None:
class PyXrefMixin:
def make_xref(self, rolename: str, domain: str, target: str,
innernode: Type[TextlikeNode] = nodes.emphasis,
contnode: Node = None, env: BuildEnvironment = None) -> Node:
contnode: Node = None, env: BuildEnvironment = None,
inliner: Inliner = None, location: Node = None) -> Node:
# we use inliner=None to make sure we get the old behaviour with a single
# pending_xref node
result = super().make_xref(rolename, domain, target, # type: ignore
innernode, contnode, env)
innernode, contnode,
env, inliner=None, location=None)
result['refspecific'] = True
result['py:module'] = env.ref_context.get('py:module')
result['py:class'] = env.ref_context.get('py:class')
@@ -313,7 +318,8 @@ class PyXrefMixin:
def make_xrefs(self, rolename: str, domain: str, target: str,
innernode: Type[TextlikeNode] = nodes.emphasis,
contnode: Node = None, env: BuildEnvironment = None) -> List[Node]:
contnode: Node = None, env: BuildEnvironment = None,
inliner: Inliner = None, location: Node = None) -> List[Node]:
delims = r'(\s*[\[\]\(\),](?:\s*or\s)?\s*|\s+or\s+|\s*\|\s*|\.\.\.)'
delims_re = re.compile(delims)
sub_targets = re.split(delims, target)
@@ -329,7 +335,7 @@ class PyXrefMixin:
results.append(contnode or innernode(sub_target, sub_target))
else:
results.append(self.make_xref(rolename, domain, sub_target,
innernode, contnode, env))
innernode, contnode, env, inliner, location))
return results
@@ -337,12 +343,14 @@ class PyXrefMixin:
class PyField(PyXrefMixin, Field):
def make_xref(self, rolename: str, domain: str, target: str,
innernode: Type[TextlikeNode] = nodes.emphasis,
contnode: Node = None, env: BuildEnvironment = None) -> Node:
contnode: Node = None, env: BuildEnvironment = None,
inliner: Inliner = None, location: Node = None) -> Node:
if rolename == 'class' and target == 'None':
# None is not a type, so use obj role instead.
rolename = 'obj'
return super().make_xref(rolename, domain, target, innernode, contnode, env)
return super().make_xref(rolename, domain, target, innernode, contnode,
env, inliner, location)
class PyGroupedField(PyXrefMixin, GroupedField):
@@ -352,12 +360,14 @@ class PyGroupedField(PyXrefMixin, GroupedField):
class PyTypedField(PyXrefMixin, TypedField):
def make_xref(self, rolename: str, domain: str, target: str,
innernode: Type[TextlikeNode] = nodes.emphasis,
contnode: Node = None, env: BuildEnvironment = None) -> Node:
contnode: Node = None, env: BuildEnvironment = None,
inliner: Inliner = None, location: Node = None) -> Node:
if rolename == 'class' and target == 'None':
# None is not a type, so use obj role instead.
rolename = 'obj'
return super().make_xref(rolename, domain, target, innernode, contnode, env)
return super().make_xref(rolename, domain, target, innernode, contnode,
env, inliner, location)
class PyObject(ObjectDescription[Tuple[str, str]]):

View File

@@ -8,19 +8,22 @@
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
from typing import TYPE_CHECKING, Any, Dict, List, Tuple, Type, Union, cast
from docutils import nodes
from docutils.nodes import Node
from docutils.parsers.rst.states import Inliner
from sphinx import addnodes
from sphinx.environment import BuildEnvironment
from sphinx.util import logging
from sphinx.util.typing import TextlikeNode
if TYPE_CHECKING:
from sphinx.directive import ObjectDescription
logger = logging.getLogger(__name__)
def _is_single_paragraph(node: nodes.field_body) -> bool:
"""True if the node only contains one paragraph (and system messages)."""
@@ -62,39 +65,62 @@ class Field:
def make_xref(self, rolename: str, domain: str, target: str,
innernode: Type[TextlikeNode] = addnodes.literal_emphasis,
contnode: Node = None, env: BuildEnvironment = None) -> Node:
contnode: Node = None, env: BuildEnvironment = None,
inliner: Inliner = None, location: Node = None) -> Node:
# note: for backwards compatibility env is last, but not optional
assert env is not None
assert (inliner is None) == (location is None), (inliner, location)
if not rolename:
return contnode or innernode(target, target)
refnode = addnodes.pending_xref('', refdomain=domain, refexplicit=False,
reftype=rolename, reftarget=target)
refnode += contnode or innernode(target, target)
if env:
env.get_domain(domain).process_field_xref(refnode)
return refnode
role = env.get_domain(domain).role(rolename)
if role is None or inliner is None:
if role is None and inliner is not None:
msg = "Problem in %s domain: field is supposed "
msg += "to use role '%s', but that role is not in the domain."
logger.warning(msg, domain, rolename, location=location)
if inliner is None:
# msg = "Field for %s domain using role '%s' does not run the role."
# msg += " No inliner provided."
# logger.warning(msg, domain, rolename)
pass
refnode = addnodes.pending_xref('', refdomain=domain, refexplicit=False,
reftype=rolename, reftarget=target)
refnode += contnode or innernode(target, target)
if env:
env.get_domain(domain).process_field_xref(refnode)
return refnode
lineno = logging.get_source_line(location)[1]
ns, messages = role(rolename, target, target, lineno, inliner, {}, [])
return nodes.inline(target, '', *ns)
def make_xrefs(self, rolename: str, domain: str, target: str,
innernode: Type[TextlikeNode] = addnodes.literal_emphasis,
contnode: Node = None, env: BuildEnvironment = None) -> List[Node]:
return [self.make_xref(rolename, domain, target, innernode, contnode, env)]
contnode: Node = None, env: BuildEnvironment = None,
inliner: Inliner = None, location: Node = None) -> List[Node]:
return [self.make_xref(rolename, domain, target, innernode, contnode,
env, inliner, location)]
def make_entry(self, fieldarg: str, content: List[Node]) -> Tuple[str, List[Node]]:
return (fieldarg, content)
def make_field(self, types: Dict[str, List[Node]], domain: str,
item: Tuple, env: BuildEnvironment = None) -> nodes.field:
item: Tuple, env: BuildEnvironment = None,
inliner: Inliner = None, location: Node = None) -> nodes.field:
fieldarg, content = item
fieldname = nodes.field_name('', self.label)
if fieldarg:
fieldname += nodes.Text(' ')
fieldname.extend(self.make_xrefs(self.rolename, domain,
fieldarg, nodes.Text, env=env))
fieldarg, nodes.Text,
env=env, inliner=inliner, location=location))
if len(content) == 1 and (
isinstance(content[0], nodes.Text) or
(isinstance(content[0], nodes.inline) and len(content[0]) == 1 and
isinstance(content[0][0], nodes.Text))):
content = self.make_xrefs(self.bodyrolename, domain,
content[0].astext(), contnode=content[0], env=env)
content[0].astext(), contnode=content[0],
env=env, inliner=inliner, location=location)
fieldbody = nodes.field_body('', nodes.paragraph('', '', *content))
return nodes.field('', fieldname, fieldbody)
@@ -121,13 +147,15 @@ class GroupedField(Field):
self.can_collapse = can_collapse
def make_field(self, types: Dict[str, List[Node]], domain: str,
items: Tuple, env: BuildEnvironment = None) -> nodes.field:
items: Tuple, env: BuildEnvironment = None,
inliner: Inliner = None, location: Node = None) -> nodes.field:
fieldname = nodes.field_name('', self.label)
listnode = self.list_type()
for fieldarg, content in items:
par = nodes.paragraph()
par.extend(self.make_xrefs(self.rolename, domain, fieldarg,
addnodes.literal_strong, env=env))
addnodes.literal_strong,
env=env, inliner=inliner, location=location))
par += nodes.Text(' -- ')
par += content
listnode += nodes.list_item('', par)
@@ -170,7 +198,8 @@ class TypedField(GroupedField):
self.typerolename = typerolename
def make_field(self, types: Dict[str, List[Node]], domain: str,
items: Tuple, env: BuildEnvironment = None) -> nodes.field:
items: Tuple, env: BuildEnvironment = None,
inliner: Inliner = None, location: Node = None) -> nodes.field:
def handle_item(fieldarg: str, content: str) -> nodes.paragraph:
par = nodes.paragraph()
par.extend(self.make_xrefs(self.rolename, domain, fieldarg,
@@ -184,7 +213,8 @@ class TypedField(GroupedField):
if len(fieldtype) == 1 and isinstance(fieldtype[0], nodes.Text):
typename = fieldtype[0].astext()
par.extend(self.make_xrefs(self.typerolename, domain, typename,
addnodes.literal_emphasis, env=env))
addnodes.literal_emphasis, env=env,
inliner=inliner, location=location))
else:
par += fieldtype
par += nodes.Text(')')
@@ -227,7 +257,7 @@ class DocFieldTransformer:
"""Transform a single field list *node*."""
typemap = self.typemap
entries: List[Union[nodes.field, Tuple[Field, Any]]] = []
entries: List[Union[nodes.field, Tuple[Field, Any, Node]]] = []
groupindices: Dict[str, int] = {}
types: Dict[str, Dict] = {}
@@ -317,16 +347,16 @@ class DocFieldTransformer:
# get one entry per field
if typedesc.is_grouped:
if typename in groupindices:
group = cast(Tuple[Field, List], entries[groupindices[typename]])
group = cast(Tuple[Field, List, Node], entries[groupindices[typename]])
else:
groupindices[typename] = len(entries)
group = (typedesc, [])
group = (typedesc, [], field)
entries.append(group)
new_entry = typedesc.make_entry(fieldarg, [translatable_content])
group[1].append(new_entry)
else:
new_entry = typedesc.make_entry(fieldarg, [translatable_content])
entries.append((typedesc, new_entry))
entries.append((typedesc, new_entry, field))
# step 2: all entries are collected, construct the new field list
new_list = nodes.field_list()
@@ -335,10 +365,11 @@ class DocFieldTransformer:
# pass-through old field
new_list += entry
else:
fieldtype, items = entry
fieldtype, items, location = entry
fieldtypes = types.get(fieldtype.name, {})
env = self.directive.state.document.settings.env
new_list += fieldtype.make_field(fieldtypes, self.directive.domain,
items, env=env)
inliner = self.directive.state.inliner
new_list += fieldtype.make_field(fieldtypes, self.directive.domain, items,
env=env, inliner=inliner, location=location)
node.replace_self(new_list)