#!/usr/bin/python3 import sys import textwrap import yaml LABEL_WIDTH = 20 unknown = 0 blocked = 1 finished = 2 ready = 3 next = 4 goal = 5 statuses = { 'blocked': blocked, 'finished': finished, 'ready': ready, 'next': next, 'goal': goal, } def nodeattrs(done): attrs = { unknown: {'fillcolor': '#ff0000', 'shape': 'diamond'}, blocked: {'fillcolor': '#f4bada', 'shape': 'rectangle'}, finished: {'fillcolor': '#eeeeee', 'shape': 'circle'}, ready: {'fillcolor': '#ffffff'}, next: {'fillcolor': '#00cc00'}, goal: {'fillcolor': '#00eeee', 'shape': 'diamond'}, } a = dict(attrs[done]) if 'style' not in a: a['style'] = 'filled' return ' '.join('{}="{}"'.format(key, a[key]) for key in a) def find_unknown(tasklist): return [t for t in tasklist if t['status'] == unknown] def all_deps(tasks, task, status): for dep_name in task.get('depends', []): dep = tasks[dep_name] if dep['status'] != status: return False return True def any_dep(tasks, task, status): for dep_name in task.get('depends', []): dep = tasks[dep_name] if dep['status'] == status: return True return False def add_missing(tasks): missing = {} for task in tasks.values(): for dep in task.get('depends', []): if dep not in tasks: missing[dep] = { 'label': '{} IS MISSING'.format(dep), } tasks.update(missing) def add_parents(tasks): for task in tasks.values(): for dep in task.get('depends', []): assert dep in tasks dep = tasks[dep] dep['parents'] = dep.get('parents', []) + [task] def mark_goals(tasks): for task in tasks.values(): if 'status' not in task: if not task.get('parents'): task['status'] = 'goal' def set_status(tasks): add_missing(tasks) add_parents(tasks) mark_goals(tasks) tasklist = list(tasks.values()) for task in tasklist: if 'status' not in task: task['status'] = unknown else: task['status'] = statuses[task['status']] deps = [ dep for dep in task.get('depends', []) if dep in tasks ] task['depends'] = deps # for task in tasklist: # print(task['status'], ' '.join(task['label'].split())) # sys.exit(0) unknown_tasks = find_unknown(tasklist) while unknown_tasks: for t in unknown_tasks: if not t.get('depends', []): t['status'] = ready else: if all_deps(tasks, t, finished): t['status'] = ready else: t['status'] = blocked unknown_tasks = find_unknown(tasklist) def wrap_labels(tasks): for name in tasks: task = tasks[name] if 'label' in task: label = task['label'] if '\n' not in label: label = textwrap.wrap(label, width=LABEL_WIDTH) label = '\n'.join(label) task['label'] = label obj = yaml.safe_load(sys.stdin) ikiwiki = sys.argv[1:] == ['ikiwiki'] if ikiwiki: print('[[!graph src="""') else: print('digraph "project" {') tasks = obj.get('tasks', obj) wrap_labels(tasks) set_status(tasks) for name, task in tasks.items(): print('{} [label="{}"]'.format(name, task['label'])) status = task['status'] print('{} [{}]'.format(name, nodeattrs(status))) for dep in task.get('depends', []): print('{} -> {}'.format(dep, name)) if ikiwiki: print('"""]]') else: print('}')