Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proof of Concept: Only create a target for object descriptions #9675

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 24 additions & 0 deletions sphinx/directives/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class ObjectDescription(SphinxDirective, Generic[T]):
final_argument_whitespace = True
option_spec: OptionSpec = {
'noindex': directives.flag,
'hidden': directives.flag,
}

# types of doc fields that this directive handles, see sphinx.util.docfields
Expand Down Expand Up @@ -170,6 +171,7 @@ def run(self) -> List[Node]:
# 'desctype' is a backwards compatible attribute
node['objtype'] = node['desctype'] = self.objtype
node['noindex'] = noindex = ('noindex' in self.options)
node['hidden'] = 'hidden' in self.options
if self.domain:
node['classes'].append(self.domain)
node['classes'].append(node['objtype'])
Expand Down Expand Up @@ -212,8 +214,30 @@ def run(self) -> List[Node]:
DocFieldTransformer(self).transform_all(contentnode)
self.env.temp_data['object'] = None
self.after_content()

if node['hidden']:
node = self.replace_node_with_target(node)
self.set_source_info(node)

return [self.indexnode, node]

# TODO: This method does not need access to self, make a (non-class) function out of it?
def replace_node_with_target(self, node: nodes.Node) -> nodes.target:

def collect_ids(node: nodes.Node) -> List[str]:
if isinstance(node, nodes.Element):
ids: List[str] = node.get('ids', [])
for c in node.children:
ids.extend(collect_ids(c))
return ids
else:
return []

ids: List[str] = collect_ids(node)
target_node = nodes.target()
target_node['ids'] = ids
return target_node


class DefaultRole(SphinxDirective):
"""
Expand Down
6 changes: 4 additions & 2 deletions sphinx/domains/c.py
Original file line number Diff line number Diff line change
Expand Up @@ -3140,9 +3140,11 @@ class CObject(ObjectDescription[ASTDeclaration]):
names=('rtype',)),
]

option_spec: OptionSpec = {
option_spec: OptionSpec = ObjectDescription.option_spec.copy()
option_spec.update({
'noindexentry': directives.flag,
}
})
del option_spec['noindex'] # is in ObjectDescription but doesn't make sense here

def _add_enumerator_to_parent(self, ast: ASTDeclaration) -> None:
assert ast.objectType == 'enumerator'
Expand Down
6 changes: 4 additions & 2 deletions sphinx/domains/cpp.py
Original file line number Diff line number Diff line change
Expand Up @@ -6948,10 +6948,12 @@ class CPPObject(ObjectDescription[ASTDeclaration]):
names=('returns', 'return')),
]

option_spec: OptionSpec = {
option_spec: OptionSpec = ObjectDescription.option_spec.copy()
option_spec.update({
'noindexentry': directives.flag,
'tparam-line-spec': directives.flag,
}
})
del option_spec['noindex'] # is in ObjectDescription but doesn't make sense here

def _add_enumerator_to_parent(self, ast: ASTDeclaration) -> None:
assert ast.objectType == 'enumerator'
Expand Down
6 changes: 3 additions & 3 deletions sphinx/domains/javascript.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@ class JSObject(ObjectDescription[Tuple[str, str]]):
#: based on directive nesting
allow_nesting = False

option_spec: OptionSpec = {
'noindex': directives.flag,
option_spec: OptionSpec = ObjectDescription.option_spec.copy()
option_spec.update({
'noindexentry': directives.flag,
}
})

def handle_signature(self, sig: str, signode: desc_signature) -> Tuple[str, str]:
"""Breaks down construct signatures
Expand Down
6 changes: 3 additions & 3 deletions sphinx/domains/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -389,13 +389,13 @@ class PyObject(ObjectDescription[Tuple[str, str]]):
:cvar allow_nesting: Class is an object that allows for nested namespaces
:vartype allow_nesting: bool
"""
option_spec: OptionSpec = {
'noindex': directives.flag,
option_spec: OptionSpec = ObjectDescription.option_spec.copy()
option_spec.update({
'noindexentry': directives.flag,
'module': directives.unchanged,
'canonical': directives.unchanged,
'annotation': directives.unchanged,
}
})

doc_field_types = [
PyTypedField('parameter', label=_('Parameters'),
Expand Down
75 changes: 75 additions & 0 deletions sphinx/transforms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,80 @@ def apply(self, **kwargs: Any) -> None:
node.attributes.update(info)


class ReorderConsecutiveTargetAndIndexNodes(SphinxTransform):
"""Index nodes interspersed between target nodes prevent other
Transformations from combining those target nodes. This transformation
reorders them:

Given the following ``document`` as input::

<document>
<target ids="id1" ...>
<index entries="...1...">
<target ids="id2" ...>
<target ids="id3" ...>
<index entries="...2...">
<target ids="id4" ...>

The transformed result will be::

<document>
<index entries="...1...">
<index entries="...2...">
<target ids="id1" ...>
<target ids="id2" ...>
<target ids="id3" ...>
<target ids="id4" ...>
"""

# Priority must smaller than the one of PropagateTargets transform, which
# has 260.
default_priority = 250

def apply(self, **kwargs: Any) -> None:
for target in self.document.traverse(nodes.target):
self.reorder_around(target)

def reorder_around(self, start_node: nodes.Node) -> None:
# collect all follow up target or index sibling nodes (including node
# itself). Note that we cannot use the 'condition' to filter for index
# and target as we want *consecutive* target/index nodes.
nodes_to_reorder: List[Union[nodes.target, addnodes.index]] = []
for node in start_node.traverse(condition=None,
include_self=True,
descend=False,
siblings=True):
if not isinstance(node, nodes.target) and \
not isinstance(node, addnodes.index):
break # consecutive strike is broken
nodes_to_reorder.append(node)

if len(nodes_to_reorder) < 2:
return # Nothing to reorder

# Since we have at least two siblings, their parent is not None and
# supports children (e.g. is not Text)

parent_node: nodes.Element = nodes_to_reorder[0].parent
assert parent_node == nodes_to_reorder[-1].parent
first_idx = parent_node.index(nodes_to_reorder[0])
last_idx = parent_node.index(nodes_to_reorder[-1])
assert first_idx + len(nodes_to_reorder) - 1 == last_idx

def sortkey(node: nodes.Node):
if isinstance(node, addnodes.index):
return 1
elif isinstance(node, nodes.target):
return 2
else:
raise Error('This cannot happen')
# Important: The sort algorithm used must be a stable sort.
nodes_to_reorder.sort(key = sortkey)

# '+1' since slices are excluding the right hand index
parent_node[first_idx:last_idx+1] = nodes_to_reorder


def setup(app: "Sphinx") -> Dict[str, Any]:
app.add_transform(ApplySourceWorkaround)
app.add_transform(ExtraTranslatableNodes)
Expand All @@ -420,6 +494,7 @@ def setup(app: "Sphinx") -> Dict[str, Any]:
app.add_transform(SphinxSmartQuotes)
app.add_transform(DoctreeReadEvent)
app.add_transform(ManpageLink)
app.add_transform(ReorderConsecutiveTargetAndIndexNodes)

return {
'version': 'builtin',
Expand Down