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
“Inherit” role 💥
.. 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.
Register a new role ❌
Possible but, Problem: I want to keep the functionality from the py
domain.
Add role to :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.
Nested Parsing/Roles 😑(nearly)
This question 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 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:
- I know this is somewhat easy using MyST parsing, but currently I cannot parse this text globally via MyST.
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.