#!/usr/bin/env python3 """CLI: batch HTML prototypes of 1C forms → Gitea wiki (form_*.md + attachments). Примеры: python3 form_wiki_export.py push -b crm3-26.rhana --dry-run python3 form_wiki_export.py push -c configs/form_wiki.crm3-26.json python3 form_wiki_export.py build -b crm3-26 -b crm3-26.rhana -o out/form-wiki python3 form_wiki_export.py clean --wiki-url https://git.p7net.ru/1c/crm3_26.wiki.git """ from __future__ import annotations import argparse import json import re import sys from pathlib import Path TOOL_ROOT = Path(__file__).resolve().parent if str(TOOL_ROOT) not in sys.path: sys.path.insert(0, str(TOOL_ROOT)) from export1c_help import __status__, __version__ # noqa: E402 from export1c_help.form_wiki_export import ( # noqa: E402 DEFAULT_WIKI_PREFIX, build_forms_wiki_output, clean_forms_wiki, discover_forms_for_bases, load_form_wiki_settings, normalize_wiki_prefix, push_forms_wiki, resolve_search_roots, ) from export1c_help.resolve import ( # noqa: E402 ResolveError, resolve_wiki_url, slug_for_path, to_wiki_clone_url, ) from export1c_help.wiki import WikiPushError # noqa: E402 def _build_parser() -> argparse.ArgumentParser: p = argparse.ArgumentParser( prog="form_wiki_export", description=( f"Полный обход Form.xml → HTML + wiki-страницы form_*. " f"v{__version__} ({__status__})." ), ) p.add_argument("--version", action="version", version=f"%(prog)s {__version__}") sub = p.add_subparsers(dest="cmd", required=True) build = sub.add_parser("build", help="Собрать form_*.md/html локально без push") _add_common_args(build) build.set_defaults(func=cmd_build) push = sub.add_parser("push", help="Собрать и выгрузить form_* в wiki") _add_common_args(push) push.add_argument("--dry-run", action="store_true", help="Без git push") push.set_defaults(func=cmd_push) clean = sub.add_parser("clean", help="Удалить form_* из wiki по manifest.export_forms.json") _add_wiki_args(clean) clean.add_argument("--dry-run", action="store_true") clean.set_defaults(func=cmd_clean) return p def _add_wiki_args(p: argparse.ArgumentParser) -> None: g = p.add_mutually_exclusive_group(required=False) g.add_argument("-c", "--config", type=Path, help="JSON: bases, search_roots, wiki_url, out_path, wiki_prefix") g.add_argument("-b", "--base", action="append", help="Конфигурация (можно несколько раз)") p.add_argument("--wiki-url", default=None, help="Явный URL wiki git") p.add_argument( "--wiki-prefix", default=None, help=f"Префикс имён wiki-страниц (по умолчанию {DEFAULT_WIKI_PREFIX!r}; " "переопределяет wiki_prefix из JSON)", ) p.add_argument("--remote", default="origin") p.add_argument("--work-dir", type=Path, default=None) p.add_argument("-o", "--out", type=Path, default=None, help="Каталог сборки") p.add_argument("--branch", default="main") p.add_argument("-m", "--message", default=None) def _add_common_args(p: argparse.ArgumentParser) -> None: _add_wiki_args(p) def _load_settings(args: argparse.Namespace, *, require_bases: bool = True) -> tuple[dict, Path | None]: config_path: Path | None = None settings: dict = {} if args.config: settings = load_form_wiki_settings(Path(args.config)) config_path = Path(settings["_config_path"]) bases = args.base or settings.get("bases") or [] if require_bases and not bases: raise ResolveError("нужен -b/--base (можно несколько) или bases/base в -c/--config") settings["bases"] = [str(x) for x in bases] cli_prefix = getattr(args, "wiki_prefix", None) if cli_prefix: settings["wiki_prefix"] = normalize_wiki_prefix(cli_prefix) else: settings["wiki_prefix"] = normalize_wiki_prefix(settings.get("wiki_prefix")) return settings, config_path def _resolve_out_dir(args: argparse.Namespace, settings: dict, slug: str) -> Path: raw = args.out or settings.get("out_path") or settings.get("out") if raw: path = Path(str(raw)).expanduser() if not path.is_absolute(): return (Path.cwd() / path).resolve() return path.resolve() return (TOOL_ROOT / "out" / f"form-wiki-{slug}").resolve() def _resolve_wiki_url( args: argparse.Namespace, settings: dict, *, first_base: str, search_roots: list[Path], ) -> tuple[str, str]: if args.wiki_url or settings.get("wiki_url"): wiki_url = to_wiki_clone_url(str(args.wiki_url or settings.get("wiki_url"))) slug = re.sub(r"[^\w.\-]+", "-", first_base, flags=re.UNICODE).strip("-") or "forms" return wiki_url, slug from export1c_help.form_prototype import resolve_base_dir config_root = resolve_base_dir(first_base, search_roots) wiki_url = resolve_wiki_url(config_root, wiki_url=None, remote=args.remote) slug = slug_for_path(config_root) return wiki_url, slug def cmd_build(args: argparse.Namespace) -> int: try: settings, config_path = _load_settings(args) search_roots = resolve_search_roots(settings, config_path) wiki_url, slug = _resolve_wiki_url( args, settings, first_base=settings["bases"][0], search_roots=search_roots ) records = discover_forms_for_bases( settings["bases"], search_roots=search_roots, prefix=settings["wiki_prefix"] ) except (ResolveError, OSError, json.JSONDecodeError) as exc: print(f"error: {exc}", file=sys.stderr) return 1 out = _resolve_out_dir(args, settings, slug) print(f"bases: {', '.join(settings['bases'])}") print(f"prefix: {settings['wiki_prefix']}") print(f"forms: {len(records)}") print(f"out: {out}") print(f"wiki_url: {wiki_url}") stats = build_forms_wiki_output( records, out_dir=out, clean=True, prefix=settings["wiki_prefix"] ) skip_note = f", skipped={stats.skipped}" if stats.skipped else "" print( f"OK: built forms={stats.forms_total}, md={stats.markdown_files}, " f"html={stats.html_files}, files={stats.files_written}{skip_note} → {out}" ) return 0 def cmd_push(args: argparse.Namespace) -> int: try: settings, config_path = _load_settings(args) search_roots = resolve_search_roots(settings, config_path) wiki_url, slug = _resolve_wiki_url( args, settings, first_base=settings["bases"][0], search_roots=search_roots ) records = discover_forms_for_bases( settings["bases"], search_roots=search_roots, prefix=settings["wiki_prefix"] ) except (ResolveError, OSError, json.JSONDecodeError) as exc: print(f"error: {exc}", file=sys.stderr) return 1 out = _resolve_out_dir(args, settings, slug) work = (args.work_dir or (TOOL_ROOT / "out" / f"wiki_clone-{slug}")).resolve() print(f"bases: {', '.join(settings['bases'])}") print(f"prefix: {settings['wiki_prefix']}") print(f"forms: {len(records)}") print(f"out: {out}") print(f"work: {work}") print(f"wiki_url: {wiki_url}") build_stats = build_forms_wiki_output( records, out_dir=out, clean=True, prefix=settings["wiki_prefix"] ) skip_note = f", skipped={build_stats.skipped}" if build_stats.skipped else "" print( f"build: forms={build_stats.forms_total}, md={build_stats.markdown_files}, " f"html={build_stats.html_files}, files={build_stats.files_written}{skip_note}" ) msg = args.message or ( f"form_wiki_export v{__version__}: sync {build_stats.forms_total} forms " f"({build_stats.files_written} files)" ) try: push_stats = push_forms_wiki( wiki_url=wiki_url, out_dir=out, work_dir=work, message=msg, branch=args.branch, dry_run=args.dry_run, ) except WikiPushError as exc: print(f"push failed:\n{exc}", file=sys.stderr) return 1 if args.dry_run: print( f"dry-run: files={push_stats.files_written}, deleted={push_stats.files_deleted}; " "push skipped" ) return 0 print( f"OK: pushed forms={push_stats.forms_total}, files={push_stats.files_written}, " f"deleted={push_stats.files_deleted} → {wiki_url}" ) return 0 def cmd_clean(args: argparse.Namespace) -> int: try: if args.wiki_url: wiki_url = to_wiki_clone_url(args.wiki_url) slug = "forms" else: settings, config_path = _load_settings(args, require_bases=True) search_roots = resolve_search_roots(settings, config_path) wiki_url, slug = _resolve_wiki_url( args, settings, first_base=settings["bases"][0], search_roots=search_roots ) except (ResolveError, OSError, json.JSONDecodeError) as exc: print(f"error: {exc}", file=sys.stderr) return 1 work = (args.work_dir or (TOOL_ROOT / "out" / f"wiki_clone-{slug}")).resolve() print(f"wiki_url: {wiki_url}") print(f"work: {work}") msg = args.message or f"form_wiki_export v{__version__}: cleanup form_* pages" try: stats = clean_forms_wiki( wiki_url=wiki_url, work_dir=work, message=msg, branch=args.branch, dry_run=args.dry_run, ) except WikiPushError as exc: print(f"clean failed:\n{exc}", file=sys.stderr) return 1 if args.dry_run: print(f"dry-run: would delete={stats.files_deleted}; push skipped") return 0 print(f"OK: cleaned, deleted={stats.files_deleted} → {wiki_url}") return 0 def main(argv: list[str] | None = None) -> int: parser = _build_parser() args = parser.parse_args(argv) return args.func(args) if __name__ == "__main__": raise SystemExit(main())