diff options
Diffstat (limited to 'pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/manual.py')
-rw-r--r-- | pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/manual.py | 153 |
1 files changed, 77 insertions, 76 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 780a5f38c32ac..2e83ac90b5b6f 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 @@ -4,16 +4,90 @@ import json from abc import abstractmethod from collections.abc import Mapping, Sequence from pathlib import Path -from typing import Any, cast, NamedTuple, Optional, Union +from typing import Any, cast, Generic, NamedTuple, Optional, Union from xml.sax.saxutils import escape, quoteattr import markdown_it from markdown_it.token import Token -from . import options +from . import md, options from .docbook import DocBookRenderer, Heading from .md import Converter +class BaseConverter(Converter[md.TR], Generic[md.TR]): + _base_paths: list[Path] + + def convert(self, file: Path) -> str: + self._base_paths = [ file ] + try: + with open(file, 'r') as f: + return self._render(f.read()) + except Exception as e: + raise RuntimeError(f"failed to render manual {file}") from e + + def _parse(self, src: str) -> list[Token]: + tokens = super()._parse(src) + for token in tokens: + if token.type != "fence" or not token.info.startswith("{=include=} "): + continue + typ = token.info[12:].strip() + if typ == 'options': + token.type = 'included_options' + self._parse_options(token) + elif typ in [ 'sections', 'chapters', 'preface', 'parts', 'appendix' ]: + token.type = 'included_' + typ + self._parse_included_blocks(token) + else: + raise RuntimeError(f"unsupported structural include type '{typ}'") + return tokens + + def _parse_included_blocks(self, token: Token) -> None: + assert token.map + included = token.meta['included'] = [] + for (lnum, line) in enumerate(token.content.splitlines(), token.map[0] + 2): + line = line.strip() + path = self._base_paths[-1].parent / line + if path in self._base_paths: + raise RuntimeError(f"circular include found in line {lnum}") + try: + self._base_paths.append(path) + with open(path, 'r') as f: + tokens = self._parse(f.read()) + included.append((tokens, path)) + self._base_paths.pop() + except Exception as e: + raise RuntimeError(f"processing included file {path} from line {lnum}") from e + + def _parse_options(self, token: Token) -> None: + assert token.map + + items = {} + for (lnum, line) in enumerate(token.content.splitlines(), token.map[0] + 2): + if len(args := line.split(":", 1)) != 2: + raise RuntimeError(f"options directive with no argument in line {lnum}") + (k, v) = (args[0].strip(), args[1].strip()) + if k in items: + raise RuntimeError(f"duplicate options directive {k} in line {lnum}") + items[k] = v + try: + id_prefix = items.pop('id-prefix') + varlist_id = items.pop('list-id') + source = items.pop('source') + except KeyError as e: + raise RuntimeError(f"options directive {e} missing in block at line {token.map[0] + 1}") + if items.keys(): + raise RuntimeError( + f"unsupported options directives in block at line {token.map[0] + 1}", + " ".join(items.keys())) + + try: + with open(self._base_paths[-1].parent / source, 'r') as f: + token.meta['id-prefix'] = id_prefix + token.meta['list-id'] = varlist_id + token.meta['source'] = json.load(f) + except Exception as e: + raise RuntimeError(f"processing options block in line {token.map[0] + 1}") from e + class ManualDocBookRenderer(DocBookRenderer): _toplevel_tag: str _revision: str @@ -113,84 +187,11 @@ class ManualDocBookRenderer(DocBookRenderer): info = f" language={quoteattr(token.info)}" if token.info != "" else "" return f"<programlisting{info}>\n{escape(token.content)}</programlisting>" -class DocBookConverter(Converter[ManualDocBookRenderer]): - _base_paths: list[Path] - +class DocBookConverter(BaseConverter[ManualDocBookRenderer]): def __init__(self, manpage_urls: Mapping[str, str], revision: str): super().__init__() self._renderer = ManualDocBookRenderer('book', revision, manpage_urls) - def convert(self, file: Path) -> str: - self._base_paths = [ file ] - try: - with open(file, 'r') as f: - return self._render(f.read()) - except Exception as e: - raise RuntimeError(f"failed to render manual {file}") from e - - def _parse(self, src: str) -> list[Token]: - tokens = super()._parse(src) - for token in tokens: - if token.type != "fence" or not token.info.startswith("{=include=} "): - continue - typ = token.info[12:].strip() - if typ == 'options': - token.type = 'included_options' - self._parse_options(token) - elif typ in [ 'sections', 'chapters', 'preface', 'parts', 'appendix' ]: - token.type = 'included_' + typ - self._parse_included_blocks(token) - else: - raise RuntimeError(f"unsupported structural include type '{typ}'") - return tokens - - def _parse_included_blocks(self, token: Token) -> None: - assert token.map - included = token.meta['included'] = [] - for (lnum, line) in enumerate(token.content.splitlines(), token.map[0] + 2): - line = line.strip() - path = self._base_paths[-1].parent / line - if path in self._base_paths: - raise RuntimeError(f"circular include found in line {lnum}") - try: - self._base_paths.append(path) - with open(path, 'r') as f: - tokens = self._parse(f.read()) - included.append((tokens, path)) - self._base_paths.pop() - except Exception as e: - raise RuntimeError(f"processing included file {path} from line {lnum}") from e - - def _parse_options(self, token: Token) -> None: - assert token.map - - items = {} - for (lnum, line) in enumerate(token.content.splitlines(), token.map[0] + 2): - if len(args := line.split(":", 1)) != 2: - raise RuntimeError(f"options directive with no argument in line {lnum}") - (k, v) = (args[0].strip(), args[1].strip()) - if k in items: - raise RuntimeError(f"duplicate options directive {k} in line {lnum}") - items[k] = v - try: - id_prefix = items.pop('id-prefix') - varlist_id = items.pop('list-id') - source = items.pop('source') - except KeyError as e: - raise RuntimeError(f"options directive {e} missing in block at line {token.map[0] + 1}") - if items.keys(): - raise RuntimeError( - f"unsupported options directives in block at line {token.map[0] + 1}", - " ".join(items.keys())) - - try: - with open(self._base_paths[-1].parent / source, 'r') as f: - token.meta['id-prefix'] = id_prefix - token.meta['list-id'] = varlist_id - token.meta['source'] = json.load(f) - except Exception as e: - raise RuntimeError(f"processing options block in line {token.map[0] + 1}") from e - def _build_cli_db(p: argparse.ArgumentParser) -> None: |