about summary refs log tree commit diff
path: root/doc/rub
diff options
context:
space:
mode:
Diffstat (limited to 'doc/rub')
-rwxr-xr-xdoc/rub95
1 files changed, 95 insertions, 0 deletions
diff --git a/doc/rub b/doc/rub
new file mode 100755
index 0000000..d1113a2
--- /dev/null
+++ b/doc/rub
@@ -0,0 +1,95 @@
+#!/usr/bin/env python3
+from contextlib import contextmanager
+from functools import partial
+from pathlib import Path
+from shutil import which
+from subprocess import PIPE, Popen
+from urllib.request import Request, urlopen
+from xml.sax.saxutils import unescape
+
+from lxml.etree import Element, XML
+from lxml.html import (fragment_fromstring as from_html_fragment,
+                       fragments_fromstring as from_html_fragments)
+from mistune import html as from_md
+from pygments import highlight
+from pygments.formatters import HtmlFormatter
+from pygments.lexers import get_lexer_by_name, guess_lexer
+from rub import rub, xml
+
+PYGMENTS_FORMATTER = HtmlFormatter(lineseparator='<br/>')
+KATEX_SERVER = '''
+var http = require('http');
+var katex = require('katex');
+
+let server = http.createServer((request, response) => {
+  if (request.method === 'POST') {
+    response.writeHead(200, 'OK');
+    var body = '';
+    request.on('data', (chunk) => body += chunk);
+    request.on('end', () => {
+      response.write(katex.renderToString(body, {
+        displayMode: request.headers['x-katex-mode'] === 'display',
+        output: 'mathml',
+      }));
+      response.end();
+    });
+  } else {
+    response.writeHead(405, 'Method Not Allowed');
+    response.end();
+  }
+});
+server.listen(() => {
+  console.log('http://localhost:' + server.address().port);
+});
+'''
+
+
+def mistune(extension, context, input_node, output_parent):
+    """Render Markdown to HTML."""
+    tmp = Element('tmp')
+    xml.recurse(extension, context, input_node, tmp)
+    tmp[0].text = input_node.text
+    text = xml.serialize_content(tmp[0]).decode()
+    for i in from_html_fragments(from_md(unescape(text))):
+        extension.apply_templates(context, i, output_parent)
+
+
+def pygments(extension, context, input_node, output_parent):
+    """Highlight code syntax in HTML."""
+    code = input_node.text
+    lang = input_node.get('lang')
+    lexer = guess_lexer(code) if lang is None else get_lexer_by_name(lang)
+    output = from_html_fragment(highlight(code, lexer, PYGMENTS_FORMATTER))
+    output.tail = input_node.tail
+    output_parent.append(output)
+
+
+def katex(extension, context, input_node, output_parent, url, display=False):
+    """Render LaTeX to content MathML."""
+    with urlopen(Request(url, xml.serialize_content(input_node),
+                         {'X-KaTeX-Mode': 'display'} if display else {})) as r:
+        output = XML(r.read())[0]  # remove span[@class='katex']
+        output.tail = input_node.tail
+        output_parent.append(output)
+
+
+@contextmanager
+def katex_server():
+    server = Popen(which('node'), stdin=PIPE, stdout=PIPE, text=True)
+    try:
+        server.stdin.write(KATEX_SERVER)
+        server.stdin.close()
+        yield partial(katex, url=server.stdout.readline().strip())
+    finally:
+        server.kill()
+
+
+with katex_server() as mathml:
+    wd = Path(__file__).parent
+    rub(xml.Processor(wd/'html.xslt',
+                      partial(Path.with_suffix, suffix='.html'),
+                      markdown=mistune, highlight=pygments,
+                      math=partial(mathml, display=True), m=mathml),
+        xml.Processor(wd/'rss.xslt',
+                      partial(Path.with_name, name='index.xml')),
+        wd/'base', wd/'pages', wd/'cache', wd/'out')