about summary refs log tree commit diff
path: root/pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/md.py
diff options
context:
space:
mode:
Diffstat (limited to 'pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/md.py')
-rw-r--r--pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/md.py198
1 files changed, 81 insertions, 117 deletions
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 96cc8af69bce9..e8fee1b713282 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
@@ -1,6 +1,6 @@
 from abc import ABC
 from collections.abc import Mapping, MutableMapping, Sequence
-from typing import Any, Callable, cast, get_args, Iterable, Literal, NoReturn, Optional
+from typing import Any, Callable, cast, Generic, get_args, Iterable, Literal, NoReturn, Optional, TypeVar
 
 import dataclasses
 import re
@@ -44,11 +44,11 @@ AttrBlockKind = Literal['admonition', 'example']
 
 AdmonitionKind = Literal["note", "caution", "tip", "important", "warning"]
 
-class Renderer(markdown_it.renderer.RendererProtocol):
+class Renderer:
     _admonitions: dict[AdmonitionKind, tuple[RenderFn, RenderFn]]
     _admonition_stack: list[AdmonitionKind]
 
-    def __init__(self, manpage_urls: Mapping[str, str], parser: Optional[markdown_it.MarkdownIt] = None):
+    def __init__(self, manpage_urls: Mapping[str, str]):
         self._manpage_urls = manpage_urls
         self.rules = {
             'text': self.text,
@@ -104,169 +104,120 @@ class Renderer(markdown_it.renderer.RendererProtocol):
     def _join_inline(self, ls: Iterable[str]) -> str:
         return "".join(ls)
 
-    def admonition_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-                        env: MutableMapping[str, Any]) -> str:
+    def admonition_open(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         tag = token.meta['kind']
         self._admonition_stack.append(tag)
-        return self._admonitions[tag][0](token, tokens, i, options, env)
-    def admonition_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-                         env: MutableMapping[str, Any]) -> str:
-        return self._admonitions[self._admonition_stack.pop()][1](token, tokens, i, options, env)
+        return self._admonitions[tag][0](token, tokens, i)
+    def admonition_close(self, token: Token, tokens: Sequence[Token], i: int) -> str:
+        return self._admonitions[self._admonition_stack.pop()][1](token, tokens, i)
 
-    def render(self, tokens: Sequence[Token], options: OptionsDict,
-               env: MutableMapping[str, Any]) -> str:
+    def render(self, tokens: Sequence[Token]) -> str:
         def do_one(i: int, token: Token) -> str:
             if token.type == "inline":
                 assert token.children is not None
-                return self.renderInline(token.children, options, env)
+                return self.renderInline(token.children)
             elif token.type in self.rules:
-                return self.rules[token.type](tokens[i], tokens, i, options, env)
+                return self.rules[token.type](tokens[i], tokens, i)
             else:
                 raise NotImplementedError("md token not supported yet", token)
         return self._join_block(map(lambda arg: do_one(*arg), enumerate(tokens)))
-    def renderInline(self, tokens: Sequence[Token], options: OptionsDict,
-                     env: MutableMapping[str, Any]) -> str:
+    def renderInline(self, tokens: Sequence[Token]) -> str:
         def do_one(i: int, token: Token) -> str:
             if token.type in self.rules:
-                return self.rules[token.type](tokens[i], tokens, i, options, env)
+                return self.rules[token.type](tokens[i], tokens, i)
             else:
                 raise NotImplementedError("md token not supported yet", token)
         return self._join_inline(map(lambda arg: do_one(*arg), enumerate(tokens)))
 
-    def text(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-             env: MutableMapping[str, Any]) -> str:
+    def text(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         raise RuntimeError("md token not supported", token)
-    def paragraph_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-                       env: MutableMapping[str, Any]) -> str:
+    def paragraph_open(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         raise RuntimeError("md token not supported", token)
-    def paragraph_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-                        env: MutableMapping[str, Any]) -> str:
+    def paragraph_close(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         raise RuntimeError("md token not supported", token)
-    def hardbreak(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-                  env: MutableMapping[str, Any]) -> str:
+    def hardbreak(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         raise RuntimeError("md token not supported", token)
-    def softbreak(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-                  env: MutableMapping[str, Any]) -> str:
+    def softbreak(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         raise RuntimeError("md token not supported", token)
-    def code_inline(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-                    env: MutableMapping[str, Any]) -> str:
+    def code_inline(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         raise RuntimeError("md token not supported", token)
-    def code_block(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-                   env: MutableMapping[str, Any]) -> str:
+    def code_block(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         raise RuntimeError("md token not supported", token)
-    def link_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-                  env: MutableMapping[str, Any]) -> str:
+    def link_open(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         raise RuntimeError("md token not supported", token)
-    def link_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-                   env: MutableMapping[str, Any]) -> str:
+    def link_close(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         raise RuntimeError("md token not supported", token)
-    def list_item_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-                       env: MutableMapping[str, Any]) -> str:
+    def list_item_open(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         raise RuntimeError("md token not supported", token)
-    def list_item_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-                        env: MutableMapping[str, Any]) -> str:
+    def list_item_close(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         raise RuntimeError("md token not supported", token)
-    def bullet_list_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-                         env: MutableMapping[str, Any]) -> str:
+    def bullet_list_open(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         raise RuntimeError("md token not supported", token)
-    def bullet_list_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-                          env: MutableMapping[str, Any]) -> str:
+    def bullet_list_close(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         raise RuntimeError("md token not supported", token)
-    def em_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-                env: MutableMapping[str, Any]) -> str:
+    def em_open(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         raise RuntimeError("md token not supported", token)
-    def em_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-                 env: MutableMapping[str, Any]) -> str:
+    def em_close(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         raise RuntimeError("md token not supported", token)
-    def strong_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-                    env: MutableMapping[str, Any]) -> str:
+    def strong_open(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         raise RuntimeError("md token not supported", token)
-    def strong_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-                     env: MutableMapping[str, Any]) -> str:
+    def strong_close(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         raise RuntimeError("md token not supported", token)
-    def fence(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-              env: MutableMapping[str, Any]) -> str:
+    def fence(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         raise RuntimeError("md token not supported", token)
-    def blockquote_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-                        env: MutableMapping[str, Any]) -> str:
+    def blockquote_open(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         raise RuntimeError("md token not supported", token)
-    def blockquote_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-                         env: MutableMapping[str, Any]) -> str:
+    def blockquote_close(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         raise RuntimeError("md token not supported", token)
-    def note_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-                  env: MutableMapping[str, Any]) -> str:
+    def note_open(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         raise RuntimeError("md token not supported", token)
-    def note_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-                   env: MutableMapping[str, Any]) -> str:
+    def note_close(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         raise RuntimeError("md token not supported", token)
-    def caution_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-                     env: MutableMapping[str, Any]) -> str:
+    def caution_open(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         raise RuntimeError("md token not supported", token)
-    def caution_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-                      env: MutableMapping[str, Any]) -> str:
+    def caution_close(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         raise RuntimeError("md token not supported", token)
-    def important_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-                       env: MutableMapping[str, Any]) -> str:
+    def important_open(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         raise RuntimeError("md token not supported", token)
-    def important_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-                        env: MutableMapping[str, Any]) -> str:
+    def important_close(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         raise RuntimeError("md token not supported", token)
-    def tip_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-                 env: MutableMapping[str, Any]) -> str:
+    def tip_open(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         raise RuntimeError("md token not supported", token)
-    def tip_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-                  env: MutableMapping[str, Any]) -> str:
+    def tip_close(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         raise RuntimeError("md token not supported", token)
-    def warning_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-                     env: MutableMapping[str, Any]) -> str:
+    def warning_open(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         raise RuntimeError("md token not supported", token)
-    def warning_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-                      env: MutableMapping[str, Any]) -> str:
+    def warning_close(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         raise RuntimeError("md token not supported", token)
-    def dl_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-                env: MutableMapping[str, Any]) -> str:
+    def dl_open(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         raise RuntimeError("md token not supported", token)
-    def dl_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-                 env: MutableMapping[str, Any]) -> str:
+    def dl_close(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         raise RuntimeError("md token not supported", token)
-    def dt_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-                env: MutableMapping[str, Any]) -> str:
+    def dt_open(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         raise RuntimeError("md token not supported", token)
-    def dt_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-                 env: MutableMapping[str, Any]) -> str:
+    def dt_close(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         raise RuntimeError("md token not supported", token)
-    def dd_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-                env: MutableMapping[str, Any]) -> str:
+    def dd_open(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         raise RuntimeError("md token not supported", token)
-    def dd_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-                 env: MutableMapping[str, Any]) -> str:
+    def dd_close(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         raise RuntimeError("md token not supported", token)
-    def myst_role(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-                  env: MutableMapping[str, Any]) -> str:
+    def myst_role(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         raise RuntimeError("md token not supported", token)
-    def attr_span_begin(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-                        env: MutableMapping[str, Any]) -> str:
+    def attr_span_begin(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         raise RuntimeError("md token not supported", token)
-    def attr_span_end(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-                      env: MutableMapping[str, Any]) -> str:
+    def attr_span_end(self, token: Token, tokens: Sequence[Token], i: int) -> 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:
+    def heading_open(self, token: Token, tokens: Sequence[Token], i: int) -> 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:
+    def heading_close(self, token: Token, tokens: Sequence[Token], i: int) -> 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:
+    def ordered_list_open(self, token: Token, tokens: Sequence[Token], i: int) -> 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:
+    def ordered_list_close(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         raise RuntimeError("md token not supported", token)
-    def example_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-                     env: MutableMapping[str, Any]) -> str:
+    def example_open(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         raise RuntimeError("md token not supported", token)
-    def example_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
-                      env: MutableMapping[str, Any]) -> str:
+    def example_close(self, token: Token, tokens: Sequence[Token], i: int) -> str:
         raise RuntimeError("md token not supported", token)
 
 def _is_escaped(src: str, pos: int) -> bool:
@@ -466,12 +417,26 @@ def _block_attr(md: markdown_it.MarkdownIt) -> None:
 
     md.core.ruler.push("block_attr", block_attr)
 
-class Converter(ABC):
-    __renderer__: Callable[[Mapping[str, str], markdown_it.MarkdownIt], Renderer]
+TR = TypeVar('TR', bound='Renderer')
 
-    def __init__(self, manpage_urls: Mapping[str, str]):
-        self._manpage_urls = manpage_urls
+class Converter(ABC, Generic[TR]):
+    # we explicitly disable markdown-it rendering support and use our own entirely.
+    # rendering is well separated from parsing and our renderers carry much more state than
+    # markdown-it easily acknowledges as 'good' (unless we used the untyped env args to
+    # shuttle that state around, which is very fragile)
+    class ForbiddenRenderer(markdown_it.renderer.RendererProtocol):
+        __output__ = "none"
+
+        def __init__(self, parser: Optional[markdown_it.MarkdownIt]):
+            pass
+
+        def render(self, tokens: Sequence[Token], options: OptionsDict,
+                   env: MutableMapping[str, Any]) -> str:
+            raise NotImplementedError("do not use Converter._md.renderer. 'tis a silly place")
+
+    _renderer: TR
 
+    def __init__(self) -> None:
         self._md = markdown_it.MarkdownIt(
             "commonmark",
             {
@@ -479,7 +444,7 @@ class Converter(ABC):
                 'html': False,       # not useful since we target many formats
                 'typographer': True, # required for smartquotes
             },
-            renderer_cls=lambda parser: self.__renderer__(self._manpage_urls, parser)
+            renderer_cls=self.ForbiddenRenderer
         )
         self._md.use(
             container_plugin,
@@ -496,10 +461,9 @@ class Converter(ABC):
         self._md.use(_block_attr)
         self._md.enable(["smartquotes", "replacements"])
 
-    def _parse(self, src: str, env: Optional[MutableMapping[str, Any]] = None) -> list[Token]:
-        return self._md.parse(src, env if env is not None else {})
+    def _parse(self, src: str) -> list[Token]:
+        return self._md.parse(src, {})
 
-    def _render(self, src: str, env: Optional[MutableMapping[str, Any]] = None) -> str:
-        env = {} if env is None else env
-        tokens = self._parse(src, env)
-        return self._md.renderer.render(tokens, self._md.options, env) # type: ignore[no-any-return]
+    def _render(self, src: str) -> str:
+        tokens = self._parse(src)
+        return self._renderer.render(tokens)