#!/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()