summary refs log tree commit diff
path: root/pkgs/tools/nix/nixos-render-docs
diff options
context:
space:
mode:
authorpennae <github@quasiparticle.net>2023-02-18 20:48:12 +0100
committerpennae <github@quasiparticle.net>2023-02-21 18:26:40 +0100
commit768794d6c11b5e37f954405a3f03d63ef45897f6 (patch)
treecd1b0a40f0b9fa739f19b732a4d58729b5a2fe52 /pkgs/tools/nix/nixos-render-docs
parent163b667352e19411473fdf8603f0883c1b106d58 (diff)
downloadnixpkgs-768794d6c11b5e37f954405a3f03d63ef45897f6.tar
nixpkgs-768794d6c11b5e37f954405a3f03d63ef45897f6.tar.gz
nixpkgs-768794d6c11b5e37f954405a3f03d63ef45897f6.tar.bz2
nixpkgs-768794d6c11b5e37f954405a3f03d63ef45897f6.tar.lz
nixpkgs-768794d6c11b5e37f954405a3f03d63ef45897f6.tar.xz
nixpkgs-768794d6c11b5e37f954405a3f03d63ef45897f6.tar.zst
nixpkgs-768794d6c11b5e37f954405a3f03d63ef45897f6.zip
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.
Diffstat (limited to 'pkgs/tools/nix/nixos-render-docs')
-rw-r--r--pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/manual.py6
-rw-r--r--pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/manual_structure.py43
2 files changed, 45 insertions, 4 deletions
diff --git a/pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/manual.py b/pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/manual.py
index 7e1923f35ec..858ecad9c11 100644
--- a/pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/manual.py
+++ b/pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/manual.py
@@ -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':
diff --git a/pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/manual_structure.py b/pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/manual_structure.py
index 32b6287b34a..93a8ecc3f93 100644
--- a/pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/manual_structure.py
+++ b/pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/manual_structure.py
@@ -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)