nixos-render-docs: add options html renderer

it's not hooked up to anything yet, but that will come soon. there's a
bit of docbook compat here that must be interoperable with the actual
docbook exporter, but luckily it's not all that much.
This commit is contained in:
pennae
2023-02-19 23:03:32 +01:00
parent 82e62614e9
commit d520d55dee

View File

@@ -1,14 +1,15 @@
from __future__ import annotations from __future__ import annotations
import argparse import argparse
import html
import json import json
import xml.sax.saxutils as xml
from abc import abstractmethod from abc import abstractmethod
from collections.abc import Mapping, Sequence from collections.abc import Mapping, Sequence
from markdown_it.token import Token from markdown_it.token import Token
from typing import Any, Generic, Optional from typing import Any, Generic, Optional
from urllib.parse import quote from urllib.parse import quote
from xml.sax.saxutils import escape, quoteattr
import markdown_it import markdown_it
@@ -17,7 +18,9 @@ from . import parallel
from .asciidoc import AsciiDocRenderer, asciidoc_escape from .asciidoc import AsciiDocRenderer, asciidoc_escape
from .commonmark import CommonMarkRenderer from .commonmark import CommonMarkRenderer
from .docbook import DocBookRenderer, make_xml_id from .docbook import DocBookRenderer, make_xml_id
from .html import HTMLRenderer
from .manpage import ManpageRenderer, man_escape from .manpage import ManpageRenderer, man_escape
from .manual_structure import XrefTarget
from .md import Converter, md_escape, md_make_code from .md import Converter, md_escape, md_make_code
from .types import OptionLoc, Option, RenderedOption from .types import OptionLoc, Option, RenderedOption
@@ -240,10 +243,10 @@ class DocBookConverter(BaseConverter[OptionsDocBookRenderer]):
def _decl_def_entry(self, href: Optional[str], name: str) -> list[str]: def _decl_def_entry(self, href: Optional[str], name: str) -> list[str]:
if href is not None: if href is not None:
href = " xlink:href=" + quoteattr(href) href = " xlink:href=" + xml.quoteattr(href)
return [ return [
f"<member><filename{href}>", f"<member><filename{href}>",
escape(name), xml.escape(name),
"</filename></member>" "</filename></member>"
] ]
@@ -273,8 +276,8 @@ class DocBookConverter(BaseConverter[OptionsDocBookRenderer]):
result += [ result += [
"<varlistentry>", "<varlistentry>",
# NOTE adding extra spaces here introduces spaces into xref link expansions # NOTE adding extra spaces here introduces spaces into xref link expansions
(f"<term xlink:href={quoteattr('#' + id)} xml:id={quoteattr(id)}>" + (f"<term xlink:href={xml.quoteattr('#' + id)} xml:id={xml.quoteattr(id)}>" +
f"<option>{escape(name)}</option></term>"), f"<option>{xml.escape(name)}</option></term>"),
"<listitem>" "<listitem>"
] ]
result += opt.lines result += opt.lines
@@ -524,6 +527,109 @@ class AsciiDocConverter(BaseConverter[OptionsAsciiDocRenderer]):
return "\n".join(result) return "\n".join(result)
class OptionsHTMLRenderer(OptionDocsRestrictions, HTMLRenderer):
# TODO docbook compat. must be removed together with the matching docbook handlers.
def ordered_list_open(self, token: Token, tokens: Sequence[Token], i: int) -> str:
token.meta['compact'] = False
return super().ordered_list_open(token, tokens, i)
def bullet_list_open(self, token: Token, tokens: Sequence[Token], i: int) -> str:
token.meta['compact'] = False
return super().bullet_list_open(token, tokens, i)
def fence(self, token: Token, tokens: Sequence[Token], i: int) -> str:
# TODO use token.info. docbook doesn't so we can't yet.
return f'<pre class="programlisting">{html.escape(token.content)}</pre>'
class HTMLConverter(BaseConverter[OptionsHTMLRenderer]):
__option_block_separator__ = ""
def __init__(self, manpage_urls: Mapping[str, str], revision: str, markdown_by_default: bool,
varlist_id: str, id_prefix: str, xref_targets: Mapping[str, XrefTarget]):
super().__init__(revision, markdown_by_default)
self._xref_targets = xref_targets
self._varlist_id = varlist_id
self._id_prefix = id_prefix
self._renderer = OptionsHTMLRenderer(manpage_urls, self._xref_targets)
def _parallel_render_prepare(self) -> Any:
return (self._renderer._manpage_urls, self._revision, self._markdown_by_default,
self._varlist_id, self._id_prefix, self._xref_targets)
@classmethod
def _parallel_render_init_worker(cls, a: Any) -> HTMLConverter:
return cls(*a)
def _render_code(self, option: dict[str, Any], key: str) -> list[str]:
if lit := option_is(option, key, 'literalDocBook'):
raise RuntimeError("can't render html in the presence of docbook")
else:
return super()._render_code(option, key)
def _render_description(self, desc: str | dict[str, Any]) -> list[str]:
if isinstance(desc, str) and not self._markdown_by_default:
raise RuntimeError("can't render html in the presence of docbook")
else:
return super()._render_description(desc)
def _related_packages_header(self) -> list[str]:
return [
'<p><span class="emphasis"><em>Related packages:</em></span></p>',
]
def _decl_def_header(self, header: str) -> list[str]:
return [
f'<p><span class="emphasis"><em>{header}:</em></span></p>',
'<table border="0" summary="Simple list" class="simplelist">'
]
def _decl_def_entry(self, href: Optional[str], name: str) -> list[str]:
if href is not None:
href = f' href="{html.escape(href, True)}"'
return [
"<tr><td>",
f'<code class="filename"><a class="filename" {href} target="_top">',
f'{html.escape(name)}',
'</a></code>',
"</td></tr>"
]
def _decl_def_footer(self) -> list[str]:
return [ "</table>" ]
def finalize(self) -> str:
result = []
result += [
'<div class="variablelist">',
f'<a id="{html.escape(self._varlist_id, True)}"></a>',
' <dl class="variablelist">',
]
for (name, opt) in self._sorted_options():
id = make_xml_id(self._id_prefix + name)
target = self._xref_targets[id]
result += [
'<dt>',
' <span class="term">',
# docbook compat, these could be one tag
f' <a id="{html.escape(id, True)}"></a><a class="term" href="{target.href()}">'
# no spaces here (and string merging) for docbook output compat
f'<code class="option">{html.escape(name)}</code>',
' </a>',
' </span>',
'</dt>',
'<dd>',
]
result += opt.lines
result += [
"</dd>",
]
result += [
" </dl>",
"</div>"
]
return "\n".join(result)
def _build_cli_db(p: argparse.ArgumentParser) -> None: def _build_cli_db(p: argparse.ArgumentParser) -> None:
p.add_argument('--manpage-urls', required=True) p.add_argument('--manpage-urls', required=True)
p.add_argument('--revision', required=True) p.add_argument('--revision', required=True)