HTML.py

Summary

Lines:

194 of 198 covered (98.0%)

Branches:

28 of 28 taken (100.0%), 0 lines partially

Coverage:

98.2%

Directory:

Sphinx

Missed lines:

627, 638, 653, 664

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