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.
This commit is contained in:
pennae 2023-01-25 01:56:47 +01:00
parent 00a1b41c3b
commit 82d5698e22
4 changed files with 87 additions and 2 deletions

View file

@ -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'<anchor xml:id={quoteattr(cast(str, token.attrs["id"]))} />'
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"<orderedlist{start}>"
def ordered_list_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
env: MutableMapping[str, Any]) -> str:
return f"</orderedlist>"
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<title>'
def heading_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
env: MutableMapping[str, Any]) -> str:
return '</title>'
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[-1].container_tag}>")
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", {})

View file

@ -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

View file

@ -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'):

View file

@ -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
)