#!/usr/bin/env python3 """CLI: HTML/текстовый прототип управляемой формы 1С из Form.xml. Версия: 0.2.3 Автор: Michael BAG Примеры: python3 form_xml_to_html.py -b crm3-26 -o Справочник.Номенклатура -f ФормаЭлемента -p out/form-prototypes python3 form_xml_to_html.py -b crm3-26.rhana -o Общие -f РК_ФормаНастроекРана -O /tmp/form.html -T python3 form_xml_to_html.py -c configs/form_prototype.crm3-26.rhana.json -f РК_ФормаНастроекРана -T -M python3 form_xml_to_html.py -b crm3-26.rhana -o Общие -f РК_ФормаНастроекРана -T -M -t -O /tmp/form.txt """ from __future__ import annotations import argparse import json import sys from pathlib import Path from export1c_help.form_prototype import ( __author__, __email__, __version__, build_output_path, compose_text_report, default_search_roots, export_form_prototype, load_settings_file, parse_form, resolve_form_xml, ) from export1c_help.resolve import ResolveError def _build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( prog="form_xml_to_html", description=( "HTML/текстовый прототип формы 1С по -b/-o/-f " f"(v{__version__}, {__author__})" ), ) parser.add_argument( "-b", "--base", help="Имя/путь каталога конфигурации (или …/src)", ) parser.add_argument( "-o", "--object", help="Объект: Справочник.Номенклатура | Документ.X | Общие", ) parser.add_argument( "-f", "--form", required=True, help="Имя формы (для общих форм — имя общей формы)", ) parser.add_argument( "-p", "--out-path", help=( "Каталог вывода HTML: /<конфа>/<тип>/<объект>/<форма>.html" ), ) parser.add_argument( "-O", "--out-file", help="Файл вывода (если нет -p): HTML или текст при -t", ) parser.add_argument( "-T", "--elements-tree", action="store_true", help="Дерево элементов (консоль / в HTML / в текстовый файл)", ) parser.add_argument( "-M", "--text-maket", action="store_true", help="Текстовый макет «как в окне» (консоль / в HTML / в текстовый файл)", ) parser.add_argument( "-t", "--text-only", action="store_true", help="Только текст в -O (без HTML); нужны -T и/или -M", ) parser.add_argument( "-c", "--config", help="JSON с параметрами (base, object, out_path, out_file, search_roots), без формы", ) parser.add_argument( "--version", action="version", version=f"form_xml_to_html {__version__} — {__author__} ({__email__})", ) return parser def _resolve_path(raw: str, *, config_path: Path | None) -> Path: path = Path(raw).expanduser() if path.is_absolute(): return path.resolve() cwd_path = (Path.cwd() / path).resolve() if path.parts[0] in (".", "..") or cwd_path.parent.exists(): return cwd_path if config_path is not None: return (config_path.parent / path).resolve() tool_root = Path(__file__).resolve().parent return (tool_root / path).resolve() def main(argv: list[str] | None = None) -> int: parser = _build_parser() args = parser.parse_args(argv) settings: dict = {} config_path: Path | None = None if args.config: try: settings = load_settings_file(Path(args.config)) config_path = Path(settings["_config_path"]) except (ResolveError, OSError, json.JSONDecodeError) as exc: print(f"error: {exc}", file=sys.stderr) return 2 base = args.base or settings.get("base") object_ref = args.object or settings.get("object") out_path_raw = args.out_path or settings.get("out_path") out_file_raw = args.out_file or settings.get("out_file") or settings.get("out") want_tree = bool(args.elements_tree) want_maket = bool(args.text_maket) text_only = bool(args.text_only) if not base: parser.error("нужен -b/--base или base в -c/--config") if not object_ref: parser.error("нужен -o/--object или object в -c/--config") if text_only and not out_file_raw: parser.error("при -t/--text-only нужен -O/--out-file") if text_only and not (want_tree or want_maket): parser.error("при -t/--text-only укажите -T и/или -M") if text_only and out_path_raw: parser.error("при -t/--text-only используйте -O, не -p") want_html = not text_only and bool(out_path_raw or out_file_raw) if not want_html and not (want_tree or want_maket): parser.error( "укажите вывод: -p/--out-path или -O/--out-file для HTML, " "либо -T/-M для текста в консоль (или -t -O для текстового файла)" ) search_roots = default_search_roots(config_path) extra_roots = settings.get("search_roots") or [] if isinstance(extra_roots, list): base_for_rel = config_path.parent if config_path else Path.cwd() for item in extra_roots: p = Path(str(item)).expanduser() if not p.is_absolute(): p = (base_for_rel / p).resolve() search_roots.insert(0, p) try: form_xml, _config_root, base_name, type_dir, object_name = resolve_form_xml( base=str(base), object_ref=str(object_ref), form_name=args.form, search_roots=search_roots, ) form = parse_form(form_xml) display_title = f"{object_name} · {args.form.strip()}" form["title"] = display_title text_report = compose_text_report( form, include_tree=want_tree, include_maket=want_maket ) # Текст в консоль, если -O не задан (и есть что показывать) if not out_file_raw and (want_tree or want_maket) and text_report: sys.stdout.write(text_report) if not text_report.endswith("\n"): sys.stdout.write("\n") written: Path | None = None if text_only: out_file = _resolve_path(str(out_file_raw), config_path=config_path) out_file.parent.mkdir(parents=True, exist_ok=True) out_file.write_text(text_report, encoding="utf-8") written = out_file elif want_html: if out_path_raw: out_root = _resolve_path(str(out_path_raw), config_path=config_path) output_html = build_output_path( out_root, base_name=base_name, type_dir=type_dir, object_name=object_name, form_name=args.form.strip(), ) else: output_html = _resolve_path(str(out_file_raw), config_path=config_path) # В HTML дерево/макет добавляются при -O (и при -p тоже, если флаги заданы) # По ТЗ: при заданном -O — в HTML; без -O текст в консоль уже выше. embed_tree = want_tree and bool(out_file_raw or out_path_raw) embed_maket = want_maket and bool(out_file_raw or out_path_raw) # Уточнение ТЗ: «Если -O не задан → консоль; иначе → html с деревом». # При только -p без -O: HTML без обязательного встраивания; дерево уже в консоли. if out_file_raw and not out_path_raw: embed_tree = want_tree embed_maket = want_maket elif out_path_raw and not out_file_raw: # -p без -O: HTML базовый; -T/-M уже ушли в консоль embed_tree = False embed_maket = False else: # и -p, и -O: HTML в структуру -p, в файл встраиваем при наличии флагов embed_tree = want_tree embed_maket = want_maket export_form_prototype( form=form, output_html=output_html, display_title=display_title, include_elements_tree=embed_tree, include_text_maket=embed_maket, ) written = output_html except ResolveError as exc: print(f"error: {exc}", file=sys.stderr) return 1 if written is not None: print(f"OK: {written}", file=sys.stderr) print(f"src: {form_xml}", file=sys.stderr) print(f"form_xml_to_html {__version__} — {__author__}", file=sys.stderr) return 0 if __name__ == "__main__": raise SystemExit(main())