diff options
Diffstat (limited to 'nixos/lib/make-options-doc/mergeJSON.py')
-rw-r--r-- | nixos/lib/make-options-doc/mergeJSON.py | 146 |
1 files changed, 145 insertions, 1 deletions
diff --git a/nixos/lib/make-options-doc/mergeJSON.py b/nixos/lib/make-options-doc/mergeJSON.py index 44a188a08c99..9510b1e59a20 100644 --- a/nixos/lib/make-options-doc/mergeJSON.py +++ b/nixos/lib/make-options-doc/mergeJSON.py @@ -41,6 +41,150 @@ def unpivot(options: Dict[Key, Option]) -> Dict[str, JSON]: result[opt.name] = opt.value return result +# converts in-place! +def convertMD(options: Dict[str, Any]) -> str: + import mistune + import re + from xml.sax.saxutils import escape, quoteattr + + admonitions = { + '.warning': 'warning', + '.important': 'important', + '.note': 'note' + } + class Renderer(mistune.renderers.BaseRenderer): + def _get_method(self, name): + try: + return super(Renderer, self)._get_method(name) + except AttributeError: + def not_supported(children, **kwargs): + raise NotImplementedError("md node not supported yet", name, children, **kwargs) + return not_supported + + def text(self, text): + return escape(text) + def paragraph(self, text): + return text + "\n\n" + def codespan(self, text): + return f"<literal>{text}</literal>" + def block_code(self, text, info=None): + info = f" language={quoteattr(info)}" if info is not None else "" + return f"<programlisting{info}>\n{text}</programlisting>" + def link(self, link, text=None, title=None): + if link[0:1] == '#': + attr = "linkend" + link = quoteattr(link[1:]) + else: + # try to faithfully reproduce links that were of the form <link href="..."/> + # in docbook format + if text == link: + text = "" + attr = "xlink:href" + link = quoteattr(link) + return f"<link {attr}={link}>{text}</link>" + def list(self, text, ordered, level, start=None): + if ordered: + raise NotImplementedError("ordered lists not supported yet") + return f"<itemizedlist>\n{text}\n</itemizedlist>" + def list_item(self, text, level): + return f"<listitem><para>{text}</para></listitem>\n" + def block_text(self, text): + return text + def emphasis(self, text): + return f"<emphasis>{text}</emphasis>" + def strong(self, text): + return f"<emphasis role=\"strong\">{text}</emphasis>" + def admonition(self, text, kind): + if kind not in admonitions: + raise NotImplementedError(f"admonition {kind} not supported yet") + tag = admonitions[kind] + # we don't keep whitespace here because usually we'll contain only + # a single paragraph and the original docbook string is no longer + # available to restore the trailer. + return f"<{tag}><para>{text.rstrip()}</para></{tag}>" + def command(self, text): + return f"<command>{escape(text)}</command>" + def option(self, text): + return f"<option>{escape(text)}</option>" + def file(self, text): + return f"<filename>{escape(text)}</filename>" + def manpage(self, page, section): + title = f"<refentrytitle>{escape(page)}</refentrytitle>" + vol = f"<manvolnum>{escape(section)}</manvolnum>" + return f"<citerefentry>{title}{vol}</citerefentry>" + + def finalize(self, data): + return "".join(data) + + plugins = [] + + COMMAND_PATTERN = r'\{command\}`(.*?)`' + def command(md): + def parse(self, m, state): + return ('command', m.group(1)) + md.inline.register_rule('command', COMMAND_PATTERN, parse) + md.inline.rules.append('command') + plugins.append(command) + + FILE_PATTERN = r'\{file\}`(.*?)`' + def file(md): + def parse(self, m, state): + return ('file', m.group(1)) + md.inline.register_rule('file', FILE_PATTERN, parse) + md.inline.rules.append('file') + plugins.append(file) + + OPTION_PATTERN = r'\{option\}`(.*?)`' + def option(md): + def parse(self, m, state): + return ('option', m.group(1)) + md.inline.register_rule('option', OPTION_PATTERN, parse) + md.inline.rules.append('option') + plugins.append(option) + + MANPAGE_PATTERN = r'\{manpage\}`(.*?)\((.+?)\)`' + def manpage(md): + def parse(self, m, state): + return ('manpage', m.group(1), m.group(2)) + md.inline.register_rule('manpage', MANPAGE_PATTERN, parse) + md.inline.rules.append('manpage') + plugins.append(manpage) + + ADMONITION_PATTERN = re.compile(r'^::: \{([^\n]*?)\}\n(.*?)^:::\n', flags=re.MULTILINE|re.DOTALL) + def admonition(md): + def parse(self, m, state): + return { + 'type': 'admonition', + 'children': self.parse(m.group(2), state), + 'params': [ m.group(1) ], + } + md.block.register_rule('admonition', ADMONITION_PATTERN, parse) + md.block.rules.append('admonition') + plugins.append(admonition) + + def convertString(text: str) -> str: + rendered = mistune.markdown(text, renderer=Renderer(), plugins=plugins) + # keep trailing spaces so we can diff the generated XML to check for conversion bugs. + return rendered.rstrip() + text[len(text.rstrip()):] + + def optionIs(option: Dict[str, Any], key: str, typ: str) -> bool: + if key not in option: return False + if type(option[key]) != dict: return False + if '_type' not in option[key]: return False + return option[key]['_type'] == typ + + for (name, option) in options.items(): + if optionIs(option, 'description', 'mdDoc'): + option['description'] = convertString(option['description']['text']) + if optionIs(option, 'example', 'literalMD'): + docbook = convertString(option['example']['text']) + option['example'] = { '_type': 'literalDocBook', 'text': docbook } + if optionIs(option, 'default', 'literalMD'): + docbook = convertString(option['default']['text']) + option['default'] = { '_type': 'literalDocBook', 'text': docbook } + + return options + warningsAreErrors = sys.argv[1] == "--warnings-are-errors" optOffset = 1 if warningsAreErrors else 0 options = pivot(json.load(open(sys.argv[1 + optOffset], 'r'))) @@ -92,4 +236,4 @@ if hasWarnings and warningsAreErrors: file=sys.stderr) sys.exit(1) -json.dump(unpivot(options), fp=sys.stdout) +json.dump(convertMD(unpivot(options)), fp=sys.stdout) |