|
| 1 | +""" |
| 2 | +Custom roles for the Matplotlib documentation. |
| 3 | +
|
| 4 | +.. warning:: |
| 5 | +
|
| 6 | + These roles are considered semi-public. They are only intended to be used in |
| 7 | + the Matplotlib documentation. |
| 8 | +
|
| 9 | +However, it can happen that downstream packages end up pulling these roles into |
| 10 | +their documentation, which will result in documentation build errors. The following |
| 11 | +describes the exact mechanism and how to fix the errors. |
| 12 | +
|
| 13 | +There are two ways, Matplotlib docstrings can end up in downstream documentation. |
| 14 | +You have to subclass a Matplotlib class and either use the ``:inherited-members:`` |
| 15 | +option in your autodoc configuration, or you have to override a method without |
| 16 | +specifying a new docstring; the new method will inherit the original docstring and |
| 17 | +still render in your autodoc. If the docstring contains one of the custom sphinx |
| 18 | +roles, you'll see one of the following error messages: |
| 19 | +
|
| 20 | +.. code-block:: none |
| 21 | +
|
| 22 | + Unknown interpreted text role "mpltype". |
| 23 | + Unknown interpreted text role "rc". |
| 24 | +
|
| 25 | +To fix this, you can add this module as extension to your sphinx :file:`conf.py`:: |
| 26 | +
|
| 27 | + extensions = [ |
| 28 | + 'matplotlib.sphinxext.roles', |
| 29 | + # Other extensions. |
| 30 | + ] |
| 31 | +
|
| 32 | +.. warning:: |
| 33 | +
|
| 34 | + Direct use of these roles in other packages is not officially supported. We |
| 35 | + reserve the right to modify or remove these roles without prior notification. |
| 36 | +""" |
| 37 | + |
| 38 | +from urllib.parse import urlsplit, urlunsplit |
| 39 | + |
| 40 | +from docutils import nodes |
| 41 | + |
| 42 | +import matplotlib |
| 43 | +from matplotlib import rcParamsDefault |
| 44 | + |
| 45 | + |
| 46 | +class _QueryReference(nodes.Inline, nodes.TextElement): |
| 47 | + """ |
| 48 | + Wraps a reference or pending reference to add a query string. |
| 49 | +
|
| 50 | + The query string is generated from the attributes added to this node. |
| 51 | +
|
| 52 | + Also equivalent to a `~docutils.nodes.literal` node. |
| 53 | + """ |
| 54 | + |
| 55 | + def to_query_string(self): |
| 56 | + """Generate query string from node attributes.""" |
| 57 | + return '&'.join(f'{name}={value}' for name, value in self.attlist()) |
| 58 | + |
| 59 | + |
| 60 | +def _visit_query_reference_node(self, node): |
| 61 | + """ |
| 62 | + Resolve *node* into query strings on its ``reference`` children. |
| 63 | +
|
| 64 | + Then act as if this is a `~docutils.nodes.literal`. |
| 65 | + """ |
| 66 | + query = node.to_query_string() |
| 67 | + for refnode in node.findall(nodes.reference): |
| 68 | + uri = urlsplit(refnode['refuri'])._replace(query=query) |
| 69 | + refnode['refuri'] = urlunsplit(uri) |
| 70 | + |
| 71 | + self.visit_literal(node) |
| 72 | + |
| 73 | + |
| 74 | +def _depart_query_reference_node(self, node): |
| 75 | + """ |
| 76 | + Act as if this is a `~docutils.nodes.literal`. |
| 77 | + """ |
| 78 | + self.depart_literal(node) |
| 79 | + |
| 80 | + |
| 81 | +def _rcparam_role(name, rawtext, text, lineno, inliner, options=None, content=None): |
| 82 | + """ |
| 83 | + Sphinx role ``:rc:`` to highlight and link ``rcParams`` entries. |
| 84 | +
|
| 85 | + Usage: Give the desired ``rcParams`` key as parameter. |
| 86 | +
|
| 87 | + :code:`:rc:`figure.dpi`` will render as: :rc:`figure.dpi` |
| 88 | + """ |
| 89 | + # Generate a pending cross-reference so that Sphinx will ensure this link |
| 90 | + # isn't broken at some point in the future. |
| 91 | + title = f'rcParams["{text}"]' |
| 92 | + target = 'matplotlibrc-sample' |
| 93 | + ref_nodes, messages = inliner.interpreted(title, f'{title} <{target}>', |
| 94 | + 'ref', lineno) |
| 95 | + |
| 96 | + qr = _QueryReference(rawtext, highlight=text) |
| 97 | + qr += ref_nodes |
| 98 | + node_list = [qr] |
| 99 | + |
| 100 | + # The default backend would be printed as "agg", but that's not correct (as |
| 101 | + # the default is actually determined by fallback). |
| 102 | + if text in rcParamsDefault and text != "backend": |
| 103 | + node_list.extend([ |
| 104 | + nodes.Text(' (default: '), |
| 105 | + nodes.literal('', repr(rcParamsDefault[text])), |
| 106 | + nodes.Text(')'), |
| 107 | + ]) |
| 108 | + |
| 109 | + return node_list, messages |
| 110 | + |
| 111 | + |
| 112 | +def _mpltype_role(name, rawtext, text, lineno, inliner, options=None, content=None): |
| 113 | + """ |
| 114 | + Sphinx role ``:mpltype:`` for custom matplotlib types. |
| 115 | +
|
| 116 | + In Matplotlib, there are a number of type-like concepts that do not have a |
| 117 | + direct type representation; example: color. This role allows to properly |
| 118 | + highlight them in the docs and link to their definition. |
| 119 | +
|
| 120 | + Currently supported values: |
| 121 | +
|
| 122 | + - :code:`:mpltype:`color`` will render as: :mpltype:`color` |
| 123 | +
|
| 124 | + """ |
| 125 | + mpltype = text |
| 126 | + type_to_link_target = { |
| 127 | + 'color': 'colors_def', |
| 128 | + } |
| 129 | + if mpltype not in type_to_link_target: |
| 130 | + raise ValueError(f"Unknown mpltype: {mpltype!r}") |
| 131 | + |
| 132 | + node_list, messages = inliner.interpreted( |
| 133 | + mpltype, f'{mpltype} <{type_to_link_target[mpltype]}>', 'ref', lineno) |
| 134 | + return node_list, messages |
| 135 | + |
| 136 | + |
| 137 | +def setup(app): |
| 138 | + app.add_role("rc", _rcparam_role) |
| 139 | + app.add_role("mpltype", _mpltype_role) |
| 140 | + app.add_node( |
| 141 | + _QueryReference, |
| 142 | + html=(_visit_query_reference_node, _depart_query_reference_node), |
| 143 | + latex=(_visit_query_reference_node, _depart_query_reference_node), |
| 144 | + text=(_visit_query_reference_node, _depart_query_reference_node), |
| 145 | + ) |
| 146 | + return {"version": matplotlib.__version__, |
| 147 | + "parallel_read_safe": True, "parallel_write_safe": True} |
0 commit comments