#!/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 . # # =*= 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()