2023-06-10 13:34:44 +00:00
|
|
|
import subprocess
|
|
|
|
import ops_cache
|
|
|
|
import copy
|
|
|
|
import tempfile
|
2023-06-12 22:00:35 +00:00
|
|
|
import os
|
2023-06-10 13:34:44 +00:00
|
|
|
|
2023-06-18 21:56:30 +00:00
|
|
|
|
2023-06-10 13:55:43 +00:00
|
|
|
@ops_cache.cache
|
2023-06-19 22:09:08 +00:00
|
|
|
def gen(headline_id, graph, doc_to_headline_remapping):
|
2023-06-10 13:34:44 +00:00
|
|
|
reference_node = headline_id
|
|
|
|
|
2023-06-18 22:07:40 +00:00
|
|
|
linked_from_internal = set()
|
2023-06-10 13:34:44 +00:00
|
|
|
g = copy.deepcopy(graph)
|
2023-06-19 22:09:08 +00:00
|
|
|
|
|
|
|
if 'id:' + reference_node in doc_to_headline_remapping:
|
|
|
|
reference_node = doc_to_headline_remapping['id:' + reference_node].split(':', 1)[1]
|
|
|
|
|
2023-06-10 13:34:44 +00:00
|
|
|
centered_graph = { reference_node: g[reference_node] }
|
2023-06-18 22:07:40 +00:00
|
|
|
for l in g[reference_node]['links']:
|
|
|
|
lt = l['target']
|
|
|
|
if lt.startswith("id:"):
|
|
|
|
lt = lt[3:]
|
|
|
|
linked_from_internal.add(lt)
|
2023-06-10 13:34:44 +00:00
|
|
|
del g[reference_node]
|
|
|
|
new_nodes = True
|
|
|
|
|
|
|
|
in_emacs_tree = {
|
|
|
|
reference_node: set(),
|
|
|
|
}
|
|
|
|
|
|
|
|
while new_nodes:
|
|
|
|
new_nodes = False
|
|
|
|
removed = set()
|
|
|
|
for k, v in g.items():
|
2023-06-19 22:09:08 +00:00
|
|
|
if 'id:' + k in doc_to_headline_remapping:
|
|
|
|
k = doc_to_headline_remapping['id:' + k].split(':', 1)[1]
|
|
|
|
|
2023-06-10 13:34:44 +00:00
|
|
|
for link in v["links"]:
|
|
|
|
if link["target"].startswith("id:"):
|
|
|
|
link["target"] = link["target"][3:]
|
|
|
|
if link['target'] in centered_graph and link.get('relation') == 'in':
|
|
|
|
centered_graph[k] = v
|
|
|
|
|
|
|
|
for l in v["links"]:
|
|
|
|
if l.get('relation') == 'in':
|
|
|
|
t = l['target']
|
|
|
|
if t.startswith("id:"):
|
|
|
|
t = t[3:]
|
|
|
|
|
|
|
|
if '[' in t:
|
|
|
|
# Special case, to be handled on org_rw
|
|
|
|
continue
|
|
|
|
|
|
|
|
if t not in in_emacs_tree:
|
|
|
|
in_emacs_tree[t] = set()
|
|
|
|
in_emacs_tree[t].add(k)
|
|
|
|
|
|
|
|
v['links'] = [
|
|
|
|
l for l in v["links"]
|
|
|
|
if l.get('relation') != 'in'
|
|
|
|
]
|
2023-06-18 21:56:30 +00:00
|
|
|
for l in v['links']:
|
|
|
|
lt = l['target']
|
|
|
|
if lt.startswith("id:"):
|
|
|
|
lt = lt[3:]
|
|
|
|
linked_from_internal.add(lt)
|
2023-06-10 13:34:44 +00:00
|
|
|
|
|
|
|
removed.add(k)
|
|
|
|
new_nodes = True
|
|
|
|
break
|
|
|
|
for k in removed:
|
|
|
|
del g[k]
|
|
|
|
|
|
|
|
in_emacs = set(centered_graph.keys())
|
|
|
|
|
|
|
|
# One more round for the rest, not requiring "in"
|
|
|
|
for k, v in g.items():
|
2023-06-19 22:09:08 +00:00
|
|
|
if 'id:' + k in doc_to_headline_remapping:
|
|
|
|
k = doc_to_headline_remapping['id:' + k].split(':', 1)[1]
|
|
|
|
|
2023-06-18 21:56:30 +00:00
|
|
|
backlinked = False
|
2023-06-10 13:34:44 +00:00
|
|
|
for link in v["links"]:
|
|
|
|
if link["target"].startswith("id:"):
|
|
|
|
link["target"] = link["target"][3:]
|
|
|
|
if link['target'] in in_emacs:
|
|
|
|
centered_graph[k] = v
|
2023-06-18 21:56:30 +00:00
|
|
|
backlinked = True
|
2023-06-10 13:34:44 +00:00
|
|
|
removed.add(k)
|
2023-06-18 21:56:30 +00:00
|
|
|
if not backlinked and (k in linked_from_internal):
|
|
|
|
centered_graph[k] = v
|
|
|
|
removed.add(k)
|
2023-06-10 13:34:44 +00:00
|
|
|
|
|
|
|
g = centered_graph
|
|
|
|
|
|
|
|
with tempfile.NamedTemporaryFile(suffix='.dot', mode='wt') as f:
|
|
|
|
f.write('digraph {\n')
|
|
|
|
f.write('maxiter=1000\n')
|
|
|
|
f.write('splines=curved\n')
|
|
|
|
# f.write('splines=spline\n') # Not supported with edges to cluster
|
|
|
|
f.write('node[shape=rect]\n')
|
|
|
|
|
|
|
|
def draw_subgraph(node_id):
|
|
|
|
f.write("subgraph cluster_{} {{\n".format(node_id.replace("-", "_")))
|
2023-06-18 21:56:30 +00:00
|
|
|
f.write(' URL="./{}.node.html"\n'.format(node_id))
|
2023-06-10 13:34:44 +00:00
|
|
|
|
2023-06-18 21:56:30 +00:00
|
|
|
f.write(" label=\"{}\"\n".format(g[node_id]['title'].replace("\"", "'")))
|
2023-06-10 13:34:44 +00:00
|
|
|
f.write("\n")
|
|
|
|
|
|
|
|
# print("T: {}".format(in_emacs_tree), file=sys.stderr)
|
|
|
|
for k in in_emacs_tree[node_id]:
|
|
|
|
v = g[k]
|
|
|
|
|
|
|
|
if k in in_emacs_tree:
|
|
|
|
draw_subgraph(k)
|
2023-06-18 21:56:30 +00:00
|
|
|
else:
|
|
|
|
print(" _" + k.replace("-", "_") + "[label=\"" + v["title"].replace("\"", "'") + "\", URL=\"" + k + ".node.html\"];", file=f)
|
|
|
|
|
2023-06-10 13:34:44 +00:00
|
|
|
|
2023-06-18 21:56:30 +00:00
|
|
|
f.write("\n}\n")
|
2023-06-10 13:34:44 +00:00
|
|
|
|
|
|
|
draw_subgraph(reference_node)
|
|
|
|
|
|
|
|
for k, v in g.items():
|
|
|
|
if k not in in_emacs:
|
|
|
|
print("_" + k.replace("-", "_") + "[label=\"" + v["title"].replace("\"", "'") + "\", URL=\"" + k + ".node.html\"];", file=f)
|
|
|
|
|
|
|
|
for k, v in g.items():
|
2023-06-18 21:56:30 +00:00
|
|
|
link_src = '_' + k.replace("-", "_")
|
|
|
|
if k in in_emacs_tree:
|
|
|
|
link_src = 'cluster_{}'.format(k.replace("-", "_"))
|
|
|
|
|
2023-06-10 13:34:44 +00:00
|
|
|
for link in v["links"]:
|
|
|
|
if link["target"].startswith("id:"):
|
|
|
|
link["target"] = link["target"][3:]
|
|
|
|
|
|
|
|
if '[' in link['target']:
|
|
|
|
# Special case, to be handled on org_rw
|
|
|
|
continue
|
|
|
|
if link['target'] not in g:
|
|
|
|
# Irrelevant
|
|
|
|
continue
|
|
|
|
if link['target'] in in_emacs_tree:
|
|
|
|
t = 'cluster_{}'.format(link['target'].replace("-", "_"))
|
|
|
|
else:
|
|
|
|
t = "_" + link["target"].replace("-", "_")
|
2023-06-18 21:56:30 +00:00
|
|
|
print(link_src + "->" + t, file=f)
|
2023-06-10 13:34:44 +00:00
|
|
|
|
|
|
|
f.write('}\n')
|
2023-06-12 22:00:35 +00:00
|
|
|
f.flush()
|
2023-06-10 13:34:44 +00:00
|
|
|
|
|
|
|
with tempfile.NamedTemporaryFile(suffix='.svg') as fsvg:
|
2023-06-12 22:00:35 +00:00
|
|
|
subprocess.call(['fdp', f.name, '-Tsvg', '-o', fsvg.name])
|
2023-06-10 13:34:44 +00:00
|
|
|
fsvg.seek(0)
|
|
|
|
return fsvg.read().decode()
|