Skip to content

Instantly share code, notes, and snippets.

@sitano
Last active April 26, 2021 16:27
Show Gist options
  • Save sitano/fbf230e2baaf4e6913467c247291ca22 to your computer and use it in GitHub Desktop.
Save sitano/fbf230e2baaf4e6913467c247291ca22 to your computer and use it in GitHub Desktop.
parse output of the clang -H and render it into GraphViz dot
#!/usr/bin/python3
import argparse
import os.path
import re
import sys
import json
parser = argparse.ArgumentParser()
parser.add_argument('--file', required=True)
parser.add_argument('--base', default=".")
parser.add_argument('--max-level', dest='max_level', type=int, default=10000)
parser.add_argument('--print-tree', dest='print_tree', type=bool, default=False)
parser.add_argument('--output', dest='output', type=str, default='')
args = parser.parse_args()
class Scope:
def __init__(self, prefix):
self.prefix = prefix
self.edges = []
self.child = {}
def push(self, _from, _to):
self.edges.append([_from, _to])
def __repr__(self):
return json.dumps(self, default=lambda o: o.__dict__)
root = Scope(".cc")
tree = {root.prefix: root}
stack = [root.prefix]
def longest_common_component_index(_from, _to):
i = -1
while i + 1 < len(_from):
j = _from.find('/', i + 1)
if j < 0 or j >= len(_to) or _from[:j] != _to[:j]:
break
i = j
return i
assert longest_common_component_index("a", "b") == -1
assert longest_common_component_index("a/b/c", "a/b/c") == 3
assert longest_common_component_index("a/b/c", "a/x/c") == 1
def longest_common_component(_from, _to):
i = longest_common_component_index(_from, _to)
if i <= 0:
return ""
return _from[:i]
assert longest_common_component("a/b/c", "a/b/c") == "a/b"
def push_edge(_from, _to):
lcc = longest_common_component(_from, _to)
if lcc != root.prefix:
lccs = lcc.split("/")
parent = root
for item in lccs:
prefix = item
if parent != root:
prefix = parent.prefix + "/" + item
if prefix not in parent.child:
obj = Scope(prefix)
parent.child[prefix] = obj
tree[prefix] = obj
parent = parent.child[prefix]
obj = root
if lcc:
obj = tree[lcc]
obj.push(_from, _to)
def depth(line):
return line.find(' ')
def pop_until(depth):
while len(stack) > depth:
stack.pop()
def update_stack(name, depth, base):
if base + depth <= len(stack):
pop_until(base + depth - 1)
stack.append(name)
def get_name(line):
name = line[line.find(' ') + 1:].strip()
if name[0] == '.':
name = args.base + '/' + name
if name.startswith('seastar/include/'):
name = name[len('seastar/include/'):]
return os.path.normpath(name)
dot_format = re.compile('[^a-zA-Z0-9/.+_-]')
def dot_name(name):
return dot_format.sub('_', name)
def fill_tree():
fd = open(args.file, 'r')
stack_base = len(stack)
for line in fd.readlines():
line_depth = depth(line)
if line_depth <= args.max_level:
name = get_name(line)
update_stack(name, line_depth, stack_base)
push_edge(stack[-2:][0], stack[-1:][0])
# print(name)
# print(stack)
fd.close()
fill_tree()
if args.print_tree:
print(root)
# https://gist.github.com/hsun/2991630
def render():
f = open(args.output, 'w') if args.output else sys.stdout
f.write('digraph G {\n')
f.write(' ranksep=1.0;\n')
f.write(' node [fontname=Helvetica,fontsize=10];\n\n')
def render_children(parent, indent):
indentstd = ' ' * indent
for item in parent.edges:
f.write('%s"%s" -> "%s";\n' % (indentstd, dot_name(item[0]), dot_name(item[1])))
def dfs(parent, indent):
def to_render(node):
return node is not root and node.prefix
indentstd = ' ' * indent
if to_render(parent):
f.write("%ssubgraph \"%s\" {\n" % (indentstd, parent.prefix))
f.write('%s ranksep=1.0;\n' % (indentstd))
for item in parent.child.values():
dfs(item, indent+1)
f.write("%s\n" % indentstd)
render_children(parent, indent + 1)
if to_render(parent):
f.write("%s label=\"%s\";\n" % (indentstd, parent.prefix))
f.write(" graph[style=dotted];\n")
f.write("%s}\n" % indentstd)
dfs(root, 1)
f.write('}\n')
f.close()
render()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment