mirror of
https://github.com/NixOS/nixpkgs.git
synced 2024-11-23 15:33:13 +00:00
nixos-render-docs: check book structure
text content in the toplevel file of a book will not render properly. the first proper element will be a preface, part, or chapter anyway, and those require includes to produce. parts do not currently allow headings in the part file itself, but that's mainly a renderer limitation. we can add support for headings in part intros when we need them in all other cases includes must be followed by either another include, a heading, or end of file. text content could not be properly linked to from a TOC without a preceding heading.
This commit is contained in:
parent
163b667352
commit
768794d6c1
@ -12,7 +12,7 @@ from markdown_it.token import Token
|
||||
|
||||
from . import md, options
|
||||
from .docbook import DocBookRenderer, Heading
|
||||
from .manual_structure import check_titles, FragmentType, TocEntryType
|
||||
from .manual_structure import check_structure, FragmentType, is_include, TocEntryType
|
||||
from .md import Converter
|
||||
|
||||
class BaseConverter(Converter[md.TR], Generic[md.TR]):
|
||||
@ -30,9 +30,9 @@ class BaseConverter(Converter[md.TR], Generic[md.TR]):
|
||||
|
||||
def _parse(self, src: str) -> list[Token]:
|
||||
tokens = super()._parse(src)
|
||||
check_titles(self._current_type[-1], tokens)
|
||||
check_structure(self._current_type[-1], tokens)
|
||||
for token in tokens:
|
||||
if token.type != "fence" or not token.info.startswith("{=include=} "):
|
||||
if not is_include(token):
|
||||
continue
|
||||
typ = token.info[12:].strip()
|
||||
if typ == 'options':
|
||||
|
@ -8,7 +8,41 @@ FragmentType = Literal['preface', 'part', 'chapter', 'section', 'appendix']
|
||||
# in the TOC all fragments are allowed, plus the all-encompassing book.
|
||||
TocEntryType = Literal['book', 'preface', 'part', 'chapter', 'section', 'appendix']
|
||||
|
||||
def check_titles(kind: TocEntryType, tokens: Sequence[Token]) -> None:
|
||||
def is_include(token: Token) -> bool:
|
||||
return token.type == "fence" and token.info.startswith("{=include=} ")
|
||||
|
||||
# toplevel file must contain only the title headings and includes, anything else
|
||||
# would cause strange rendering.
|
||||
def _check_book_structure(tokens: Sequence[Token]) -> None:
|
||||
for token in tokens[6:]:
|
||||
if not is_include(token):
|
||||
assert token.map
|
||||
raise RuntimeError(f"unexpected content in line {token.map[0] + 1}, "
|
||||
"expected structural include")
|
||||
|
||||
# much like books, parts may not contain headings other than their title heading.
|
||||
# this is a limitation of the current renderers that do not handle this case well
|
||||
# even though it is supported in docbook (and probably supportable anywhere else).
|
||||
def _check_part_structure(tokens: Sequence[Token]) -> None:
|
||||
_check_fragment_structure(tokens)
|
||||
for token in tokens[3:]:
|
||||
if token.type == 'heading_open':
|
||||
assert token.map
|
||||
raise RuntimeError(f"unexpected heading in line {token.map[0] + 1}")
|
||||
|
||||
# two include blocks must either be adjacent or separated by a heading, otherwise
|
||||
# we cannot generate a correct TOC (since there'd be nothing to link to between
|
||||
# the two includes).
|
||||
def _check_fragment_structure(tokens: Sequence[Token]) -> None:
|
||||
for i, token in enumerate(tokens):
|
||||
if is_include(token) \
|
||||
and i + 1 < len(tokens) \
|
||||
and not (is_include(tokens[i + 1]) or tokens[i + 1].type == 'heading_open'):
|
||||
assert token.map
|
||||
raise RuntimeError(f"unexpected content in line {token.map[0] + 1}, "
|
||||
"expected heading or structural include")
|
||||
|
||||
def check_structure(kind: TocEntryType, tokens: Sequence[Token]) -> None:
|
||||
wanted = { 'h1': 'title' }
|
||||
wanted |= { 'h2': 'subtitle' } if kind == 'book' else {}
|
||||
for (i, (tag, role)) in enumerate(wanted.items()):
|
||||
@ -46,3 +80,10 @@ def check_titles(kind: TocEntryType, tokens: Sequence[Token]) -> None:
|
||||
raise RuntimeError(f"heading in line {token.map[0] + 1} skips one or more heading levels, "
|
||||
"which is currently not allowed")
|
||||
last_heading_level = level
|
||||
|
||||
if kind == 'book':
|
||||
_check_book_structure(tokens)
|
||||
elif kind == 'part':
|
||||
_check_part_structure(tokens)
|
||||
else:
|
||||
_check_fragment_structure(tokens)
|
||||
|
Loading…
Reference in New Issue
Block a user