HTML.py
Summary
- Lines:
185 of 185 covered (100.0%)
- Branches:
28 of 28 taken (100.0%), 0 lines partially
- Coverage:
100.0%
- Directory:
Source
pyTooling/Sphinx/HTML.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"""
32Visitors writing pyTooling.Sphinx's nodes in HTML.
33
34Each node has a ``visit_*`` and a ``depart_*`` function and a ``translate*`` pair of both, which
35:data:`~pyTooling.Sphinx.NODES` registers.
36"""
37from docutils import nodes
38from sphinx.writers.html5 import HTML5Translator
39
40from pyTooling.Decorators import export
41from pyTooling.Sphinx.Node import Abbreviation, CopyButton, Landscape, TreeDescription, TreeItem, TreeLabel
42from pyTooling.Sphinx.Node import TreeLegend, TreeLegendItem, TreeSeparator
43from pyTooling.Sphinx.Node import COVERAGE_STATES, DOCUMENTATION_STATES, CoverageListing, DocCoverageListing
44from pyTooling.Sphinx.Node import Emoji, LineBreak, ReportIndentation, TaskListCheckbox
45from pyTooling.Sphinx.Node import visitFunc, departFunc
46
47
48__all__ = [
49 "translateLandscape", "translateTreeItem", "translateTreeLabel", "translateTreeSeparator", "translateTreeDescription",
50 "translateTreeLegend", "translateTreeLegendItem", "translateAbbreviation", "translateCoverageListing",
51 "translateDocCoverageListing", "translateCopyButton", "translateEmoji", "translateTaskListCheckbox",
52 "translateLineBreak", "COPY_ICONS"
53]
54
55_SVG = (
56 'aria-hidden="true" focusable="false" width="1em" height="1em" viewBox="0 0 24 24" fill="none" '
57 'stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"'
58)
59"""The attributes of a copy button's icons: hidden from a screen reader, as large as the text, and drawn as lines."""
60
61#: The icons of a copy button, as inline SVG hidden from a screen reader: two sheets of paper, and a check mark, which
62#: the stylesheet shows instead while the button reports a copied text.
63COPY_ICONS = (
64 f'<svg class="copy-icon" {_SVG}><rect x="8" y="8" width="12" height="12" rx="2"/>'
65 '<path d="M16 8V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2"/></svg>'
66 f'<svg class="copied-icon" {_SVG}><path d="M5 12l5 5L20 7"/></svg>'
67)
68
69
70@export
71def visit_TreeItem(translator: HTML5Translator, node: TreeItem) -> None:
72 """
73 Open an entry in HTML: a list item, and a ``<details>`` element if the entry has children.
74
75 The root of a tree with descriptions states the width of the column of the entries' texts, as CSS custom property
76 ``--pyTooling-tree-column``, which its descendants inherit.
77
78 :param translator: The HTML translator writing the page.
79 :param node: The entry.
80 """
81 if node["columnWidth"] is None:
82 translator.body.append(translator.starttag(node, "li", ""))
83 else:
84 translator.body.append(
85 translator.starttag(node, "li", "", style=f"--pyTooling-tree-column: {node['columnWidth']}")
86 )
87
88 if node["expanded"] is not None:
89 translator.body.append("<details open>" if node["expanded"] else "<details>")
90
91
92@export
93def depart_TreeItem(translator: HTML5Translator, node: TreeItem) -> None:
94 """
95 Close an entry in HTML.
96
97 :param translator: The HTML translator writing the page.
98 :param node: The entry.
99 """
100 if node["expanded"] is not None:
101 translator.body.append("</details>")
102 translator.body.append("</li>\n")
103
104
105@export
106def visit_TreeLabel(translator: HTML5Translator, node: TreeLabel) -> None:
107 """
108 Open an entry's text in HTML, behind its icons.
109
110 An entry with children opens a ``<summary>`` and writes both expander icons, the stylesheet showing one of them.
111 Any other entry writes an empty expander instead, so its text is aligned with its siblings'. Icons are hidden from
112 a screen reader.
113
114 In a tree with descriptions, the text ends with a :class:`~pyTooling.Sphinx.Node.TreeDescription`: the entry is a
115 row, whose first column holds the icons and the text, and whose second column the description. The row states the
116 entry's level as CSS custom property ``--pyTooling-tree-depth``, from which the stylesheet computes how wide the
117 first column is at this level.
118
119 :param translator: The HTML translator writing the page.
120 :param node: The entry's text.
121 """
122 if node["expandedIcon"] is not None:
123 translator.body.append("<summary>")
124
125 if isinstance(node.children[-1], TreeDescription):
126 translator.body.append(
127 f'<span class="tree-row" style="--pyTooling-tree-depth: {node["level"]}"><span class="tree-label">'
128 )
129
130 if node["expandedIcon"] is None:
131 translator.body.append('<span class="tree-expander" aria-hidden="true"></span>')
132 else:
133 for state, stateIcon in (("expanded", node["expandedIcon"]), ("collapsed", node["collapsedIcon"])):
134 translator.body.append(
135 f'<span class="tree-expander tree-{state}" aria-hidden="true">{translator.encode(stateIcon)}</span>'
136 )
137
138 if node["icon"] != "":
139 translator.body.append(f'<span class="tree-icon" aria-hidden="true">{translator.encode(node["icon"])}</span>')
140
141 translator.body.append('<span class="tree-text">')
142
143
144@export
145def depart_TreeLabel(translator: HTML5Translator, node: TreeLabel) -> None:
146 """
147 Close an entry's text in HTML - or its row, in a tree with descriptions -, and its ``<summary>`` if the entry has
148 children.
149
150 :param translator: The HTML translator writing the page.
151 :param node: The entry's text.
152 """
153 translator.body.append("</span>")
154 if node["expandedIcon"] is not None:
155 translator.body.append("</summary>")
156
157
158@export
159def visit_TreeSeparator(translator: HTML5Translator, node: TreeSeparator) -> None:
160 """
161 Skip the separator between an entry's text and its description in HTML, which draws the description in a column of
162 its own.
163
164 :param translator: The HTML translator writing the page.
165 :param node: The separator.
166 :raises SkipNode: Always, so neither the separator's text nor its depart function is written.
167 """
168 raise nodes.SkipNode()
169
170
171@export
172def depart_TreeSeparator(translator: HTML5Translator, node: TreeSeparator) -> None:
173 """
174 Close the separator between an entry's text and its description in HTML, which is never called:
175 :func:`visit_TreeSeparator` skips the node.
176
177 :param translator: The HTML translator writing the page.
178 :param node: The separator.
179 """
180
181
182@export
183def visit_TreeDescription(translator: HTML5Translator, node: TreeDescription) -> None:
184 """
185 Open an entry's description in HTML: close the entry's text and the first column, and open the second column.
186
187 An entry without a description has an empty one, so every row of a tree with descriptions has both columns.
188
189 :param translator: The HTML translator writing the page.
190 :param node: The description.
191 """
192 translator.body.append('</span></span><span class="tree-description">')
193
194
195@export
196def depart_TreeDescription(translator: HTML5Translator, node: TreeDescription) -> None:
197 """
198 Close an entry's description in HTML.
199
200 :param translator: The HTML translator writing the page.
201 :param node: The description.
202 """
203 translator.body.append("</span>")
204
205
206@export
207def visit_TreeLegend(translator: HTML5Translator, node: TreeLegend) -> None:
208 """
209 Open a tree's legend in HTML: a paragraph, hidden from a screen reader like the icons it explains.
210
211 :param translator: The HTML translator writing the page.
212 :param node: The legend.
213 """
214 translator.body.append(translator.starttag(node, "p", "", **{"aria-hidden": "true"}))
215
216
217@export
218def depart_TreeLegend(translator: HTML5Translator, node: TreeLegend) -> None:
219 """
220 Close a tree's legend in HTML.
221
222 :param translator: The HTML translator writing the page.
223 :param node: The legend.
224 """
225 translator.body.append("</p>\n")
226
227
228@export
229def visit_TreeLegendItem(translator: HTML5Translator, node: TreeLegendItem) -> None:
230 """
231 Open an item of a tree's legend in HTML: its icon, and the text explaining it.
232
233 :param translator: The HTML translator writing the page.
234 :param node: The legend's item.
235 """
236 translator.body.append(
237 f'<span class="tree-legend-item"><span class="tree-icon">{translator.encode(node["icon"])}</span>'
238 '<span class="tree-text">'
239 )
240
241
242@export
243def depart_TreeLegendItem(translator: HTML5Translator, node: TreeLegendItem) -> None:
244 """
245 Close an item of a tree's legend in HTML.
246
247 :param translator: The HTML translator writing the page.
248 :param node: The legend's item.
249 """
250 translator.body.append("</span></span>")
251
252
253@export
254def visit_Landscape(translator: HTML5Translator, node: Landscape) -> None:
255 """
256 Open a landscape container in HTML, which writes nothing: a web page has no page orientation.
257
258 :param translator: The HTML translator writing the page.
259 :param node: The landscape container.
260 """
261
262
263@export
264def depart_Landscape(translator: HTML5Translator, node: Landscape) -> None:
265 """
266 Close a landscape container in HTML, which writes nothing.
267
268 :param translator: The HTML translator writing the page.
269 :param node: The landscape container.
270 """
271
272
273translateLandscape: tuple[visitFunc, departFunc] = (visit_Landscape, depart_Landscape)
274"""Visit and depart function writing a :class:`~pyTooling.Sphinx.Node.Landscape` in HTML."""
275
276translateTreeItem: tuple[visitFunc, departFunc] = (visit_TreeItem, depart_TreeItem)
277"""Visit and depart function writing a :class:`~pyTooling.Sphinx.Node.TreeItem` in HTML."""
278
279translateTreeLabel: tuple[visitFunc, departFunc] = (visit_TreeLabel, depart_TreeLabel)
280"""Visit and depart function writing a :class:`~pyTooling.Sphinx.Node.TreeLabel` in HTML."""
281
282translateTreeSeparator: tuple[visitFunc, departFunc] = (visit_TreeSeparator, depart_TreeSeparator)
283"""Visit and depart function skipping a :class:`~pyTooling.Sphinx.Node.TreeSeparator` in HTML."""
284
285translateTreeDescription: tuple[visitFunc, departFunc] = (visit_TreeDescription, depart_TreeDescription)
286"""Visit and depart function writing a :class:`~pyTooling.Sphinx.Node.TreeDescription` in HTML."""
287
288translateTreeLegend: tuple[visitFunc, departFunc] = (visit_TreeLegend, depart_TreeLegend)
289"""Visit and depart function writing a :class:`~pyTooling.Sphinx.Node.TreeLegend` in HTML."""
290
291translateTreeLegendItem: tuple[visitFunc, departFunc] = (visit_TreeLegendItem, depart_TreeLegendItem)
292"""Visit and depart function writing a :class:`~pyTooling.Sphinx.Node.TreeLegendItem` in HTML."""
293
294
295@export
296def visit_Abbreviation(translator: HTML5Translator, node: Abbreviation) -> None:
297 """
298 Open an abbreviation in HTML: an ``<abbr>`` element.
299
300 :param translator: The HTML translator writing the page.
301 :param node: The abbreviation.
302 """
303 translator.body.append(translator.starttag(node, "abbr", "", CLASS="pytooling-abbreviation"))
304
305
306@export
307def depart_Abbreviation(translator: HTML5Translator, node: Abbreviation) -> None:
308 """
309 Close an abbreviation in HTML, behind the box the stylesheet shows on hover.
310
311 The box shows the long form and, if the node carries one, the summary of the abbreviation's description. The short
312 form is shown in front of the long form only if a title replaced it in the text.
313
314 :param translator: The HTML translator writing the page.
315 :param node: The abbreviation.
316 """
317 long = translator.encode(node["long"])
318 short = "" if node.astext() == node["short"] else f'<strong>{translator.encode(node["short"])}</strong> '
319 summary = node.get("summary", "")
320 summary = f'<span class="pytooling-abbreviation-summary">{translator.encode(summary)}</span>' if summary != "" else ""
321 translator.body.append(
322 f'<span class="pytooling-abbreviation-box" role="tooltip">{short}{long}{summary}</span></abbr>'
323 )
324
325
326translateAbbreviation: tuple[visitFunc, departFunc] = (visit_Abbreviation, depart_Abbreviation)
327"""Visit and depart function writing an :class:`~pyTooling.Sphinx.Node.Abbreviation` in HTML."""
328
329
330@export
331def highlightTokens(translator: HTML5Translator, tokens: list[tuple[str, str]]) -> str:
332 """
333 Write a listing line's tokens in HTML, each in a ``<span>`` of its Pygments class, so Sphinx' Pygments stylesheet
334 colors it.
335
336 :param translator: The HTML translator writing the page.
337 :param tokens: The line's tokens as ``(CSS class, text)`` pairs; a token without a class is written as text.
338 :returns: The line's HTML.
339 """
340 return "".join(
341 translator.encode(text) if cssClass == "" else f'<span class="{cssClass}">{translator.encode(text)}</span>'
342 for cssClass, text in tokens
343 )
344
345
346@export
347def writeListing(translator: HTML5Translator, node: nodes.Element, lines: list[str], states: dict[str, str]) -> None:
348 """
349 Write a listing in HTML: a header with the file's path and a legend of the states, then the lines.
350
351 The lines are a ``<pre>`` inside the ``<div>`` elements of Sphinx' highlighting, so Sphinx' Pygments stylesheet colors
352 the tokens.
353
354 :param translator: The HTML translator writing the page.
355 :param node: The listing, whose attribute ``path`` holds the file's path.
356 :param lines: The lines, each written as HTML.
357 :param states: The states the legend explains, by CSS class suffix: the label of each.
358 """
359 classes = "highlight-default notranslate report-coverage-listing"
360 legend = "".join(
361 f'<span class="report-coverage-legend-{state or "none"}">{translator.encode(label)}</span>'
362 for state, label in states.items()
363 )
364 translator.body.append(translator.starttag(node, "div", "", CLASS=classes))
365 translator.body.append(
366 f'<div class="report-coverage-header"><span class="report-coverage-path">{translator.encode(node["path"])}</span>'
367 f'<span class="report-coverage-legend">{legend}</span></div>'
368 )
369 translator.body.append('<div class="highlight"><pre>')
370 translator.body.extend(lines)
371 translator.body.append("</pre></div></div>\n")
372
373
374@export
375def visit_CoverageListing(translator: HTML5Translator, node: CoverageListing) -> None:
376 """
377 Write a source file's code coverage in HTML, completely.
378
379 A header shows the file's path and a legend of the coverage states (see :func:`writeListing`). Each line is a
380 ``<span>`` with class ``report-line`` and ``report-line-<state>``, holding its number, its hits - if the report has
381 any - and its tokens. A line, which ran without taking all its branches, says on hover how many were taken. The
382 node's text - the plain source - isn't written again.
383
384 :param translator: The HTML translator writing the page.
385 :param node: The listing.
386 :raises SkipNode: Always, so the node's text isn't written again.
387 """
388 withHits = any(hits is not None for _, _, hits, _, _, _ in node["lines"])
389
390 lines: list[str] = []
391 for number, status, hits, branches, coveredBranches, tokens in node["lines"]:
392 classes = "report-line" if status == "" else f"report-line report-line-{status}"
393 attributes = f' id="L{number}"' if node["anchors"] else ""
394 if status == "partial":
395 attributes += f' title="{coveredBranches} of {branches} branches taken"'
396
397 hitsText = "" if hits is None else f"{hits}"
398 gutter = f'<span class="linenos">{number}</span>'
399 if withHits:
400 gutter += f'<span class="linenos report-hits">{hitsText}</span>'
401
402 lines.append(f'<span class="{classes}"{attributes}>{gutter}{highlightTokens(translator, tokens)}\n</span>')
403
404 writeListing(translator, node, lines, COVERAGE_STATES)
405
406 raise nodes.SkipNode
407
408
409@export
410def depart_CoverageListing(translator: HTML5Translator, node: CoverageListing) -> None:
411 """
412 Close a source file's code coverage in HTML, which writes nothing: :func:`visit_CoverageListing` wrote it all.
413
414 :param translator: The HTML translator writing the page.
415 :param node: The listing.
416 """
417
418
419translateCoverageListing: tuple[visitFunc, departFunc] = (visit_CoverageListing, depart_CoverageListing)
420"""Visit and depart function writing a :class:`~pyTooling.Sphinx.Node.CoverageListing` in HTML."""
421
422
423@export
424def visit_DocCoverageListing(translator: HTML5Translator, node: DocCoverageListing) -> None:
425 """
426 Write a Python module's documentation coverage in HTML, completely.
427
428 A header shows the module's path and a legend of the documentation states (see :func:`writeListing`). Each line is
429 a ``<span>`` with class ``report-line`` and ``report-line-<state>``, holding its number and its tokens; an elided
430 body's ``...`` has no number and the class ``report-line-elided``. The node's text - the condensed source - isn't
431 written again.
432
433 :param translator: The HTML translator writing the page.
434 :param node: The listing.
435 :raises SkipNode: Always, so the node's text isn't written again.
436 """
437 lines: list[str] = []
438 for number, status, tokens in node["lines"]:
439 classes = "report-line" if status == "" else f"report-line report-line-{status}"
440 if number is None:
441 classes += " report-line-elided"
442
443 gutter = f'<span class="linenos">{"" if number is None else number}</span>'
444 lines.append(f'<span class="{classes}">{gutter}{highlightTokens(translator, tokens)}\n</span>')
445
446 writeListing(translator, node, lines, DOCUMENTATION_STATES)
447
448 raise nodes.SkipNode
449
450
451@export
452def visit_CopyButton(translator: HTML5Translator, node: CopyButton) -> None:
453 """
454 Write a copy button in HTML, completely: a ``<button>`` with the text to copy as attribute ``data-copy``.
455
456 The button's label is its accessible name and its tooltip; its icons are :data:`COPY_ICONS`. The script
457 :file:`pyTooling.js` copies the text on a click.
458
459 :param translator: The HTML translator writing the page.
460 :param node: The copy button.
461 :raises SkipNode: Always, as the button is written completely.
462 """
463 translator.body.append(translator.starttag(
464 node, "button", "", type="button", CLASS="pytooling-copy", title=node["label"],
465 **{"aria-label": node["label"], "data-copy": node["copy"]}
466 ))
467 translator.body.append(f"{COPY_ICONS}</button>")
468
469 raise nodes.SkipNode
470
471
472@export
473def depart_DocCoverageListing(translator: HTML5Translator, node: DocCoverageListing) -> None:
474 """
475 Close a Python module's documentation coverage in HTML, which writes nothing: :func:`visit_DocCoverageListing` wrote
476 it all.
477
478 :param translator: The HTML translator writing the page.
479 :param node: The listing.
480 """
481
482
483translateDocCoverageListing: tuple[visitFunc, departFunc] = (visit_DocCoverageListing, depart_DocCoverageListing)
484"""Visit and depart function writing a :class:`~pyTooling.Sphinx.Node.DocCoverageListing` in HTML."""
485
486
487@export
488def depart_CopyButton(translator: HTML5Translator, node: CopyButton) -> None:
489 """
490 Close a copy button in HTML, which writes nothing: :func:`visit_CopyButton` wrote it all.
491
492 :param translator: The HTML translator writing the page.
493 :param node: The copy button.
494 """
495
496
497translateCopyButton: tuple[visitFunc, departFunc] = (visit_CopyButton, depart_CopyButton)
498"""Visit and depart function writing a :class:`~pyTooling.Sphinx.Node.CopyButton` in HTML."""
499
500
501@export
502def visit_Emoji(translator: HTML5Translator, node: Emoji) -> None:
503 """
504 Open an emoji in HTML: a ``<span>`` with the emoji's shortcode as tooltip.
505
506 :param translator: The HTML translator writing the page.
507 :param node: The emoji.
508 """
509 translator.body.append(translator.starttag(node, "span", "", CLASS="github-emoji", title=f":{node['shortcode']}:"))
510
511
512@export
513def depart_Emoji(translator: HTML5Translator, node: Emoji) -> None:
514 """
515 Close an emoji in HTML.
516
517 :param translator: The HTML translator writing the page.
518 :param node: The emoji.
519 """
520 translator.body.append("</span>")
521
522
523translateEmoji: tuple[visitFunc, departFunc] = (visit_Emoji, depart_Emoji)
524"""Visit and depart function writing an :class:`~pyTooling.Sphinx.Node.Emoji` in HTML."""
525
526
527@export
528def visit_TaskListCheckbox(translator: HTML5Translator, node: TaskListCheckbox) -> None:
529 """
530 Write a task list item's checkbox in HTML, completely: a disabled ``<input>`` of type ``checkbox``, checked if the
531 task is done.
532
533 :param translator: The HTML translator writing the page.
534 :param node: The checkbox.
535 :raises SkipNode: Always, so the node's text isn't written.
536 """
537 checked = ' checked=""' if node["checked"] else ""
538 label = "Completed task" if node["checked"] else "Incomplete task"
539 translator.body.append(
540 f'<input class="github-task-list-checkbox" type="checkbox" disabled=""{checked} aria-label="{label}" />'
541 )
542
543 raise nodes.SkipNode
544
545
546@export
547def depart_TaskListCheckbox(translator: HTML5Translator, node: TaskListCheckbox) -> None:
548 """
549 Close a task list item's checkbox in HTML, which writes nothing: :func:`visit_TaskListCheckbox` wrote it all.
550
551 :param translator: The HTML translator writing the page.
552 :param node: The checkbox.
553 """
554
555
556translateTaskListCheckbox: tuple[visitFunc, departFunc] = (visit_TaskListCheckbox, depart_TaskListCheckbox)
557"""Visit and depart function writing a :class:`~pyTooling.Sphinx.Node.TaskListCheckbox` in HTML."""
558
559
560@export
561def visit_LineBreak(translator: HTML5Translator, node: LineBreak) -> None:
562 """
563 Write a line break in HTML, completely: ``<br />``.
564
565 :param translator: The HTML translator writing the page.
566 :param node: The line break.
567 :raises SkipNode: Always, so the node's text isn't written.
568 """
569 translator.body.append("<br />\n")
570
571 raise nodes.SkipNode
572
573
574@export
575def depart_LineBreak(translator: HTML5Translator, node: LineBreak) -> None:
576 """
577 Close a line break in HTML, which writes nothing: :func:`visit_LineBreak` wrote it all.
578
579 :param translator: The HTML translator writing the page.
580 :param node: The line break.
581 """
582
583
584translateLineBreak: tuple[visitFunc, departFunc] = (visit_LineBreak, depart_LineBreak)
585"""Visit and depart function writing a :class:`~pyTooling.Sphinx.Node.LineBreak` in HTML."""
586
587
588@export
589def visit_ReportIndentation(translator: HTML5Translator, node: ReportIndentation) -> None:
590 """
591 Open an indented name of a report table in HTML: a ``<span>`` setting the CSS custom property
592 ``--pyTooling-report-level`` to the number of indentation steps.
593
594 :param translator: The HTML translator writing the page.
595 :param node: The indented name.
596 """
597 translator.body.append(translator.starttag(
598 node, "span", "", CLASS="report-indentation", style=f"--pyTooling-report-level: {node['level']}"
599 ))
600
601
602@export
603def depart_ReportIndentation(translator: HTML5Translator, node: ReportIndentation) -> None:
604 """
605 Close an indented name of a report table in HTML.
606
607 :param translator: The HTML translator writing the page.
608 :param node: The indented name.
609 """
610 translator.body.append("</span>")
611
612
613translateReportIndentation: tuple[visitFunc, departFunc] = (visit_ReportIndentation, depart_ReportIndentation)
614"""Visit and depart function writing a :class:`~pyTooling.Sphinx.Node.ReportIndentation` in HTML."""