Release 0.5.0: form_wiki_export — выгрузка HTML-форм в Gitea wiki.
Полный обход Form.xml, настраиваемый wiki_prefix, русские типы в именах, ссылки без percent-encoding. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,282 @@
|
||||
#!/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())
|
||||
Reference in New Issue
Block a user