Files
novelmaster/noma/scripts/update_state.py
T
2026-06-23 20:29:02 +08:00

254 lines
7.4 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Update State - 状态更新脚本
用途:
- 更新 state.json 中的各种状态信息
- 支持多种更新操作:添加审查报告、更新实体、修改进度等
- 提供 CLI 接口供 Skills 调用
典型用法:
python update_state.py --project-root <root> --add-review "1-10" "审查报告/第 1-10 章审查报告.md"
python update_state.py --project-root <root> --update-entity 角色 萧炎 '{"境界": "斗皇"}'
python update_state.py --project-root <root> --set-chapter-meta 100 '{"word_count": 2300}'
"""
from __future__ import annotations
import argparse
import json
import os
import sys
from pathlib import Path
from datetime import datetime
# 添加 scripts 目录到 sys.path
scripts_dir = Path(__file__).resolve().parent
sys.path.insert(0, str(scripts_dir))
from runtime_compat import enable_windows_utf8_stdio, normalize_windows_path
def load_state(state_file: Path) -> dict:
"""加载 state.json"""
if not state_file.exists():
raise FileNotFoundError(f"state.json not found: {state_file}")
with open(state_file, 'r', encoding='utf-8') as f:
return json.load(f)
def save_state(state_file: Path, state: dict) -> None:
"""保存 state.json(带文件锁保护)"""
try:
from filelock import FileLock
except ImportError:
FileLock = None
lock_file = state_file.with_suffix('.lock')
if FileLock:
with FileLock(str(lock_file)):
_write_state(state_file, state)
else:
_write_state(state_file, state)
def _write_state(state_file: Path, state: dict) -> None:
"""实际写入文件"""
import tempfile
import shutil
# 原子写入:先写临时文件,再替换
dir_path = state_file.parent
dir_path.mkdir(parents=True, exist_ok=True)
fd, tmp_path = tempfile.mkstemp(dir=str(dir_path), suffix='.tmp')
try:
with os.fdopen(fd, 'w', encoding='utf-8') as f:
json.dump(state, f, ensure_ascii=False, indent=2)
f.write('\n')
shutil.move(str(tmp_path), str(state_file))
except Exception:
if os.path.exists(tmp_path):
os.unlink(tmp_path)
raise
def cmd_add_review(args: argparse.Namespace) -> int:
"""
添加审查报告记录
用法:
--add-review "章节范围" "报告路径"
"""
state_file = args.project_root / ".noma" / "novel_data" / "state.json"
state = load_state(state_file)
chapter_range = args.chapter_range
report_path = args.report_path
# 初始化 review_checkpoints
state.setdefault("review_checkpoints", [])
# 添加新记录
new_record = {
"chapter_range": chapter_range,
"report_path": report_path,
"timestamp": datetime.now().isoformat(),
"status": "completed"
}
state["review_checkpoints"].append(new_record)
# 保存
save_state(state_file, state)
print(f"✅ Added review checkpoint: {chapter_range} -> {report_path}")
return 0
def cmd_update_entity(args: argparse.Namespace) -> int:
"""
更新实体属性
用法:
--update-entity <类型> <ID> <JSON 数据>
"""
state_file = args.project_root / ".noma" / "novel_data" / "state.json"
state = load_state(state_file)
entity_type = args.entity_type # 角色/地点/物品/势力
entity_id = args.entity_id
updates = json.loads(args.data)
# 确保 entities_v3 存在
state.setdefault("entities_v3", {})
state["entities_v3"].setdefault(entity_type, {})
# 获取或创建实体
if entity_id not in state["entities_v3"][entity_type]:
state["entities_v3"][entity_type][entity_id] = {
"id": entity_id,
"name": entity_id,
"type": entity_type,
"tier": "次要"
}
# 更新属性
entity = state["entities_v3"][entity_type][entity_id]
entity.update(updates)
# 保存
save_state(state_file, state)
print(f"✅ Updated entity: {entity_type}/{entity_id}")
print(f" Fields updated: {list(updates.keys())}")
return 0
def cmd_set_chapter_meta(args: argparse.Namespace) -> int:
"""
设置章节元数据
用法:
--set-chapter-meta <章节号> <JSON 数据>
"""
state_file = args.project_root / ".noma" / "novel_data" / "state.json"
state = load_state(state_file)
chapter_num = int(args.chapter)
meta = json.loads(args.data)
# 确保 chapter_meta 存在
state.setdefault("chapter_meta", {})
# 转换为字符串键
chapter_key = str(chapter_num)
# 合并现有数据
if chapter_key not in state["chapter_meta"]:
state["chapter_meta"][chapter_key] = {}
state["chapter_meta"][chapter_key].update(meta)
# 保存
save_state(state_file, state)
print(f"✅ Set chapter meta for chapter {chapter_num}")
print(f" Keys: {list(meta.keys())}")
return 0
def cmd_set_progress(args: argparse.Namespace) -> int:
"""
设置项目进度
用法:
--set-progress --current-chapter <N>
"""
state_file = args.project_root / ".noma" / "novel_data" / "state.json"
state = load_state(state_file)
# 更新 project 信息
state.setdefault("project", {})
state["project"]["current_chapter"] = args.current_chapter
state["project"]["last_updated"] = datetime.now().isoformat()
# 保存
save_state(state_file, state)
print(f"✅ Updated progress: current_chapter = {args.current_chapter}")
return 0
def main() -> None:
parser = argparse.ArgumentParser(description="Update state.json")
parser.add_argument("--project-root", type=str, required=True, help="项目根目录")
subparsers = parser.add_subparsers(dest="command", help="命令")
# add-review 命令
p_review = subparsers.add_parser("add-review", help="添加审查报告记录")
p_review.add_argument("chapter_range", help="章节范围 (如 '1-10')")
p_review.add_argument("report_path", help="报告文件路径")
p_review.set_defaults(func=cmd_add_review)
# update-entity 命令
p_entity = subparsers.add_parser("update-entity", help="更新实体属性")
p_entity.add_argument("entity_type", help="实体类型 (角色/地点/物品/势力)")
p_entity.add_argument("entity_id", help="实体 ID")
p_entity.add_argument("data", help="JSON 格式的属性数据")
p_entity.set_defaults(func=cmd_update_entity)
# set-chapter-meta 命令
p_meta = subparsers.add_parser("set-chapter-meta", help="设置章节元数据")
p_meta.add_argument("chapter", help="章节号")
p_meta.add_argument("data", help="JSON 格式的元数据")
p_meta.set_defaults(func=cmd_set_chapter_meta)
# set-progress 命令
p_progress = subparsers.add_parser("set-progress", help="设置项目进度")
p_progress.add_argument("--current-chapter", type=int, required=True, help="当前章节号")
p_progress.set_defaults(func=cmd_set_progress)
# 解析参数
args = parser.parse_args()
if not args.command:
parser.print_help()
sys.exit(1)
# 规范化路径
args.project_root = normalize_windows_path(args.project_root).resolve()
# 执行命令
enable_windows_utf8_stdio(skip_in_pytest=True)
sys.exit(args.func(args))
if __name__ == "__main__":
main()