d91c454099
Keep wiki_clone* across convert --clean and skip copying the clone into itself (0.3.5). Co-authored-by: Cursor <cursoragent@cursor.com>
329 lines
10 KiB
Python
329 lines
10 KiB
Python
#!/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_identity,
|
|
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-<slug>)",
|
|
)
|
|
w.add_argument(
|
|
"-o",
|
|
"--out",
|
|
type=Path,
|
|
default=None,
|
|
help="Промежуточный каталог MD (по умолчанию: out/wiki-md-<slug>)",
|
|
)
|
|
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)
|
|
identity = read_config_identity(src)
|
|
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: {identity.name or '—'}")
|
|
print(f"config_display: {identity.display_name or '—'}")
|
|
print(f"config_vendor: {identity.vendor or '—'}")
|
|
print(f"config_version: {identity.version}")
|
|
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}")).resolve()
|
|
work = (args.work_dir or (TOOL_ROOT / "out" / f"wiki_clone-{slug}")).resolve()
|
|
branch = args.branch
|
|
|
|
print(f"wiki_url: {wiki_url}")
|
|
print(f"src: {src}")
|
|
print(f"out: {out}")
|
|
print(f"work: {work}")
|
|
|
|
try:
|
|
work.relative_to(out)
|
|
except ValueError:
|
|
nested = False
|
|
else:
|
|
nested = work != out
|
|
if nested:
|
|
print(
|
|
"note: wiki clone is inside --out; "
|
|
"convert will keep wiki_clone* dirs and skip copying them into themselves",
|
|
file=sys.stderr,
|
|
)
|
|
|
|
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
|
|
if prev is None and (work / "manifest.json").is_file():
|
|
prev = 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 prev and 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:
|
|
if not (work / ".git").exists():
|
|
prepare_wiki_clone(wiki_url, work, branch=branch)
|
|
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())
|