AIDeveloper-PC/requirements_generator/utils/output_writer.py

223 lines
8.5 KiB
Python
Raw Normal View History

2026-03-05 09:24:49 +00:00
# utils/output_writer.py - 代码文件 & JSON 输出工具
2026-03-04 18:09:45 +00:00
import os
import json
from pathlib import Path
2026-03-05 09:24:49 +00:00
from typing import Dict, List
2026-03-04 18:09:45 +00:00
2026-03-06 13:50:01 +00:00
from gui_ai_developer import config
2026-03-04 18:09:45 +00:00
VALID_TYPES = {
"integer", "string", "boolean", "float",
"list", "dict", "object", "void", "any",
}
VALID_INOUT = {"in", "out", "inout"}
2026-03-05 05:38:26 +00:00
# ══════════════════════════════════════════════════════
# 目录管理
# ══════════════════════════════════════════════════════
2026-03-04 18:09:45 +00:00
def build_project_output_dir(project_name: str) -> str:
2026-03-05 05:38:26 +00:00
safe = "".join(c if c.isalnum() or c in "-_" else "_" for c in project_name)
return os.path.join(config.OUTPUT_BASE_DIR, safe)
2026-03-04 18:09:45 +00:00
def ensure_project_dir(project_name: str) -> str:
2026-03-05 05:38:26 +00:00
"""确保项目根输出目录存在,并创建 __init__.py"""
2026-03-04 18:09:45 +00:00
output_dir = build_project_output_dir(project_name)
os.makedirs(output_dir, exist_ok=True)
init_file = os.path.join(output_dir, "__init__.py")
if not os.path.exists(init_file):
2026-03-05 05:38:26 +00:00
Path(init_file).write_text("# Auto-generated project package\n", encoding="utf-8")
2026-03-04 18:09:45 +00:00
return output_dir
2026-03-05 05:38:26 +00:00
def ensure_module_dir(output_dir: str, module: str) -> str:
"""确保模块子目录存在,并创建 __init__.py"""
module_dir = os.path.join(output_dir, module)
os.makedirs(module_dir, exist_ok=True)
init_file = os.path.join(module_dir, "__init__.py")
if not os.path.exists(init_file):
Path(init_file).write_text(
f"# Auto-generated module package: {module}\n", encoding="utf-8"
)
return module_dir
2026-03-04 18:09:45 +00:00
2026-03-05 05:38:26 +00:00
# ══════════════════════════════════════════════════════
# README
# ══════════════════════════════════════════════════════
2026-03-04 18:09:45 +00:00
def write_project_readme(
2026-03-05 05:38:26 +00:00
output_dir: str,
project_name: str,
project_description: str,
2026-03-04 18:09:45 +00:00
requirements_summary: str,
2026-03-05 05:38:26 +00:00
modules: List[str] = None,
2026-03-04 18:09:45 +00:00
) -> str:
2026-03-05 05:38:26 +00:00
"""生成项目 README.md"""
module_section = ""
if modules:
2026-03-05 09:24:49 +00:00
module_list = "\n".join(f"- `{m}/`" for m in sorted(set(modules)))
2026-03-05 05:38:26 +00:00
module_section = f"\n## 功能模块\n\n{module_list}\n"
2026-03-04 18:09:45 +00:00
2026-03-05 05:38:26 +00:00
content = f"""# {project_name}
2026-03-04 18:09:45 +00:00
> Auto-generated by Requirement Analyzer
2026-03-05 05:38:26 +00:00
{project_description or ""}
{module_section}
2026-03-04 18:09:45 +00:00
## 功能需求列表
{requirements_summary}
"""
2026-03-05 05:38:26 +00:00
path = os.path.join(output_dir, "README.md")
Path(path).write_text(content, encoding="utf-8")
return path
2026-03-04 18:09:45 +00:00
# ══════════════════════════════════════════════════════
# 函数签名 JSON 导出
# ══════════════════════════════════════════════════════
def build_signatures_document(
2026-03-05 05:38:26 +00:00
project_name: str,
2026-03-04 18:09:45 +00:00
project_description: str,
2026-03-05 05:38:26 +00:00
signatures: List[dict],
2026-03-04 18:09:45 +00:00
) -> dict:
2026-03-05 09:24:49 +00:00
"""
构建顶层签名文档结构::
{
"project": "<name>",
"description": "<description>",
"functions": [ ... ]
}
"""
2026-03-04 18:09:45 +00:00
return {
"project": project_name,
"description": project_description or "",
"functions": signatures,
}
def patch_signatures_with_url(
2026-03-05 05:38:26 +00:00
signatures: List[dict],
2026-03-04 18:09:45 +00:00
func_name_to_url: Dict[str, str],
) -> List[dict]:
2026-03-05 09:24:49 +00:00
"""
将代码文件路径回写到签名的 "url" 字段紧跟 "type" 之后
Args:
signatures: 签名列表in-place 修改
func_name_to_url: {函数名: 文件绝对路径}
Returns:
修改后的签名列表
"""
2026-03-04 18:09:45 +00:00
for sig in signatures:
2026-03-05 05:38:26 +00:00
url = func_name_to_url.get(sig.get("name", ""), "")
2026-03-04 18:09:45 +00:00
_insert_field_after(sig, after_key="type", new_key="url", new_value=url)
return signatures
2026-03-05 05:38:26 +00:00
def _insert_field_after(d: dict, after_key: str, new_key: str, new_value) -> None:
2026-03-05 09:24:49 +00:00
"""在有序 dict 中将 new_key 插入到 after_key 之后"""
2026-03-04 18:09:45 +00:00
if new_key in d:
d[new_key] = new_value
return
items = list(d.items())
2026-03-05 05:38:26 +00:00
insert_pos = next((i + 1 for i, (k, _) in enumerate(items) if k == after_key), len(items))
2026-03-04 18:09:45 +00:00
items.insert(insert_pos, (new_key, new_value))
d.clear()
d.update(items)
def write_function_signatures_json(
2026-03-05 05:38:26 +00:00
output_dir: str,
signatures: List[dict],
project_name: str,
2026-03-04 18:09:45 +00:00
project_description: str,
2026-03-05 05:38:26 +00:00
file_name: str = "function_signatures.json",
2026-03-04 18:09:45 +00:00
) -> str:
2026-03-05 09:24:49 +00:00
"""将签名列表导出为 JSON 文件"""
2026-03-04 18:09:45 +00:00
os.makedirs(output_dir, exist_ok=True)
document = build_signatures_document(project_name, project_description, signatures)
file_path = os.path.join(output_dir, file_name)
with open(file_path, "w", encoding="utf-8") as f:
json.dump(document, f, ensure_ascii=False, indent=2)
return file_path
# ══════════════════════════════════════════════════════
# 签名结构校验
# ══════════════════════════════════════════════════════
def validate_signature_schema(signature: dict) -> List[str]:
2026-03-05 09:24:49 +00:00
"""校验单个函数签名结构,返回错误列表(空列表表示通过)"""
2026-03-04 18:09:45 +00:00
errors: List[str] = []
2026-03-05 09:24:49 +00:00
2026-03-04 18:09:45 +00:00
for key in ("name", "requirement_id", "description", "type", "parameters"):
if key not in signature:
errors.append(f"缺少顶层字段: '{key}'")
if "url" in signature:
if not isinstance(signature["url"], str):
errors.append("'url' 字段必须是字符串类型")
elif signature["url"] == "":
2026-03-05 05:38:26 +00:00
errors.append("'url' 字段不能为空字符串")
2026-03-04 18:09:45 +00:00
params = signature.get("parameters", {})
2026-03-05 05:38:26 +00:00
if isinstance(params, dict):
2026-03-04 18:09:45 +00:00
for pname, pdef in params.items():
if not isinstance(pdef, dict):
errors.append(f"参数 '{pname}' 定义必须是 dict")
continue
if "type" not in pdef:
2026-03-05 05:38:26 +00:00
errors.append(f"参数 '{pname}' 缺少 'type'")
2026-03-04 18:09:45 +00:00
else:
parts = [p.strip() for p in pdef["type"].split("|")]
if not all(p in VALID_TYPES for p in parts):
2026-03-05 05:38:26 +00:00
errors.append(f"参数 '{pname}' type='{pdef['type']}' 含不合法类型")
2026-03-04 18:09:45 +00:00
if "inout" not in pdef:
2026-03-05 05:38:26 +00:00
errors.append(f"参数 '{pname}' 缺少 'inout'")
2026-03-04 18:09:45 +00:00
elif pdef["inout"] not in VALID_INOUT:
2026-03-05 05:38:26 +00:00
errors.append(f"参数 '{pname}' inout='{pdef['inout']}' 应为 in/out/inout")
2026-03-04 18:09:45 +00:00
if "required" not in pdef:
2026-03-05 05:38:26 +00:00
errors.append(f"参数 '{pname}' 缺少 'required'")
2026-03-04 18:09:45 +00:00
elif not isinstance(pdef["required"], bool):
2026-03-05 05:38:26 +00:00
errors.append(f"参数 '{pname}' 'required' 应为布尔值")
2026-03-04 18:09:45 +00:00
ret = signature.get("return")
if ret is None:
2026-03-05 05:38:26 +00:00
errors.append("缺少 'return' 字段")
elif isinstance(ret, dict):
2026-03-04 18:09:45 +00:00
ret_type = ret.get("type")
if not ret_type:
2026-03-05 05:38:26 +00:00
errors.append("'return' 缺少 'type'")
2026-03-04 18:09:45 +00:00
elif ret_type not in VALID_TYPES:
2026-03-05 05:38:26 +00:00
errors.append(f"'return.type'='{ret_type}' 不合法")
2026-03-04 18:09:45 +00:00
is_void = (ret_type == "void")
for sub_key in ("on_success", "on_failure"):
sub = ret.get(sub_key)
if is_void:
if sub is not None:
2026-03-05 05:38:26 +00:00
errors.append(f"void 函数 'return.{sub_key}' 应为 null")
2026-03-04 18:09:45 +00:00
else:
if sub is None:
2026-03-05 05:38:26 +00:00
errors.append(f"非 void 函数缺少 'return.{sub_key}'")
elif isinstance(sub, dict):
if not sub.get("value"):
errors.append(f"'return.{sub_key}.value' 不能为空")
if not sub.get("description"):
2026-03-04 18:09:45 +00:00
errors.append(f"'return.{sub_key}.description' 不能为空")
return errors
def validate_all_signatures(signatures: List[dict]) -> Dict[str, List[str]]:
2026-03-05 09:24:49 +00:00
"""批量校验,返回 {函数名: [错误]} 字典(仅含有错误的条目)"""
2026-03-05 05:38:26 +00:00
return {
sig.get("name", f"unknown_{i}"): errs
for i, sig in enumerate(signatures)
if (errs := validate_signature_schema(sig))
2026-03-05 09:24:49 +00:00
}