#!/usr/bin/env python3 """List timestamped 1C saved-state watch runs.""" from __future__ import annotations import argparse import importlib.util import json import sys from pathlib import Path from typing import Any REPO_ROOT = Path(__file__).resolve().parents[1] def load_json(path: Path) -> dict[str, Any]: return json.loads(path.read_text(encoding="utf-8-sig")) def write_json(path: Path, data: dict[str, Any]) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") def render_markdown(data: dict[str, Any]) -> str: module_path = REPO_ROOT / "scripts" / "render_1c_saved_state_watch_run_list_markdown.py" spec = importlib.util.spec_from_file_location("saved_state_watch_run_list_markdown", module_path) if spec is None or spec.loader is None: raise RuntimeError(f"Cannot load watch run list renderer: {module_path}") module = importlib.util.module_from_spec(spec) sys.modules[spec.name] = module spec.loader.exec_module(module) return module.render(data) def check_run_list(list_path: Path, check_output: Path) -> None: module_path = REPO_ROOT / "scripts" / "check_1c_saved_state_watch_run_list.py" spec = importlib.util.spec_from_file_location("saved_state_watch_run_list_check", module_path) if spec is None or spec.loader is None: raise RuntimeError(f"Cannot load watch run list checker: {module_path}") module = importlib.util.module_from_spec(spec) sys.modules[spec.name] = module spec.loader.exec_module(module) result = module.check_list(list_path) module.write_json(check_output, result) if not result.get("passed"): raise RuntimeError(f"Saved-state watch run list check failed: {check_output}") def manifest_path(run_dir: Path) -> Path: return run_dir / "saved-state-watch-run.json" def check_passed(path: str | None) -> bool | None: if not path: return None check_path = Path(path) if not check_path.exists(): return None try: return load_json(check_path).get("passed") except Exception: return None def compact_run(path: Path, manifest: dict[str, Any]) -> dict[str, Any]: counts = manifest.get("counts") or {} return { "run": path.name, "run_dir": str(path.resolve()), "database": manifest.get("database"), "previous_report": manifest.get("previous_report"), "report": manifest.get("report"), "markdown": manifest.get("markdown"), "manifest_markdown": manifest.get("manifest_markdown"), "manifest_check": manifest.get("manifest_check"), "manifest_check_passed": check_passed(manifest.get("manifest_check")), "delta": manifest.get("delta"), "delta_markdown": manifest.get("delta_markdown"), "delta_check": manifest.get("delta_check"), "delta_check_passed": check_passed(manifest.get("delta_check")), "counts": { "object_changes": counts.get("object_changes"), "system_changes": counts.get("system_changes"), "delta_objects_added": counts.get("delta_objects_added"), "delta_objects_removed": counts.get("delta_objects_removed"), "delta_objects_changed": counts.get("delta_objects_changed"), "delta_objects_unchanged": counts.get("delta_objects_unchanged"), }, } def has_delta_changes(run: dict[str, Any]) -> bool: counts = run.get("counts") or {} return any(int(counts.get(key) or 0) > 0 for key in ("delta_objects_added", "delta_objects_removed", "delta_objects_changed")) def list_runs(root: Path, *, limit: int | None, only_with_delta: bool, only_changed: bool) -> dict[str, Any]: root = root.resolve() runs: list[dict[str, Any]] = [] if root.exists(): for child in sorted((item for item in root.iterdir() if item.is_dir()), key=lambda item: item.name, reverse=True): path = manifest_path(child) if not path.exists(): continue try: run = compact_run(child, load_json(path)) except Exception as exc: run = { "run": child.name, "run_dir": str(child), "error": str(exc), } if only_with_delta and not run.get("delta"): continue if only_changed and not has_delta_changes(run): continue runs.append(run) if limit is not None and len(runs) >= limit: break return { "schema": "onec_saved_state_watch_run_list.v1", "output_root": str(root), "filters": { "limit": limit, "only_with_delta": only_with_delta, "only_changed": only_changed, }, "runs": runs, "latest": runs[0] if runs else None, "counts": { "runs": len(runs), "with_delta": sum(1 for run in runs if run.get("delta")), "with_delta_changes": sum(1 for run in runs if has_delta_changes(run)), }, "safety": { "read_only": True, "sql_write_performed": False, }, } def main() -> int: parser = argparse.ArgumentParser(description="List timestamped 1C saved-state watch runs.") parser.add_argument("--root", type=Path, required=True) parser.add_argument("--limit", type=int) parser.add_argument("--only-with-delta", action="store_true") parser.add_argument("--only-changed", action="store_true") parser.add_argument("--output", type=Path) parser.add_argument("--markdown-output", type=Path) parser.add_argument("--skip-markdown", action="store_true") parser.add_argument("--check-output", type=Path) parser.add_argument("--skip-check", action="store_true") args = parser.parse_args() result = list_runs(args.root, limit=args.limit, only_with_delta=args.only_with_delta, only_changed=args.only_changed) markdown_output = args.markdown_output if markdown_output is None and args.output and not args.skip_markdown: markdown_output = args.output.with_suffix(".md") if markdown_output and not args.skip_markdown: markdown_output = markdown_output.resolve() result["markdown"] = str(markdown_output) check_output = args.check_output if check_output is None and args.output and not args.skip_check: check_output = args.output.with_name(f"{args.output.stem}-check.json") if check_output and not args.skip_check: check_output = check_output.resolve() result["check"] = str(check_output) if args.output: write_json(args.output, result) if markdown_output and not args.skip_markdown: markdown_output.parent.mkdir(parents=True, exist_ok=True) markdown_output.write_text(render_markdown(result), encoding="utf-8") if args.output: write_json(args.output, result) if check_output and not args.skip_check: if not args.output: raise SystemExit("Use --output when watch run list check output is enabled.") check_run_list(args.output, check_output) print(json.dumps({"output": str(args.output) if args.output else None, "schema": result["schema"], "counts": result["counts"]}, ensure_ascii=False)) return 0 if __name__ == "__main__": raise SystemExit(main())