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

Only create a target for object descriptions #10478

Merged
merged 32 commits into from Jul 28, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
415eed4
ObjectDescription: Add option :hidden:
latosha-maltba Jun 4, 2022
46f13ec
ObjectDescription: Hide contents if :hidden: is given
latosha-maltba Jun 4, 2022
477d3f1
ObjectDescription: Record source location for targets
latosha-maltba Jun 4, 2022
30f9cc8
ObjectDescription: Replace collect_ids() with pure function
latosha-maltba Jun 4, 2022
a8ffd73
ObjectDescription: Make replace_node_with_target() a static method
latosha-maltba Jun 4, 2022
4b9116c
ObjectDescription: Factor out collect_ids() method
latosha-maltba Jun 4, 2022
61693f1
ObjectDescription: Simplify replace_node_with_target()
latosha-maltba Jun 4, 2022
f97de0b
ObjectDescription: Inline replace_node_with_target()
latosha-maltba Jun 4, 2022
54b909e
ObjectDescription: Rename :hidden: to :notypesetting:
latosha-maltba Jun 4, 2022
2a3f189
Document :notypesetting: directive option for domains
latosha-maltba Jun 4, 2022
0ebf888
Domain CPP: Update `option_spec` of superclass instead replacing it
latosha-maltba Jun 4, 2022
a6ff71c
Domain C and CPP: Drop ``:notypesetting:`` from alias object
latosha-maltba Jun 4, 2022
28633ef
Domain JavaScript: Fix order of index and target nodes
latosha-maltba Jun 4, 2022
3fdcc97
Domain Python: Fix order of index and target nodes
latosha-maltba Jun 4, 2022
fd01cd0
ObjectDescription: Do not create a target node without ids
latosha-maltba Jun 4, 2022
d975c60
ObjectDescription: Add tests for :notypesetting: option
latosha-maltba Jun 4, 2022
3e791c9
Reorder consecutive index and target nodes
latosha-maltba Jun 4, 2022
0b6ed33
Domain Python: Fix tests due to new index/target node order
latosha-maltba Jun 4, 2022
d1db8f2
Add tests for ReorderConsecutiveTargetAndIndexNodes
latosha-maltba Jun 4, 2022
fb0ad6c
CHANGES: Add entry for ``:notypesetting:``
latosha-maltba Jun 4, 2022
c3885db
Merge branch 'master' into object_description_run_5.x
AA-Turner Jul 28, 2023
6f73587
typing
AA-Turner Jul 28, 2023
2857f26
style
AA-Turner Jul 28, 2023
75977dd
notypesetting -> no-typesetting
AA-Turner Jul 28, 2023
a5a708a
Don't copy ``ObjectDescription.option_spec``
AA-Turner Jul 28, 2023
5050ec5
Add to reST domain
AA-Turner Jul 28, 2023
6f45edb
Add to JSModule and PythonModule
AA-Turner Jul 28, 2023
184773c
Updates
AA-Turner Jul 28, 2023
321816b
Quotation marks
AA-Turner Jul 28, 2023
40484b5
rename
AA-Turner Jul 28, 2023
3c46064
rename
AA-Turner Jul 28, 2023
1db991f
Update CHANGES entry.
AA-Turner Jul 28, 2023
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
4 changes: 4 additions & 0 deletions CHANGES
Expand Up @@ -34,6 +34,10 @@ Deprecated
Features added
--------------

* Most domain directives, e.g. ``.. py:function::``, now support the option
``:notypesetting:`` to suppress their output and only create a linkable
anchor (#9662, #9671, #9675, #10478)

Bugs fixed
----------

Expand Down
19 changes: 19 additions & 0 deletions doc/usage/restructuredtext/domains.rst
Expand Up @@ -50,13 +50,19 @@ give the directive option flag ``:noindexentry:``.
If you want to typeset an object description, without even making it available
for cross-referencing, you can give the directive option flag ``:noindex:``
(which implies ``:noindexentry:``).
If you do not want to typeset anything, you can give the directive option flag
``:notypesetting:``. This can for example be used to create only a target and
index entry for later reference.
Though, note that not every directive in every domain may support these
options.

.. versionadded:: 3.2
The directive option ``noindexentry`` in the Python, C, C++, and Javascript
domains.

.. versionadded:: 5.1
The directive option ``notypesetting``.

An example using a Python domain directive::

.. py:function:: spam(eggs)
Expand All @@ -83,6 +89,19 @@ you could say ::
As you can see, both directive and role names contain the domain name and the
directive name.

The directive option ``:notypesetting:`` can be used to create a target (and
index entry) which can later be referenced by the roles provided by the domain.
This is particularly useful for literate programming::

.. py:function:: spam(eggs)
:notypesetting:
.. code::

def spam(eggs):
pass

The function :py:func:`spam` does nothing.

.. rubric:: Default Domain

For documentation describing objects from solely one domain, authors will not
Expand Down
30 changes: 29 additions & 1 deletion sphinx/directives/__init__.py
Expand Up @@ -51,6 +51,7 @@ class ObjectDescription(SphinxDirective, Generic[T]):
final_argument_whitespace = True
option_spec: OptionSpec = {
'noindex': directives.flag,
'notypesetting': directives.flag,
}

# types of doc fields that this directive handles, see sphinx.util.docfields
Expand Down Expand Up @@ -161,6 +162,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['notypesetting'] = 'notypesetting' in self.options
if self.domain:
node['classes'].append(self.domain)
node['classes'].append(node['objtype'])
Expand Down Expand Up @@ -203,7 +205,33 @@ def run(self) -> List[Node]:
DocFieldTransformer(self).transform_all(contentnode)
self.env.temp_data['object'] = None
self.after_content()
return [self.indexnode, node]

ret: List[nodes.Node] = []
ret.append(self.indexnode)
if not node['notypesetting']:
ret.append(node)
else:
# replace the node with a target node containing all the ids of
# this node and its children.
# It might happen that there are no ids (e.g. with noindex).
# In this case creating a target without an id breaks docutils
# assumption about targets. Thus, we skip that in this case.
ids = self.__class__.collect_ids(node)
if ids:
targetnode = nodes.target(ids=ids)
self.set_source_info(targetnode)
ret.append(targetnode)
return ret

@staticmethod
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(ObjectDescription.collect_ids(c))
return ids
else:
return []


class DefaultRole(SphinxDirective):
Expand Down
13 changes: 9 additions & 4 deletions sphinx/domains/c.py
Expand Up @@ -3141,9 +3141,11 @@ class CObject(ObjectDescription[ASTDeclaration]):
Description of a C language object.
"""

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 Expand Up @@ -3615,10 +3617,13 @@ def apply(self, **kwargs: Any) -> None:


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

def run(self) -> List[Node]:
"""
Expand Down
13 changes: 9 additions & 4 deletions sphinx/domains/cpp.py
Expand Up @@ -7083,10 +7083,12 @@ class CPPObject(ObjectDescription[ASTDeclaration]):
can_collapse=True),
]

option_spec: OptionSpec = {
option_spec: OptionSpec = ObjectDescription.option_spec.copy()
latosha-maltba marked this conversation as resolved.
Show resolved Hide resolved
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 Expand Up @@ -7604,10 +7606,13 @@ def apply(self, **kwargs: Any) -> None:


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

def run(self) -> List[Node]:
"""
Expand Down
13 changes: 7 additions & 6 deletions sphinx/domains/javascript.py
Expand Up @@ -37,10 +37,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({
latosha-maltba marked this conversation as resolved.
Show resolved Hide resolved
'noindexentry': directives.flag,
}
})

def get_display_prefix(self) -> List[Node]:
#: what is displayed right before the documentation entry
Expand Down Expand Up @@ -272,12 +272,13 @@ def run(self) -> List[Node]:
domain.note_object(mod_name, 'module', node_id,
location=(self.env.docname, self.lineno))

target = nodes.target('', '', ids=[node_id], ismod=True)
self.state.document.note_explicit_target(target)
ret.append(target)
# The node order is: index node first, then target node
indextext = _('%s (module)') % mod_name
inode = addnodes.index(entries=[('single', indextext, node_id, '', None)])
ret.append(inode)
target = nodes.target('', '', ids=[node_id], ismod=True)
self.state.document.note_explicit_target(target)
ret.append(target)
return ret

def make_old_id(self, modname: str) -> str:
Expand Down
9 changes: 5 additions & 4 deletions sphinx/domains/python.py
Expand Up @@ -421,13 +421,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 Expand Up @@ -999,10 +999,11 @@ def run(self) -> List[Node]:

# the platform and synopsis aren't printed; in fact, they are only
# used in the modindex currently
ret.append(target)
indextext = '%s; %s' % (pairindextypes['module'], modname)
inode = addnodes.index(entries=[('pair', indextext, node_id, '', None)])
# The node order is: index node first, then target node.
ret.append(inode)
ret.append(target)
return ret

def make_old_id(self, name: str) -> str:
Expand Down
78 changes: 77 additions & 1 deletion sphinx/transforms/__init__.py
Expand Up @@ -3,7 +3,7 @@
import re
import unicodedata
import warnings
from typing import TYPE_CHECKING, Any, Dict, Generator, List, Optional, Tuple, cast
from typing import TYPE_CHECKING, Any, Dict, Generator, List, Optional, Tuple, Union, cast

import docutils
from docutils import nodes
Expand Down Expand Up @@ -417,6 +417,81 @@ def apply(self, **kwargs: Any) -> None:
)


class ReorderConsecutiveTargetAndIndexNodes(SphinxTransform):
"""Index nodes interspersed between target nodes prevent other
Transformations from combining those target nodes,
e.g. ``PropagateTargets``. 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.findall(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]] = []
node: nodes.Node
for node in start_node.findall(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) -> int:
if isinstance(node, addnodes.index):
return 1
elif isinstance(node, nodes.target):
return 2
else:
raise Exception('This cannot happen! (Unreachable code reached)')
# 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 @@ -433,6 +508,7 @@ def setup(app: "Sphinx") -> Dict[str, Any]:
app.add_transform(DoctreeReadEvent)
app.add_transform(ManpageLink)
app.add_transform(GlossarySorter)
app.add_transform(ReorderConsecutiveTargetAndIndexNodes)

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