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)
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)
@ -636,7 +637,7 @@ class HTMLConverter(BaseConverter[ManualHTMLRenderer]):
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])
@ -651,7 +652,7 @@ class HTMLConverter(BaseConverter[ManualHTMLRenderer]):
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

View File

@ -1,6 +1,6 @@
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
@ -426,31 +426,37 @@ def _block_attr(md: markdown_it.MarkdownIt) -> None:
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}")
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')
@ -494,7 +500,7 @@ class Converter(ABC, Generic[TR]):
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]: