Signed-off-by: sairate <sairate@sina.cn>
This commit is contained in:
commit
88a0b40cdc
|
@ -0,0 +1,3 @@
|
|||
BAIDU_API_KEY = 4icZSO1OlMCU2ZiRMhgGCXFu
|
||||
BAIDU_SECRET_KEY = 6wJldJ08m1jIX9hb0ULcJrIJ9D1OJW3c
|
||||
DEEPSEEK_API_KEY = sk-f15b44b6b3344cdd820e59acebce9d2c
|
|
@ -0,0 +1,79 @@
|
|||
# AI代码分析工具 v4.3
|
||||
|
||||
该工具基于人工智能,能够分析 Python 代码,提供详细的语法检查、逻辑分析和优化建议,并能自动生成流程图与类图。通过 Graphviz 和 PlantUML,用户可以更好地理解代码的结构和流程。
|
||||
|
||||
## 安装与配置
|
||||
|
||||
### 1. 安装 Python 环境
|
||||
|
||||
- 请确保您的机器上已安装 Python 3.x。若尚未安装,请访问 [Python 官方网站](https://www.python.org/downloads/) 下载并安装。
|
||||
|
||||
- 在安装 Python 后,确保 `pip` 工具已正确配置。您可以通过以下命令检查 Python 和 pip 是否安装成功:
|
||||
```bash
|
||||
python --version
|
||||
pip --version
|
||||
```
|
||||
|
||||
- 安装必要的 Python 库:
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
`requirements.txt` 文件包含了工具所依赖的所有 Python 库,包括 `openai`, `tkinter`, `Pillow`, `graphviz`, `python-dotenv` 等。
|
||||
|
||||
### 2. 安装 Java 环境
|
||||
|
||||
- 本工具依赖 PlantUML 生成 UML 图,因此需要安装 Java 环境。请访问 [Java 官网](https://www.java.com/en/download/) 下载并安装适用于您操作系统的 Java 版本。
|
||||
|
||||
- 安装完成后,检查 Java 环境是否配置成功:
|
||||
```bash
|
||||
java -version
|
||||
```
|
||||
|
||||
确保显示的版本是您已安装的 Java 版本。
|
||||
|
||||
### 3. 配置 PlantUML
|
||||
|
||||
- 确保目录下有 `plantuml.jar` 文件,该文件是 PlantUML 的核心文件。
|
||||
|
||||
### 4. 配置环境变量
|
||||
|
||||
- 创建一个 `.env` 文件,并在其中添加 `DEEPSEEK_API_KEY`,这是您用于 DeepSeek API 的密钥。格式如下:
|
||||
```
|
||||
DEEPSEEK_API_KEY=your_api_key_here
|
||||
```
|
||||
|
||||
### 5. 运行配置脚本 `config.py`
|
||||
|
||||
在项目根目录中找到并运行 `config.py` 配置文件。该脚本会帮助您检查并配置工具所需的环境设置,确保所有依赖项正确安装。运行命令如下:
|
||||
```bash
|
||||
python config.py
|
||||
```
|
||||
|
||||
该脚本将自动检查 Java 和 Python 环境是否配置正确,并确保 PlantUML 配置无误。
|
||||
|
||||
### 6. 运行工具
|
||||
|
||||
配置完成后,您可以启动工具:
|
||||
```bash
|
||||
python app.py
|
||||
```
|
||||
|
||||
该命令将启动应用程序,您可以通过界面输入代码并开始分析。
|
||||
|
||||
## 使用方法
|
||||
|
||||
1. **输入代码**:在界面中输入 Python 代码。
|
||||
2. **开始分析**:点击“开始分析”,工具会返回分析结果,包括:
|
||||
- 语法检查与修正建议
|
||||
- 逻辑分析和优化建议
|
||||
- 生成流程图和类图
|
||||
|
||||
## 贡献
|
||||
|
||||
欢迎大家贡献代码,提供 Bug 修复或功能增强!请通过 Pull Requests 提交您的修改。
|
||||
|
||||
## 许可
|
||||
|
||||
本项目采用 MIT 许可协议,详情请见 [LICENSE]。
|
||||
|
|
@ -0,0 +1,508 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import re
|
||||
import queue
|
||||
import threading
|
||||
import tkinter as tk
|
||||
import tempfile
|
||||
import subprocess
|
||||
from tkinter import ttk, messagebox, scrolledtext
|
||||
from graphviz import Source
|
||||
from openai import OpenAI
|
||||
from dotenv import load_dotenv
|
||||
from PIL import Image, ImageTk
|
||||
|
||||
# 兼容性处理
|
||||
try:
|
||||
resample_mode = Image.Resampling.LANCZOS
|
||||
except AttributeError:
|
||||
resample_mode = Image.ANTIALIAS
|
||||
|
||||
load_dotenv()
|
||||
|
||||
# 配置常量
|
||||
DEEPSEEK_API_KEY = os.getenv("DEEPSEEK_API_KEY")
|
||||
PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__))
|
||||
PLANTUML_JAR = os.path.join(PROJECT_ROOT, "plantuml-1.2025.2.jar")
|
||||
FONT_NAME = "Microsoft YaHei"
|
||||
|
||||
# 线程安全队列
|
||||
analysis_queue = queue.Queue()
|
||||
diagram_queue = queue.Queue()
|
||||
|
||||
# 临时文件目录
|
||||
TEMP_DIR = tempfile.mkdtemp()
|
||||
|
||||
# 界面样式配置
|
||||
STYLE_CONFIG = {
|
||||
"font_family": FONT_NAME,
|
||||
"font_size": 11,
|
||||
"dark_bg": "#2d2d2d",
|
||||
"light_bg": "#f0f0f0",
|
||||
"primary": "#007acc",
|
||||
"success": "#4CAF50",
|
||||
"danger": "#f44336",
|
||||
"text_primary": "#ffffff",
|
||||
"text_secondary": "#333333",
|
||||
"code_bg": "#f8f9fa"
|
||||
}
|
||||
|
||||
|
||||
class EnhancedCanvas(tk.Canvas):
|
||||
"""支持缩放和平移的画布组件"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.scale_factor = 1.0
|
||||
self.last_x = 0
|
||||
self.last_y = 0
|
||||
self.image_item = None
|
||||
|
||||
# 事件绑定
|
||||
self.bind("<MouseWheel>", self.zoom)
|
||||
self.bind("<ButtonPress-2>", self.start_pan)
|
||||
self.bind("<B2-Motion>", self.pan)
|
||||
self.bind("<Button-4>", self.zoom) # Linux支持
|
||||
self.bind("<Button-5>", self.zoom)
|
||||
|
||||
def zoom(self, event):
|
||||
"""鼠标滚轮缩放"""
|
||||
scale = 1.1 if event.delta > 0 or event.num == 4 else 0.9
|
||||
self.scale_factor *= scale
|
||||
self.scale_factor = max(0.5, min(3.0, self.scale_factor))
|
||||
|
||||
x = self.canvasx(event.x)
|
||||
y = self.canvasy(event.y)
|
||||
self.scale(tk.ALL, x, y, scale, scale)
|
||||
self.configure(scrollregion=self.bbox(tk.ALL))
|
||||
|
||||
def start_pan(self, event):
|
||||
"""开始平移"""
|
||||
self.last_x = event.x
|
||||
self.last_y = event.y
|
||||
self.scan_mark(event.x, event.y)
|
||||
|
||||
def pan(self, event):
|
||||
"""平移视图"""
|
||||
self.scan_dragto(event.x, event.y, gain=1)
|
||||
self.configure(scrollregion=self.bbox(tk.ALL))
|
||||
|
||||
def update_image(self, img_path):
|
||||
"""更新画布图片"""
|
||||
if self.image_item:
|
||||
self.delete(self.image_item)
|
||||
|
||||
try:
|
||||
img = Image.open(img_path)
|
||||
self.tk_img = ImageTk.PhotoImage(img)
|
||||
self.image_item = self.create_image(0, 0, anchor=tk.NW, image=self.tk_img)
|
||||
self.configure(scrollregion=self.bbox(tk.ALL))
|
||||
except Exception as e:
|
||||
messagebox.showerror("图片加载失败", str(e))
|
||||
|
||||
|
||||
class MarkdownRenderer(scrolledtext.ScrolledText):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.configure(
|
||||
state='disabled',
|
||||
wrap=tk.WORD,
|
||||
padx=15,
|
||||
pady=15,
|
||||
font=(STYLE_CONFIG['font_family'], STYLE_CONFIG['font_size'])
|
||||
)
|
||||
self._init_tags()
|
||||
|
||||
def _init_tags(self):
|
||||
self.tag_configure('h1', font=(FONT_NAME, 16, 'bold'), spacing3=10)
|
||||
self.tag_configure('h2', font=(FONT_NAME, 14, 'bold'), spacing2=8)
|
||||
self.tag_configure('bold', font=(FONT_NAME, STYLE_CONFIG['font_size'], 'bold'))
|
||||
self.tag_configure('italic', font=(FONT_NAME, STYLE_CONFIG['font_size'], 'italic'))
|
||||
self.tag_configure('code',
|
||||
background=STYLE_CONFIG['code_bg'],
|
||||
relief='sunken',
|
||||
borderwidth=1,
|
||||
font='Consolas 10')
|
||||
|
||||
def render(self, markdown_text):
|
||||
self.configure(state='normal')
|
||||
self.delete(1.0, tk.END)
|
||||
|
||||
lines = markdown_text.split('\n')
|
||||
in_code_block = False
|
||||
|
||||
for line in lines:
|
||||
if line.strip().startswith('```'):
|
||||
in_code_block = not in_code_block
|
||||
continue
|
||||
|
||||
if in_code_block:
|
||||
self.insert(tk.END, line + '\n', 'code')
|
||||
continue
|
||||
|
||||
if line.startswith('# '):
|
||||
self.insert(tk.END, line[2:] + '\n', 'h1')
|
||||
elif line.startswith('## '):
|
||||
self.insert(tk.END, line[3:] + '\n', 'h2')
|
||||
else:
|
||||
processed_line = self._process_inline_formatting(line)
|
||||
self.insert(tk.END, processed_line + '\n')
|
||||
|
||||
self.configure(state='disabled')
|
||||
|
||||
def _process_inline_formatting(self, line):
|
||||
line = re.sub(r'\*\*(.*?)\*\*', lambda m: self._apply_tag(m.group(1), 'bold'), line)
|
||||
line = re.sub(r'\*(.*?)\*', lambda m: self._apply_tag(m.group(1), 'italic'), line)
|
||||
return line
|
||||
|
||||
def _apply_tag(self, text, tag):
|
||||
self.insert(tk.END, text, tag)
|
||||
return ''
|
||||
|
||||
|
||||
class AsyncAnalyzer(threading.Thread):
|
||||
"""异步分析线程"""
|
||||
|
||||
def __init__(self, code, callback):
|
||||
super().__init__()
|
||||
self.code = code
|
||||
self.callback = callback
|
||||
self.daemon = True
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
client = OpenAI(
|
||||
api_key=DEEPSEEK_API_KEY,
|
||||
base_url="https://api.deepseek.com"
|
||||
)
|
||||
response = client.chat.completions.create(
|
||||
model="deepseek-chat",
|
||||
messages=[
|
||||
{"role": "system", "content": "请严格按以下Markdown格式分析代码:\n"
|
||||
"## 语法检查\n- 错误列表\n- 修正建议\n\n"
|
||||
"## 逻辑分析\n1. 步骤说明\n2. 流程图(必须使用标准DOT语法,用```dot标记)\n"
|
||||
"3. 类图(用```plantuml标记)\n\n"
|
||||
"## 优化建议\n- 性能优化\n- 可读性建议\n\n"
|
||||
"DOT语法要求:\n"
|
||||
"1. 使用有意义的节点名称\n"
|
||||
"2. 所有边使用 -> 符号\n"
|
||||
"3. 节点属性用方括号包裹"},
|
||||
{"role": "user", "content": f"分析代码:\n```python\n{self.code}\n```"}
|
||||
],
|
||||
temperature=0.3
|
||||
)
|
||||
self.callback(response.choices[0].message.content, None)
|
||||
except Exception as e:
|
||||
self.callback(None, str(e))
|
||||
|
||||
|
||||
class DiagramGenerator:
|
||||
@staticmethod
|
||||
def generate_flow(dot_code, filename):
|
||||
"""生成中文流程图"""
|
||||
try:
|
||||
font_config = f'''
|
||||
graph [fontname="{FONT_NAME}"];
|
||||
node [fontname="{FONT_NAME}"];
|
||||
edge [fontname="{FONT_NAME}"];
|
||||
'''
|
||||
dot_code = re.sub(r'(digraph\s*\w*\s*{)', f'\\1\n{font_config}', dot_code)
|
||||
|
||||
filepath = os.path.join(TEMP_DIR, filename)
|
||||
src = Source(dot_code, format='png', engine='dot', encoding='utf-8')
|
||||
output_path = src.render(filepath, cleanup=True)
|
||||
return output_path
|
||||
except Exception as e:
|
||||
return f"流程图错误:{str(e)}"
|
||||
|
||||
@staticmethod
|
||||
def generate_class(plantuml_code, filename):
|
||||
"""生成类图(英文)"""
|
||||
try:
|
||||
plantuml_code = re.sub(r'^\s*```\s*plantuml\s*\n', '', plantuml_code, flags=re.IGNORECASE)
|
||||
plantuml_code = re.sub(r'\n\s*```\s*$', '', plantuml_code)
|
||||
|
||||
uml_path = os.path.join(TEMP_DIR, f"{filename}.puml")
|
||||
img_path = os.path.join(TEMP_DIR, f"{filename}.png")
|
||||
|
||||
with open(uml_path, 'w', encoding='utf-8') as f:
|
||||
f.write(plantuml_code)
|
||||
|
||||
cmd = [
|
||||
'java',
|
||||
'-Djava.awt.headless=true',
|
||||
'-jar', PLANTUML_JAR,
|
||||
'-tpng',
|
||||
uml_path,
|
||||
'-o', TEMP_DIR
|
||||
]
|
||||
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True,
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
error_msg = result.stdout.strip()
|
||||
return f"类图生成失败:{error_msg.split('ERROR')[-1].strip()}"
|
||||
|
||||
return img_path
|
||||
except subprocess.TimeoutExpired:
|
||||
return "错误:生成超时(30秒)"
|
||||
except Exception as e:
|
||||
return f"系统错误:{str(e)}"
|
||||
finally:
|
||||
if os.path.exists(uml_path):
|
||||
os.remove(uml_path)
|
||||
|
||||
|
||||
class CodeAnalyzerApp:
|
||||
def __init__(self, root):
|
||||
self.root = root
|
||||
self._init_ui()
|
||||
self._init_state()
|
||||
self._start_queue_processor()
|
||||
|
||||
def _init_ui(self):
|
||||
"""初始化界面"""
|
||||
self.root.title("AI代码分析工具 v4.3")
|
||||
self.root.geometry("1440x960")
|
||||
|
||||
# 主布局
|
||||
main_paned = ttk.PanedWindow(self.root, orient=tk.VERTICAL)
|
||||
main_paned.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
# 上部区域
|
||||
top_pane = ttk.PanedWindow(main_paned, orient=tk.HORIZONTAL)
|
||||
main_paned.add(top_pane)
|
||||
|
||||
# 代码输入区
|
||||
input_frame = ttk.LabelFrame(top_pane, text=" 代码输入 ", padding=10)
|
||||
top_pane.add(input_frame, weight=1)
|
||||
self.code_input = scrolledtext.ScrolledText(
|
||||
input_frame,
|
||||
font=('Consolas', 11),
|
||||
wrap=tk.WORD,
|
||||
padx=10,
|
||||
pady=10
|
||||
)
|
||||
self.code_input.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
# 分析结果区
|
||||
result_frame = ttk.Frame(top_pane)
|
||||
top_pane.add(result_frame, weight=1)
|
||||
self.md_view = MarkdownRenderer(result_frame)
|
||||
self.md_view.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
# 下部图表区
|
||||
bottom_pane = ttk.PanedWindow(main_paned, orient=tk.HORIZONTAL)
|
||||
main_paned.add(bottom_pane, weight=1)
|
||||
|
||||
# 流程图面板
|
||||
flow_frame = ttk.LabelFrame(bottom_pane, text=" 流程图 ", padding=5)
|
||||
bottom_pane.add(flow_frame, weight=1)
|
||||
self.flow_canvas = EnhancedCanvas(
|
||||
flow_frame,
|
||||
bg="white",
|
||||
bd=2,
|
||||
relief=tk.GROOVE
|
||||
)
|
||||
self.flow_canvas.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
# 类图面板
|
||||
class_frame = ttk.LabelFrame(bottom_pane, text=" 类图 ", padding=5)
|
||||
bottom_pane.add(class_frame, weight=1)
|
||||
self.class_canvas = EnhancedCanvas(
|
||||
class_frame,
|
||||
bg="white",
|
||||
bd=2,
|
||||
relief=tk.GROOVE
|
||||
)
|
||||
self.class_canvas.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
# 控制栏
|
||||
self._init_controls()
|
||||
|
||||
def _init_controls(self):
|
||||
"""初始化控制组件"""
|
||||
control_frame = ttk.Frame(self.root)
|
||||
control_frame.pack(fill=tk.X, pady=5)
|
||||
|
||||
self.analyze_btn = ttk.Button(
|
||||
control_frame,
|
||||
text="开始分析",
|
||||
command=self._start_analysis
|
||||
)
|
||||
self.analyze_btn.pack(side=tk.LEFT, padx=10)
|
||||
|
||||
ttk.Button(
|
||||
control_frame,
|
||||
text="重置视图",
|
||||
command=self._reset_view
|
||||
).pack(side=tk.RIGHT, padx=10)
|
||||
|
||||
# 状态栏
|
||||
self.status_var = tk.StringVar()
|
||||
status_bar = ttk.Label(
|
||||
self.root,
|
||||
textvariable=self.status_var,
|
||||
relief=tk.SUNKEN,
|
||||
anchor=tk.W
|
||||
)
|
||||
status_bar.pack(side=tk.BOTTOM, fill=tk.X)
|
||||
|
||||
def _init_state(self):
|
||||
"""初始化状态"""
|
||||
self.is_processing = False
|
||||
|
||||
def _start_analysis(self):
|
||||
"""启动分析任务"""
|
||||
if self.is_processing:
|
||||
return
|
||||
|
||||
code = self.code_input.get("1.0", tk.END).strip()
|
||||
if len(code) < 20:
|
||||
messagebox.showwarning("输入错误", "请输入有效的代码内容")
|
||||
return
|
||||
|
||||
self.is_processing = True
|
||||
self.analyze_btn.config(state=tk.DISABLED)
|
||||
self.status_var.set("正在分析代码...")
|
||||
|
||||
AsyncAnalyzer(
|
||||
code=code,
|
||||
callback=self._handle_analysis_result
|
||||
).start()
|
||||
|
||||
def _handle_analysis_result(self, result, error):
|
||||
"""处理分析结果"""
|
||||
self.is_processing = False
|
||||
self.analyze_btn.config(state=tk.NORMAL)
|
||||
|
||||
if error:
|
||||
self.status_var.set(f"分析失败:{error}")
|
||||
messagebox.showerror("分析错误", error)
|
||||
return
|
||||
|
||||
self.md_view.render(result)
|
||||
self.status_var.set("正在生成图表...")
|
||||
self._process_diagrams(result)
|
||||
|
||||
def _process_diagrams(self, analysis_result):
|
||||
"""处理图表生成"""
|
||||
dot_code = self._extract_code_block(analysis_result, 'dot')
|
||||
plantuml_code = self._extract_code_block(analysis_result, 'plantuml')
|
||||
|
||||
if dot_code:
|
||||
threading.Thread(
|
||||
target=self._generate_diagram,
|
||||
args=('flow', dot_code, 'flow_diagram'),
|
||||
daemon=True
|
||||
).start()
|
||||
|
||||
if plantuml_code:
|
||||
threading.Thread(
|
||||
target=self._generate_diagram,
|
||||
args=('class', plantuml_code, 'class_diagram'),
|
||||
daemon=True
|
||||
).start()
|
||||
|
||||
def _generate_diagram(self, diagram_type, code, filename):
|
||||
"""生成并更新图表"""
|
||||
try:
|
||||
if diagram_type == 'flow':
|
||||
path = DiagramGenerator.generate_flow(code, filename)
|
||||
else:
|
||||
path = DiagramGenerator.generate_class(code, filename)
|
||||
|
||||
diagram_queue.put((diagram_type, path))
|
||||
except Exception as e:
|
||||
diagram_queue.put((diagram_type, f"生成错误:{str(e)}"))
|
||||
|
||||
def _start_queue_processor(self):
|
||||
"""启动队列处理"""
|
||||
|
||||
def process():
|
||||
if not diagram_queue.empty():
|
||||
diagram_type, path = diagram_queue.get()
|
||||
if diagram_type == 'flow':
|
||||
self._update_flow_diagram(path)
|
||||
else:
|
||||
self._update_class_diagram(path)
|
||||
|
||||
self.root.after(100, process)
|
||||
|
||||
process()
|
||||
|
||||
def _update_flow_diagram(self, img_path):
|
||||
"""更新流程图"""
|
||||
if img_path.startswith(("错误:", "流程图错误:")):
|
||||
self.status_var.set(img_path)
|
||||
messagebox.showerror("流程图错误", img_path.split(":", 1)[-1])
|
||||
return
|
||||
|
||||
try:
|
||||
self.flow_canvas.update_image(img_path)
|
||||
self.status_var.set("流程图已更新")
|
||||
except Exception as e:
|
||||
messagebox.showerror("流程图加载失败", str(e))
|
||||
|
||||
def _update_class_diagram(self, img_path):
|
||||
"""更新类图"""
|
||||
if img_path.startswith(("错误:", "类图生成失败:")):
|
||||
messagebox.showerror("类图错误", img_path.split(":", 1)[-1])
|
||||
return
|
||||
|
||||
try:
|
||||
self.class_canvas.update_image(img_path)
|
||||
self.status_var.set("类图已更新")
|
||||
except Exception as e:
|
||||
messagebox.showerror("类图加载失败", str(e))
|
||||
|
||||
def _reset_view(self):
|
||||
"""重置视图"""
|
||||
for canvas in [self.flow_canvas, self.class_canvas]:
|
||||
canvas.scale_factor = 1.0
|
||||
canvas.scale(tk.ALL, 0, 0, 1, 1)
|
||||
canvas.configure(scrollregion=canvas.bbox(tk.ALL))
|
||||
|
||||
@staticmethod
|
||||
def _extract_code_block(text, lang):
|
||||
"""提取代码块"""
|
||||
pattern = rf'```{lang}\n(.*?)\n```'
|
||||
match = re.search(pattern, text, re.DOTALL)
|
||||
return match.group(1).strip() if match else None
|
||||
|
||||
|
||||
def check_environment():
|
||||
"""环境检查"""
|
||||
try:
|
||||
subprocess.run(
|
||||
['java', '-version'],
|
||||
check=True,
|
||||
stderr=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
timeout=10,
|
||||
creationflags=subprocess.CREATE_NO_WINDOW if os.name == 'nt' else 0
|
||||
)
|
||||
|
||||
if not os.path.exists(PLANTUML_JAR):
|
||||
messagebox.showerror("配置错误", f"未找到PlantUML JAR文件:\n{PLANTUML_JAR}")
|
||||
return False
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
messagebox.showerror("环境错误", f"Java环境异常:{str(e)}")
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if not check_environment():
|
||||
exit(1)
|
||||
|
||||
root = tk.Tk()
|
||||
app = CodeAnalyzerApp(root)
|
||||
root.mainloop()
|
|
@ -0,0 +1,79 @@
|
|||
import os
|
||||
import subprocess
|
||||
import requests
|
||||
from tqdm import tqdm
|
||||
|
||||
# 保存到脚本所在目录
|
||||
download_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
# 配置文件信息
|
||||
files = {
|
||||
"graphviz": {
|
||||
"url": "https://blog.sairate.top/upload/app/windows_10_cmake_Release_graphviz-install-12.2.1-win64.exe",
|
||||
"filename": "graphviz-install-12.2.1-win64.exe",
|
||||
"install_cmd": lambda path: [path, '/S', '/AddToPath=1']
|
||||
},
|
||||
"cmake": {
|
||||
"url": "https://blog.sairate.top/upload/app/cmake-4.0.0-rc4-windows-x86_64.msi",
|
||||
"filename": "cmake-4.0.0-rc4-windows-x86_64.msi",
|
||||
"install_cmd": lambda path: [
|
||||
"msiexec", "/i", path, "/qn", "ADD_CMAKE_TO_PATH=System"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
# 单独下载但不安装的文件
|
||||
optional_files = {
|
||||
"plantuml": {
|
||||
"url": "https://blog.sairate.top/upload/app/plantuml-1.2025.2.jar",
|
||||
"filename": "plantuml-1.2025.2.jar"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def download_file(url, filename):
|
||||
file_path = os.path.join(download_dir, filename)
|
||||
print(f"开始下载 {filename} ...")
|
||||
response = requests.get(url, stream=True)
|
||||
total = int(response.headers.get('content-length', 0))
|
||||
|
||||
with open(file_path, 'wb') as file, tqdm(
|
||||
desc=filename,
|
||||
total=total,
|
||||
unit='iB',
|
||||
unit_scale=True,
|
||||
unit_divisor=1024,
|
||||
) as bar:
|
||||
for data in response.iter_content(chunk_size=1024):
|
||||
size = file.write(data)
|
||||
bar.update(size)
|
||||
|
||||
print(f"下载完成:{filename}")
|
||||
return file_path
|
||||
|
||||
|
||||
def install_file(file_path, install_command):
|
||||
print(f"开始安装 {file_path} ...")
|
||||
subprocess.run(install_command(file_path), check=True)
|
||||
print(f"安装完成:{file_path}")
|
||||
|
||||
|
||||
def main():
|
||||
# 安装需要安装的文件
|
||||
for name, info in files.items():
|
||||
try:
|
||||
file_path = download_file(info["url"], info["filename"])
|
||||
install_file(file_path, info["install_cmd"])
|
||||
except Exception as e:
|
||||
print(f"处理 {name} 时出错: {e}")
|
||||
|
||||
# 下载可选文件
|
||||
for name, info in optional_files.items():
|
||||
try:
|
||||
download_file(info["url"], info["filename"])
|
||||
except Exception as e:
|
||||
print(f"下载 {name} 时出错: {e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Binary file not shown.
Loading…
Reference in New Issue