Reputation: 3947
What I am trying to achieve is to (manually) insert a html-class to certain elements using the sphinx' python domain
For example I have this string:
Lore Ipsum :py:mod:`dataclasses`
Which results in:
Lore Ipsum <a class="reference external" href="...dataclasses.html#module-dataclasses">
<code class="xref py py-mod docutils literal notranslate">
<span class="pre">dataclass</span>
</code>
</a>
To the a
or code.class
I would like to add an additional class. e.g. "injected"
, to have the following result
<code class="xref py py-mod docutils literal notranslate injected">
Some research I did to find a solution
.. role : injected_mod(?py:mod?)
:class: injected
:injected_mod:`dataclasses`
Problem: I do not know what to put into the brackets, I think I cannot use domains there -> Not a valid role.
Possible but, Problem: I want to keep the functionality from the py
domain.
:py:
domain ❓# conf.py
def setup():
app.add_role_to_domain("py", "injected", PyXRefRole())
What works: Adds a "py-injected"
class that I could work with
Problem?: The lookup feature and linking does not work to py:module
, i.e. no <a class="reference external"
. I haven't been able to determine where in the sphinx module the lookup feature takes place and if it possible to extend the PyXRefRole
to do both.
The question of composing roles is similar and provides a useful answer in the comboroles extension.
This is somewhat nice as I can combine it with a role directive to add the class.
:inject:`:py:mod:\`dataclasses\``
Problem: This adds an extra <span class=injected>
around the resulting block from py:mod
, instead of modifying the existing tags.
I am not sure if nested parsing if somewhat overkill, but so far I have not found a solution to add an additional class. I think using comboroles looks the most promising currently to continue with, but I am not yet sure how to extend it or port its utility to a custom role_function that injects a class instead of nesting an additional tag. I guess I need to access and modify to nodes in a custom function, but this is where I am stuck.
Notes:
Upvotes: 1
Views: 136
Reputation: 929
The role-extension syntax uses just the role name in parentheses, cf. the "role" directive doc. The role-name is added to the role's classes by default. A different class value (or several custom class values) can be specified with the "class" option.
The following works in plain Docutils (with a dummy "py:mod" role).
.. role:: py:mod(literal)
.. role:: injected(py:mod)
.. role:: injected2(py:mod)
:class: inject another-class
This is :injected:`injected text`
and :injected2:`injected, too`.
The XML output shows that the "py:mod" role is extended.
<document source="/tmp/inj.rst">
<paragraph>This is <literal classes="injected">injected text</literal>
and <literal classes="inject another-class">injected, too</literal>.</paragraph>
</document>
However, while Docutils' standard roles and custom roles based on them can be extended in Sphinx, too, this does not work with roles added by Sphinx. :( This is not limited to domains, e.g.::
.. role:: cref(ref)
:class: custom-class
results in the ERROR
/tmp/test/index.rst:20: ERROR: Error in "role" directive:
no content permitted.
IMO, this is a bug (or missing feature) in Sphinx.
Upvotes: 0
Reputation: 3947
For now I found this workaround using the comboroles extension
# conf.py
rst_prolog = """
.. role:: inject-role
:class: inject
"""
from sphinxnotes.comboroles import CompositeRole
class InjectClassRole(CompositeRole):
def run(self):
allnodes, messages = super().run()
inner = allnodes[0].children[0]
inner.attributes["classes"].extend(allnodes[0].attributes["classes"]) # type: ignore[attr-defined]
inner.parent = None
return [inner], messages
def setup(app):
app.add_role("inject", InjectClassRole(["inject-role"], nested_parse=True))
With this I can use
:inject:`:py:mod:\`dataclasses\``
which will parse :py:mod:
and :inject-role:
, but instead of creating a new <span class="inject">
will add the inject
class to the tag created by the inner role. I still wonder if this is a bit overkill.
Sidenote: A hardcoded combo-role can also be created like this:
app.add_role("inject_mod", InjectClassRole(["inject", "py:mod"], nested_parse=False))
# Usage
:inject_mod:`dataclasses`
Instead of relying on an additional extra inject-role
one could adjust the InjectClassRole.__init__
with an additional parameter to be injected in the run
method.
Upvotes: 1