"""把 Gitea Wiki 任务归档人工按需导出到 docs/task。""" from __future__ import annotations import argparse import re from pathlib import Path from typing import Any from new_task_archive import safe_title from wiki_docs import ( DEFAULT_CONFIG, ROOT, WikiClient, WikiDocsError, dirty_paths, load_config, parse_mirror, write_mirror, ) TASK_PAGE_PATTERN = re.compile(r"^Task-(?P\d+)-(?P.+)$") def task_revision(metadata: dict[str, Any], page_name: str) -> str: last_commit = metadata.get("last_commit") revision = last_commit.get("sha") if isinstance(last_commit, dict) else None if not isinstance(revision, str) or not revision: raise WikiDocsError(f"Wiki 页面缺少 revision:{page_name}") return revision def existing_task_mirrors(root: Path = ROOT) -> dict[str, Path]: """按镜像头匹配已有文件,兼容历史自定义文件名。""" mirrors: dict[str, Path] = {} task_dir = root / "docs" / "task" if not task_dir.is_dir(): return mirrors for path in task_dir.glob("*.md"): try: metadata, _ = parse_mirror(path.read_text(encoding="utf-8")) except (OSError, UnicodeDecodeError, WikiDocsError) as exc: raise WikiDocsError(f"已有任务镜像无效 {path.name}:{exc}") from exc page_name = metadata.get("wiki_page", "") if not TASK_PAGE_PATTERN.fullmatch(page_name): raise WikiDocsError(f"已有任务镜像页面名无效 {path.name}:{page_name}") if page_name in mirrors: raise WikiDocsError(f"任务页面存在重复本地镜像:{page_name}") mirrors[page_name] = path return mirrors def task_target(page_name: str, root: Path = ROOT) -> Path: match = TASK_PAGE_PATTERN.fullmatch(page_name) if match is None: raise WikiDocsError(f"不是任务归档页面:{page_name}") title = safe_title(match.group("title")) if not title: raise WikiDocsError(f"任务归档标题无效:{page_name}") return root / "docs" / "task" / f"{match.group('number')}-{title}.md" def export_task_archives( client: WikiClient, *, export_all: bool = False, root: Path = ROOT ) -> list[str]: """增量或全量读取任务归档;绝不删除本地文件。""" dirty = dirty_paths(["docs/task"], root) if dirty: raise WikiDocsError( "本地任务镜像存在未提交改动,已停止以防覆盖:\n" + "\n".join(dirty) ) existing = existing_task_mirrors(root) pages = [] for metadata in client.list_pages(): title = metadata.get("title") if isinstance(title, str) and TASK_PAGE_PATTERN.fullmatch(title): pages.append((int(title.split("-", 2)[1]), title, metadata)) pages.sort(key=lambda item: (item[0], item[1])) messages: list[str] = [] targets: set[Path] = set() for _, page_name, metadata in pages: target = existing.get(page_name, task_target(page_name, root)) if target in targets: raise WikiDocsError(f"多个任务页面映射到同一本地路径:{target.name}") targets.add(target) revision = task_revision(metadata, page_name) if not export_all and target.is_file(): local_metadata, _ = parse_mirror(target.read_text(encoding="utf-8")) if ( local_metadata.get("wiki_page") == page_name and local_metadata.get("wiki_revision") == revision ): messages.append(f"跳过:{target.relative_to(root)} <- {page_name}@{revision[:12]}") continue page = client.get_page_from_metadata(metadata, page_name) changed = write_mirror(target, page) action = "已导出" if changed else "无变化" messages.append(f"{action}:{target.relative_to(root)} <- {page_name}@{revision[:12]}") return messages def main() -> int: parser = argparse.ArgumentParser(description="人工按需导出 Gitea Wiki 任务归档") parser.add_argument( "--all", action="store_true", help="全量读取全部线上任务归档;默认按 revision 增量" ) parser.add_argument( "--config", default=str(DEFAULT_CONFIG), help="核心 Wiki 映射配置" ) args = parser.parse_args() try: config = load_config(Path(args.config).resolve()) messages = export_task_archives( WikiClient(config), export_all=args.all ) except WikiDocsError as exc: print(f"错误:{exc}") return 1 for message in messages: print(message) print("任务归档全量导出完成" if args.all else "任务归档增量导出完成") return 0 if __name__ == "__main__": raise SystemExit(main())