#!/usr/bin/env python3 """CLI: export 1C Help (ru.html) to Markdown / Gitea/GitLab wiki.""" from __future__ import annotations import argparse 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.config_version import ( # noqa: E402 read_config_name, read_config_version, ) from export1c_help.export import export_help # noqa: E402 from export1c_help.resolve import ( # noqa: E402 ResolveError, git_remote_url, git_toplevel, resolve_config_root, resolve_src_root, resolve_wiki_url, slug_for_path, ) from export1c_help.wiki import WikiPushError, prepare_wiki_clone, push_wiki # noqa: E402 def build_parser() -> argparse.ArgumentParser: p = argparse.ArgumentParser( prog="export_1c_help", description=( f"Выгрузка встроенной справки любой конфигурации 1С в Markdown / wiki. " f"v{__version__} ({__status__})." ), ) p.add_argument("--version", action="version", version=f"%(prog)s {__version__}") sub = p.add_subparsers(dest="cmd", required=True) i = sub.add_parser( "info", help="Показать src, версию, git remote и wiki URL для конфигурации", ) _add_config_path_args(i) i.add_argument("--remote", default="origin", help="Git remote (по умолчанию origin)") i.add_argument( "--wiki-url", default=None, help="Явный wiki URL (иначе из remote конфигурации)", ) i.set_defaults(func=cmd_info) c = sub.add_parser("convert", help="Конвертация Help → Markdown в каталог") _add_convert_args(c) c.set_defaults(func=cmd_convert) w = sub.add_parser( "push", help="convert + push в wiki (явный --wiki-url или wiki репозитория конфигурации)", ) _add_convert_args(w, require_out=False) w.add_argument( "--wiki-url", default=None, help=( "URL wiki-репозитория (…/repo.wiki.git). " "Если не задан — берётся из git remote конфигурации (…/repo.git → …/repo.wiki.git)" ), ) w.add_argument( "--remote", default="origin", help="Git remote конфигурации для авто-wiki URL (по умолчанию origin)", ) w.add_argument( "--work-dir", type=Path, default=None, help="Локальный clone wiki (по умолчанию: out/wiki_clone-)", ) w.add_argument( "-o", "--out", type=Path, default=None, help="Промежуточный каталог MD (по умолчанию: out/wiki-md-)", ) w.add_argument( "--wipe-unmanaged", action="store_true", help=( "Удалить из wiki все .md/.json/картинки, не только страницы справки " "(опасно: сотрёт пользовательские статьи)" ), ) w.add_argument( "--keep-unmanaged", action="store_true", help=argparse.SUPPRESS, # legacy no-op: preserving user pages is now default ) w.add_argument("-m", "--message", default=None, help="Сообщение коммита wiki") w.add_argument("--branch", default="main") w.add_argument("--dry-run", action="store_true", help="Только convert, без git push") w.set_defaults(func=cmd_push) return p def _add_config_path_args(p: argparse.ArgumentParser) -> argparse._MutuallyExclusiveGroup: g = p.add_mutually_exclusive_group(required=True) g.add_argument( "-c", "--config", type=Path, help="Корень выгрузки конфигурации (каталог с src/) или сам src/", ) g.add_argument( "-s", "--src", type=Path, help="Каталог src выгрузки (эквивалент -c …/src)", ) return g def _add_convert_args(p: argparse.ArgumentParser, *, require_out: bool = True) -> None: _add_config_path_args(p) if require_out: p.add_argument( "-o", "--out", type=Path, required=True, help="Каталог результата (wiki working tree)", ) p.add_argument( "--objects-only", action="store_true", help="Только справка объектов (без Forms/*/Ext/Help)", ) p.add_argument( "--keep-empty", action="store_true", help="Не пропускать почти пустые страницы", ) p.add_argument( "--clean", action="store_true", help="Очистить --out перед записью (кроме .git)", ) p.add_argument("--source-label", default=None, help="Подпись источника в Home.md") p.add_argument( "--prev-manifest", type=Path, default=None, help="Предыдущий manifest.json (жизненный цикл страниц)", ) p.add_argument( "--config-version", default=None, help="Версия конфигурации (иначе VERSION / Configuration.xml)", ) def _config_input(args: argparse.Namespace) -> Path: return args.config if getattr(args, "config", None) else args.src def _resolve_src(args: argparse.Namespace) -> Path: return resolve_src_root(_config_input(args)) def _print_stats(stats, dest: Path) -> None: print( f"OK: config={stats.config_version} " f"written={stats.pages_written} " f"+{stats.created_count}/~{stats.edited_count}/={stats.unchanged_count}/-{stats.deleted_count} " f"skipped_empty={stats.pages_skipped_empty} " f"total={stats.pages_total} " f"images={stats.images_copied} → {dest}" ) def cmd_info(args: argparse.Namespace) -> int: try: src = _resolve_src(args) root = resolve_config_root(src) wiki = resolve_wiki_url(src, wiki_url=args.wiki_url, remote=args.remote) except ResolveError as exc: print(f"error: {exc}", file=sys.stderr) return 1 top = git_toplevel(root) remote_url = git_remote_url(root, remote=args.remote) print(f"config_root: {root}") print(f"src: {src}") print(f"config_name: {read_config_name(src) or '—'}") print(f"config_version: {read_config_version(src)}") print(f"git_toplevel: {top or '—'}") print(f"git_remote({args.remote}): {remote_url or '—'}") print(f"wiki_url: {wiki}") print(f"slug: {slug_for_path(src)}") return 0 def cmd_convert(args: argparse.Namespace) -> int: try: src = _resolve_src(args) except ResolveError as exc: print(f"error: {exc}", file=sys.stderr) return 1 stats = export_help( src, args.out, include_forms=not args.objects_only, skip_empty=not args.keep_empty, clean=args.clean, source_label=args.source_label or str(src), prev_manifest_path=args.prev_manifest, config_version=args.config_version, ) _print_stats(stats, args.out) return 0 def cmd_push(args: argparse.Namespace) -> int: try: src = _resolve_src(args) wiki_url = resolve_wiki_url( src, wiki_url=args.wiki_url, remote=args.remote ) except ResolveError as exc: print(f"error: {exc}", file=sys.stderr) return 1 slug = slug_for_path(src) out = args.out or (TOOL_ROOT / "out" / f"wiki-md-{slug}") work = args.work_dir or (TOOL_ROOT / "out" / f"wiki_clone-{slug}") branch = args.branch print(f"wiki_url: {wiki_url}") print(f"src: {src}") try: prepare_wiki_clone(wiki_url, work, branch=branch) except WikiPushError as exc: print(f"wiki prepare failed:\n{exc}", file=sys.stderr) return 1 prev = args.prev_manifest or (work / "manifest.json") cfg = args.config_version or read_config_version(src) stats = export_help( src, out, include_forms=not args.objects_only, skip_empty=not args.keep_empty, clean=True, source_label=args.source_label or str(src), prev_manifest_path=prev if Path(prev).is_file() else None, config_version=cfg, ) print( f"convert: config={stats.config_version} " f"written={stats.pages_written} " f"+{stats.created_count}/~{stats.edited_count}/={stats.unchanged_count}/-{stats.deleted_count} " f"→ {out}" ) if args.dry_run: print("dry-run: push skipped") return 0 message = args.message or ( f"export_1c_help v{__version__}: sync help cfg {stats.config_version} " f"({stats.pages_written} pages, +{stats.created_count}/~{stats.edited_count}/-{stats.deleted_count})" ) try: push_wiki( out, wiki_url, work_dir=work, message=message, branch=branch, wipe_unmanaged=args.wipe_unmanaged, already_prepared=True, ) except WikiPushError as exc: print(f"push failed:\n{exc}", file=sys.stderr) return 1 print(f"OK: pushed → {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())