From 82d5698e228745db173adfa2868c35258ca171f7 Mon Sep 17 00:00:00 2001 From: pennae Date: Wed, 25 Jan 2023 01:56:47 +0100 Subject: [PATCH] nixos-render-docs: add headings and ordered lists headings are not supported in options docs (since it's unclear what that would be in the final manual, and the docbook stylesheets already have trouble rendering all docbook constructs correctly). ordered lists should be supported, but obviously nothing uses them yet. --- .../src/nixos_render_docs/docbook.py | 46 ++++++++++++++++++- .../src/nixos_render_docs/md.py | 16 +++++++ .../src/nixos_render_docs/options.py | 13 +++++- .../src/tests/test_options.py | 14 ++++++ 4 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 pkgs/tools/nix/nixos-render-docs/src/tests/test_options.py diff --git a/pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/docbook.py b/pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/docbook.py index 39ce50cc0421..3050093a5bd5 100644 --- a/pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/docbook.py +++ b/pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/docbook.py @@ -1,6 +1,6 @@ from collections.abc import Mapping, MutableMapping, Sequence from frozendict import frozendict # type: ignore[attr-defined] -from typing import Any, cast, Optional +from typing import Any, cast, Optional, NamedTuple import markdown_it from markdown_it.token import Token @@ -25,16 +25,27 @@ def make_xml_id(s: str) -> str: class Deflist: has_dd = False +class Heading(NamedTuple): + container_tag: str + level: int + class DocBookRenderer(Renderer): __output__ = "docbook" _link_tags: list[str] _deflists: list[Deflist] + _headings: list[Heading] def __init__(self, manpage_urls: Mapping[str, str], parser: Optional[markdown_it.MarkdownIt] = None): super().__init__(manpage_urls, parser) self._link_tags = [] self._deflists = [] + self._headings = [] + def render(self, tokens: Sequence[Token], options: OptionsDict, + env: MutableMapping[str, Any]) -> str: + result = super().render(tokens, options, env) + result += self._close_headings(None, env) + return result def renderInline(self, tokens: Sequence[Token], options: OptionsDict, env: MutableMapping[str, Any]) -> str: # HACK to support docbook links and xrefs. link handling is only necessary because the docbook @@ -203,3 +214,36 @@ class DocBookRenderer(Renderer): def inline_anchor(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict, env: MutableMapping[str, Any]) -> str: return f'' + def ordered_list_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict, + env: MutableMapping[str, Any]) -> str: + start = f' startingnumber="{token.attrs["start"]}"' if 'start' in token.attrs else "" + return f"" + def ordered_list_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict, + env: MutableMapping[str, Any]) -> str: + return f"" + def heading_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict, + env: MutableMapping[str, Any]) -> str: + hlevel = int(token.tag[1:]) + result = self._close_headings(hlevel, env) + (tag, attrs) = self._heading_tag(token, tokens, i, options, env) + self._headings.append(Heading(tag, hlevel)) + attrs_str = "".join([ f" {k}={quoteattr(v)}" for k, v in attrs.items() ]) + return result + f'<{tag}{attrs_str}>\n' + def heading_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict, + env: MutableMapping[str, Any]) -> str: + return '' + + def _close_headings(self, level: Optional[int], env: MutableMapping[str, Any]) -> str: + # we rely on markdown-it producing h{1..6} tags in token.tag for this to work + result = [] + while len(self._headings): + if level is None or self._headings[-1].level >= level: + result.append(f"") + self._headings.pop() + else: + break + return "\n".join(result) + + def _heading_tag(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict, + env: MutableMapping[str, Any]) -> tuple[str, dict[str, str]]: + return ("section", {}) diff --git a/pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/md.py b/pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/md.py index 3f4aa7c47679..7aaa56031a3e 100644 --- a/pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/md.py +++ b/pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/md.py @@ -64,6 +64,10 @@ class Renderer(markdown_it.renderer.RendererProtocol): "container_admonition_open": self.admonition_open, "container_admonition_close": self.admonition_close, "inline_anchor": self.inline_anchor, + "heading_open": self.heading_open, + "heading_close": self.heading_close, + "ordered_list_open": self.ordered_list_open, + "ordered_list_close": self.ordered_list_close, } self._admonitions = { @@ -218,6 +222,18 @@ class Renderer(markdown_it.renderer.RendererProtocol): def inline_anchor(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict, env: MutableMapping[str, Any]) -> str: raise RuntimeError("md token not supported", token) + def heading_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict, + env: MutableMapping[str, Any]) -> str: + raise RuntimeError("md token not supported", token) + def heading_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict, + env: MutableMapping[str, Any]) -> str: + raise RuntimeError("md token not supported", token) + def ordered_list_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict, + env: MutableMapping[str, Any]) -> str: + raise RuntimeError("md token not supported", token) + def ordered_list_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict, + env: MutableMapping[str, Any]) -> str: + raise RuntimeError("md token not supported", token) def _is_escaped(src: str, pos: int) -> bool: found = 0 diff --git a/pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/options.py b/pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/options.py index d165a5937091..839f29d43704 100644 --- a/pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/options.py +++ b/pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/options.py @@ -2,6 +2,9 @@ import argparse import json from abc import abstractmethod +from collections.abc import MutableMapping, Sequence +from markdown_it.utils import OptionsDict +from markdown_it.token import Token from typing import Any, Optional from xml.sax.saxutils import escape, quoteattr @@ -145,8 +148,16 @@ class BaseConverter(Converter): @abstractmethod def finalize(self) -> str: raise NotImplementedError() +class OptionsDocBookRenderer(DocBookRenderer): + def heading_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict, + env: MutableMapping[str, Any]) -> str: + raise RuntimeError("md token not supported in options doc", token) + def heading_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict, + env: MutableMapping[str, Any]) -> str: + raise RuntimeError("md token not supported in options doc", token) + class DocBookConverter(BaseConverter): - __renderer__ = DocBookRenderer + __renderer__ = OptionsDocBookRenderer def _render_code(self, option: dict[str, Any], key: str) -> list[str]: if lit := option_is(option, key, 'literalDocBook'): diff --git a/pkgs/tools/nix/nixos-render-docs/src/tests/test_options.py b/pkgs/tools/nix/nixos-render-docs/src/tests/test_options.py new file mode 100644 index 000000000000..5a02fabde0fb --- /dev/null +++ b/pkgs/tools/nix/nixos-render-docs/src/tests/test_options.py @@ -0,0 +1,14 @@ +import nixos_render_docs + +from markdown_it.token import Token +import pytest + +def test_option_headings() -> None: + c = nixos_render_docs.options.DocBookConverter({}, 'local', 'none', 'vars', 'opt-', False) + with pytest.raises(RuntimeError) as exc: + c._render("# foo") + assert exc.value.args[0] == 'md token not supported in options doc' + assert exc.value.args[1] == Token( + type='heading_open', tag='h1', nesting=1, attrs={}, map=[0, 1], level=0, children=None, + content='', markup='#', info='', meta={}, block=True, hidden=False + )