about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorNguyễn Gia Phong <mcsinyx@disroot.org>2023-03-20 04:42:43 +0900
committerNguyễn Gia Phong <mcsinyx@disroot.org>2023-03-20 04:45:51 +0900
commit38ab620c055e31568a792cef1c278aa608db7cf8 (patch)
treee5c049a9dc833d8845c079982fb293f92c24dc09 /src
parent9897b80e34bb64b138a340acd37a44c2ab02c17c (diff)
downloadrub-38ab620c055e31568a792cef1c278aa608db7cf8.tar.gz
Switch to XML templating
Diffstat (limited to 'src')
-rw-r--r--src/rub/__init__.py62
-rw-r--r--src/rub/__main__.py21
-rw-r--r--src/rub/xml.py70
3 files changed, 115 insertions, 38 deletions
diff --git a/src/rub/__init__.py b/src/rub/__init__.py
index dd963e8..904f47a 100644
--- a/src/rub/__init__.py
+++ b/src/rub/__init__.py
@@ -1,5 +1,5 @@
 # Package initialization
-# Copyright (C) 2022  Nguyễn Gia Phong
+# Copyright (C) 2022-2023  Nguyễn Gia Phong
 #
 # This file is part of rub.
 #
@@ -16,33 +16,61 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with rub.  If not, see <https://www.gnu.org/licenses/>.
 
+from functools import cached_property
 from os import walk
-from os.path import join
 from pathlib import Path
 from shutil import copytree, rmtree
 
-from jinja2 import Environment
-from markdown_it import MarkdownIt
-from mdit_py_plugins.footnote import footnote_plugin
+from rub import xml
 
-__all__ = ['glob_files', 'markdown', 'replace']
-md = MarkdownIt('gfm-like').use(footnote_plugin)
-jinja = Environment()
+__all__ = ['Rubber', 'xml']
 
 
-def glob_files(root: Path, suffix: str = '') -> list[Path]:
+def glob_files(root: Path, suffix=''):
     """Return the list of all files in given directory, recursively."""
-    return [Path(path)/file for path, dirs, files in walk(root)
+    return [Path(path).relative_to(root)/file
+            for path, dirs, files in walk(root)
             for file in files if file.endswith(suffix)]
 
 
-def markdown(source: Path, destination: Path) -> None:
-    """Convert source Markdown to destination HTML segment."""
-    template = jinja.from_string(source.read_text())
-    destination.write_text(md.render(template.render()))
-
-
-def replace(source: Path, destination: Path) -> None:
+def replace(source: Path, destination: Path):
     """Replace destination with source directory."""
     rmtree(destination, ignore_errors=True)
     copytree(source, destination, dirs_exist_ok=True)
+
+
+class Rubber:
+    """Static generator."""
+
+    def __init__(self, generate_article, base, src, cache, out):
+        self.generate_article = generate_article
+        self.base, self.src = base, src
+        self.cache, self.out = cache, out
+
+    @cached_property
+    def tasks(self):
+        def assox():
+            for k in dir(self):
+                if not k.startswith('task_'): continue
+                v = getattr(self, k)
+                if callable(v): yield k, v
+
+        return dict(assox())
+
+    def task_base(self):
+        paths = glob_files(self.base)
+        return {'doc': 'copy base directory',
+                'file_dep': [self.base/path for path in paths],
+                'actions': [(replace, [self.base, self.out])],
+                'targets': [self.out/path for path in paths],
+                'clean': True}
+
+    def task_articles(self):
+        """process articles into XHTML"""
+        for path in glob_files(self.src, '.xml'):
+            source = self.src / path
+            destination = self.out / path
+            yield {'name': path, 'doc': f'process {path} into XHTML',
+                   'file_dep': [source],
+                   'actions': [(self.generate_article, [source, destination])],
+                   'targets': [destination], 'clean': True}
diff --git a/src/rub/__main__.py b/src/rub/__main__.py
deleted file mode 100644
index 8c17c86..0000000
--- a/src/rub/__main__.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# Package's executable
-# Copyright (C) 2022  Nguyễn Gia Phong
-#
-# This file is part of rub.
-#
-# Rub is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as published
-# by the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# Rub is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with rub.  If not, see <https://www.gnu.org/licenses/>.
-
-from doit import run
-
-if __name__ == '__main__': run(globals())
diff --git a/src/rub/xml.py b/src/rub/xml.py
new file mode 100644
index 0000000..ed61a8b
--- /dev/null
+++ b/src/rub/xml.py
@@ -0,0 +1,70 @@
+# XML processing abstractions
+# Copyright (C) 2023  Nguyễn Gia Phong
+#
+# This file is part of rub.
+#
+# Rub is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Rub is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with rub.  If not, see <https://www.gnu.org/licenses/>.
+
+from copy import deepcopy
+from functools import partial
+from pathlib import Path
+
+from lxml.etree import QName, XML, XSLT, XSLTExtension
+
+__all__ = ['NS', 'generator', 'recurse']
+
+NS = 'https://rub.parody'
+
+
+def recurse(extension, context, input_node, output_parent):
+    """Apply template recursively on input node."""
+    output = deepcopy(input_node)
+    for i in output: output.remove(i)
+    for i in input_node:
+        for j in extension.apply_templates(context, i):
+            if not isinstance(j, str):
+                output.append(deepcopy(j))
+            elif len(output) == 0:
+                if output.text is None:
+                    output.text = j
+                else:
+                    output.text += j
+            elif output[-1].tail is None:
+                output[-1].tail = j
+            else:
+                output[-1].tail += j
+    output_parent.append(output)
+
+
+class Evaluator(XSLTExtension):
+    def __init__(self, **handlers):
+        self.handlers = {QName(NS, k).text: v for k, v in handlers.items()}
+        super().__init__()
+
+    def execute(self, context, self_node, input_node, output_parent):
+        handle = self.handlers.get(input_node.tag, recurse)
+        handle(self, context, input_node, output_parent)
+
+
+def generator(xslt, **handlers):
+    """Return a function taking an XML file and apply given XSLT."""
+    stylesheet = xslt.read_bytes()
+    extensions = {(NS, 'eval'): Evaluator(**handlers)}
+    transform = XSLT(XML(stylesheet), extensions=extensions)
+
+    def make(src, dest):
+        dest.parent.mkdir(mode=0o755, parents=True, exist_ok=True)
+        dest.write_text(str(transform(XML(src.read_bytes()))))
+
+    return make