From 563cc53e7ae98c65db16f17e8eff4ba2d5434f01 Mon Sep 17 00:00:00 2001 From: Haoyang Date: Mon, 4 Mar 2024 02:10:51 -0600 Subject: [PATCH] Initial commit --- .../__pycache__/code_analyzer.cpython-39.pyc | Bin 0 -> 1121 bytes analyzer/code_analyzer.py | 73 ++++++++++++++++++ output_graph | 12 +++ server/app.py | 23 ++++++ server/requirements.txt | 3 + start.txt | 1 + test/example.py | 10 +++ .../graph_visualizer.cpython-39.pyc | Bin 0 -> 558 bytes visualizer/graph_visualizer.py | 15 ++++ 9 files changed, 137 insertions(+) create mode 100644 analyzer/__pycache__/code_analyzer.cpython-39.pyc create mode 100644 analyzer/code_analyzer.py create mode 100644 output_graph create mode 100644 server/app.py create mode 100644 server/requirements.txt create mode 100644 start.txt create mode 100644 test/example.py create mode 100644 visualizer/__pycache__/graph_visualizer.cpython-39.pyc create mode 100644 visualizer/graph_visualizer.py diff --git a/analyzer/__pycache__/code_analyzer.cpython-39.pyc b/analyzer/__pycache__/code_analyzer.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1de1ff65bdb1d6d15da6d745e19f34f213600d8a GIT binary patch literal 1121 zcmZ`&&1xGl5FTl@>y2xhB&C#=5_%{FL&*b_QW_lEn=fetT?}Gb$zk2i+L6|U80X|X zh;z&<=~{Z|EA-SEt(|WABhc(fGn)BkzS*?fZ3C_+pTFym9N-%rnoHo}9d7#)g#yJ2 zNZ51Kf5O#ThI?))FJ1z%3j0x!W1f%dE<$rFCJJ z7KQCIC#){Uw_sZM>gQnadi2R^V@F4exhc|8kB+tdQcY(g%KiK_xAU~fG5sjr;233S zklWMY?9#yZQ1{s4;2!F~oc|C16^5w*p|grlf)zIhSd3VQbTf%I<+T1RCw6UwT-quvGwlTN z<$}~!i5OyGhB|6!NFC3W>CmZ5g2-zW)vj?4kYX}hDj!&n?Xtz=f7C;M!+u1>QTwEH zZ7I*EYF?1uk@5mbuPuhUV|pYWlK7q2Yt&LhED|X`ZOmd33x9WSVu`#vs_B2Kh-kQB zSJ_)Xouw}j0PQ_S~! FzW~H`^Z@_> literal 0 HcmV?d00001 diff --git a/analyzer/code_analyzer.py b/analyzer/code_analyzer.py new file mode 100644 index 0000000..1ab3239 --- /dev/null +++ b/analyzer/code_analyzer.py @@ -0,0 +1,73 @@ +import ast +import networkx as nx +import matplotlib.pyplot as plt + +class CodeAnalyzer(ast.NodeVisitor): + def __init__(self): + self.G = nx.DiGraph() + self.current_class = None + self.current_function = None # Add this attribute to keep track of the current function + + def visit_ClassDef(self, node): + # Add the class as a node and make it the current context + self.G.add_node(node.name, type='class', color='lightblue', shape='box') + self.current_class = node.name + self.generic_visit(node) + self.current_class = None + + def visit_FunctionDef(self, node): + function_name = f"{self.current_class}.{node.name}" if self.current_class else node.name + self.G.add_node(function_name, type='function', color='lightgreen') + self.current_function = function_name # Set the current function + if self.current_class: + # If we're in a class context, add an edge from the class to the method + self.G.add_edge(self.current_class, function_name, type='contains') + self.generic_visit(node) + self.current_function = None # Reset the current function + + def visit_Call(self, node): + caller_name = self.current_function if self.current_function else self.current_class + callee = None + if isinstance(node.func, ast.Attribute) and isinstance(node.func.value, ast.Name): + callee = f"{node.func.value.id}.{node.func.attr}" + elif isinstance(node.func, ast.Name): + callee = node.func.id + if caller_name and callee: + self.G.add_edge(caller_name, callee, type='calls') + self.generic_visit(node) + + def visit_Assign(self, node): + if isinstance(node.value, ast.Call) and isinstance(node.value.func, ast.Name): + instance_name = ''.join([target.id for target in node.targets if isinstance(target, ast.Name)]) + class_name = node.value.func.id + self.G.add_node(instance_name, type='instance', color='orange') + self.G.add_edge(instance_name, class_name, type='instance_of') + self.generic_visit(node) + +def analyze_code(file_path): + with open(file_path, "r") as source: + node = ast.parse(source.read(), filename=file_path) + analyzer = CodeAnalyzer() + analyzer.visit(node) + + # Set node color based on type, use a default color if 'color' is not present + default_color = 'grey' + node_colors = [data.get('color', default_color) for _, data in analyzer.G.nodes(data=True)] + + # Draw the graph + pos = nx.spring_layout(analyzer.G) + nx.draw(analyzer.G, pos, with_labels=True, node_color=node_colors, arrows=True) + + # Draw edge labels + edge_labels = {(u, v): data['type'] for u, v, data in analyzer.G.edges(data=True) if 'type' in data} + nx.draw_networkx_edge_labels(analyzer.G, pos, edge_labels=edge_labels) + + plt.show() + + +if __name__ == "__main__": + import sys + if len(sys.argv) != 2: + print("Usage: python code_analyzer.py ") + else: + analyze_code(sys.argv[1]) diff --git a/output_graph b/output_graph new file mode 100644 index 0000000..eb7ee6b --- /dev/null +++ b/output_graph @@ -0,0 +1,12 @@ +// Code Structure +digraph { + node [shape=circle] + MyClass [shape=box] + "MyClass.method_a" + "MyClass.method_b" + "self.method_a" -> "MyClass.method_a" + function_c + instance -> MyClass [label="instance of"] + Global -> MyClass + "instance.method_a" -> method_a +} diff --git a/server/app.py b/server/app.py new file mode 100644 index 0000000..d477437 --- /dev/null +++ b/server/app.py @@ -0,0 +1,23 @@ +# server/app.py +import sys +import os + +# 将项目根目录(即包含analyzer和visualizer目录的那一级)添加到sys.path +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) + +from analyzer.code_analyzer import analyze_code +from visualizer.graph_visualizer import create_graph +from flask import Flask, request, jsonify + + +app = Flask(__name__) + +@app.route('/analyze', methods=['POST']) +def analyze(): + code = request.data.decode("utf-8") + functions, calls = analyze_code(code) + # 为了Demo,这里我们直接返回分析结果,实际应用中可能需要进一步处理 + return jsonify({"functions": functions, "calls": calls}) + +if __name__ == '__main__': + app.run(debug=True) diff --git a/server/requirements.txt b/server/requirements.txt new file mode 100644 index 0000000..0476081 --- /dev/null +++ b/server/requirements.txt @@ -0,0 +1,3 @@ +flask +graphviz +pygraphviz diff --git a/start.txt b/start.txt new file mode 100644 index 0000000..ca09f88 --- /dev/null +++ b/start.txt @@ -0,0 +1 @@ +.\venv\Scripts\activate diff --git a/test/example.py b/test/example.py new file mode 100644 index 0000000..5264551 --- /dev/null +++ b/test/example.py @@ -0,0 +1,10 @@ +class MyClass: + def method_a(self): + pass + + def method_b(self): + self.method_a() + +def function_c(): + instance = MyClass() + instance.method_a() diff --git a/visualizer/__pycache__/graph_visualizer.cpython-39.pyc b/visualizer/__pycache__/graph_visualizer.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..242ec47badc99f247ef52ee91f2d274ca23910f3 GIT binary patch literal 558 zcmYjNy>1gh5T4!JbBtwMDJUWuT;`h4BZNS9L6yb zFTz7?izs*nDrQfLl9A@y*>Ap?ot=EKm;u?-_kZvg1Na>-4~^616}dhkkRVwFHA@)- z$z}8nQZB_7lK3xWAz}V#Q|?vXT^6D4&SDCJugUdC0z-WyzhwvRU;`hH50T^FnTuTX zgERQLG~BTpMwyd?aKb?k>Xa0ecVbkW8sYfo_=e`7b0Q-tWPD4t5@?#q`SAGS5?9-6 zZLnT#jk1NY3d4LwTi4i_;Yn*vXU#J`YIgcgbGGj<5|)a-#H-{;g>QJ#v=V)!(WC<9 z9(~-YvN3+5TdN98Ckh*hDvAB0Pqryc**4n8MP60fN3u0u1UeOg`3Y)HJ^#H~KhNH3 zR62V-SXJc>W*gLBOxtB^dTsTt)Hbim0ae!DhvRN{_rzIu?dOHU+~97^iLl~h0$?*f sWlKJ10cT7ti91iCbUNz#av1+M(&I!AU_nwpeNng4R`@ckKnIxf|LH=I4FCWD literal 0 HcmV?d00001 diff --git a/visualizer/graph_visualizer.py b/visualizer/graph_visualizer.py new file mode 100644 index 0000000..99b083f --- /dev/null +++ b/visualizer/graph_visualizer.py @@ -0,0 +1,15 @@ +# visualizer/graph_visualizer.py +from graphviz import Digraph + +def create_graph(functions, calls): + dot = Digraph(comment='The System Structure') + + for func in functions: + dot.node(func, func) + + for call in set(calls): + if call in functions: + dot.edge(func, call) + + print(dot.source) + dot.render('output/system_structure.gv', view=True)