mirror of
https://github.com/NixOS/nixpkgs.git
synced 2024-11-16 14:54:29 +01:00
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:
parent
00a1b41c3b
commit
82d5698e22
4 changed files with 87 additions and 2 deletions
|
@ -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", {})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'):
|
||||
|
|
14
pkgs/tools/nix/nixos-render-docs/src/tests/test_options.py
Normal file
14
pkgs/tools/nix/nixos-render-docs/src/tests/test_options.py
Normal 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
|
||||
)
|
Loading…
Reference in a new issue