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.
This commit is contained in:
pennae
2023-06-21 18:46:02 +02:00
parent 8c2d14a6b8
commit e5e738b72a
2 changed files with 26 additions and 19 deletions

View File

@@ -569,23 +569,24 @@ class HTMLConverter(BaseConverter[ManualHTMLRenderer]):
self._redirection_targets.add(into) self._redirection_targets.add(into)
return tokens 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): for (i, token) in enumerate(tokens):
if token.type == "example_title_open": if token.type == title_open:
title = tokens[i + 1] title = tokens[i + 1]
assert title.type == 'inline' and title.children assert title.type == 'inline' and title.children
# the prefix is split into two tokens because the xref title_html will want # 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. # only the first of the two, but both must be rendered into the example itself.
title.children = ( title.children = (
[ [
Token('text', '', 0, content=f'Example {start}'), Token('text', '', 0, content=f'{prefix} {start}'),
Token('text', '', 0, content='. ') Token('text', '', 0, content='. ')
] + title.children ] + title.children
) )
start += 1 start += 1
elif token.type.startswith('included_') and token.type != 'included_options': elif token.type.startswith('included_') and token.type != 'included_options':
for sub, _path in token.meta['included']: for sub, _path in token.meta['included']:
start = self._number_examples(sub, start) start = self._number_block(block, prefix, sub, start)
return start return start
# xref | (id, type, heading inlines, file, starts new file) # xref | (id, type, heading inlines, file, starts new file)
@@ -636,7 +637,7 @@ class HTMLConverter(BaseConverter[ManualHTMLRenderer]):
toc_html = f"{n}. {title_html}" toc_html = f"{n}. {title_html}"
title_html = f"Appendix {n}" title_html = f"Appendix {n}"
elif typ == 'example': 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 toc_html, title = self._renderer.renderInline(inlines.children[2:]), title_html
# xref title wants only the prepended text, sans the trailing colon and space # xref title wants only the prepended text, sans the trailing colon and space
title_html = self._renderer.renderInline(inlines.children[0:1]) title_html = self._renderer.renderInline(inlines.children[0:1])
@@ -651,7 +652,7 @@ class HTMLConverter(BaseConverter[ManualHTMLRenderer]):
return XrefTarget(id, title_html, toc_html, re.sub('<.*?>', '', title), path, drop_fragment) return XrefTarget(id, title_html, toc_html, re.sub('<.*?>', '', title), path, drop_fragment)
def _postprocess(self, infile: Path, outfile: Path, tokens: Sequence[Token]) -> None: 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) xref_queue = self._collect_ids(tokens, outfile.name, 'book', True)
failed = False failed = False

View File

@@ -1,6 +1,6 @@
from abc import ABC from abc import ABC
from collections.abc import Mapping, MutableMapping, Sequence 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 dataclasses
import re import re
@@ -426,31 +426,37 @@ def _block_attr(md: markdown_it.MarkdownIt) -> None:
md.core.ruler.push("block_attr", block_attr) 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 find title headings of blocks and stick them into meta for renderers, then
remove them from the token stream. also checks whether any example contains a remove them from the token stream. also checks whether any block contains a
non-title heading since those would make toc generation extremely complicated. 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] in_example = [False]
for i, token in enumerate(state.tokens): for i, token in enumerate(state.tokens):
if token.type == 'example_open': if token.type == open:
if state.tokens[i + 1].type == 'heading_open': if state.tokens[i + 1].type == 'heading_open':
assert state.tokens[i + 3].type == 'heading_close' assert state.tokens[i + 3].type == 'heading_close'
state.tokens[i + 1].type = 'example_title_open' state.tokens[i + 1].type = title_open
state.tokens[i + 3].type = 'example_title_close' state.tokens[i + 3].type = title_close
else: else:
assert token.map 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) in_example.append(True)
elif token.type == 'example_close': elif token.type == close:
in_example.pop() in_example.pop()
elif token.type == 'heading_open' and in_example[-1]: elif token.type == 'heading_open' and in_example[-1]:
assert token.map 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}")
md.core.ruler.push("example_titles", example_titles) def do_add(md: markdown_it.MarkdownIt) -> None:
md.core.ruler.push(f"{block}_titles", block_titles)
return do_add
TR = TypeVar('TR', bound='Renderer') TR = TypeVar('TR', bound='Renderer')
@@ -494,7 +500,7 @@ class Converter(ABC, Generic[TR]):
self._md.use(_heading_ids) self._md.use(_heading_ids)
self._md.use(_compact_list_attr) self._md.use(_compact_list_attr)
self._md.use(_block_attr) self._md.use(_block_attr)
self._md.use(_example_titles) self._md.use(_block_titles("example"))
self._md.enable(["smartquotes", "replacements"]) self._md.enable(["smartquotes", "replacements"])
def _parse(self, src: str) -> list[Token]: def _parse(self, src: str) -> list[Token]: