Roles.py

Summary

Lines:

21 of 21 covered (100.0%)

Coverage:

100.0%

Directory:

Sphinx

Source

pyTooling/Sphinx/Roles.pycoveredpartialuncoveredexcludednot executable
1# ==================================================================================================================== #
2#             _____           _ _               ____        _     _                                                    #
3#  _ __  _   |_   _|__   ___ | (_)_ __   __ _  / ___| _ __ | |__ (_)_ __ __  __                                        #
4# | '_ \| | | || |/ _ \ / _ \| | | '_ \ / _` | \___ \| '_ \| '_ \| | '_ \\ \/ /                                        #
5# | |_) | |_| || | (_) | (_) | | | | | | (_| |_ ___) | |_) | | | | | | | |>  <                                         #
6# | .__/ \__, ||_|\___/ \___/|_|_|_| |_|\__, (_)____/| .__/|_| |_|_|_| |_/_/\_\                                        #
7# |_|    |___/                          |___/        |_|                                                               #
8# ==================================================================================================================== #
9# Authors:                                                                                                             #
10#   Patrick Lehmann                                                                                                    #
11#                                                                                                                      #
12# License:                                                                                                             #
13# ==================================================================================================================== #
14# Copyright 2026-2026 Patrick Lehmann - Bötzingen, Germany                                                             #
15#                                                                                                                      #
16# Licensed under the Apache License, Version 2.0 (the "License");                                                      #
17# you may not use this file except in compliance with the License.                                                     #
18# You may obtain a copy of the License at                                                                              #
19#                                                                                                                      #
20#   http://www.apache.org/licenses/LICENSE-2.0                                                                         #
21#                                                                                                                      #
22# Unless required by applicable law or agreed to in writing, software                                                  #
23# distributed under the License is distributed on an "AS IS" BASIS,                                                    #
24# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.                                             #
25# See the License for the specific language governing permissions and                                                  #
26# limitations under the License.                                                                                       #
27#                                                                                                                      #
28# SPDX-License-Identifier: Apache-2.0                                                                                  #
29# ==================================================================================================================== #
30#
31"""
32The ReST roles shared by pyTooling's documentation and its sibling projects.
33
34Every project used to declare these in its own :file:`doc/prolog.inc`, as ReST source re-parsed into every document
35of every project - and eighteen hand-copied files had already drifted apart. Registering them from an extension
36makes one declaration serve all of them, and puts the styling in a stylesheet instead of a ``raw:: html`` block.
37
38.. seealso::
39
40   :mod:`pyTooling.Sphinx`
41      |rarr| The extension registering these, and what else it brings.
42"""
43from typing                      import Any, Optional as Nullable
44
45from docutils                    import nodes
46from docutils.parsers.rst.roles  import code_role
47from docutils.parsers.rst.states import Inliner
48from docutils.utils              import unescape
49
50from pyTooling.Decorators        import export
51
52
53__all__ = ["STYLE_ROLES", "PYTHON_CODE_ROLE", "BREAK_ROLES"]
54
55#: The style roles, mapping a role's name to the CSS classes it applies.
56STYLE_ROLES = {
57	"bolditalic": ("bolditalic", ),
58	"underline":  ("underline", ),
59	"strike":     ("strike", ),
60	"xlarge":     ("xlarge", ),
61	"red":        ("colorred", ),
62	"green":      ("colorgreen", ),
63	"blue":       ("colorblue", ),
64	"purple":     ("colorpurple", ),
65	"deletion":   ("colorred", "strike"),
66	"addition":   ("colorgreen", ),
67}
68
69#: Name of the role rendering inline Python code.
70PYTHON_CODE_ROLE = "pycode"
71
72#: The break roles, mapping a role's name to what each output format writes for it.
73#:
74#: A :class:`~docutils.nodes.raw` node carries the format it is meant for and a writer ignores every other one, so
75#: emitting all of them lets each builder pick its own. That is what makes ``|br|`` work outside HTML.
76BREAK_ROLES = {
77	"br": {"html": "<br />",  "latex": r"\\"},
78	"hr": {"html": "<hr />",  "latex": r"\par\noindent\rule{\textwidth}{0.4pt}\par"},
79}
80
81
82@export
83def styleRole(
84	name: str,
85	rawText: str,
86	text: str,
87	lineNumber: int,
88	inliner: Inliner,
89	options: Nullable[dict[str, Any]] = None,
90	content: Nullable[list[str]] = None
91) -> tuple[list[nodes.Node], list[nodes.system_message]]:
92	"""
93	Render inline text with the CSS classes registered for the role's name.
94
95	This is what ``.. role:: red`` with ``:class: colorred`` does in a prolog, without the prolog: the role's name is
96	looked up in :data:`STYLE_ROLES` and the classes it maps to are put on an :class:`~docutils.nodes.inline` node.
97
98	:param name:       Name the role was invoked by, which is what selects the classes.
99	:param rawText:    The role's text including its markup.
100	:param text:       The role's text with the markup removed.
101	:param lineNumber: Line the role was used on.
102	:param inliner:    The inliner that called this role.
103	:param options:    Options given to the role; unused, because the classes come from the role's name.
104	:param content:    Content given to the role; unused.
105	:returns:          Tuple of the produced nodes and the system messages, as a docutils role returns.
106	"""
107	classes = STYLE_ROLES.get(name, (name, ))
108
109	return [nodes.inline(rawText, unescape(text), classes=list(classes))], []
110
111
112@export
113def pythonCodeRole(
114	name: str,
115	rawText: str,
116	text: str,
117	lineNumber: int,
118	inliner: Inliner,
119	options: Nullable[dict[str, Any]] = None,
120	content: Nullable[list[str]] = None
121) -> tuple[list[nodes.Node], list[nodes.system_message]]:
122	r"""
123	Render inline Python code, syntax-highlighted.
124
125	The docutils ``code`` role does the work; this fixes its language to Python and adds the ``highlight`` class, so
126	a page writes ``:pycode:`isinstance(x, int)``` instead of repeating the options at every use.
127
128	:param name:       Name the role was invoked by; unused, because the language is fixed.
129	:param rawText:    The role's text including its markup.
130	:param text:       The role's text with the markup removed.
131	:param lineNumber: Line the role was used on.
132	:param inliner:    The inliner that called this role.
133	:param options:    Options given to the role; replaced by the fixed ones.
134	:param content:    Content given to the role, handed to the ``code`` role unchanged.
135	:returns:          Tuple of the produced nodes and the system messages, as a docutils role returns.
136	"""
137	options = {"language": "python", "classes": ["highlight"]}
138
139	return code_role(name, rawText, text, lineNumber, inliner, options, [] if content is None else content)
140
141
142@export
143def breakRole(
144	name: str,
145	rawText: str,
146	text: str,
147	lineNumber: int,
148	inliner: Inliner,
149	options: Nullable[dict[str, Any]] = None,
150	content: Nullable[list[str]] = None
151) -> tuple[list[nodes.Node], list[nodes.system_message]]:
152	"""
153	Emit a line or a horizontal break in every output format that has one.
154
155	A :class:`~docutils.nodes.raw` node names the format it is for, and a writer skips the ones that aren't its own -
156	so one node per format is emitted and each builder takes what it understands. Written as a ``raw:: html`` block,
157	which is what these were before, a break reached HTML and nothing else.
158
159	:param name:       Name the role was invoked by, which selects what is written.
160	:param rawText:    The role's text including its markup.
161	:param text:       The role's text with the markup removed; unused, a break has no content.
162	:param lineNumber: Line the role was used on.
163	:param inliner:    The inliner that called this role.
164	:param options:    Options given to the role; unused.
165	:param content:    Content given to the role; unused.
166	:returns:          Tuple of the produced nodes and the system messages, as a docutils role returns.
167	"""
168	return [nodes.raw("", written, format=outputFormat) for outputFormat, written in BREAK_ROLES[name].items()], []