summaryrefslogtreecommitdiff
path: root/projgraph
blob: db1707791f142f86dbb095c585d6775834d4b0f0 (plain)
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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#!/usr/bin/python3

import sys

import yaml


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'},
        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)


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)
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('}')