4 generates call graph of given python code file
5 in dot format input for graphviz.
8 * statically tried to figure out functions calls
9 * does not understand classes
10 * algorithm is naive and may not statically find
21 except: import sets
; set = sets
.Set
23 def annotate_ast_list(ast_list
):
25 if code
in symbol
.sym_name
: code
= symbol
.sym_name
[code
]
26 else: code
= token
.tok_name
[code
]
29 for index
, item
in enumerate(ast_list
):
30 if index
== 0: continue
31 if isinstance(item
, list):
32 ast_list
[index
] = annotate_ast_list(item
)
35 def get_atom_name(atom
):
37 first_child_code
= first_child
[0]
38 if first_child_code
!= token
.NAME
: return None
41 def get_fn_call_data(ast_list
):
42 if len(ast_list
) < 3: return None
43 first_child
, second_child
= ast_list
[1:3]
44 first_child_code
= first_child
[0]
45 if first_child_code
!= symbol
.atom
: return None
46 fn_name
= get_atom_name(first_child
)
48 second_child_code
= second_child
[0]
49 if second_child_code
!= symbol
.trailer
: return None
51 if len(second_child
) < 3: return None
52 if second_child
[1][0] == token
.LPAR
and second_child
[-1][0] == token
.RPAR
:
56 def find_fn_call(ast_list
, calls
):
58 if code
== symbol
.power
:
59 fn_name
= get_fn_call_data(ast_list
)
60 if fn_name
!= None and getattr(__builtins__
, fn_name
, None) == None: calls
.add(fn_name
)
62 for item
in ast_list
[1:]:
63 if isinstance(item
, list):
64 find_fn_call(item
, calls
)
66 def process_fn(fn_ast_list
, call_graph
):
67 dummy
, dummy
, func_name
= fn_ast_list
[:3]
68 dummy
, func_name
= func_name
71 find_fn_call(fn_ast_list
, calls
)
73 call_graph
[func_name
] = list(calls
)
75 def construct_call_graph(ast_list
, call_graph
):
77 if code
== symbol
.funcdef
:
78 process_fn(ast_list
, call_graph
)
80 for item
in ast_list
[1:]:
81 if isinstance(item
, list):
82 construct_call_graph(item
, call_graph
)
86 def generate_dot_code(python_code
):
87 ast
= parser
.suite(python_code
)
88 ast_list
= parser
.ast2list(ast
)
89 #annotated_ast_list = annotate_ast_list(ast_list)
90 #pprint.pprint(annotated_ast_list)
93 construct_call_graph(ast_list
, call_graph
)
94 #pprint.pprint(call_graph)
98 dot
.append("digraph G {")
99 dot
.append("rankdir=LR")
100 for from_fn
, to_fns
in call_graph
.iteritems():
102 dot
.append('%s;' % from_fn
)
105 if to_fn
not in call_graph
: continue
106 dot
.append('%s -> %s;' % (from_fn
, to_fn
))
109 return '\n'.join(dot
)
111 if __name__
== '__main__':
112 oparser
= optparse
.OptionParser()
114 oparser
.add_option('-i', '--input-file', default
=None, metavar
='FILE', help='python code file to process')
116 options
, args
= oparser
.parse_args()
118 if options
.input_file
:
119 python_code
= open(options
.input_file
).read()
121 python_code
= sys
.stdin
.read()
123 dot_code
= generate_dot_code(python_code
)