summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDennis Kobert <dennis@kobert.dev>2020-09-01 11:59:28 +0200
committerDennis Kobert <dennis@kobert.dev>2020-09-01 11:59:28 +0200
commit1a97aa3953758ef6965d71b88c15963b473da2d3 (patch)
treecc688f74c911c17f6fbbc700faa9e3b6f6472a96
Add gannt generation tool
l---------.arcconfig1
-rwxr-xr-xgannt.py222
2 files changed, 223 insertions, 0 deletions
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)