From 1a97aa3953758ef6965d71b88c15963b473da2d3 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Tue, 1 Sep 2020 11:59:28 +0200 Subject: Add gannt generation tool --- .arcconfig | 1 + gannt.py | 222 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 223 insertions(+) create mode 120000 .arcconfig create mode 100755 gannt.py diff --git a/.arcconfig b/.arcconfig new file mode 120000 index 0000000..b947811 --- /dev/null +++ b/.arcconfig @@ -0,0 +1 @@ +../dill/.arcconfig \ No newline at end of file diff --git a/gannt.py b/gannt.py new file mode 100755 index 0000000..393aed7 --- /dev/null +++ b/gannt.py @@ -0,0 +1,222 @@ +#!/usr/bin/env python3 +import os +import sys +import time +from collections import defaultdict +from datetime import datetime, timedelta +from pprint import pp + +import plantuml +from phabricator import Phabricator + +p = plantuml.PlantUML(url="http://www.plantuml.com/plantuml/svg/") + +preamble = """ +@startgantt +Project starts at 9th of July 2020 +saturday are closed +sunday are closed +""" +# 2020-07-27 to 2020-08-2 is closed + +postamble = """ +@endgantt +""" + + +def maxi(day): + #if 18 > day < 22: + # return 0 + wday = day % 7 + if wday in (0, 4): + return 6 + if wday in (2, 3): + return 1 + return 3 + + +pstart = datetime(2020, 7, 9) + + +def inf(n, v): + while v: + yield n + n += 1 + + +def generate_svg(name, text): + print(f"generating: {text}") + try: + res = p.processes(plantuml_text=(text)) + except Exception: + print(p.get_url(plantuml_text=(text))) + return + + svg = "/tmp/" + name + ".svg" + pdf = "/tmp/" + name + ".pdf" + open(svg, "wb").write(res) + os.system(f"inkscape {svg} -o {pdf}") + + +phab = Phabricator() # This will use your ~/.arcrc file +phab.user.whoami() + +tasks = phab.maniphest.search(queryKey="all") +ids = {task["phid"]: task["id"] for task in tasks.data} +vertices = {} +tree = defaultdict(list) +for task in tasks.data: + if task["id"] < 5: + continue + itask = dict(phab.maniphest.info(task_id=task["id"])) + for k, v in task.items(): + itask[k] = v + itask["weight"] = 3**(itask["fields"]["priority"]["value"] / 10) + vertices[task["id"]] = itask + parents = itask["dependsOnTaskPHIDs"] + for parent in parents: + if parent in ids: + tree[task["id"]].append(ids[parent]) +tree = defaultdict(list, { + vertices[k]["id"]: [vertices[t]["id"] for t in v] + for k, v in tree.items() +}) +newtree = defaultdict(list) +for k, v in tree.items(): + for i in v: + newtree[i].append(k) +print(newtree) + +diffs = {} +diff = phab.differential.query() +for d in diff: + for line in d["summary"].split("\n"): + try: + t = int(line.split("T")[-1]) + diffs[t] = int(d["dateCreated"]) + except: + pass + +weighted = sorted(vertices.keys(), key=lambda t: (-vertices[t]["weight"], t)) +dependent = [] +for v in vertices: + item = None + for n, a in enumerate(weighted): + if all(i in dependent for i in tree[a]): + item = n + break + if item is None: + print("err: o nowes") + nr = weighted.pop(item) + dependent.append(nr) +for i in reversed(dependent): + for k in tree[i]: + vertices[k]["weight"] += (vertices[i]["weight"] / len(tree[i])) +available = sorted(vertices.keys(), key=lambda t: (-vertices[t]["weight"], t)) +done = [] +sstart = (datetime.now() - pstart).days + +for k, v in vertices.items(): + v["title"] = (" " * 12) + v["title"] + v["due"] = sstart + 1 + if v["isClosed"]: + v["start"] = (datetime.fromtimestamp(int(v["fields"]["dateClosed"])) - + pstart).days - 2 + if k in diffs: + v["start"] = (datetime.fromtimestamp(diffs[k]) - pstart).days + else: + v["start"] = (datetime.fromtimestamp(int( + v["fields"]["dateClosed"])) - pstart).days - 2 + v["due"] = (datetime.fromtimestamp(int(v["fields"]["dateClosed"])) - + pstart).days + done.insert(0, k) + available.remove(k) + if k in diffs: + v["start"] = (datetime.fromtimestamp(diffs[k]) - pstart).days + +done = sorted(done, key=lambda t: (-vertices[t]["weight"], t)) +print(done) +inwork = [] +for day in inf(sstart, available): + newwork = [] + for task in inwork: + if task["due"] <= day: + done.append(task["id"]) + else: + newwork.append(task) + inwork = newwork + while available and len(inwork) < maxi(day): + item = None + for n, a in enumerate(available): + if all(i in done for i in tree[a]): + item = n + break + if item is None: + print("err: o nowes") + nr = available.pop(item) + v = vertices[nr] + if nr not in diffs: + v["start"] = day + v["due"] = day + 2 + inwork.append(v) +midamble = [] + + +def pof(day): + if type(day) == datetime: + return day + else: + return pstart + timedelta(days=day) + + +def dof(day): + return str(pof(day)).split(" ")[0] + + +def m(n): + if n < 0: + return str(-n) + " days before " + else: + return str(n) + " days after " + + +projects = { + p["phid"]: p["fields"]["color"]["key"] + for p in phab.project.search(queryKey="all")["data"] +} + + +def g(v): + try: + return projects[v["projectPHIDs"][0]] + except: + return "grey" + + +done = sorted(done, + key=lambda t: (vertices[t]["start"], vertices[t]["weight"])) +dependent = [] +while True: + item = None + for n, a in enumerate(done): + if all(i in dependent for i in tree[a]): + item = n + break + if item is None: + break + nr = done.pop(item) + dependent.append(nr) + +for k in dependent: + v = vertices[k] + needings = tuple(map(vertices.__getitem__, tree[k])) + midamble.append( + f"[{v['title']}] lasts {v['due'] - v['start']} days and is colored in {g(v)} and starts " + + + (f"on {dof(v['start'])}" if not needings else + f"{m(v['start'] - needings[0]['due'] - 1)} [{needings[0]['title']}]'s end" + )) # to {dof(v['due'])}") + +midamble = "\n".join(midamble) +amble = f"{preamble}{midamble}{postamble}" +generate_svg("out", amble) -- cgit v1.2.3