|
|
#!/usr/bin/env python3
|
|
|
"""
|
|
|
CodeDetect API文档生成工具
|
|
|
|
|
|
自动从源代码生成API文档,支持:
|
|
|
- REST API端点文档
|
|
|
- WebSocket事件文档
|
|
|
- 内部模块API文档
|
|
|
- 配置参数文档
|
|
|
- 示例代码生成
|
|
|
"""
|
|
|
|
|
|
import ast
|
|
|
import os
|
|
|
import sys
|
|
|
import json
|
|
|
import re
|
|
|
import inspect
|
|
|
import importlib.util
|
|
|
from pathlib import Path
|
|
|
from typing import Dict, List, Any, Optional, Tuple
|
|
|
from dataclasses import dataclass, asdict
|
|
|
from datetime import datetime
|
|
|
import yaml
|
|
|
import markdown
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
class APIEndpoint:
|
|
|
"""API端点信息"""
|
|
|
method: str
|
|
|
path: str
|
|
|
description: str
|
|
|
parameters: List[Dict[str, Any]]
|
|
|
responses: Dict[str, Dict[str, Any]]
|
|
|
examples: List[Dict[str, Any]]
|
|
|
authentication: bool
|
|
|
rate_limit: Optional[str] = None
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
class WebSocketEvent:
|
|
|
"""WebSocket事件信息"""
|
|
|
event: str
|
|
|
direction: str # 'client_to_server' or 'server_to_client'
|
|
|
description: str
|
|
|
payload: Dict[str, Any]
|
|
|
examples: List[Dict[str, Any]]
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
class ModuleAPI:
|
|
|
"""模块API信息"""
|
|
|
module_name: str
|
|
|
description: str
|
|
|
classes: List[Dict[str, Any]]
|
|
|
functions: List[Dict[str, Any]]
|
|
|
constants: List[Dict[str, Any]]
|
|
|
examples: List[str]
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
class ConfigParameter:
|
|
|
"""配置参数信息"""
|
|
|
name: str
|
|
|
type: str
|
|
|
default_value: Any
|
|
|
description: str
|
|
|
required: bool
|
|
|
options: Optional[List[str]] = None
|
|
|
|
|
|
|
|
|
class APIDocGenerator:
|
|
|
"""API文档生成器"""
|
|
|
|
|
|
def __init__(self, project_root: str, output_dir: str):
|
|
|
self.project_root = Path(project_root)
|
|
|
self.output_dir = Path(output_dir)
|
|
|
self.output_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
|
# 初始化数据结构
|
|
|
self.api_endpoints: List[APIEndpoint] = []
|
|
|
self.websocket_events: List[WebSocketEvent] = []
|
|
|
self.module_apis: List[ModuleAPI] = []
|
|
|
self.config_parameters: List[ConfigParameter] = []
|
|
|
|
|
|
# 忽略的文件和目录
|
|
|
self.ignore_patterns = [
|
|
|
'__pycache__',
|
|
|
'.git',
|
|
|
'.pytest_cache',
|
|
|
'venv',
|
|
|
'env',
|
|
|
'node_modules',
|
|
|
'build',
|
|
|
'dist',
|
|
|
'*.pyc'
|
|
|
]
|
|
|
|
|
|
def generate_all_docs(self):
|
|
|
"""生成所有API文档"""
|
|
|
print("🚀 开始生成API文档...")
|
|
|
|
|
|
# 扫描源代码
|
|
|
self._scan_source_code()
|
|
|
|
|
|
# 解析Flask路由
|
|
|
self._parse_flask_routes()
|
|
|
|
|
|
# 解析WebSocket事件
|
|
|
self._parse_websocket_events()
|
|
|
|
|
|
# 解析配置文件
|
|
|
self._parse_config_files()
|
|
|
|
|
|
# 生成各种文档
|
|
|
self._generate_rest_api_docs()
|
|
|
self._generate_websocket_docs()
|
|
|
self._generate_module_docs()
|
|
|
self._generate_config_docs()
|
|
|
self._generate_examples()
|
|
|
self._generate_index()
|
|
|
|
|
|
print("✅ API文档生成完成!")
|
|
|
|
|
|
def _scan_source_code(self):
|
|
|
"""扫描源代码文件"""
|
|
|
print("📂 扫描源代码...")
|
|
|
|
|
|
src_dir = self.project_root / "src"
|
|
|
if not src_dir.exists():
|
|
|
print(f"⚠️ 源代码目录不存在: {src_dir}")
|
|
|
return
|
|
|
|
|
|
for py_file in src_dir.rglob("*.py"):
|
|
|
if any(pattern in str(py_file) for pattern in self.ignore_patterns):
|
|
|
continue
|
|
|
|
|
|
try:
|
|
|
self._parse_python_file(py_file)
|
|
|
except Exception as e:
|
|
|
print(f"⚠️ 解析文件失败 {py_file}: {e}")
|
|
|
|
|
|
def _parse_python_file(self, file_path: Path):
|
|
|
"""解析Python文件"""
|
|
|
try:
|
|
|
with open(file_path, 'r', encoding='utf-8') as f:
|
|
|
content = f.read()
|
|
|
|
|
|
tree = ast.parse(content)
|
|
|
|
|
|
module_name = self._get_module_name(file_path)
|
|
|
module_api = ModuleAPI(
|
|
|
module_name=module_name,
|
|
|
description="",
|
|
|
classes=[],
|
|
|
functions=[],
|
|
|
constants=[],
|
|
|
examples=[]
|
|
|
)
|
|
|
|
|
|
for node in ast.walk(tree):
|
|
|
if isinstance(node, ast.ClassDef):
|
|
|
self._parse_class(node, module_api)
|
|
|
elif isinstance(node, ast.FunctionDef):
|
|
|
self._parse_function(node, module_api)
|
|
|
elif isinstance(node, ast.Assign):
|
|
|
self._parse_assign(node, module_api)
|
|
|
|
|
|
if module_api.classes or module_api.functions:
|
|
|
self.module_apis.append(module_api)
|
|
|
|
|
|
except Exception as e:
|
|
|
print(f"⚠️ 解析Python文件失败 {file_path}: {e}")
|
|
|
|
|
|
def _parse_class(self, node: ast.ClassDef, module_api: ModuleAPI):
|
|
|
"""解析类定义"""
|
|
|
class_info = {
|
|
|
"name": node.name,
|
|
|
"docstring": ast.get_docstring(node) or "",
|
|
|
"methods": [],
|
|
|
"properties": []
|
|
|
}
|
|
|
|
|
|
for item in node.body:
|
|
|
if isinstance(item, ast.FunctionDef):
|
|
|
method_info = {
|
|
|
"name": item.name,
|
|
|
"docstring": ast.get_docstring(item) or "",
|
|
|
"parameters": self._get_function_parameters(item),
|
|
|
"returns": self._get_function_returns(item),
|
|
|
"decorators": [self._get_decorator_name(d) for d in item.decorator_list]
|
|
|
}
|
|
|
class_info["methods"].append(method_info)
|
|
|
|
|
|
module_api.classes.append(class_info)
|
|
|
|
|
|
def _parse_function(self, node: ast.FunctionDef, module_api: ModuleAPI):
|
|
|
"""解析函数定义"""
|
|
|
if not hasattr(node, 'parent_class'): # 不在类中的函数
|
|
|
function_info = {
|
|
|
"name": node.name,
|
|
|
"docstring": ast.get_docstring(node) or "",
|
|
|
"parameters": self._get_function_parameters(node),
|
|
|
"returns": self._get_function_returns(node),
|
|
|
"decorators": [self._get_decorator_name(d) for d in node.decorator_list]
|
|
|
}
|
|
|
module_api.functions.append(function_info)
|
|
|
|
|
|
def _parse_assign(self, node: ast.Assign, module_api: ModuleAPI):
|
|
|
"""解析赋值语句"""
|
|
|
for target in node.targets:
|
|
|
if isinstance(target, ast.Name):
|
|
|
if target.id.isupper(): # 常量
|
|
|
constant_info = {
|
|
|
"name": target.id,
|
|
|
"value": self._get_constant_value(node.value),
|
|
|
"docstring": "" # 可以从注释中提取
|
|
|
}
|
|
|
module_api.constants.append(constant_info)
|
|
|
|
|
|
def _get_module_name(self, file_path: Path) -> str:
|
|
|
"""获取模块名"""
|
|
|
relative_path = file_path.relative_to(self.project_root / "src")
|
|
|
module_path = str(relative_path.with_suffix('')).replace(os.sep, '.')
|
|
|
return module_path
|
|
|
|
|
|
def _get_function_parameters(self, node: ast.FunctionDef) -> List[Dict[str, Any]]:
|
|
|
"""获取函数参数"""
|
|
|
parameters = []
|
|
|
|
|
|
for arg in node.args.args:
|
|
|
param_info = {
|
|
|
"name": arg.arg,
|
|
|
"type": "",
|
|
|
"default": None,
|
|
|
"docstring": ""
|
|
|
}
|
|
|
|
|
|
# 尝试从类型注解获取类型
|
|
|
if arg.annotation:
|
|
|
param_info["type"] = self._get_type_string(arg.annotation)
|
|
|
|
|
|
parameters.append(param_info)
|
|
|
|
|
|
return parameters
|
|
|
|
|
|
def _get_function_returns(self, node: ast.FunctionDef) -> str:
|
|
|
"""获取函数返回类型"""
|
|
|
if node.returns:
|
|
|
return self._get_type_string(node.returns)
|
|
|
return ""
|
|
|
|
|
|
def _get_type_string(self, node: ast.AST) -> str:
|
|
|
"""获取类型字符串"""
|
|
|
if isinstance(node, ast.Name):
|
|
|
return node.id
|
|
|
elif isinstance(node, ast.Attribute):
|
|
|
return f"{node.value.id}.{node.attr}"
|
|
|
elif isinstance(node, ast.Subscript):
|
|
|
value = self._get_type_string(node.value)
|
|
|
slice_value = self._get_type_string(node.slice)
|
|
|
return f"{value}[{slice_value}]"
|
|
|
else:
|
|
|
return ""
|
|
|
|
|
|
def _get_decorator_name(self, node: ast.AST) -> str:
|
|
|
"""获取装饰器名称"""
|
|
|
if isinstance(node, ast.Name):
|
|
|
return node.id
|
|
|
elif isinstance(node, ast.Attribute):
|
|
|
return f"{node.value.id}.{node.attr}"
|
|
|
return ""
|
|
|
|
|
|
def _get_constant_value(self, node: ast.AST) -> Any:
|
|
|
"""获取常量值"""
|
|
|
if isinstance(node, ast.Constant):
|
|
|
return node.value
|
|
|
elif isinstance(node, ast.List):
|
|
|
return [self._get_constant_value(elt) for elt in node.elts]
|
|
|
elif isinstance(node, ast.Dict):
|
|
|
return {
|
|
|
self._get_constant_value(k): self._get_constant_value(v)
|
|
|
for k, v in zip(node.keys, node.values)
|
|
|
}
|
|
|
else:
|
|
|
return str(node)
|
|
|
|
|
|
def _parse_flask_routes(self):
|
|
|
"""解析Flask路由"""
|
|
|
print("🌐 解析Flask路由...")
|
|
|
|
|
|
api_files = [
|
|
|
self.project_root / "src" / "ui" / "api.py",
|
|
|
self.project_root / "src" / "api" / "routes.py"
|
|
|
]
|
|
|
|
|
|
for file_path in api_files:
|
|
|
if not file_path.exists():
|
|
|
continue
|
|
|
|
|
|
try:
|
|
|
self._parse_flask_file(file_path)
|
|
|
except Exception as e:
|
|
|
print(f"⚠️ 解析Flask文件失败 {file_path}: {e}")
|
|
|
|
|
|
def _parse_flask_file(self, file_path: Path):
|
|
|
"""解析Flask文件"""
|
|
|
with open(file_path, 'r', encoding='utf-8') as f:
|
|
|
content = f.read()
|
|
|
|
|
|
# 使用正则表达式提取路由信息
|
|
|
route_pattern = r'@(\w+)\.route\([\'"]([^\'\"]+)[\'"](?:,\s*methods=\[([^\]]+)\])?\)'
|
|
|
|
|
|
for match in re.finditer(route_pattern, content):
|
|
|
blueprint = match.group(1)
|
|
|
path = match.group(2)
|
|
|
methods = match.group(3)
|
|
|
|
|
|
if methods:
|
|
|
method_list = [m.strip().strip('\'"') for m in methods.split(',')]
|
|
|
else:
|
|
|
method_list = ['GET']
|
|
|
|
|
|
for method in method_list:
|
|
|
# 提取函数文档字符串
|
|
|
func_start = match.end()
|
|
|
func_pattern = r'def\s+(\w+)\s*\([^)]*\):\s*"""([^"]*)"""'
|
|
|
func_match = re.search(func_pattern, content[func_start:])
|
|
|
|
|
|
description = ""
|
|
|
if func_match:
|
|
|
description = func_match.group(2).strip()
|
|
|
|
|
|
endpoint = APIEndpoint(
|
|
|
method=method,
|
|
|
path=path,
|
|
|
description=description,
|
|
|
parameters=[],
|
|
|
responses={},
|
|
|
examples=[],
|
|
|
authentication=True
|
|
|
)
|
|
|
|
|
|
self.api_endpoints.append(endpoint)
|
|
|
|
|
|
def _parse_websocket_events(self):
|
|
|
"""解析WebSocket事件"""
|
|
|
print("🔌 解析WebSocket事件...")
|
|
|
|
|
|
# 从现有文档和代码中提取WebSocket事件
|
|
|
events = [
|
|
|
WebSocketEvent(
|
|
|
event="file_upload",
|
|
|
direction="client_to_server",
|
|
|
description="客户端上传文件",
|
|
|
payload={"file_id": "string", "filename": "string", "size": "number"},
|
|
|
examples=[{
|
|
|
"event": "file_upload",
|
|
|
"data": {"file_id": "file_123", "filename": "test.c", "size": 1024}
|
|
|
}]
|
|
|
),
|
|
|
WebSocketEvent(
|
|
|
event="verification_progress",
|
|
|
direction="server_to_client",
|
|
|
description="验证进度更新",
|
|
|
payload={"job_id": "string", "progress": "number", "status": "string"},
|
|
|
examples=[{
|
|
|
"event": "verification_progress",
|
|
|
"data": {"job_id": "job_456", "progress": 75, "status": "running"}
|
|
|
}]
|
|
|
),
|
|
|
WebSocketEvent(
|
|
|
event="verification_result",
|
|
|
direction="server_to_client",
|
|
|
description="验证结果通知",
|
|
|
payload={"job_id": "string", "result": "object", "success": "boolean"},
|
|
|
examples=[{
|
|
|
"event": "verification_result",
|
|
|
"data": {"job_id": "job_456", "result": {...}, "success": true}
|
|
|
}]
|
|
|
)
|
|
|
]
|
|
|
|
|
|
self.websocket_events.extend(events)
|
|
|
|
|
|
def _parse_config_files(self):
|
|
|
"""解析配置文件"""
|
|
|
print("⚙️ 解析配置文件...")
|
|
|
|
|
|
config_files = [
|
|
|
self.project_root / "config" / "default.yaml",
|
|
|
self.project_root / "config" / "development.yaml",
|
|
|
self.project_root / "config" / "production.yaml"
|
|
|
]
|
|
|
|
|
|
for config_file in config_files:
|
|
|
if not config_file.exists():
|
|
|
continue
|
|
|
|
|
|
try:
|
|
|
with open(config_file, 'r', encoding='utf-8') as f:
|
|
|
config_data = yaml.safe_load(f)
|
|
|
|
|
|
self._parse_config_data(config_data, config_file.name)
|
|
|
except Exception as e:
|
|
|
print(f"⚠️ 解析配置文件失败 {config_file}: {e}")
|
|
|
|
|
|
def _parse_config_data(self, config_data: Dict[str, Any], filename: str):
|
|
|
"""解析配置数据"""
|
|
|
def traverse_config(data: Dict[str, Any], prefix: str = ""):
|
|
|
for key, value in data.items():
|
|
|
full_key = f"{prefix}.{key}" if prefix else key
|
|
|
|
|
|
if isinstance(value, dict):
|
|
|
traverse_config(value, full_key)
|
|
|
else:
|
|
|
param = ConfigParameter(
|
|
|
name=full_key,
|
|
|
type=type(value).__name__,
|
|
|
default_value=value,
|
|
|
description=f"配置参数 {full_key} 来自 {filename}",
|
|
|
required=False
|
|
|
)
|
|
|
self.config_parameters.append(param)
|
|
|
|
|
|
if isinstance(config_data, dict):
|
|
|
traverse_config(config_data)
|
|
|
|
|
|
def _generate_rest_api_docs(self):
|
|
|
"""生成REST API文档"""
|
|
|
print("📄 生成REST API文档...")
|
|
|
|
|
|
md_content = """# REST API 文档
|
|
|
|
|
|
## 概述
|
|
|
|
|
|
CodeDetect提供RESTful API用于代码验证、规范生成和结果查询。
|
|
|
|
|
|
## 基础信息
|
|
|
|
|
|
- **基础URL**: `http://localhost:8080/api`
|
|
|
- **认证方式**: Bearer Token
|
|
|
- **内容类型**: `application/json`
|
|
|
- **字符编码**: UTF-8
|
|
|
|
|
|
## 通用响应格式
|
|
|
|
|
|
所有API响应都遵循以下格式:
|
|
|
|
|
|
```json
|
|
|
{
|
|
|
"success": true,
|
|
|
"data": {},
|
|
|
"message": "操作成功",
|
|
|
"timestamp": "2024-01-01T00:00:00Z"
|
|
|
}
|
|
|
```
|
|
|
|
|
|
错误响应格式:
|
|
|
|
|
|
```json
|
|
|
{
|
|
|
"success": false,
|
|
|
"error": {
|
|
|
"code": "ERROR_CODE",
|
|
|
"message": "错误描述",
|
|
|
"details": {}
|
|
|
},
|
|
|
"timestamp": "2024-01-01T00:00:00Z"
|
|
|
}
|
|
|
```
|
|
|
|
|
|
## API端点列表
|
|
|
|
|
|
"""
|
|
|
|
|
|
# 按方法分组
|
|
|
endpoints_by_method = {}
|
|
|
for endpoint in self.api_endpoints:
|
|
|
if endpoint.method not in endpoints_by_method:
|
|
|
endpoints_by_method[endpoint.method] = []
|
|
|
endpoints_by_method[endpoint.method].append(endpoint)
|
|
|
|
|
|
for method in ['GET', 'POST', 'PUT', 'DELETE']:
|
|
|
if method in endpoints_by_method:
|
|
|
md_content += f"### {method} 端点\n\n"
|
|
|
|
|
|
for endpoint in endpoints_by_method[method]:
|
|
|
md_content += f"#### {method} {endpoint.path}\n\n"
|
|
|
|
|
|
if endpoint.description:
|
|
|
md_content += f"**描述**: {endpoint.description}\n\n"
|
|
|
|
|
|
md_content += f"**认证**: {'是' if endpoint.authentication else '否'}\n\n"
|
|
|
|
|
|
if endpoint.parameters:
|
|
|
md_content += "**参数**:\n\n"
|
|
|
for param in endpoint.parameters:
|
|
|
md_content += f"- `{param['name']}` ({param['type']}): {param.get('description', '')}\n"
|
|
|
md_content += "\n"
|
|
|
|
|
|
if endpoint.examples:
|
|
|
md_content += "**示例**:\n\n"
|
|
|
for example in endpoint.examples:
|
|
|
md_content += "```json\n"
|
|
|
md_content += json.dumps(example, indent=2, ensure_ascii=False)
|
|
|
md_content += "\n```\n\n"
|
|
|
|
|
|
md_content += "---\n\n"
|
|
|
|
|
|
# 保存文档
|
|
|
with open(self.output_dir / "rest-api.md", 'w', encoding='utf-8') as f:
|
|
|
f.write(md_content)
|
|
|
|
|
|
def _generate_websocket_docs(self):
|
|
|
"""生成WebSocket文档"""
|
|
|
print("🔌 生成WebSocket文档...")
|
|
|
|
|
|
md_content = """# WebSocket API 文档
|
|
|
|
|
|
## 概述
|
|
|
|
|
|
CodeDetect使用WebSocket提供实时通信,用于进度更新、结果通知等功能。
|
|
|
|
|
|
## 连接信息
|
|
|
|
|
|
- **WebSocket URL**: `ws://localhost:8080/ws`
|
|
|
- **协议**: WebSocket
|
|
|
- **消息格式**: JSON
|
|
|
|
|
|
## 消息格式
|
|
|
|
|
|
所有WebSocket消息都遵循以下格式:
|
|
|
|
|
|
```json
|
|
|
{
|
|
|
"event": "事件名称",
|
|
|
"data": {},
|
|
|
"timestamp": "2024-01-01T00:00:00Z"
|
|
|
}
|
|
|
```
|
|
|
|
|
|
## 事件列表
|
|
|
|
|
|
"""
|
|
|
|
|
|
# 按方向分组
|
|
|
events_by_direction = {}
|
|
|
for event in self.websocket_events:
|
|
|
if event.direction not in events_by_direction:
|
|
|
events_by_direction[event.direction] = []
|
|
|
events_by_direction[event.direction].append(event)
|
|
|
|
|
|
for direction in ['client_to_server', 'server_to_client']:
|
|
|
direction_name = "客户端 → 服务器" if direction == 'client_to_server' else "服务器 → 客户端"
|
|
|
md_content += f"### {direction_name}\n\n"
|
|
|
|
|
|
if direction in events_by_direction:
|
|
|
for event in events_by_direction[direction]:
|
|
|
md_content += f"#### {event.event}\n\n"
|
|
|
md_content += f"**描述**: {event.description}\n\n"
|
|
|
|
|
|
if event.payload:
|
|
|
md_content += "**数据结构**:\n\n"
|
|
|
md_content += "```json\n"
|
|
|
md_content += json.dumps(event.payload, indent=2, ensure_ascii=False)
|
|
|
md_content += "\n```\n\n"
|
|
|
|
|
|
if event.examples:
|
|
|
md_content += "**示例**:\n\n"
|
|
|
for example in event.examples:
|
|
|
md_content += "```json\n"
|
|
|
md_content += json.dumps(example, indent=2, ensure_ascii=False)
|
|
|
md_content += "\n```\n\n"
|
|
|
|
|
|
md_content += "---\n\n"
|
|
|
|
|
|
# 保存文档
|
|
|
with open(self.output_dir / "websocket-api.md", 'w', encoding='utf-8') as f:
|
|
|
f.write(md_content)
|
|
|
|
|
|
def _generate_module_docs(self):
|
|
|
"""生成模块文档"""
|
|
|
print("📦 生成模块文档...")
|
|
|
|
|
|
md_content = """# 内部模块 API 文档
|
|
|
|
|
|
## 概述
|
|
|
|
|
|
本文档描述了CodeDetect内部模块的API,供开发者和贡献者使用。
|
|
|
|
|
|
## 模块列表
|
|
|
|
|
|
"""
|
|
|
|
|
|
for module in self.module_apis:
|
|
|
md_content += f"### {module.module_name}\n\n"
|
|
|
|
|
|
if module.description:
|
|
|
md_content += f"**描述**: {module.description}\n\n"
|
|
|
|
|
|
if module.classes:
|
|
|
md_content += "#### 类\n\n"
|
|
|
for class_info in module.classes:
|
|
|
md_content += f"##### {class_info['name']}\n\n"
|
|
|
|
|
|
if class_info['docstring']:
|
|
|
md_content += f"**说明**: {class_info['docstring']}\n\n"
|
|
|
|
|
|
if class_info['methods']:
|
|
|
md_content += "**方法**:\n\n"
|
|
|
for method in class_info['methods']:
|
|
|
md_content += f"- `{method['name']}({', '.join(p['name'] for p in method['parameters'])})`\n"
|
|
|
if method['docstring']:
|
|
|
md_content += f" - {method['docstring']}\n"
|
|
|
md_content += "\n"
|
|
|
|
|
|
if module.functions:
|
|
|
md_content += "#### 函数\n\n"
|
|
|
for function in module.functions:
|
|
|
md_content += f"##### {function['name']}()\n\n"
|
|
|
|
|
|
if function['docstring']:
|
|
|
md_content += f"**说明**: {function['docstring']}\n\n"
|
|
|
|
|
|
if function['parameters']:
|
|
|
md_content += "**参数**:\n\n"
|
|
|
for param in function['parameters']:
|
|
|
md_content += f"- `{param['name']}`: {param['type']}\n"
|
|
|
md_content += "\n"
|
|
|
|
|
|
if function['returns']:
|
|
|
md_content += f"**返回值**: {function['returns']}\n\n"
|
|
|
|
|
|
md_content += "---\n\n"
|
|
|
|
|
|
# 保存文档
|
|
|
with open(self.output_dir / "internal-api.md", 'w', encoding='utf-8') as f:
|
|
|
f.write(md_content)
|
|
|
|
|
|
def _generate_config_docs(self):
|
|
|
"""生成配置文档"""
|
|
|
print("⚙️ 生成配置文档...")
|
|
|
|
|
|
md_content = """# 配置参数文档
|
|
|
|
|
|
## 概述
|
|
|
|
|
|
本文档描述了CodeDetect的所有配置参数及其说明。
|
|
|
|
|
|
## 配置文件位置
|
|
|
|
|
|
- 默认配置: `config/default.yaml`
|
|
|
- 开发环境: `config/development.yaml`
|
|
|
- 生产环境: `config/production.yaml`
|
|
|
|
|
|
## 配置参数列表
|
|
|
|
|
|
"""
|
|
|
|
|
|
# 按类型分组
|
|
|
params_by_type = {}
|
|
|
for param in self.config_parameters:
|
|
|
if param.type not in params_by_type:
|
|
|
params_by_type[param.type] = []
|
|
|
params_by_type[param.type].append(param)
|
|
|
|
|
|
for param_type in sorted(params_by_type.keys()):
|
|
|
md_content += f"### {param_type} 类型\n\n"
|
|
|
|
|
|
for param in params_by_type[param_type]:
|
|
|
md_content += f"#### {param.name}\n\n"
|
|
|
md_content += f"**默认值**: `{param.default_value}`\n\n"
|
|
|
md_content += f"**是否必需**: {'是' if param.required else '否'}\n\n"
|
|
|
md_content += f"**说明**: {param.description}\n\n"
|
|
|
|
|
|
if param.options:
|
|
|
md_content += "**可选值**:\n\n"
|
|
|
for option in param.options:
|
|
|
md_content += f"- `{option}`\n"
|
|
|
md_content += "\n"
|
|
|
|
|
|
md_content += "---\n\n"
|
|
|
|
|
|
# 保存文档
|
|
|
with open(self.output_dir / "configuration.md", 'w', encoding='utf-8') as f:
|
|
|
f.write(md_content)
|
|
|
|
|
|
def _generate_examples(self):
|
|
|
"""生成示例代码"""
|
|
|
print("💻 生成示例代码...")
|
|
|
|
|
|
# 创建示例目录
|
|
|
examples_dir = self.output_dir / "examples"
|
|
|
examples_dir.mkdir(exist_ok=True)
|
|
|
|
|
|
# 生成各种示例
|
|
|
examples = {
|
|
|
"basic_usage.py": """#!/usr/bin/env python3
|
|
|
"""
|
|
|
\"\"\"
|
|
|
CodeDetect基础使用示例
|
|
|
|
|
|
演示如何使用CodeDetect进行基本的代码验证。
|
|
|
\"\"\"
|
|
|
|
|
|
import asyncio
|
|
|
import json
|
|
|
from pathlib import Path
|
|
|
|
|
|
# 导入CodeDetect模块
|
|
|
from src.verify.cbmc_runner import CBMCRunner
|
|
|
from src.mutate.engine import MutationEngine
|
|
|
from src.parse.code_parser import CodeParser
|
|
|
|
|
|
|
|
|
async def basic_verification_example():
|
|
|
\"\"\"基础验证示例\"\"\"
|
|
|
print("=== 基础验证示例 ===")
|
|
|
|
|
|
# 示例代码
|
|
|
code = \"\"\"
|
|
|
int add(int a, int b) {
|
|
|
return a + b;
|
|
|
}
|
|
|
\"\"\"
|
|
|
|
|
|
# 解析代码
|
|
|
parser = CodeParser()
|
|
|
with open("temp_example.c", "w") as f:
|
|
|
f.write(code)
|
|
|
|
|
|
try:
|
|
|
# 解析函数信息
|
|
|
parse_result = parser.parse_file("temp_example.c")
|
|
|
print(f"找到 {len(parse_result.functions)} 个函数")
|
|
|
|
|
|
for func in parse_result.functions:
|
|
|
print(f"- {func.name}: {func.return_type}")
|
|
|
print(f" 参数: {[p.name + ': ' + p.type for p in func.parameters]}")
|
|
|
|
|
|
# 生成验证规范
|
|
|
specification = \"\"\"
|
|
|
void add_test() {
|
|
|
int a = __CPROVER_nondet_int();
|
|
|
int b = __CPROVER_nondet_int();
|
|
|
|
|
|
// 约束输入范围
|
|
|
__CPROVER_assume(a >= -1000 && a <= 1000);
|
|
|
__CPROVER_assume(b >= -1000 && b <= 1000);
|
|
|
|
|
|
int result = add(a, b);
|
|
|
|
|
|
// 验证结果
|
|
|
__CPROVER_assert(result == a + b, "addition_correct");
|
|
|
}
|
|
|
\"\"\"
|
|
|
|
|
|
# 运行验证
|
|
|
runner = CBMCRunner()
|
|
|
result = await runner.run_verification(
|
|
|
function_metadata={"name": "add"},
|
|
|
source_file="temp_example.c",
|
|
|
specification=specification
|
|
|
)
|
|
|
|
|
|
print(f"验证结果: {result.status}")
|
|
|
print(f"执行时间: {result.execution_time}s")
|
|
|
|
|
|
finally:
|
|
|
# 清理临时文件
|
|
|
if Path("temp_example.c").exists():
|
|
|
Path("temp_example.c").unlink()
|
|
|
|
|
|
|
|
|
async def mutation_example():
|
|
|
\"\"\"突变生成示例\"\"\"
|
|
|
print("\\n=== 突变生成示例 ===")
|
|
|
|
|
|
# 基础规范
|
|
|
base_spec = \"\"\"
|
|
|
void add_test() {
|
|
|
int a = __CPROVER_nondet_int();
|
|
|
int b = __CPROVER_nondet_int();
|
|
|
int result = add(a, b);
|
|
|
__CPROVER_assert(result == a + b, "addition_correct");
|
|
|
}
|
|
|
\"\"\"
|
|
|
|
|
|
# 函数元数据
|
|
|
function_metadata = [{
|
|
|
"name": "add",
|
|
|
"return_type": "int",
|
|
|
"parameters": [
|
|
|
{"name": "a", "type": "int"},
|
|
|
{"name": "b", "type": "int"}
|
|
|
],
|
|
|
"complexity_score": 0.3
|
|
|
}]
|
|
|
|
|
|
# 生成突变
|
|
|
engine = MutationEngine()
|
|
|
mutations = engine.generate_mutations(
|
|
|
base_spec,
|
|
|
function_metadata,
|
|
|
max_mutations=3
|
|
|
)
|
|
|
|
|
|
print(f"生成了 {len(mutations)} 个突变:")
|
|
|
for i, mutation in enumerate(mutations):
|
|
|
print(f"\\n突变 {i+1}:")
|
|
|
print(f"类型: {mutation.mutation_type}")
|
|
|
print(f"置信度: {mutation.confidence:.2f}")
|
|
|
print(f"规范:\\n{mutation.specification}")
|
|
|
|
|
|
|
|
|
async def main():
|
|
|
\"\"\"主函数\"\"\"
|
|
|
try:
|
|
|
await basic_verification_example()
|
|
|
await mutation_example()
|
|
|
except Exception as e:
|
|
|
print(f"示例执行失败: {e}")
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
asyncio.run(main())
|
|
|
""",
|
|
|
|
|
|
"freertos_example.py": """#!/usr/bin/env python3
|
|
|
"""
|
|
|
\"\"\"
|
|
|
FreeRTOS验证示例
|
|
|
|
|
|
演示如何使用CodeDetect验证FreeRTOS代码。
|
|
|
\"\"\"
|
|
|
|
|
|
import asyncio
|
|
|
from pathlib import Path
|
|
|
|
|
|
from src.verify.cbmc_runner import CBMCRunner
|
|
|
from src.mutate.engine import MutationEngine
|
|
|
|
|
|
|
|
|
async def freertos_task_verification():
|
|
|
\"\"\"FreeRTOS任务验证示例\"\"\"
|
|
|
print("=== FreeRTOS任务验证示例 ===")
|
|
|
|
|
|
# FreeRTOS任务代码
|
|
|
freertos_code = \"\"\"
|
|
|
#include "FreeRTOS.h"
|
|
|
#include "task.h"
|
|
|
|
|
|
typedef struct {
|
|
|
int task_id;
|
|
|
int counter;
|
|
|
int max_count;
|
|
|
BaseType_t should_stop;
|
|
|
} TaskParameters_t;
|
|
|
|
|
|
void vCounterTask(void *pvParameters) {
|
|
|
TaskParameters_t *params = (TaskParameters_t *)pvParameters;
|
|
|
|
|
|
// 参数验证
|
|
|
configASSERT(params != NULL);
|
|
|
configASSERT(params->task_id > 0);
|
|
|
configASSERT(params->max_count > 0);
|
|
|
|
|
|
while (params->should_stop == pdFALSE) {
|
|
|
if (params->counter < params->max_count) {
|
|
|
params->counter++;
|
|
|
}
|
|
|
vTaskDelay(pdMS_TO_TICKS(100));
|
|
|
}
|
|
|
|
|
|
vTaskDelete(NULL);
|
|
|
}
|
|
|
\"\"\"
|
|
|
|
|
|
# 保存代码到文件
|
|
|
with open("freertos_task_example.c", "w") as f:
|
|
|
f.write(freertos_code)
|
|
|
|
|
|
try:
|
|
|
# FreeRTOS验证规范
|
|
|
specification = \"\"\"
|
|
|
#include "FreeRTOS.h"
|
|
|
#include "task.h"
|
|
|
|
|
|
typedef struct {
|
|
|
int task_id;
|
|
|
int counter;
|
|
|
int max_count;
|
|
|
BaseType_t should_stop;
|
|
|
} TaskParameters_t;
|
|
|
|
|
|
void test_task_creation() {
|
|
|
TaskParameters_t params;
|
|
|
TaskHandle_t task_handle;
|
|
|
|
|
|
// 初始化参数
|
|
|
params.task_id = 1;
|
|
|
params.counter = 0;
|
|
|
params.max_count = 10;
|
|
|
params.should_stop = pdFALSE;
|
|
|
|
|
|
// 测试任务创建
|
|
|
BaseType_t result = xTaskCreate(
|
|
|
vCounterTask,
|
|
|
"CounterTask",
|
|
|
configMINIMAL_STACK_SIZE,
|
|
|
¶ms,
|
|
|
1,
|
|
|
&task_handle
|
|
|
);
|
|
|
|
|
|
__CPROVER_assert(result == pdPASS, "task_creation_success");
|
|
|
__CPROVER_assert(task_handle != NULL, "task_handle_not_null");
|
|
|
}
|
|
|
|
|
|
void test_parameter_validation() {
|
|
|
TaskParameters_t *params = (TaskParameters_t *)__CPROVER_allocate(sizeof(TaskParameters_t));
|
|
|
|
|
|
// 测试NULL指针检查
|
|
|
vCounterTask(params);
|
|
|
|
|
|
// 测试有效参数
|
|
|
params->task_id = __CPROVER_nondet_int();
|
|
|
params->max_count = __CPROVER_nondet_int();
|
|
|
__CPROVER_assume(params->task_id > 0);
|
|
|
__CPROVER_assume(params->max_count > 0);
|
|
|
|
|
|
vCounterTask(params);
|
|
|
}
|
|
|
\"\"\"
|
|
|
|
|
|
# 运行验证
|
|
|
runner = CBMCRunner()
|
|
|
result = await runner.run_verification(
|
|
|
function_metadata={"name": "vCounterTask"},
|
|
|
source_file="freertos_task_example.c",
|
|
|
specification=specification
|
|
|
)
|
|
|
|
|
|
print(f"FreeRTOS验证结果: {result.status}")
|
|
|
print(f"执行时间: {result.execution_time}s")
|
|
|
|
|
|
finally:
|
|
|
# 清理临时文件
|
|
|
if Path("freertos_task_example.c").exists():
|
|
|
Path("freertos_task_example.c").unlink()
|
|
|
|
|
|
|
|
|
async def main():
|
|
|
\"\"\"主函数\"\"\"
|
|
|
try:
|
|
|
await freertos_task_verification()
|
|
|
except Exception as e:
|
|
|
print(f"FreeRTOS示例执行失败: {e}")
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
asyncio.run(main())
|
|
|
"""
|
|
|
}
|
|
|
|
|
|
for filename, content in examples.items():
|
|
|
with open(examples_dir / filename, 'w', encoding='utf-8') as f:
|
|
|
f.write(content)
|
|
|
|
|
|
def _generate_index(self):
|
|
|
"""生成索引文档"""
|
|
|
print("📑 生成索引文档...")
|
|
|
|
|
|
md_content = f"""# CodeDetect API 文档
|
|
|
|
|
|
> 生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
|
|
|
|
|
## 概述
|
|
|
|
|
|
CodeDetect是一个基于LLM和CBMC的C代码形式化验证系统。本文档提供了完整的API参考,包括REST API、WebSocket事件、内部模块API和配置参数。
|
|
|
|
|
|
## 文档结构
|
|
|
|
|
|
### 1. [REST API](rest-api.md)
|
|
|
- API端点列表
|
|
|
- 请求/响应格式
|
|
|
- 认证和权限
|
|
|
- 示例代码
|
|
|
|
|
|
### 2. [WebSocket API](websocket-api.md)
|
|
|
- 实时通信事件
|
|
|
- 消息格式
|
|
|
- 连接管理
|
|
|
- 示例代码
|
|
|
|
|
|
### 3. [内部模块 API](internal-api.md)
|
|
|
- 核心模块接口
|
|
|
- 类和函数说明
|
|
|
- 使用示例
|
|
|
- 扩展开发
|
|
|
|
|
|
### 4. [配置参数](configuration.md)
|
|
|
- 系统配置
|
|
|
- 环境变量
|
|
|
- 参数说明
|
|
|
- 最佳实践
|
|
|
|
|
|
### 5. [示例代码](examples/)
|
|
|
- 基础使用示例
|
|
|
- FreeRTOS验证示例
|
|
|
- 自定义扩展示例
|
|
|
|
|
|
## 快速开始
|
|
|
|
|
|
### 安装依赖
|
|
|
|
|
|
```bash
|
|
|
pip install -r requirements.txt
|
|
|
```
|
|
|
|
|
|
### 基础使用
|
|
|
|
|
|
```python
|
|
|
from src.verify.cbmc_runner import CBMCRunner
|
|
|
from src.mutate.engine import MutationEngine
|
|
|
|
|
|
# 创建验证器
|
|
|
runner = CBMCRunner()
|
|
|
engine = MutationEngine()
|
|
|
|
|
|
# 运行验证
|
|
|
result = await runner.run_verification(
|
|
|
function_metadata={"name": "test_function"},
|
|
|
source_file="test.c",
|
|
|
specification="void test() { }"
|
|
|
)
|
|
|
```
|
|
|
|
|
|
## 贡献指南
|
|
|
|
|
|
如果您发现文档问题或想要贡献,请:
|
|
|
|
|
|
1. 查看源代码中的注释和文档字符串
|
|
|
2. 运行文档生成脚本
|
|
|
3. 提交Pull Request
|
|
|
|
|
|
## 技术支持
|
|
|
|
|
|
- 📧 邮箱: support@codedetect.com
|
|
|
- 📖 文档: [在线文档](https://docs.codedetect.com)
|
|
|
- 🐛 问题报告: [GitHub Issues](https://github.com/codedetect/issues)
|
|
|
|
|
|
---
|
|
|
*本文档由自动生成工具创建*
|
|
|
"""
|
|
|
|
|
|
# 保存索引文档
|
|
|
with open(self.output_dir / "README.md", 'w', encoding='utf-8') as f:
|
|
|
f.write(md_content)
|
|
|
|
|
|
|
|
|
def main():
|
|
|
"""主函数"""
|
|
|
import argparse
|
|
|
|
|
|
parser = argparse.ArgumentParser(description='CodeDetect API文档生成工具')
|
|
|
parser.add_argument('--project-root', type=str, default='.',
|
|
|
help='项目根目录 (默认: .)')
|
|
|
parser.add_argument('--output-dir', type=str, default='docs/api',
|
|
|
help='输出目录 (默认: docs/api)')
|
|
|
parser.add_argument('--verbose', action='store_true',
|
|
|
help='详细输出')
|
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
# 生成文档
|
|
|
generator = APIDocGenerator(args.project_root, args.output_dir)
|
|
|
generator.generate_all_docs()
|
|
|
|
|
|
print(f"\n📚 文档已生成到: {args.output_dir}")
|
|
|
print("📖 开始阅读: docs/api/README.md")
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
main() |