about summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
authorpennae <github@quasiparticle.net>2022-08-03 00:44:34 +0200
committerpennae <github@quasiparticle.net>2022-08-03 22:00:24 +0200
commit7a091b2686ff9997cc403bb5cecb4cb93e916c1b (patch)
tree85a5aa0ded6d02d765ce8b0d9129c71e2319bae1 /nixos
parent3aebb4a2be8821a6d8a695f0908d8567dc00de31 (diff)
nixos/make-options-doc: reuse markdown instance in mergeJSON
this doesn't construct a new (expensive) parser for every option, making
rendering about 30x faster.
Diffstat (limited to 'nixos')
-rw-r--r--nixos/lib/make-options-doc/mergeJSON.py240
1 files changed, 120 insertions, 120 deletions
diff --git a/nixos/lib/make-options-doc/mergeJSON.py b/nixos/lib/make-options-doc/mergeJSON.py
index e95352f4fe6f4..8fb0b65c82d8c 100644
--- a/nixos/lib/make-options-doc/mergeJSON.py
+++ b/nixos/lib/make-options-doc/mergeJSON.py
@@ -3,6 +3,11 @@ import json
 import sys
 from typing import Any, Dict, List
 
+# for MD conversion
+import mistune
+import re
+from xml.sax.saxutils import escape, quoteattr
+
 JSON = Dict[str, Any]
 
 class Key:
@@ -41,137 +46,132 @@ 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 __init__(self, path):
-            self.path = path
-        def _get_method(self, name):
-            try:
-                return super(Renderer, self)._get_method(name)
-            except AttributeError:
-                def not_supported(*args, **kwargs):
-                    raise NotImplementedError("md node not supported yet", self.path, name, args, **kwargs)
-                return not_supported
-
-        def text(self, text):
-            return escape(text)
-        def paragraph(self, text):
-            return text + "\n\n"
-        def newline(self):
-            return "<literallayout>\n</literallayout>"
-        def codespan(self, text):
-            return f"<literal>{escape(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{escape(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 block_quote(self, text):
-            return f"<blockquote><para>{text}</para></blockquote>"
-        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 = []
-
+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(*args, **kwargs):
+                raise NotImplementedError("md node not supported yet", name, args, **kwargs)
+            return not_supported
+
+    def text(self, text):
+        return escape(text)
+    def paragraph(self, text):
+        return text + "\n\n"
+    def newline(self):
+        return "<literallayout>\n</literallayout>"
+    def codespan(self, text):
+        return f"<literal>{escape(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{escape(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 block_quote(self, text):
+        return f"<blockquote><para>{text}</para></blockquote>"
+    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)
+
+def p_command(md):
     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)
+    def parse(self, m, state):
+        return ('command', m.group(1))
+    md.inline.register_rule('command', COMMAND_PATTERN, parse)
+    md.inline.rules.append('command')
 
+def p_file(md):
     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)
+    def parse(self, m, state):
+        return ('file', m.group(1))
+    md.inline.register_rule('file', FILE_PATTERN, parse)
+    md.inline.rules.append('file')
 
+def p_option(md):
     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)
+    def parse(self, m, state):
+        return ('option', m.group(1))
+    md.inline.register_rule('option', OPTION_PATTERN, parse)
+    md.inline.rules.append('option')
 
+def p_manpage(md):
     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)
+    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')
 
+def p_admonition(md):
     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 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')
+
+md = mistune.create_markdown(renderer=Renderer(), plugins=[
+    p_command, p_file, p_option, p_manpage, p_admonition
+])
 
+# converts in-place!
+def convertMD(options: Dict[str, Any]) -> str:
     def convertString(path: str, text: str) -> str:
-        rendered = mistune.markdown(text, renderer=Renderer(path), plugins=plugins)
-        # keep trailing spaces so we can diff the generated XML to check for conversion bugs.
-        return rendered.rstrip() + text[len(text.rstrip()):]
+        try:
+            rendered = md(text)
+            # keep trailing spaces so we can diff the generated XML to check for conversion bugs.
+            return rendered.rstrip() + text[len(text.rstrip()):]
+        except:
+            print(f"error in {path}")
+            raise
 
     def optionIs(option: Dict[str, Any], key: str, typ: str) -> bool:
         if key not in option: return False