nixos-render-docs: genericize block numbering

examples and figures behave identically regarding numbering and titling,
they just don't share a common number space. make the numbering/titling
function generic over block types now so figures can just use it.

pennae e5e738b7 8c2d14a6

Changed files
+26 -19
pkgs
tools
nix
nixos-render-docs
src
nixos_render_docs
+7 -6
pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/manual.py
···
self._redirection_targets.add(into)
return tokens
-
def _number_examples(self, tokens: Sequence[Token], start: int = 1) -> int:
+
def _number_block(self, block: str, prefix: str, tokens: Sequence[Token], start: int = 1) -> int:
+
title_open, title_close = f'{block}_title_open', f'{block}_title_close'
for (i, token) in enumerate(tokens):
-
if token.type == "example_title_open":
+
if token.type == title_open:
title = tokens[i + 1]
assert title.type == 'inline' and title.children
# the prefix is split into two tokens because the xref title_html will want
# only the first of the two, but both must be rendered into the example itself.
title.children = (
[
-
Token('text', '', 0, content=f'Example {start}'),
+
Token('text', '', 0, content=f'{prefix} {start}'),
Token('text', '', 0, content='. ')
] + title.children
)
start += 1
elif token.type.startswith('included_') and token.type != 'included_options':
for sub, _path in token.meta['included']:
-
start = self._number_examples(sub, start)
+
start = self._number_block(block, prefix, sub, start)
return start
# xref | (id, type, heading inlines, file, starts new file)
···
toc_html = f"{n}. {title_html}"
title_html = f"Appendix {n}"
elif typ == 'example':
-
# skip the prepended `Example N. ` from _number_examples
+
# skip the prepended `Example N. ` from numbering
toc_html, title = self._renderer.renderInline(inlines.children[2:]), title_html
# xref title wants only the prepended text, sans the trailing colon and space
title_html = self._renderer.renderInline(inlines.children[0:1])
···
return XrefTarget(id, title_html, toc_html, re.sub('<.*?>', '', title), path, drop_fragment)
def _postprocess(self, infile: Path, outfile: Path, tokens: Sequence[Token]) -> None:
-
self._number_examples(tokens)
+
self._number_block('example', "Example", tokens)
xref_queue = self._collect_ids(tokens, outfile.name, 'book', True)
failed = False
+19 -13
pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/md.py
···
from abc import ABC
from collections.abc import Mapping, MutableMapping, Sequence
-
from typing import Any, cast, Generic, get_args, Iterable, Literal, NoReturn, Optional, TypeVar
+
from typing import Any, Callable, cast, Generic, get_args, Iterable, Literal, NoReturn, Optional, TypeVar
import dataclasses
import re
···
md.core.ruler.push("block_attr", block_attr)
-
def _example_titles(md: markdown_it.MarkdownIt) -> None:
+
def _block_titles(block: str) -> Callable[[markdown_it.MarkdownIt], None]:
+
open, close = f'{block}_open', f'{block}_close'
+
title_open, title_close = f'{block}_title_open', f'{block}_title_close'
+
"""
-
find title headings of examples and stick them into meta for renderers, then
-
remove them from the token stream. also checks whether any example contains a
+
find title headings of blocks and stick them into meta for renderers, then
+
remove them from the token stream. also checks whether any block contains a
non-title heading since those would make toc generation extremely complicated.
"""
-
def example_titles(state: markdown_it.rules_core.StateCore) -> None:
+
def block_titles(state: markdown_it.rules_core.StateCore) -> None:
in_example = [False]
for i, token in enumerate(state.tokens):
-
if token.type == 'example_open':
+
if token.type == open:
if state.tokens[i + 1].type == 'heading_open':
assert state.tokens[i + 3].type == 'heading_close'
-
state.tokens[i + 1].type = 'example_title_open'
-
state.tokens[i + 3].type = 'example_title_close'
+
state.tokens[i + 1].type = title_open
+
state.tokens[i + 3].type = title_close
else:
assert token.map
-
raise RuntimeError(f"found example without title in line {token.map[0] + 1}")
+
raise RuntimeError(f"found {block} without title in line {token.map[0] + 1}")
in_example.append(True)
-
elif token.type == 'example_close':
+
elif token.type == close:
in_example.pop()
elif token.type == 'heading_open' and in_example[-1]:
assert token.map
-
raise RuntimeError(f"unexpected non-title heading in example in line {token.map[0] + 1}")
+
raise RuntimeError(f"unexpected non-title heading in {block} in line {token.map[0] + 1}")
+
+
def do_add(md: markdown_it.MarkdownIt) -> None:
+
md.core.ruler.push(f"{block}_titles", block_titles)
-
md.core.ruler.push("example_titles", example_titles)
+
return do_add
TR = TypeVar('TR', bound='Renderer')
···
self._md.use(_heading_ids)
self._md.use(_compact_list_attr)
self._md.use(_block_attr)
-
self._md.use(_example_titles)
+
self._md.use(_block_titles("example"))
self._md.enable(["smartquotes", "replacements"])
def _parse(self, src: str) -> list[Token]: