2026-03-05 06:01:40 +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 06:01:40 +00:00
|
|
|
|
from typing import Dict, List, Any
|
2026-03-04 18:09:45 +00:00
|
|
|
|
|
|
|
|
|
|
import config
|
|
|
|
|
|
|
|
|
|
|
|
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 06:01:40 +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:
|
|
|
|
|
|
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]:
|
|
|
|
|
|
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-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:
|
|
|
|
|
|
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]:
|
|
|
|
|
|
errors: List[str] = []
|
|
|
|
|
|
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 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 06:01:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ══════════════════════════════════════════════════════
|
|
|
|
|
|
# 项目信息渲染(需求-模块-代码关系树)
|
|
|
|
|
|
# ══════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
|
|
def render_project_info(full_info: Dict[str, Any], console) -> None:
|
|
|
|
|
|
"""
|
|
|
|
|
|
使用 rich 将项目完整信息(需求-模块-代码关系)渲染到终端。
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
full_info: DBManager.get_project_full_info() 返回的字典
|
|
|
|
|
|
console: rich.console.Console 实例
|
|
|
|
|
|
"""
|
|
|
|
|
|
from rich.table import Table
|
|
|
|
|
|
from rich.panel import Panel
|
|
|
|
|
|
from rich.tree import Tree
|
|
|
|
|
|
from rich.text import Text
|
|
|
|
|
|
|
|
|
|
|
|
project = full_info["project"]
|
|
|
|
|
|
stats = full_info["stats"]
|
|
|
|
|
|
modules = full_info["modules"]
|
|
|
|
|
|
raw_reqs = full_info["raw_requirements"]
|
|
|
|
|
|
|
|
|
|
|
|
# ── 项目基本信息 ──────────────────────────────────
|
|
|
|
|
|
info_table = Table(show_header=False, box=None, padding=(0, 2))
|
|
|
|
|
|
info_table.add_column("key", style="bold cyan", width=14)
|
|
|
|
|
|
info_table.add_column("value", style="white")
|
|
|
|
|
|
info_table.add_row("项目 ID", str(project.id))
|
|
|
|
|
|
info_table.add_row("项目名称", project.name)
|
|
|
|
|
|
info_table.add_row("目标语言", project.language)
|
|
|
|
|
|
info_table.add_row("描述", project.description or "(无)")
|
|
|
|
|
|
info_table.add_row("输出目录", project.output_dir or "(未生成)")
|
|
|
|
|
|
info_table.add_row("创建时间", str(project.created_at)[:19])
|
|
|
|
|
|
info_table.add_row("更新时间", str(project.updated_at)[:19])
|
|
|
|
|
|
console.print(Panel(info_table, title="[bold cyan]📁 项目信息[/bold cyan]",
|
|
|
|
|
|
border_style="cyan"))
|
|
|
|
|
|
|
|
|
|
|
|
# ── 统计摘要 ──────────────────────────────────────
|
|
|
|
|
|
stat_table = Table(show_header=False, box=None, padding=(0, 3))
|
|
|
|
|
|
stat_table.add_column("k", style="bold yellow", width=16)
|
|
|
|
|
|
stat_table.add_column("v", style="white")
|
|
|
|
|
|
stat_table.add_row("原始需求数", str(stats["raw_req_count"]))
|
|
|
|
|
|
stat_table.add_row("功能需求数", str(stats["func_req_count"]))
|
|
|
|
|
|
stat_table.add_row("已生成代码", f"{stats['generated_count']} / {stats['func_req_count']}")
|
|
|
|
|
|
stat_table.add_row("功能模块数", str(stats["module_count"]))
|
|
|
|
|
|
stat_table.add_row("代码文件数", str(stats["code_file_count"]))
|
|
|
|
|
|
console.print(Panel(stat_table, title="[bold yellow]📊 统计摘要[/bold yellow]",
|
|
|
|
|
|
border_style="yellow"))
|
|
|
|
|
|
|
|
|
|
|
|
# ── 原始需求列表 ──────────────────────────────────
|
|
|
|
|
|
if raw_reqs:
|
|
|
|
|
|
raw_table = Table(title="📝 原始需求", show_lines=True)
|
|
|
|
|
|
raw_table.add_column("ID", style="dim", width=5)
|
|
|
|
|
|
raw_table.add_column("来源", style="cyan", width=8)
|
|
|
|
|
|
raw_table.add_column("文件名", width=20)
|
|
|
|
|
|
raw_table.add_column("内容摘要", width=55)
|
|
|
|
|
|
raw_table.add_column("创建时间", width=20)
|
|
|
|
|
|
for rr in raw_reqs:
|
|
|
|
|
|
raw_table.add_row(
|
|
|
|
|
|
str(rr.id),
|
|
|
|
|
|
rr.source_type,
|
|
|
|
|
|
rr.source_name or "-",
|
|
|
|
|
|
(rr.content[:80] + "...") if len(rr.content) > 80 else rr.content,
|
|
|
|
|
|
str(rr.created_at)[:19],
|
|
|
|
|
|
)
|
|
|
|
|
|
console.print(raw_table)
|
|
|
|
|
|
|
|
|
|
|
|
# ── 需求-模块-代码 关系树 ─────────────────────────
|
|
|
|
|
|
if not modules:
|
|
|
|
|
|
console.print("[dim]暂无功能需求[/dim]")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
priority_color = {"high": "red", "medium": "yellow", "low": "green"}
|
|
|
|
|
|
status_icon = {"generated": "✅", "pending": "⏳", "failed": "❌"}
|
|
|
|
|
|
|
|
|
|
|
|
root = Tree(
|
|
|
|
|
|
f"[bold cyan]🗂 {project.name}[/bold cyan] "
|
|
|
|
|
|
f"[dim]({stats['func_req_count']} 需求 · "
|
|
|
|
|
|
f"{stats['module_count']} 模块 · "
|
|
|
|
|
|
f"{stats['code_file_count']} 代码文件)[/dim]"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
for module_name in sorted(modules.keys()):
|
|
|
|
|
|
mod_data = modules[module_name]
|
|
|
|
|
|
req_list = mod_data["requirements"]
|
|
|
|
|
|
mod_count = len(req_list)
|
|
|
|
|
|
gen_count = sum(1 for r in req_list if r["req"].status == "generated")
|
|
|
|
|
|
|
|
|
|
|
|
mod_branch = root.add(
|
|
|
|
|
|
f"[magenta bold]📦 {module_name}[/magenta bold] "
|
|
|
|
|
|
f"[dim]{gen_count}/{mod_count} 已生成[/dim]"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
for item in req_list:
|
|
|
|
|
|
req = item["req"]
|
|
|
|
|
|
code_files = item["code_files"]
|
|
|
|
|
|
p_color = priority_color.get(req.priority, "white")
|
|
|
|
|
|
s_icon = status_icon.get(req.status, "❓")
|
|
|
|
|
|
|
|
|
|
|
|
req_label = (
|
|
|
|
|
|
f"{s_icon} [bold]{req.title}[/bold] "
|
|
|
|
|
|
f"[{p_color}][{req.priority}][/{p_color}] "
|
|
|
|
|
|
f"[dim]REQ.{req.index_no:02d} · ID={req.id}[/dim]"
|
|
|
|
|
|
)
|
|
|
|
|
|
req_branch = mod_branch.add(req_label)
|
|
|
|
|
|
|
|
|
|
|
|
# 需求详情子节点
|
|
|
|
|
|
req_branch.add(
|
|
|
|
|
|
f"[dim]函数名: [/dim][code]{req.function_name}[/code]"
|
|
|
|
|
|
)
|
|
|
|
|
|
req_branch.add(
|
|
|
|
|
|
f"[dim]描述: [/dim]{req.description[:80]}"
|
|
|
|
|
|
+ ("..." if len(req.description) > 80 else "")
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 代码文件子节点
|
|
|
|
|
|
if code_files:
|
|
|
|
|
|
code_branch = req_branch.add("[green]📄 代码文件[/green]")
|
|
|
|
|
|
for cf in code_files:
|
|
|
|
|
|
exists = os.path.exists(cf.file_path)
|
|
|
|
|
|
icon = "🟢" if exists else "🔴"
|
|
|
|
|
|
cf_text = Text()
|
|
|
|
|
|
cf_text.append(f"{icon} ", style="")
|
|
|
|
|
|
cf_text.append(cf.file_name, style="bold green" if exists else "bold red")
|
|
|
|
|
|
cf_text.append(f" [{cf.language}]", style="dim")
|
|
|
|
|
|
cf_text.append(f" {cf.file_path}", style="dim italic")
|
|
|
|
|
|
code_branch.add(cf_text)
|
|
|
|
|
|
else:
|
|
|
|
|
|
req_branch.add("[dim]📄 暂无代码文件[/dim]")
|
|
|
|
|
|
|
|
|
|
|
|
console.print(Panel(root, title="[bold green]🔗 需求 · 模块 · 代码 关系树[/bold green]",
|
|
|
|
|
|
border_style="green", padding=(1, 2)))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def render_project_list(projects: list, stats_map: Dict[int, Dict], console) -> None:
|
|
|
|
|
|
"""
|
|
|
|
|
|
渲染所有项目列表(含统计列)。
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
projects: Project 对象列表
|
|
|
|
|
|
stats_map: {project_id: stats_dict}
|
|
|
|
|
|
console: rich.console.Console 实例
|
|
|
|
|
|
"""
|
|
|
|
|
|
from rich.table import Table
|
|
|
|
|
|
|
|
|
|
|
|
if not projects:
|
|
|
|
|
|
console.print("[dim]暂无项目,请先运行 `python main.py run` 创建项目。[/dim]")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
table = Table(title=f"📋 项目列表(共 {len(projects)} 个)", show_lines=True)
|
|
|
|
|
|
table.add_column("ID", style="cyan bold", width=5)
|
|
|
|
|
|
table.add_column("项目名称", style="bold", width=22)
|
|
|
|
|
|
table.add_column("语言", style="magenta", width=8)
|
|
|
|
|
|
table.add_column("功能需求", style="yellow", width=8)
|
|
|
|
|
|
table.add_column("已生成", style="green", width=8)
|
|
|
|
|
|
table.add_column("模块数", width=6)
|
|
|
|
|
|
table.add_column("代码文件", width=8)
|
|
|
|
|
|
table.add_column("描述", width=28)
|
|
|
|
|
|
table.add_column("创建时间", width=20)
|
|
|
|
|
|
|
|
|
|
|
|
for p in projects:
|
|
|
|
|
|
st = stats_map.get(p.id, {})
|
|
|
|
|
|
func_total = st.get("func_req_count", 0)
|
|
|
|
|
|
gen_count = st.get("generated_count", 0)
|
|
|
|
|
|
gen_cell = (
|
|
|
|
|
|
f"[green]{gen_count}[/green]"
|
|
|
|
|
|
if gen_count == func_total and func_total > 0
|
|
|
|
|
|
else f"[yellow]{gen_count}[/yellow]"
|
|
|
|
|
|
)
|
|
|
|
|
|
table.add_row(
|
|
|
|
|
|
str(p.id),
|
|
|
|
|
|
p.name,
|
|
|
|
|
|
p.language,
|
|
|
|
|
|
str(func_total),
|
|
|
|
|
|
gen_cell,
|
|
|
|
|
|
str(st.get("module_count", 0)),
|
|
|
|
|
|
str(st.get("code_file_count", 0)),
|
|
|
|
|
|
(p.description[:28] + "...") if p.description and len(p.description) > 28
|
|
|
|
|
|
else (p.description or "[dim](无)[/dim]"),
|
|
|
|
|
|
str(p.created_at)[:19],
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
console.print(table)
|