about summary refs log tree commit diff
path: root/doc/rub
blob: d1113a2cec049b53837bc865da26ed143be7fa0d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
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')