Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Peters-17 committed Mar 4, 2024
0 parents commit 563cc53
Show file tree
Hide file tree
Showing 9 changed files with 137 additions and 0 deletions.
Binary file added analyzer/__pycache__/code_analyzer.cpython-39.pyc
Binary file not shown.
73 changes: 73 additions & 0 deletions analyzer/code_analyzer.py
Original file line number Diff line number Diff line change
@@ -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 <path_to_python_file>")
else:
analyze_code(sys.argv[1])
12 changes: 12 additions & 0 deletions output_graph
Original file line number Diff line number Diff line change
@@ -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
}
23 changes: 23 additions & 0 deletions server/app.py
Original file line number Diff line number Diff line change
@@ -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)
3 changes: 3 additions & 0 deletions server/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
flask
graphviz
pygraphviz
1 change: 1 addition & 0 deletions start.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.\venv\Scripts\activate
10 changes: 10 additions & 0 deletions test/example.py
Original file line number Diff line number Diff line change
@@ -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()
Binary file not shown.
15 changes: 15 additions & 0 deletions visualizer/graph_visualizer.py
Original file line number Diff line number Diff line change
@@ -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)

0 comments on commit 563cc53

Please sign in to comment.