import subprocess import ops_cache import copy import tempfile import os @ops_cache.cache def gen(headline_id, graph, doc_to_headline_remapping): reference_node = headline_id linked_from_internal = set() g = copy.deepcopy(graph) if 'id:' + reference_node in doc_to_headline_remapping: reference_node = doc_to_headline_remapping['id:' + reference_node].split(':', 1)[1] centered_graph = { reference_node: g[reference_node] } for l in g[reference_node]['links']: lt = l['target'] if lt.startswith("id:"): lt = lt[3:] linked_from_internal.add(lt) 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(): if 'id:' + k in doc_to_headline_remapping: k = doc_to_headline_remapping['id:' + k].split(':', 1)[1] 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' ] for l in v['links']: lt = l['target'] if lt.startswith("id:"): lt = lt[3:] linked_from_internal.add(lt) 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(): if 'id:' + k in doc_to_headline_remapping: k = doc_to_headline_remapping['id:' + k].split(':', 1)[1] backlinked = False 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 backlinked = True removed.add(k) if not backlinked and (k in linked_from_internal): centered_graph[k] = v removed.add(k) 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("-", "_"))) f.write(' URL="./{}.node.html"\n'.format(node_id)) f.write(" label=\"{}\"\n".format(g[node_id]['title'].replace("\"", "'"))) 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) else: print(" _" + k.replace("-", "_") + "[label=\"" + v["title"].replace("\"", "'") + "\", URL=\"" + k + ".node.html\"];", file=f) f.write("\n}\n") 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(): link_src = '_' + k.replace("-", "_") if k in in_emacs_tree: link_src = 'cluster_{}'.format(k.replace("-", "_")) 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("-", "_") print(link_src + "->" + t, file=f) f.write('}\n') f.flush() with tempfile.NamedTemporaryFile(suffix='.svg') as fsvg: subprocess.call(['fdp', f.name, '-Tsvg', '-o', fsvg.name]) fsvg.seek(0) return fsvg.read().decode()