Merge pull request #296384 from pennae/remove-docbook-docs-support

nixos/docs: remove docbook support machinery

authored by Silvan Mosberger and committed by GitHub 1f7ac8f5 b2245dab

+36 -484
+1 -15
nixos/lib/make-options-doc/default.nix
··· 232 232 echo "file json-br $dst/options.json.br" >> $out/nix-support/hydra-build-products 233 233 ''; 234 234 235 - optionsDocBook = lib.warn "optionsDocBook is deprecated since 23.11 and will be removed in 24.05" 236 - (pkgs.runCommand "options-docbook.xml" { 237 - nativeBuildInputs = [ 238 - pkgs.nixos-render-docs 239 - ]; 240 - } '' 241 - nixos-render-docs -j $NIX_BUILD_CORES options docbook \ 242 - --manpage-urls ${pkgs.path + "/doc/manpage-urls.json"} \ 243 - --revision ${lib.escapeShellArg revision} \ 244 - --document-type ${lib.escapeShellArg documentType} \ 245 - --varlist-id ${lib.escapeShellArg variablelistId} \ 246 - --id-prefix ${lib.escapeShellArg optionIdPrefix} \ 247 - ${optionsJSON}/share/doc/nixos/options.json \ 248 - "$out" 249 - ''); 235 + optionsDocBook = throw "optionsDocBook has been removed in 24.05"; 250 236 }
-247
pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/docbook.py
··· 1 - from collections.abc import Mapping, Sequence 2 - from typing import cast, Optional, NamedTuple 3 - 4 - from markdown_it.token import Token 5 - from xml.sax.saxutils import escape, quoteattr 6 - 7 - from .md import Renderer 8 - 9 - _xml_id_translate_table = { 10 - ord('*'): ord('_'), 11 - ord('<'): ord('_'), 12 - ord(' '): ord('_'), 13 - ord('>'): ord('_'), 14 - ord('['): ord('_'), 15 - ord(']'): ord('_'), 16 - ord(':'): ord('_'), 17 - ord('"'): ord('_'), 18 - } 19 - def make_xml_id(s: str) -> str: 20 - return s.translate(_xml_id_translate_table) 21 - 22 - class Deflist: 23 - has_dd = False 24 - 25 - class Heading(NamedTuple): 26 - container_tag: str 27 - level: int 28 - # special handling for <part> titles: whether partinfo was already closed from elsewhere 29 - # or still needs closing. 30 - partintro_closed: bool = False 31 - 32 - class DocBookRenderer(Renderer): 33 - _link_tags: list[str] 34 - _deflists: list[Deflist] 35 - _headings: list[Heading] 36 - _attrspans: list[str] 37 - 38 - def __init__(self, manpage_urls: Mapping[str, str]): 39 - super().__init__(manpage_urls) 40 - self._link_tags = [] 41 - self._deflists = [] 42 - self._headings = [] 43 - self._attrspans = [] 44 - 45 - def render(self, tokens: Sequence[Token]) -> str: 46 - result = super().render(tokens) 47 - result += self._close_headings(None) 48 - return result 49 - def renderInline(self, tokens: Sequence[Token]) -> str: 50 - # HACK to support docbook links and xrefs. link handling is only necessary because the docbook 51 - # manpage stylesheet converts - in urls to a mathematical minus, which may be somewhat incorrect. 52 - for i, token in enumerate(tokens): 53 - if token.type != 'link_open': 54 - continue 55 - token.tag = 'link' 56 - # turn [](#foo) into xrefs 57 - if token.attrs['href'][0:1] == '#' and tokens[i + 1].type == 'link_close': # type: ignore[index] 58 - token.tag = "xref" 59 - # turn <x> into links without contents 60 - if tokens[i + 1].type == 'text' and tokens[i + 1].content == token.attrs['href']: 61 - tokens[i + 1].content = '' 62 - 63 - return super().renderInline(tokens) 64 - 65 - def text(self, token: Token, tokens: Sequence[Token], i: int) -> str: 66 - return escape(token.content) 67 - def paragraph_open(self, token: Token, tokens: Sequence[Token], i: int) -> str: 68 - return "<para>" 69 - def paragraph_close(self, token: Token, tokens: Sequence[Token], i: int) -> str: 70 - return "</para>" 71 - def hardbreak(self, token: Token, tokens: Sequence[Token], i: int) -> str: 72 - return "<literallayout>\n</literallayout>" 73 - def softbreak(self, token: Token, tokens: Sequence[Token], i: int) -> str: 74 - # should check options.breaks() and emit hard break if so 75 - return "\n" 76 - def code_inline(self, token: Token, tokens: Sequence[Token], i: int) -> str: 77 - return f"<literal>{escape(token.content)}</literal>" 78 - def code_block(self, token: Token, tokens: Sequence[Token], i: int) -> str: 79 - return f"<programlisting>{escape(token.content)}</programlisting>" 80 - def link_open(self, token: Token, tokens: Sequence[Token], i: int) -> str: 81 - self._link_tags.append(token.tag) 82 - href = cast(str, token.attrs['href']) 83 - (attr, start) = ('linkend', 1) if href[0] == '#' else ('xlink:href', 0) 84 - return f"<{token.tag} {attr}={quoteattr(href[start:])}>" 85 - def link_close(self, token: Token, tokens: Sequence[Token], i: int) -> str: 86 - return f"</{self._link_tags.pop()}>" 87 - def list_item_open(self, token: Token, tokens: Sequence[Token], i: int) -> str: 88 - return "<listitem>" 89 - def list_item_close(self, token: Token, tokens: Sequence[Token], i: int) -> str: 90 - return "</listitem>\n" 91 - # HACK open and close para for docbook change size. remove soon. 92 - def bullet_list_open(self, token: Token, tokens: Sequence[Token], i: int) -> str: 93 - spacing = ' spacing="compact"' if token.meta.get('compact', False) else '' 94 - return f"<para><itemizedlist{spacing}>\n" 95 - def bullet_list_close(self, token: Token, tokens: Sequence[Token], i: int) -> str: 96 - return "\n</itemizedlist></para>" 97 - def em_open(self, token: Token, tokens: Sequence[Token], i: int) -> str: 98 - return "<emphasis>" 99 - def em_close(self, token: Token, tokens: Sequence[Token], i: int) -> str: 100 - return "</emphasis>" 101 - def strong_open(self, token: Token, tokens: Sequence[Token], i: int) -> str: 102 - return "<emphasis role=\"strong\">" 103 - def strong_close(self, token: Token, tokens: Sequence[Token], i: int) -> str: 104 - return "</emphasis>" 105 - def fence(self, token: Token, tokens: Sequence[Token], i: int) -> str: 106 - info = f" language={quoteattr(token.info)}" if token.info != "" else "" 107 - return f"<programlisting{info}>{escape(token.content)}</programlisting>" 108 - def blockquote_open(self, token: Token, tokens: Sequence[Token], i: int) -> str: 109 - return "<para><blockquote>" 110 - def blockquote_close(self, token: Token, tokens: Sequence[Token], i: int) -> str: 111 - return "</blockquote></para>" 112 - def note_open(self, token: Token, tokens: Sequence[Token], i: int) -> str: 113 - return "<para><note>" 114 - def note_close(self, token: Token, tokens: Sequence[Token], i: int) -> str: 115 - return "</note></para>" 116 - def caution_open(self, token: Token, tokens: Sequence[Token], i: int) -> str: 117 - return "<para><caution>" 118 - def caution_close(self, token: Token, tokens: Sequence[Token], i: int) -> str: 119 - return "</caution></para>" 120 - def important_open(self, token: Token, tokens: Sequence[Token], i: int) -> str: 121 - return "<para><important>" 122 - def important_close(self, token: Token, tokens: Sequence[Token], i: int) -> str: 123 - return "</important></para>" 124 - def tip_open(self, token: Token, tokens: Sequence[Token], i: int) -> str: 125 - return "<para><tip>" 126 - def tip_close(self, token: Token, tokens: Sequence[Token], i: int) -> str: 127 - return "</tip></para>" 128 - def warning_open(self, token: Token, tokens: Sequence[Token], i: int) -> str: 129 - return "<para><warning>" 130 - def warning_close(self, token: Token, tokens: Sequence[Token], i: int) -> str: 131 - return "</warning></para>" 132 - # markdown-it emits tokens based on the html syntax tree, but docbook is 133 - # slightly different. html has <dl>{<dt/>{<dd/>}}</dl>, 134 - # docbook has <variablelist>{<varlistentry><term/><listitem/></varlistentry>}<variablelist> 135 - # we have to reject multiple definitions for the same term for time being. 136 - def dl_open(self, token: Token, tokens: Sequence[Token], i: int) -> str: 137 - self._deflists.append(Deflist()) 138 - return "<para><variablelist>" 139 - def dl_close(self, token: Token, tokens: Sequence[Token], i: int) -> str: 140 - self._deflists.pop() 141 - return "</variablelist></para>" 142 - def dt_open(self, token: Token, tokens: Sequence[Token], i: int) -> str: 143 - self._deflists[-1].has_dd = False 144 - return "<varlistentry><term>" 145 - def dt_close(self, token: Token, tokens: Sequence[Token], i: int) -> str: 146 - return "</term>" 147 - def dd_open(self, token: Token, tokens: Sequence[Token], i: int) -> str: 148 - if self._deflists[-1].has_dd: 149 - raise Exception("multiple definitions per term not supported") 150 - self._deflists[-1].has_dd = True 151 - return "<listitem>" 152 - def dd_close(self, token: Token, tokens: Sequence[Token], i: int) -> str: 153 - return "</listitem></varlistentry>" 154 - def myst_role(self, token: Token, tokens: Sequence[Token], i: int) -> str: 155 - if token.meta['name'] == 'command': 156 - return f"<command>{escape(token.content)}</command>" 157 - if token.meta['name'] == 'file': 158 - return f"<filename>{escape(token.content)}</filename>" 159 - if token.meta['name'] == 'var': 160 - return f"<varname>{escape(token.content)}</varname>" 161 - if token.meta['name'] == 'env': 162 - return f"<envar>{escape(token.content)}</envar>" 163 - if token.meta['name'] == 'option': 164 - return f"<option>{escape(token.content)}</option>" 165 - if token.meta['name'] == 'manpage': 166 - [page, section] = [ s.strip() for s in token.content.rsplit('(', 1) ] 167 - section = section[:-1] 168 - man = f"{page}({section})" 169 - title = f"<refentrytitle>{escape(page)}</refentrytitle>" 170 - vol = f"<manvolnum>{escape(section)}</manvolnum>" 171 - ref = f"<citerefentry>{title}{vol}</citerefentry>" 172 - if man in self._manpage_urls: 173 - return f"<link xlink:href={quoteattr(self._manpage_urls[man])}>{ref}</link>" 174 - else: 175 - return ref 176 - raise NotImplementedError("md node not supported yet", token) 177 - def attr_span_begin(self, token: Token, tokens: Sequence[Token], i: int) -> str: 178 - # we currently support *only* inline anchors and the special .keycap class to produce 179 - # <keycap> docbook elements. 180 - (id_part, class_part) = ("", "") 181 - if s := token.attrs.get('id'): 182 - id_part = f'<anchor xml:id={quoteattr(cast(str, s))} />' 183 - if s := token.attrs.get('class'): 184 - if s == 'keycap': 185 - class_part = "<keycap>" 186 - self._attrspans.append("</keycap>") 187 - else: 188 - return super().attr_span_begin(token, tokens, i) 189 - else: 190 - self._attrspans.append("") 191 - return id_part + class_part 192 - def attr_span_end(self, token: Token, tokens: Sequence[Token], i: int) -> str: 193 - return self._attrspans.pop() 194 - def ordered_list_open(self, token: Token, tokens: Sequence[Token], i: int) -> str: 195 - start = f' startingnumber="{token.attrs["start"]}"' if 'start' in token.attrs else "" 196 - spacing = ' spacing="compact"' if token.meta.get('compact', False) else '' 197 - return f"<orderedlist{start}{spacing}>" 198 - def ordered_list_close(self, token: Token, tokens: Sequence[Token], i: int) -> str: 199 - return "</orderedlist>" 200 - def heading_open(self, token: Token, tokens: Sequence[Token], i: int) -> str: 201 - hlevel = int(token.tag[1:]) 202 - result = self._close_headings(hlevel) 203 - (tag, attrs) = self._heading_tag(token, tokens, i) 204 - self._headings.append(Heading(tag, hlevel)) 205 - attrs_str = "".join([ f" {k}={quoteattr(v)}" for k, v in attrs.items() ]) 206 - return result + f'<{tag}{attrs_str}>\n<title>' 207 - def heading_close(self, token: Token, tokens: Sequence[Token], i: int) -> str: 208 - heading = self._headings[-1] 209 - result = '</title>' 210 - if heading.container_tag == 'part': 211 - # generate the same ids as were previously assigned manually. if this collides we 212 - # rely on outside schema validation to catch it! 213 - maybe_id = "" 214 - assert tokens[i - 2].type == 'heading_open' 215 - if id := cast(str, tokens[i - 2].attrs.get('id', "")): 216 - maybe_id = " xml:id=" + quoteattr(id + "-intro") 217 - result += f"<partintro{maybe_id}>" 218 - return result 219 - def example_open(self, token: Token, tokens: Sequence[Token], i: int) -> str: 220 - if id := cast(str, token.attrs.get('id', '')): 221 - id = f'xml:id={quoteattr(id)}' if id else '' 222 - return f'<example {id}>' 223 - def example_close(self, token: Token, tokens: Sequence[Token], i: int) -> str: 224 - return "</example>" 225 - def example_title_open(self, token: Token, tokens: Sequence[Token], i: int) -> str: 226 - return "<title>" 227 - def example_title_close(self, token: Token, tokens: Sequence[Token], i: int) -> str: 228 - return "</title>" 229 - 230 - def _close_headings(self, level: Optional[int]) -> str: 231 - # we rely on markdown-it producing h{1..6} tags in token.tag for this to work 232 - result = [] 233 - while len(self._headings): 234 - if level is None or self._headings[-1].level >= level: 235 - heading = self._headings.pop() 236 - if heading.container_tag == 'part' and not heading.partintro_closed: 237 - result.append("</partintro>") 238 - result.append(f"</{heading.container_tag}>") 239 - else: 240 - break 241 - return "\n".join(result) 242 - 243 - def _heading_tag(self, token: Token, tokens: Sequence[Token], i: int) -> tuple[str, dict[str, str]]: 244 - attrs = {} 245 - if id := token.attrs.get('id'): 246 - attrs['xml:id'] = cast(str, id) 247 - return ("section", attrs)
+9 -92
pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/manual.py
··· 13 13 from markdown_it.token import Token 14 14 15 15 from . import md, options 16 - from .docbook import DocBookRenderer, Heading, make_xml_id 17 16 from .html import HTMLRenderer, UnresolvedXrefError 18 - from .manual_structure import check_structure, FragmentType, is_include, TocEntry, TocEntryType, XrefTarget 17 + from .manual_structure import check_structure, FragmentType, is_include, make_xml_id, TocEntry, TocEntryType, XrefTarget 19 18 from .md import Converter, Renderer 20 19 21 20 class BaseConverter(Converter[md.TR], Generic[md.TR]): ··· 200 199 def included_options(self, token: Token, tokens: Sequence[Token], i: int) -> str: 201 200 raise NotImplementedError() 202 201 203 - class ManualDocBookRenderer(RendererMixin, DocBookRenderer): 204 - def __init__(self, toplevel_tag: str, revision: str, manpage_urls: Mapping[str, str]): 205 - super().__init__(toplevel_tag, revision, manpage_urls) 206 - 207 - def _render_book(self, tokens: Sequence[Token]) -> str: 208 - assert tokens[1].children 209 - assert tokens[4].children 210 - if (maybe_id := cast(str, tokens[0].attrs.get('id', ""))): 211 - maybe_id = "xml:id=" + xml.quoteattr(maybe_id) 212 - return (f'<book xmlns="http://docbook.org/ns/docbook"' 213 - f' xmlns:xlink="http://www.w3.org/1999/xlink"' 214 - f' {maybe_id} version="5.0">' 215 - f' <title>{self.renderInline(tokens[1].children)}</title>' 216 - f' <subtitle>{self.renderInline(tokens[4].children)}</subtitle>' 217 - f' {super(DocBookRenderer, self).render(tokens[6:])}' 218 - f'</book>') 219 - 220 - def _heading_tag(self, token: Token, tokens: Sequence[Token], i: int) -> tuple[str, dict[str, str]]: 221 - (tag, attrs) = super()._heading_tag(token, tokens, i) 222 - # render() has already verified that we don't have supernumerary headings and since the 223 - # book tag is handled specially we can leave the check this simple 224 - if token.tag != 'h1': 225 - return (tag, attrs) 226 - return (self._toplevel_tag, attrs | { 227 - 'xmlns': "http://docbook.org/ns/docbook", 228 - 'xmlns:xlink': "http://www.w3.org/1999/xlink", 229 - }) 230 - 231 - def _included_thing(self, tag: str, token: Token, tokens: Sequence[Token], i: int) -> str: 232 - result = [] 233 - # close existing partintro. the generic render doesn't really need this because 234 - # it doesn't have a concept of structure in the way the manual does. 235 - if self._headings and self._headings[-1] == Heading('part', 1): 236 - result.append("</partintro>") 237 - self._headings[-1] = self._headings[-1]._replace(partintro_closed=True) 238 - # must nest properly for structural includes. this requires saving at least 239 - # the headings stack, but creating new renderers is cheap and much easier. 240 - r = ManualDocBookRenderer(tag, self._revision, self._manpage_urls) 241 - for (included, path) in token.meta['included']: 242 - try: 243 - result.append(r.render(included)) 244 - except Exception as e: 245 - raise RuntimeError(f"rendering {path}") from e 246 - return "".join(result) 247 - def included_options(self, token: Token, tokens: Sequence[Token], i: int) -> str: 248 - conv = options.DocBookConverter(self._manpage_urls, self._revision, 'fragment', 249 - token.meta['list-id'], token.meta['id-prefix']) 250 - conv.add_options(token.meta['source']) 251 - return conv.finalize(fragment=True) 252 - 253 - # TODO minimize docbook diffs with existing conversions. remove soon. 254 - def paragraph_open(self, token: Token, tokens: Sequence[Token], i: int) -> str: 255 - return super().paragraph_open(token, tokens, i) + "\n " 256 - def paragraph_close(self, token: Token, tokens: Sequence[Token], i: int) -> str: 257 - return "\n" + super().paragraph_close(token, tokens, i) 258 - def code_block(self, token: Token, tokens: Sequence[Token], i: int) -> str: 259 - return f"<programlisting>\n{xml.escape(token.content)}</programlisting>" 260 - def fence(self, token: Token, tokens: Sequence[Token], i: int) -> str: 261 - info = f" language={xml.quoteattr(token.info)}" if token.info != "" else "" 262 - return f"<programlisting{info}>\n{xml.escape(token.content)}</programlisting>" 263 - 264 - class DocBookConverter(BaseConverter[ManualDocBookRenderer]): 265 - INCLUDE_ARGS_NS = "docbook" 266 - 267 - def __init__(self, manpage_urls: Mapping[str, str], revision: str): 268 - super().__init__() 269 - self._renderer = ManualDocBookRenderer('book', revision, manpage_urls) 270 - 271 202 272 203 class HTMLParameters(NamedTuple): 273 204 generator: str ··· 457 388 f' </span>' 458 389 f'</dt>' 459 390 ) 460 - # we want to look straight through parts because docbook-xsl does too, but it 391 + # we want to look straight through parts because docbook-xsl did too, but it 461 392 # also makes for more uesful top-level tocs. 462 393 next_level = walk_and_emit(child, depth - (0 if child.kind == 'part' else 1)) 463 394 if next_level: ··· 477 408 '</div>' 478 409 ) 479 410 # we don't want to generate the "Title of Contents" header for sections, 480 - # docbook doesn't and it's only distracting clutter unless it's the main table. 411 + # docbook didn't and it's only distracting clutter unless it's the main table. 481 412 # we also want to generate tocs only for a top-level section (ie, one that is 482 413 # not itself contained in another section) 483 414 print_title = toc.kind != 'section' ··· 506 437 ]) 507 438 508 439 def _make_hN(self, level: int) -> tuple[str, str]: 509 - # for some reason chapters don't increase the hN nesting count in docbook xslts. duplicate 510 - # this for consistency. 440 + # for some reason chapters didn't increase the hN nesting count in docbook xslts. 441 + # originally this was duplicated here for consistency with docbook rendering, but 442 + # it could be reevaluated and changed now that docbook is gone. 511 443 if self._toplevel_tag == 'chapter': 512 444 level -= 1 513 - # TODO docbook compat. these are never useful for us, but not having them breaks manual 514 - # compare workflows while docbook is still allowed. 445 + # this style setting is also for docbook compatibility only and could well go away. 515 446 style = "" 516 447 if level + self._hlevel_offset < 3 \ 517 448 and (self._toplevel_tag == 'section' or (self._toplevel_tag == 'chapter' and level > 0)): ··· 537 468 if into: 538 469 toc = TocEntry.of(fragments[0][0][0]) 539 470 inner.append(self._file_header(toc)) 540 - # we do not set _hlevel_offset=0 because docbook doesn't either. 471 + # we do not set _hlevel_offset=0 because docbook didn't either. 541 472 else: 542 473 inner = outer 543 474 in_dir = self._in_dir ··· 742 673 743 674 744 675 745 - def _build_cli_db(p: argparse.ArgumentParser) -> None: 746 - p.add_argument('--manpage-urls', required=True) 747 - p.add_argument('--revision', required=True) 748 - p.add_argument('infile', type=Path) 749 - p.add_argument('outfile', type=Path) 750 - 751 676 def _build_cli_html(p: argparse.ArgumentParser) -> None: 752 677 p.add_argument('--manpage-urls', required=True) 753 678 p.add_argument('--revision', required=True) ··· 761 686 p.add_argument('infile', type=Path) 762 687 p.add_argument('outfile', type=Path) 763 688 764 - def _run_cli_db(args: argparse.Namespace) -> None: 765 - with open(args.manpage_urls, 'r') as manpage_urls: 766 - md = DocBookConverter(json.load(manpage_urls), args.revision) 767 - md.convert(args.infile, args.outfile) 768 - 769 689 def _run_cli_html(args: argparse.Namespace) -> None: 770 690 with open(args.manpage_urls, 'r') as manpage_urls: 771 691 md = HTMLConverter( ··· 777 697 778 698 def build_cli(p: argparse.ArgumentParser) -> None: 779 699 formats = p.add_subparsers(dest='format', required=True) 780 - _build_cli_db(formats.add_parser('docbook')) 781 700 _build_cli_html(formats.add_parser('html')) 782 701 783 702 def run_cli(args: argparse.Namespace) -> None: 784 - if args.format == 'docbook': 785 - _run_cli_db(args) 786 - elif args.format == 'html': 703 + if args.format == 'html': 787 704 _run_cli_html(args) 788 705 else: 789 706 raise RuntimeError('format not hooked up', args)
+17
pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/manual_structure.py
··· 201 201 while len(entries) > 1: 202 202 entries[-2][1].children.append(entries.pop()[1]) 203 203 return (entries[0][1], examples, figures) 204 + 205 + _xml_id_translate_table = { 206 + ord('*'): ord('_'), 207 + ord('<'): ord('_'), 208 + ord(' '): ord('_'), 209 + ord('>'): ord('_'), 210 + ord('['): ord('_'), 211 + ord(']'): ord('_'), 212 + ord(':'): ord('_'), 213 + ord('"'): ord('_'), 214 + } 215 + # this function is needed to generate option id attributes in the same format as 216 + # the docbook toolchain did to not break existing links. we don't actually use 217 + # xml any more, that's just the legacy we're dealing with and part of our structure 218 + # now. 219 + def make_xml_id(s: str) -> str: 220 + return s.translate(_xml_id_translate_table)
+2 -123
pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/options.py
··· 17 17 from . import parallel 18 18 from .asciidoc import AsciiDocRenderer, asciidoc_escape 19 19 from .commonmark import CommonMarkRenderer 20 - from .docbook import DocBookRenderer, make_xml_id 21 20 from .html import HTMLRenderer 22 21 from .manpage import ManpageRenderer, man_escape 23 - from .manual_structure import XrefTarget 22 + from .manual_structure import make_xml_id, XrefTarget 24 23 from .md import Converter, md_escape, md_make_code 25 24 from .types import OptionLoc, Option, RenderedOption 26 25 ··· 184 183 def example_open(self, token: Token, tokens: Sequence[Token], i: int) -> str: 185 184 raise RuntimeError("md token not supported in options doc", token) 186 185 187 - class OptionsDocBookRenderer(OptionDocsRestrictions, DocBookRenderer): 188 - # TODO keep optionsDocBook diff small. remove soon if rendering is still good. 189 - def ordered_list_open(self, token: Token, tokens: Sequence[Token], i: int) -> str: 190 - token.meta['compact'] = False 191 - return super().ordered_list_open(token, tokens, i) 192 - def bullet_list_open(self, token: Token, tokens: Sequence[Token], i: int) -> str: 193 - token.meta['compact'] = False 194 - return super().bullet_list_open(token, tokens, i) 195 - 196 - class DocBookConverter(BaseConverter[OptionsDocBookRenderer]): 197 - __option_block_separator__ = "" 198 - 199 - def __init__(self, manpage_urls: Mapping[str, str], 200 - revision: str, 201 - document_type: str, 202 - varlist_id: str, 203 - id_prefix: str): 204 - super().__init__(revision) 205 - self._renderer = OptionsDocBookRenderer(manpage_urls) 206 - self._document_type = document_type 207 - self._varlist_id = varlist_id 208 - self._id_prefix = id_prefix 209 - 210 - def _parallel_render_prepare(self) -> Any: 211 - return (self._renderer._manpage_urls, self._revision, self._document_type, 212 - self._varlist_id, self._id_prefix) 213 - @classmethod 214 - def _parallel_render_init_worker(cls, a: Any) -> DocBookConverter: 215 - return cls(*a) 216 - 217 - def _related_packages_header(self) -> list[str]: 218 - return [ 219 - "<para>", 220 - " <emphasis>Related packages:</emphasis>", 221 - "</para>", 222 - ] 223 - 224 - def _decl_def_header(self, header: str) -> list[str]: 225 - return [ 226 - f"<para><emphasis>{header}:</emphasis></para>", 227 - "<simplelist>" 228 - ] 229 - 230 - def _decl_def_entry(self, href: Optional[str], name: str) -> list[str]: 231 - if href is not None: 232 - href = " xlink:href=" + xml.quoteattr(href) 233 - return [ 234 - f"<member><filename{href}>", 235 - xml.escape(name), 236 - "</filename></member>" 237 - ] 238 - 239 - def _decl_def_footer(self) -> list[str]: 240 - return [ "</simplelist>" ] 241 - 242 - def finalize(self, *, fragment: bool = False) -> str: 243 - result = [] 244 - 245 - if not fragment: 246 - result.append('<?xml version="1.0" encoding="UTF-8"?>') 247 - if self._document_type == 'appendix': 248 - result += [ 249 - '<appendix xmlns="http://docbook.org/ns/docbook"', 250 - ' xml:id="appendix-configuration-options">', 251 - ' <title>Configuration Options</title>', 252 - ] 253 - result += [ 254 - '<variablelist xmlns:xlink="http://www.w3.org/1999/xlink"', 255 - ' xmlns:nixos="tag:nixos.org"', 256 - ' xmlns="http://docbook.org/ns/docbook"', 257 - f' xml:id="{self._varlist_id}">', 258 - ] 259 - 260 - for (name, opt) in self._sorted_options(): 261 - id = make_xml_id(self._id_prefix + name) 262 - result += [ 263 - "<varlistentry>", 264 - # NOTE adding extra spaces here introduces spaces into xref link expansions 265 - (f"<term xlink:href={xml.quoteattr('#' + id)} xml:id={xml.quoteattr(id)}>" + 266 - f"<option>{xml.escape(name)}</option></term>"), 267 - "<listitem>" 268 - ] 269 - result += opt.lines 270 - result += [ 271 - "</listitem>", 272 - "</varlistentry>" 273 - ] 274 - 275 - result.append("</variablelist>") 276 - if self._document_type == 'appendix': 277 - result.append("</appendix>") 278 - 279 - return "\n".join(result) 280 - 281 186 class OptionsManpageRenderer(OptionDocsRestrictions, ManpageRenderer): 282 187 pass 283 188 ··· 578 483 579 484 return "\n".join(result) 580 485 581 - def _build_cli_db(p: argparse.ArgumentParser) -> None: 582 - p.add_argument('--manpage-urls', required=True) 583 - p.add_argument('--revision', required=True) 584 - p.add_argument('--document-type', required=True) 585 - p.add_argument('--varlist-id', required=True) 586 - p.add_argument('--id-prefix', required=True) 587 - p.add_argument("infile") 588 - p.add_argument("outfile") 589 - 590 486 def _build_cli_manpage(p: argparse.ArgumentParser) -> None: 591 487 p.add_argument('--revision', required=True) 592 488 p.add_argument("--header", type=Path) ··· 606 502 p.add_argument("infile") 607 503 p.add_argument("outfile") 608 504 609 - def _run_cli_db(args: argparse.Namespace) -> None: 610 - with open(args.manpage_urls, 'r') as manpage_urls: 611 - md = DocBookConverter( 612 - json.load(manpage_urls), 613 - revision = args.revision, 614 - document_type = args.document_type, 615 - varlist_id = args.varlist_id, 616 - id_prefix = args.id_prefix) 617 - 618 - with open(args.infile, 'r') as f: 619 - md.add_options(json.load(f)) 620 - with open(args.outfile, 'w') as f: 621 - f.write(md.finalize()) 622 - 623 505 def _run_cli_manpage(args: argparse.Namespace) -> None: 624 506 header = None 625 507 footer = None ··· 663 545 664 546 def build_cli(p: argparse.ArgumentParser) -> None: 665 547 formats = p.add_subparsers(dest='format', required=True) 666 - _build_cli_db(formats.add_parser('docbook')) 667 548 _build_cli_manpage(formats.add_parser('manpage')) 668 549 _build_cli_commonmark(formats.add_parser('commonmark')) 669 550 _build_cli_asciidoc(formats.add_parser('asciidoc')) 670 551 671 552 def run_cli(args: argparse.Namespace) -> None: 672 - if args.format == 'docbook': 673 - _run_cli_db(args) 674 - elif args.format == 'manpage': 553 + if args.format == 'manpage': 675 554 _run_cli_manpage(args) 676 555 elif args.format == 'commonmark': 677 556 _run_cli_commonmark(args)
+2 -2
pkgs/tools/nix/nixos-render-docs/src/tests/test_headings.py
··· 2 2 3 3 from markdown_it.token import Token 4 4 5 - class Converter(nrd.md.Converter[nrd.docbook.DocBookRenderer]): 5 + class Converter(nrd.md.Converter[nrd.html.HTMLRenderer]): 6 6 # actual renderer doesn't matter, we're just parsing. 7 7 def __init__(self, manpage_urls: dict[str, str]) -> None: 8 8 super().__init__() 9 - self._renderer = nrd.docbook.DocBookRenderer(manpage_urls) 9 + self._renderer = nrd.html.HTMLRenderer(manpage_urls, {}) 10 10 11 11 def test_heading_id_absent() -> None: 12 12 c = Converter({})
+2 -2
pkgs/tools/nix/nixos-render-docs/src/tests/test_lists.py
··· 3 3 4 4 from markdown_it.token import Token 5 5 6 - class Converter(nrd.md.Converter[nrd.docbook.DocBookRenderer]): 6 + class Converter(nrd.md.Converter[nrd.html.HTMLRenderer]): 7 7 # actual renderer doesn't matter, we're just parsing. 8 8 def __init__(self, manpage_urls: dict[str, str]) -> None: 9 9 super().__init__() 10 - self._renderer = nrd.docbook.DocBookRenderer(manpage_urls) 10 + self._renderer = nrd.html.HTMLRenderer(manpage_urls, {}) 11 11 12 12 @pytest.mark.parametrize("ordered", [True, False]) 13 13 def test_list_wide(ordered: bool) -> None:
+1 -1
pkgs/tools/nix/nixos-render-docs/src/tests/test_options.py
··· 4 4 import pytest 5 5 6 6 def test_option_headings() -> None: 7 - c = nixos_render_docs.options.DocBookConverter({}, 'local', 'none', 'vars', 'opt-') 7 + c = nixos_render_docs.options.HTMLConverter({}, 'local', 'vars', 'opt-', {}) 8 8 with pytest.raises(RuntimeError) as exc: 9 9 c._render("# foo") 10 10 assert exc.value.args[0] == 'md token not supported in options doc'
+2 -2
pkgs/tools/nix/nixos-render-docs/src/tests/test_plugins.py
··· 3 3 4 4 from markdown_it.token import Token 5 5 6 - class Converter(nrd.md.Converter[nrd.docbook.DocBookRenderer]): 6 + class Converter(nrd.md.Converter[nrd.html.HTMLRenderer]): 7 7 # actual renderer doesn't matter, we're just parsing. 8 8 def __init__(self, manpage_urls: dict[str, str]) -> None: 9 9 super().__init__() 10 - self._renderer = nrd.docbook.DocBookRenderer(manpage_urls) 10 + self._renderer = nrd.html.HTMLRenderer(manpage_urls, {}) 11 11 12 12 def test_attr_span_parsing() -> None: 13 13 c = Converter({})