diff --git a/scripts/gen_centered_graph.py b/scripts/gen_centered_graph.py new file mode 100644 index 0000000..ab997e8 --- /dev/null +++ b/scripts/gen_centered_graph.py @@ -0,0 +1,154 @@ +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('strict 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() diff --git a/scripts/generate.py b/scripts/generate.py index 7c28db6..cc18e2a 100644 --- a/scripts/generate.py +++ b/scripts/generate.py @@ -25,6 +25,7 @@ from org_rw import token_list_to_raw import pygments import pygments.lexers import pygments.formatters +import gen_centered_graph # Set custom states for state in ("NEXT", "MEETING", "Q", "PAUSED", "SOMETIME", "TRACK", "WAITING"): @@ -288,34 +289,6 @@ def regen_all(src_top, dest_top, *, docs=None, db=None): backlink_graph[main_headline_id] = set() backlink_graph[main_headline_id].add(backlink) - # Render docs after we've built the graph - # Render main headlines - full_graph_info = { "nodes": graph, "backlinks": backlink_graph, "main_headlines": main_headlines_by_path } - for _docpath, main_headline in main_headlines_by_path.items(): - if main_headline.doc.id: - endpath = os.path.join(dest_top, main_headline.doc.id + ".node.html") - with open(endpath, "wt") as f: - f.write(render_as_document(main_headline, main_headline.doc, headlineLevel=0, graph=full_graph_info, - title=org_rw.token_list_to_plaintext(main_headline.title.contents))) - - - # Render all headlines - for headline in all_headlines: - endpath = os.path.join(dest_top, headline.id + ".node.html") - - # Render HTML - with open(endpath, "wt") as f: - f.write(render_as_document(headline, headline.doc, headlineLevel=0, graph=full_graph_info, - title=org_rw.token_list_to_plaintext(headline.title.contents))) - files_generated += 1 - - if headline.id == INDEX_ID: - index_endpath = os.path.join(dest_top, "index.html") - with open(index_endpath, "wt") as f: - f.write(render_as_document(headline, headline.doc, headlineLevel=0, graph=full_graph_info, - title=org_rw.token_list_to_plaintext(headline.title.contents))) - files_generated += 1 - # Output graph files graphpath = os.path.join(dest_top, "graph.json") graph_explorer_path = os.path.join(dest_top, "graph.html") @@ -328,6 +301,37 @@ def regen_all(src_top, dest_top, *, docs=None, db=None): f.write(source.replace('', json.dumps(graph))) logging.info("Generated {} files".format(files_generated)) + + # Render docs after we've built the graph + # Render main headlines + full_graph_info = { "nodes": graph, "backlinks": backlink_graph, "main_headlines": main_headlines_by_path } + for _docpath, main_headline in main_headlines_by_path.items(): + if main_headline.doc.id: + endpath = os.path.join(dest_top, main_headline.doc.id + ".node.html") + with open(endpath, "wt") as f: + f.write(render_as_document(main_headline, main_headline.doc, headlineLevel=0, graph=full_graph_info, + doc_to_headline_remapping=doc_to_headline_remapping, + title=org_rw.token_list_to_plaintext(main_headline.title.contents))) + + # Render all headlines + for headline in all_headlines: + endpath = os.path.join(dest_top, headline.id + ".node.html") + + # Render HTML + with open(endpath, "wt") as f: + f.write(render_as_document(headline, headline.doc, headlineLevel=0, graph=full_graph_info, + doc_to_headline_remapping=doc_to_headline_remapping, + title=org_rw.token_list_to_plaintext(headline.title.contents))) + files_generated += 1 + + if headline.id == INDEX_ID: + index_endpath = os.path.join(dest_top, "index.html") + with open(index_endpath, "wt") as f: + f.write(render_as_document(headline, headline.doc, headlineLevel=0, graph=full_graph_info, + doc_to_headline_remapping=doc_to_headline_remapping, + title=org_rw.token_list_to_plaintext(headline.title.contents))) + files_generated += 1 + cur.close() db.commit() @@ -336,6 +340,7 @@ def regen_all(src_top, dest_top, *, docs=None, db=None): os.makedirs(attachments_dir, exist_ok=True) for base in base_dirs: data_dir = os.path.join(src_top, base, 'data') + logging.info("Copying attachments from: {}".format(data_dir)) if not os.path.exists(data_dir): continue for subdir in os.listdir(data_dir): @@ -651,7 +656,7 @@ def render_inline(tree, f, headline, graph): return ''.join(acc) -def render_as_document(headline, doc, headlineLevel, graph, title): +def render_as_document(headline, doc, headlineLevel, graph, title, doc_to_headline_remapping): if isinstance(headline.parent, org_rw.Headline): topLevelHeadline = headline.parent while isinstance(topLevelHeadline.parent, org_rw.Headline): @@ -674,7 +679,9 @@ def render_as_document(headline, doc, headlineLevel, graph, title): """ else: - return as_document(render(headline, doc, graph=graph, headlineLevel=headlineLevel), title, render_toc(doc)) + return as_document(render(headline, doc, graph=graph, headlineLevel=headlineLevel, + doc_to_headline_remapping=doc_to_headline_remapping), + title, render_toc(doc)) def render_toc(doc): acc = ['