Skip to content

Commit

Permalink
Merge pull request #9023 from jakobandersen/decl_styling
Browse files Browse the repository at this point in the history
Flexible declaration styling
  • Loading branch information
jakobandersen committed Apr 12, 2021
2 parents 32337c3 + 709339e commit 8954770
Show file tree
Hide file tree
Showing 18 changed files with 864 additions and 453 deletions.
5 changes: 5 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,17 @@ Dependencies
Incompatible changes
--------------------

* #9023: Change the CSS classes on :rst:role:`cpp:expr` and
:rst:role:`cpp:texpr`.

Deprecated
----------

Features added
--------------

* #9023: More CSS classes on domain descriptions, see :ref:`nodes` for details.

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

Expand Down
18 changes: 16 additions & 2 deletions doc/extdev/nodes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,32 @@ Doctree node classes added by Sphinx
Nodes for domain-specific object descriptions
---------------------------------------------

Top-level nodes
...............

These nodes form the top-most levels of object descriptions.

.. autoclass:: desc
.. autoclass:: desc_signature
.. autoclass:: desc_signature_line
.. autoclass:: desc_content
.. autoclass:: desc_inline

Nodes for high-level structure in signatures
............................................

These nodes occur in in non-multiline :py:class:`desc_signature` nodes
and in :py:class:`desc_signature_line` nodes.

.. autoclass:: desc_name
.. autoclass:: desc_addname

.. autoclass:: desc_type
.. autoclass:: desc_returns
.. autoclass:: desc_name
.. autoclass:: desc_parameterlist
.. autoclass:: desc_parameter
.. autoclass:: desc_optional
.. autoclass:: desc_annotation
.. autoclass:: desc_content

New admonition-like constructs
------------------------------
Expand Down
183 changes: 152 additions & 31 deletions sphinx/addnodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,25 +115,53 @@ def extract_original_messages(self) -> List[str]:
return messages


# domain-specific object descriptions (class, function etc.)
#############################################################
# Domain-specific object descriptions (class, function etc.)
#############################################################

class _desc_classes_injector(nodes.Element, not_smartquotable):
"""Helper base class for injecting a fixes list of classes.
Use as the first base class.
"""

classes: List[str] = []

def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
self['classes'].extend(self.classes)


# Top-level nodes
#################

class desc(nodes.Admonition, nodes.Element):
"""Node for object descriptions.
"""Node for a list of object signatures and a common description of them.
This node is similar to a "definition list" with one definition. It
contains one or more ``desc_signature`` and a ``desc_content``.
Contains one or more :py:class:`desc_signature` nodes
and then a single :py:class:`desc_content` node.
This node always has two classes:
- The name of the domain it belongs to, e.g., ``py`` or ``cpp``.
- The name of the object type in the domain, e.g., ``function``.
"""

# TODO: can we introduce a constructor
# that forces the specification of the domain and objtyp?

class desc_signature(nodes.Part, nodes.Inline, nodes.TextElement):
"""Node for object signatures.

The "term" part of the custom Sphinx definition list.
class desc_signature(_desc_classes_injector, nodes.Part, nodes.Inline, nodes.TextElement):
"""Node for a single object signature.
As default the signature is a single line signature,
but set ``is_multiline = True`` to describe a multi-line signature.
In that case all child nodes must be ``desc_signature_line`` nodes.
As default the signature is a single-line signature.
Set ``is_multiline = True`` to describe a multi-line signature.
In that case all child nodes must be :py:class:`desc_signature_line` nodes.
This node always has the classes ``sig``, ``sig-object``, and the domain it belongs to.
"""
# Note: the domain name is being added through a post-transform DescSigAddDomainAsClass
classes = ['sig', 'sig-object']

@property
def child_text_separator(self):
Expand All @@ -144,18 +172,63 @@ def child_text_separator(self):


class desc_signature_line(nodes.Part, nodes.Inline, nodes.FixedTextElement):
"""Node for a line in a multi-line object signatures.
"""Node for a line in a multi-line object signature.
It should only be used in a ``desc_signature`` with ``is_multiline`` set.
It should only be used as a child of a :py:class:`desc_signature`
with ``is_multiline`` set to ``True``.
Set ``add_permalink = True`` for the line that should get the permalink.
"""
sphinx_line_type = ''


class desc_content(nodes.General, nodes.Element):
"""Node for object description content.
Must be the last child node in a :py:class:`desc` node.
"""


class desc_inline(_desc_classes_injector, nodes.Inline, nodes.TextElement):
"""Node for a signature fragment in inline text.
This is for example used for roles like :rst:role:`cpp:expr`.
This node always has the classes ``sig``, ``sig-inline``,
and the name of the domain it belongs to.
"""
classes = ['sig', 'sig-inline']

def __init__(self, domain: str, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
self['classes'].append(domain)


# Nodes for high-level structure in signatures
##############################################

# nodes to use within a desc_signature or desc_signature_line

class desc_addname(nodes.Part, nodes.Inline, nodes.FixedTextElement):
"""Node for additional name parts (module name, class name)."""
class desc_name(_desc_classes_injector, nodes.Part, nodes.Inline, nodes.FixedTextElement):
"""Node for the main object name.
For example, in the declaration of a Python class ``MyModule.MyClass``,
the main name is ``MyClass``.
This node always has the class ``sig-name``.
"""
classes = ['sig-name', 'descname'] # 'descname' is for backwards compatibility


class desc_addname(_desc_classes_injector, nodes.Part, nodes.Inline, nodes.FixedTextElement):
"""Node for additional name parts for an object.
For example, in the declaration of a Python class ``MyModule.MyClass``,
the additional name part is ``MyModule.``.
This node always has the class ``sig-prename``.
"""
# 'descclassname' is for backwards compatibility
classes = ['sig-prename', 'descclassname']


# compatibility alias
Expand All @@ -168,14 +241,11 @@ class desc_type(nodes.Part, nodes.Inline, nodes.FixedTextElement):

class desc_returns(desc_type):
"""Node for a "returns" annotation (a la -> in Python)."""

def astext(self) -> str:
return ' -> ' + super().astext()


class desc_name(nodes.Part, nodes.Inline, nodes.FixedTextElement):
"""Node for the main object name."""


class desc_parameterlist(nodes.Part, nodes.Inline, nodes.FixedTextElement):
"""Node for a general parameter list."""
child_text_separator = ', '
Expand All @@ -200,14 +270,14 @@ class desc_annotation(nodes.Part, nodes.Inline, nodes.FixedTextElement):
"""Node for signature annotations (not Python 3-style annotations)."""


class desc_content(nodes.General, nodes.Element):
"""Node for object description content.
This is the "definition" part of the custom Sphinx definition list.
"""
# Leaf nodes for markup of text fragments
#########################################

# Signature text elements, generally translated to node.inline
# in SigElementFallbackTransform.
# When adding a new one, add it to SIG_ELEMENTS.

class desc_sig_element(nodes.inline):
class desc_sig_element(nodes.inline, _desc_classes_injector):
"""Common parent class of nodes for inline text of a signature."""
classes: List[str] = []

Expand All @@ -217,8 +287,20 @@ def __init__(self, rawsource: str = '', text: str = '',
self['classes'].extend(self.classes)


# to not reinvent the wheel, the classes in the following desc_sig classes
# are based on those used in Pygments

class desc_sig_space(desc_sig_element):
"""Node for a space in a signature."""
classes = ["w"]

def __init__(self, rawsource: str = '', text: str = ' ',
*children: Element, **attributes: Any) -> None:
super().__init__(rawsource, text, *children, **attributes)


class desc_sig_name(desc_sig_element):
"""Node for a name in a signature."""
"""Node for an identifier in a signature."""
classes = ["n"]


Expand All @@ -228,10 +310,44 @@ class desc_sig_operator(desc_sig_element):


class desc_sig_punctuation(desc_sig_element):
"""Node for a punctuation in a signature."""
"""Node for punctuation in a signature."""
classes = ["p"]


class desc_sig_keyword(desc_sig_element):
"""Node for a general keyword in a signature."""
classes = ["k"]


class desc_sig_keyword_type(desc_sig_element):
"""Node for a keyword which is a built-in type in a signature."""
classes = ["kt"]


class desc_sig_literal_number(desc_sig_element):
"""Node for a numeric literal in a signature."""
classes = ["m"]


class desc_sig_literal_string(desc_sig_element):
"""Node for a string literal in a signature."""
classes = ["s"]


class desc_sig_literal_char(desc_sig_element):
"""Node for a character literal in a signature."""
classes = ["sc"]


SIG_ELEMENTS = [desc_sig_space,
desc_sig_name,
desc_sig_operator,
desc_sig_punctuation,
desc_sig_keyword, desc_sig_keyword_type,
desc_sig_literal_number, desc_sig_literal_string, desc_sig_literal_char]


###############################################################
# new admonition-like constructs

class versionmodified(nodes.Admonition, nodes.TextElement):
Expand Down Expand Up @@ -336,6 +452,7 @@ class pending_xref(nodes.Inline, nodes.Element):
These nodes are resolved before writing output, in
BuildEnvironment.resolve_references.
"""
child_text_separator = ''


class pending_xref_condition(nodes.Inline, nodes.TextElement):
Expand Down Expand Up @@ -412,21 +529,25 @@ class manpage(nodes.Inline, nodes.FixedTextElement):

def setup(app: "Sphinx") -> Dict[str, Any]:
app.add_node(toctree)

app.add_node(desc)
app.add_node(desc_signature)
app.add_node(desc_signature_line)
app.add_node(desc_content)
app.add_node(desc_inline)

app.add_node(desc_name)
app.add_node(desc_addname)
app.add_node(desc_type)
app.add_node(desc_returns)
app.add_node(desc_name)
app.add_node(desc_parameterlist)
app.add_node(desc_parameter)
app.add_node(desc_optional)
app.add_node(desc_annotation)
app.add_node(desc_content)
app.add_node(desc_sig_name)
app.add_node(desc_sig_operator)
app.add_node(desc_sig_punctuation)

for n in SIG_ELEMENTS:
app.add_node(n)

app.add_node(versionmodified)
app.add_node(seealso)
app.add_node(productionlist)
Expand Down
1 change: 1 addition & 0 deletions sphinx/directives/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ def run(self) -> List[Node]:
node['noindex'] = noindex = ('noindex' in self.options)
if self.domain:
node['classes'].append(self.domain)
node['classes'].append(node['objtype'])

self.names: List[T] = []
signatures = self.get_signatures()
Expand Down

0 comments on commit 8954770

Please sign in to comment.