summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@qvarnlabs.com>2017-12-18 12:28:42 +0200
committerLars Wirzenius <liw@qvarnlabs.com>2017-12-18 12:28:42 +0200
commitaffce30bc41659bbfd60646db94fde37152f20c6 (patch)
tree93b2c7444d2cdbdf75d3756a120b1f13cce61e59
parent20b6327e856e8bfc83b01320056cb2f73c1e9ab5 (diff)
downloadql-ikiwiki-publish-affce30bc41659bbfd60646db94fde37152f20c6.tar.gz
Add: ql-ikiwiki-preprocess
-rwxr-xr-xql-ikiwiki-preprocess236
1 files changed, 236 insertions, 0 deletions
diff --git a/ql-ikiwiki-preprocess b/ql-ikiwiki-preprocess
new file mode 100755
index 0000000..2803043
--- /dev/null
+++ b/ql-ikiwiki-preprocess
@@ -0,0 +1,236 @@
+#!/usr/bin/env python3
+# Copyright 2017 QvarnLabs Ab
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# =*= License: GPL-3+ =*=
+
+
+import os
+import shutil
+import subprocess
+import sys
+import tempfile
+
+import cliapp
+
+
+def write_temp(content, suffix, tgtdir):
+ fd, filename = tempfile.mkstemp(dir=tgtdir, suffix=suffix)
+ os.close(fd)
+ with open(filename, 'w') as f:
+ f.write(content)
+ return filename
+
+
+def get_uml_title(lines):
+ prefix = 'title '
+ for line in lines:
+ line = line.strip()
+ if line.startswith(prefix):
+ return line[len(prefix):]
+ return 'title'
+
+
+def format_uml(filename):
+ subprocess.check_call(['plantuml', '-tpng', '-output', '.', filename])
+
+
+def remove_prefixes(lines):
+ prefix = ' ' * 4
+ result = []
+ for line in lines:
+ if line.startswith(prefix):
+ line = line[len(prefix):]
+ result.append(line)
+ return ''.join(result)
+
+
+def process_uml(lines, tgtdir):
+ title = get_uml_title(lines)
+ uml_text = remove_prefixes(lines)
+ filename = write_temp(uml_text, '.uml', tgtdir)
+ format_uml(filename)
+ prefix, _ = os.path.splitext(filename)
+ image = prefix + '.png'
+ relative = make_relative(os.path.abspath(tgtdir), image)
+ include = '[[!img {}]]\n'.format(relative)
+ return [include]
+
+
+def format_roadmap(text):
+ input = text.encode('utf-8')
+ output = cliapp.runcmd(['projgraph'], feed_stdin=input)
+ output_text = output.decode('utf-8')
+ output_lines = output_text.splitlines()
+ return '\n'.join(output_lines[1:-1])
+
+
+def process_roadmap(lines, tgtdir):
+ roadmap_text = remove_prefixes(lines[1:-1])
+ include = format_roadmap(roadmap_text)
+ return ['[[!graph src="""\n', include, '"""]]\n']
+
+
+def read_lines(filename):
+ with open(filename) as f:
+ return f.readlines()
+
+
+def write_lines(filename, lines):
+ with open(filename, 'w') as f:
+ f.write(''.join(lines))
+
+
+def line_matches(line, patterns):
+ prefix = ' ' * 4
+ for i, pattern in enumerate(patterns):
+ if line in [pattern, prefix + pattern]:
+ return i
+ return None
+
+
+def find_match(lines, patterns):
+ for i, line in enumerate(lines):
+ j = line_matches(line, patterns)
+ if j is not None:
+ return i, j
+ else:
+ return None, None
+
+
+def find_section(lines, sections):
+ starters = [x for x, _, _ in sections]
+ enders = [x for _, x, _ in sections]
+ funcs = [x for _, _, x in sections]
+
+ first, section = find_match(lines, starters)
+ if first is not None:
+ last, _ = find_match(lines, enders[section:section+1])
+ if last is not None:
+ return first, last+1, funcs[section]
+ return 0, len(lines), None
+
+
+def preprocess_lines(lines, tgtdir):
+ sections = [
+ ('@startuml\n', '@enduml\n', process_uml),
+ ('@startroadmap\n', '@endroadmap\n', process_roadmap),
+ ]
+
+ result = []
+ while lines:
+ start, after, func = find_section(lines, sections)
+ result.extend(lines[:start])
+ section = lines[start:after]
+ if func:
+ include = func(section, tgtdir)
+ result.extend(include)
+ else:
+ result.extend(section)
+ lines = lines[after:]
+ return result
+
+
+def preprocess_mdwn(src, tgt):
+ lines = read_lines(src)
+ tgtdir = os.path.dirname(tgt)
+ lines = preprocess_lines(lines, tgtdir)
+ write_lines(tgt, lines)
+ shutil.copystat(src, tgt)
+
+
+def copy_file(src, tgt):
+ shutil.copy(src, tgt)
+ shutil.copystat(src, tgt)
+
+
+def preprocess_file(src, tgt):
+ if src.endswith('.mdwn'):
+ preprocess_mdwn(src, tgt)
+ else:
+ copy_file(src, tgt)
+
+
+def copy_dir(src, tgt):
+ os.makedirs(tgt)
+ return lambda: shutil.copystat(src, tgt)
+
+
+def acceptable(basename):
+ return basename != '.git'
+
+
+def make_relative(dirname, pathname):
+ prefix = dirname
+ if not prefix.endswith('/'):
+ prefix += '/'
+
+ assert pathname == dirname or pathname.startswith(prefix)
+ if pathname == dirname:
+ return '.'
+ else:
+ return pathname[len(prefix):]
+
+
+def find_all(root):
+ for dirpath, subdirs, filenames in os.walk(root):
+ yield dirpath
+ for x in subdirs[:]:
+ if not acceptable(x):
+ subdirs.remove(x)
+ for basename in filenames:
+ yield os.path.join(dirpath, basename)
+
+
+def find_relative(dirname):
+ for pathname in find_all(dirname):
+ yield make_relative(dirname, pathname)
+
+
+def make_absolute(root, relative):
+ return os.path.join(root, relative)
+
+
+def preprocess(srcdir, tgtdir):
+ preprocessors = [
+ (os.path.isfile, preprocess_file),
+ (os.path.isdir, copy_dir),
+ ]
+
+ assert os.path.isdir(srcdir)
+ assert not os.path.exists(tgtdir)
+ todo = []
+ for pathname in find_relative(srcdir):
+ src = make_absolute(srcdir, pathname)
+ tgt = make_absolute(tgtdir, pathname)
+
+ for func, callback in preprocessors:
+ if func(src):
+ do = callback(src, tgt)
+ if do is not None:
+ todo.append(do)
+
+ for do in todo:
+ do()
+
+
+def main():
+ srcdir = sys.argv[1]
+ tgtdir = sys.argv[2]
+ preprocess(srcdir, tgtdir)
+
+
+if __name__ == '__main__':
+ main()