diff --git a/gradle-dependencies.svg b/gradle-dependencies.svg
new file mode 100644
index 0000000..82f9ec8
--- /dev/null
+++ b/gradle-dependencies.svg
@@ -0,0 +1,629 @@
+
+
+
+
+
diff --git a/utils/pyproject.toml b/utils/pyproject.toml
index a9fdcda..7db2e37 100644
--- a/utils/pyproject.toml
+++ b/utils/pyproject.toml
@@ -5,20 +5,23 @@ description = "Utility scripts for the Faust Edition"
authors = ["Thorsten Vitt "]
[tool.poetry.dependencies]
-python = "^3.9"
+python = ">3.9,<4.0"
requests = "^2.26.0"
lxml = "^4.6.3"
treetaggerwrapper = "^2.3"
pandas = "^1.3.0"
tqdm = "^4.61.2"
ftfy = "^6.0.3"
-scikit-image = "^0.18.2"
+scikit-image = "^0.21.0"
joblib = "^1.0.1"
ipykernel = "^6.0.3"
openpyxl = "^3.0.7"
"ruamel.yaml" = "^0.17.10"
Pillow = "^9.1.0"
rich = "^12.4.1"
+textdistance = {extras = ["needleman_wunsch"], version = "^4.5.0"}
+typer = "^0.9.0"
+pygraphviz = "^1.11"
[tool.poetry.dev-dependencies]
black = "^21.7b0"
diff --git a/utils/visualize-task-deps.py b/utils/visualize-task-deps.py
new file mode 100755
index 0000000..8d14be6
--- /dev/null
+++ b/utils/visualize-task-deps.py
@@ -0,0 +1,62 @@
+#!/usr/bin/env python3
+
+"""
+Run `gradle tiJson assemble` to produce the taskinfo, then run this script to
+generate the dependency graphs.
+"""
+
+import json
+from operator import itemgetter
+from pathlib import Path
+from typing import Annotated
+import pygraphviz as pgv
+import typer
+
+GROUP_COLORS={
+ '': 'darkred',
+ 'xproc': 'darkblue',
+ 'macrogen': 'darkgreen'
+}
+
+def is_relevant(dep: dict) -> bool:
+ if dep['type'].startswith('org.gradle.execution.plan'):
+ return False
+ return True
+
+def add_node(graph: pgv.AGraph, node: dict):
+ group = node['path'].split(':')[-2]
+ graph.add_node(
+ node['path'],
+ label='<{queuePosition}{name}>'.format_map(node),
+ group=group,
+ fontcolor=GROUP_COLORS.get(group, 'black')
+ )
+
+def add_deps(graph: pgv.AGraph, node: dict):
+ for dep in sorted(node.get('dependencies', []), key=itemgetter('queuePosition')):
+ if is_relevant(dep):
+ if not dep['path'] in graph:
+ add_node(graph, dep)
+ graph.add_edge(node['path'], dep['path'])
+ add_deps(graph, dep)
+
+
+def main(input: Annotated[Path, typer.Argument(help="JSON file generated from gradle tiJson ")] = Path("../build/taskinfo/taskinfo-assemble.json"),
+ output: Annotated[Path, typer.Argument(help="Output .dot file")] = Path('../build/gradle-dependencies.dot'),
+ pdf: bool = False,
+ svg: bool = False):
+ with input.open() as f:
+ deptree = json.load(f)
+ graph = pgv.AGraph(directed=True, rankdir="RL", ordering="out", ranksep="0.2", page="11.7 8.3")
+ graph.node_attr.update(shape='plain')
+ graph.edge_attr.update(color='gray')
+ add_deps(graph, deptree)
+ graph.write(output)
+ if pdf:
+ graph.draw(output.with_suffix('.pdf'), prog='dot')
+ if svg:
+ graph.draw(output.with_suffix('.svg'), prog='dot')
+
+
+if __name__ == "__main__":
+ typer.run(main)