|
| 1 | +#!/usr/bin/env python3 |
| 2 | + |
| 3 | +"""Modify the bibliography entries created by sphinxcontrib.bibtex to be sphinx-design cards.""" |
| 4 | + |
| 5 | +# The default `:::{bibliography}` blocks are not very nicely formatted. |
| 6 | +# In the docutils tree, they look like this: |
| 7 | +# |
| 8 | +# <citation backrefs="id2" docname="user-guide/user-manual/travel-time-matrices" ids="fink-r5py-2022"> |
| 9 | +# <label support_smartquotes="False"> |
| 10 | +# Fink et al., 2022 |
| 11 | +# </label> |
| 12 | +# <paragraph> |
| 13 | +# Fink, C., Klumpenhouwer, W., Saraiva, M., Pereira, R., & Tenkanen, H. |
| 14 | +# (2022 , September). <emphasis>r5py: Rapid Realistic Routing with R5 in |
| 15 | +# Python</emphasis>. |
| 16 | +# <reference refuri="https://doi.org/10.5281/ZENODO.7060437"> |
| 17 | +# DOI:10.5281/ZENODO.7060437 |
| 18 | +# </reference> |
| 19 | +# </paragraph> |
| 20 | +# </citation> |
| 21 | +# |
| 22 | +# After experimenting with `sphinx-design`’s cards in the data-requirements.md |
| 23 | +# and also in early versions of the citations.md documents, I started to like |
| 24 | +# their look also for the references: They are prominent, cleanly formatted, and |
| 25 | +# can be clicked as a whole. |
| 26 | +# |
| 27 | +# In the docutil tree, they would look like this: |
| 28 | +# |
| 29 | +# <container classes="sd-card sd-sphinx-override sd-mb-3 sd-shadow-sm sd-card-hover" design_component="card" is_div="True"> |
| 30 | +# <container classes="sd-card-body" design_component="card-body" is_div="True"> |
| 31 | +# <paragraph classes="sd-card-text"> |
| 32 | +# Fink, C., Klumpenhouwer, W., Saraiva, M., Pereira, R., & Tenkanen, |
| 33 | +# H., 2022: <emphasis>r5py: Rapid Realistic Routing with R⁵ in Python</emphasis>. |
| 34 | +# <reference refuri="https://doi.org/10.5281/zenodo.7060437"> |
| 35 | +# DOI:10.5281/zenodo.7060437 |
| 36 | +# </reference> |
| 37 | +# </paragraph> |
| 38 | +# </container> |
| 39 | +# <PassthroughTextElement> |
| 40 | +# <reference classes="['sd-stretched-link']" refuri="https://doi.org/10.5281/zenodo.7060437"/> |
| 41 | +# </PassthroughTextElement> |
| 42 | +# </container> |
| 43 | +# |
| 44 | +# This sphinx extension triggers at a very late stage of running sphinx, |
| 45 | +# searches for all <citations>, and converts them from the above subtree |
| 46 | +# to the below format |
| 47 | +# It uses the sphinx.transforms.post_transform hook, see |
| 48 | +# https://www.sphinx-doc.org/en/master/extdev/appapi.html#sphinx-core-events |
| 49 | + |
| 50 | + |
| 51 | +import docutils.nodes |
| 52 | +import sphinx.transforms.post_transforms |
| 53 | +import sphinx_design.shared |
| 54 | + |
| 55 | + |
| 56 | +__version__ = "0.0.1" |
| 57 | + |
| 58 | + |
| 59 | +class CitationsToSphinxDesignCardsTransformer( |
| 60 | + sphinx.transforms.post_transforms.SphinxPostTransform |
| 61 | +): |
| 62 | + default_priority = ( |
| 63 | + 198 # before anything from sphinx_design, but after sphinxcontrib.bibtex |
| 64 | + ) |
| 65 | + |
| 66 | + def apply(self, **kwargs): |
| 67 | + for node in self.document.findall(docutils.nodes.citation): |
| 68 | + self.handle(node) |
| 69 | + |
| 70 | + def handle(self, node): |
| 71 | + new_node = self._create_empty_sdcard_container() |
| 72 | + for attribute in ["backrefs", "docname", "ids"]: |
| 73 | + new_node[attribute] = node[attribute] |
| 74 | + |
| 75 | + # paragraph (first child of first child), see _create_empty...() |
| 76 | + new_paragraph = new_node[0][0] |
| 77 | + old_paragraph = node[node.first_child_matching_class(docutils.nodes.paragraph)] |
| 78 | + new_paragraph.children = old_paragraph.children |
| 79 | + |
| 80 | + # convert <tt> (docutils.nodes.literal) into more nicely formatted titles |
| 81 | + # (see also format_title() in ../_helpers/citation_style.py) |
| 82 | + for tt in new_paragraph.findall(docutils.nodes.literal): |
| 83 | + span = docutils.nodes.inline(classes=["article-title"]) |
| 84 | + span += tt.children |
| 85 | + new_paragraph.replace(tt, span) |
| 86 | + |
| 87 | + if r := new_paragraph.first_child_matching_class(docutils.nodes.reference): |
| 88 | + old_reference = new_paragraph[r] |
| 89 | + new_reference = sphinx_design.shared.PassthroughTextElement() |
| 90 | + new_reference += docutils.nodes.reference( |
| 91 | + classes=["sd-stretched-link"], |
| 92 | + refuri=old_reference["refuri"], |
| 93 | + ) |
| 94 | + new_node += new_reference |
| 95 | + |
| 96 | + # node.replace_self(new_node) # <- does not work - why? |
| 97 | + node.parent.replace(node, new_node) |
| 98 | + |
| 99 | + @staticmethod |
| 100 | + def _create_empty_sdcard_container(): |
| 101 | + container = docutils.nodes.container( |
| 102 | + classes=[ |
| 103 | + "sd-card", |
| 104 | + "sd-sphinx-override", |
| 105 | + "sd-mb-3", |
| 106 | + "sd-shadow-sm", |
| 107 | + "sd-card-hover", |
| 108 | + ], |
| 109 | + design_component="card", |
| 110 | + is_div="True", |
| 111 | + ) |
| 112 | + container += docutils.nodes.container( |
| 113 | + classes=["sd-card-body"], |
| 114 | + design_component="card-body", |
| 115 | + is_div="True", |
| 116 | + ) |
| 117 | + container[0] += docutils.nodes.paragraph( |
| 118 | + classes=["sd-card-text"], |
| 119 | + ) |
| 120 | + return container |
| 121 | + |
| 122 | + |
| 123 | +def setup(app): |
| 124 | + """ |
| 125 | + Register extension with sphinx. |
| 126 | +
|
| 127 | + This is a callback function called by sphinx when it loads the extension. |
| 128 | + """ |
| 129 | + app.add_post_transform(CitationsToSphinxDesignCardsTransformer) |
| 130 | + |
| 131 | + return { |
| 132 | + "parallel_read_safe": True, |
| 133 | + "parallel_write_safe": False, # not sure, but let’s err on the safe side |
| 134 | + } |
0 commit comments