From f9e8380348443d5c547960325b83a5ef34ae406c Mon Sep 17 00:00:00 2001 From: mihailkudravcev Date: Fri, 24 Jul 2026 09:46:44 +0300 Subject: [PATCH] Include configuration name and vendor in generated wiki footers. --- CHANGELOG.md | 6 ++ README.md | 6 +- VERSION | 2 +- export1c_help/config_version.py | 115 ++++++++++++++++++++++++-------- export1c_help/export.py | 64 +++++++++++------- export_1c_help.py | 9 ++- 6 files changed, 145 insertions(+), 57 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e235a3d..f9ff2ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to **export_1c_help** are documented in this file. +## [0.3.4] - 2026-07-24 + +### Added + +- Page footer / Home / History include configuration **display name** (Synonym) and **vendor** (Vendor) from `Configuration.xml` + ## [0.3.3] - 2026-07-23 ### Fixed diff --git a/README.md b/README.md index 7c5b9ec..b547008 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # export_1c_help -**Версия:** см. [`VERSION`](VERSION) (текущая: **0.3.0**) · [CHANGELOG](CHANGELOG.md) +**Версия:** см. [`VERSION`](VERSION) (текущая: **0.3.4**) · [CHANGELOG](CHANGELOG.md) **Лицензия:** [MIT](LICENSE) Выгрузка встроенной справки **любой** конфигурации 1С (`**/Ext/Help/ru.html`) в Markdown и публикация в wiki git-репозитория (Gitea / GitLab: `*.wiki.git`). @@ -124,8 +124,8 @@ python3 export_1c_help.py push -c /path/to/config | Где | Что | |-----|-----| -| `Home.md` | версия конфигурации **этой** выгрузки | -| подвал страницы | версия выгрузки; **создана в** / **изменена в** | +| `Home.md` | наименование, версия, разработчик этой выгрузки | +| подвал страницы | конфигурация, версия, разработчик; **создана в** / **изменена в** | | `History.md` | сводка +/-/~ и удалённые (`deleted_in`) | | `manifest.json` | lifecycle + `content_hash` + `exports[]` | diff --git a/VERSION b/VERSION index 1c09c74..42045ac 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.3.3 +0.3.4 diff --git a/export1c_help/config_version.py b/export1c_help/config_version.py index 3952e33..bc9cc91 100644 --- a/export1c_help/config_version.py +++ b/export1c_help/config_version.py @@ -1,16 +1,34 @@ -"""Read 1C configuration version from dump tree.""" +"""Read 1C configuration identity from dump tree.""" from __future__ import annotations import re +from dataclasses import dataclass from pathlib import Path _VERSION_TAG = re.compile(r"([^<]+)") -_NAME_TAG = re.compile( - r"([^<]+)", +_NAME_TAG = re.compile(r"([^<]+)") +_VENDOR_TAG = re.compile(r"([^<]*)") +_SYN_RE = re.compile( + r"\s*\s*ru\s*(.*?)", + re.DOTALL, ) +@dataclass(frozen=True) +class ConfigIdentity: + """Configuration name / version / vendor for wiki footers.""" + + version: str + name: str | None = None # technical Name + synonym: str | None = None # Synonym ru + vendor: str | None = None + + @property + def display_name(self) -> str | None: + return self.synonym or self.name + + def config_root_from_src(src_root: Path) -> Path: """src/ → configuration root (parent that usually has VERSION + Configuration.xml).""" src_root = Path(src_root).resolve() @@ -19,46 +37,89 @@ def config_root_from_src(src_root: Path) -> Path: return src_root +def _configuration_xml(src_root: Path) -> Path | None: + src_root = Path(src_root) + root = config_root_from_src(src_root) + for xml_path in ( + root / "src" / "Configuration.xml", + src_root / "Configuration.xml", + root / "Configuration.xml", + ): + if xml_path.is_file(): + return xml_path + return None + + +def _xml_unescape(s: str) -> str: + return ( + s.replace("<", "<") + .replace(">", ">") + .replace("&", "&") + .replace(""", '"') + .replace("'", "'") + ) + + def read_config_version(src_root: Path) -> str: """ Resolve configuration version. Priority: 1. /VERSION - 2. /src/Configuration.xml → - 3. /Configuration.xml → + 2. Configuration.xml → """ + return read_config_identity(src_root).version + + +def read_config_name(src_root: Path) -> str | None: + """Technical configuration Name from Configuration.xml.""" + return read_config_identity(src_root).name + + +def read_config_identity(src_root: Path) -> ConfigIdentity: + """Version, Name, Synonym (ru), Vendor from dump.""" src_root = Path(src_root) root = config_root_from_src(src_root) + + version = "unknown" version_file = root / "VERSION" if version_file.is_file(): text = version_file.read_text(encoding="utf-8-sig", errors="ignore").strip() if text: - return text.splitlines()[0].strip() + version = text.splitlines()[0].strip() - for xml_path in ( - root / "src" / "Configuration.xml", - src_root / "Configuration.xml", - root / "Configuration.xml", - ): - if not xml_path.is_file(): - continue + name: str | None = None + synonym: str | None = None + vendor: str | None = None + + xml_path = _configuration_xml(src_root) + if xml_path is not None: raw = xml_path.read_text(encoding="utf-8-sig", errors="ignore") - m = _VERSION_TAG.search(raw) + # Prefer Properties block to avoid accidental matches deeper in the file + props = raw + if "" in raw and "" in raw: + props = raw.split("", 1)[1].split("", 1)[0] + + if version == "unknown": + m = _VERSION_TAG.search(props) + if m: + version = m.group(1).strip() + + m = _NAME_TAG.search(props) if m: - return m.group(1).strip() + name = m.group(1).strip() or None - return "unknown" + m = _SYN_RE.search(props) + if m: + synonym = _xml_unescape(m.group(1).strip()) or None + m = _VENDOR_TAG.search(props) + if m: + vendor = _xml_unescape(m.group(1).strip()) or None -def read_config_name(src_root: Path) -> str | None: - src_root = Path(src_root) - root = config_root_from_src(src_root) - xml_path = root / "src" / "Configuration.xml" - if not xml_path.is_file(): - xml_path = src_root / "Configuration.xml" - if not xml_path.is_file(): - return None - raw = xml_path.read_text(encoding="utf-8-sig", errors="ignore") - m = _NAME_TAG.search(raw) - return m.group(1).strip() if m else None + return ConfigIdentity( + version=version or "unknown", + name=name, + synonym=synonym, + vendor=vendor, + ) diff --git a/export1c_help/export.py b/export1c_help/export.py index 92c08fe..45aa141 100644 --- a/export1c_help/export.py +++ b/export1c_help/export.py @@ -10,7 +10,7 @@ from datetime import datetime, timezone from pathlib import Path from export1c_help.__version__ import __version__ -from export1c_help.config_version import read_config_name, read_config_version +from export1c_help.config_version import ConfigIdentity, read_config_identity from export1c_help.convert import default_link_rewrite, extract_h1, html_to_markdown from export1c_help.discover import HelpPage, build_link_title_map, discover_help_pages from export1c_help.lifecycle import ( @@ -51,12 +51,15 @@ def re_sub_md(md: str) -> str: return t -def _page_footer(rec: PageRecord, *, config_version: str) -> str: +def _page_footer(rec: PageRecord, *, identity: ConfigIdentity) -> str: + display = identity.display_name or "—" + vendor = identity.vendor or "—" lines = [ "", "---", "", - f"*Конфигурация выгрузки: **{config_version}*** ", + f"*Конфигурация: **{display}*** ", + f"*Версия: **{identity.version}** · разработчик: **{vendor}*** ", f"*Страница создана в: **{rec.created_in}** · изменена в: **{rec.edited_in}*** ", f"*Мета: `{rec.meta_key}` · файл: `{rec.source}`*", "", @@ -69,21 +72,21 @@ def build_home( titles: dict[str, str], *, source_label: str, - config_version: str, - config_name: str | None, + identity: ConfigIdentity, life: LifecycleResult, ) -> str: by_type: dict[str, list[HelpPage]] = {} for p in pages: by_type.setdefault(p.type_dir, []).append(p) - name_line = f" (`{config_name}`)" if config_name else "" lines = [ "# Справка конфигурации", "", "Автогенерация из встроенной справки 1С (`Ext/Help/ru.html`).", "", - f"- **Версия конфигурации (эта выгрузка):** `{config_version}`{name_line}", + f"- **Конфигурация:** {identity.display_name or '—'}", + f"- **Версия (эта выгрузка):** `{identity.version}`", + f"- **Разработчик:** {identity.vendor or '—'}", f"- Источник: `{source_label}`", f"- Страниц: **{len(pages)}** " f"(+{life.created_count} / ~{life.edited_count} / ={life.unchanged_count} / -{life.deleted_count})", @@ -133,16 +136,15 @@ def build_sidebar(pages: list[HelpPage], titles: dict[str, str]) -> str: def build_history( *, - config_version: str, - config_name: str | None, + identity: ConfigIdentity, life: LifecycleResult, prev_exports: list[dict], ) -> str: - name = f" `{config_name}`" if config_name else "" lines = [ "# История выгрузок справки", "", - f"Текущая выгрузка: конфигурация **{config_version}**{name}.", + f"Текущая выгрузка: **{identity.display_name or '—'}** " + f"версия **{identity.version}**, разработчик **{identity.vendor or '—'}**.", "", "## Эта выгрузка", "", @@ -157,7 +159,7 @@ def build_history( lines.append("### Удалено в этой версии") lines.append("") for rec in life.deleted: - if rec.deleted_in != config_version: + if rec.deleted_in != identity.version: continue lines.append( f"- `{rec.meta_key}` — «{rec.title}» " @@ -183,11 +185,14 @@ def build_history( lines.append("_Нет сохранённой истории (первая выгрузка с учётом версий)._") lines.append("") else: - lines.append("| Версия конфигурации | Дата (UTC) | Страниц | + | ~ | - |") - lines.append("|---------------------|------------|---------|---|---|---|") + lines.append("| Конфигурация | Версия | Разработчик | Дата (UTC) | Страниц | + | ~ | - |") + lines.append("|--------------|--------|-------------|------------|---------|---|---|---|") for row in prev_exports: lines.append( - f"| {row.get('config_version', '?')} | {row.get('exported_at', '?')} | " + f"| {row.get('config_display') or row.get('config_name') or '—'} | " + f"{row.get('config_version', '?')} | " + f"{row.get('config_vendor') or '—'} | " + f"{row.get('exported_at', '?')} | " f"{row.get('pages_written', '?')} | {row.get('created', '?')} | " f"{row.get('edited', '?')} | {row.get('deleted', '?')} |" ) @@ -212,8 +217,15 @@ def export_help( out_dir = out_dir.resolve() stats = ExportStats() - cfg_ver = (config_version or read_config_version(src_root)).strip() or "unknown" - cfg_name = read_config_name(src_root) + identity = read_config_identity(src_root) + if config_version: + identity = ConfigIdentity( + version=config_version.strip() or identity.version, + name=identity.name, + synonym=identity.synonym, + vendor=identity.vendor, + ) + cfg_ver = identity.version stats.config_version = cfg_ver prev_manifest = load_prev_manifest(prev_manifest_path) @@ -286,7 +298,7 @@ def export_help( for rec in life.active: body = body_by_meta[rec.meta_key] - md = body.rstrip() + _page_footer(rec, config_version=cfg_ver) + md = body.rstrip() + _page_footer(rec, identity=identity) (out_dir / rec.filename).write_text(md, encoding="utf-8") stats.pages_written += 1 @@ -312,6 +324,9 @@ def export_help( 0, { "config_version": prev_manifest.get("config_version"), + "config_name": prev_manifest.get("config_name"), + "config_display": prev_manifest.get("config_display"), + "config_vendor": prev_manifest.get("config_vendor"), "exported_at": prev_manifest.get("exported_at"), "pages_written": prev_manifest.get("pages_written"), "created": prev_manifest.get("created_count"), @@ -332,8 +347,7 @@ def export_help( written_pages, titles, source_label=label, - config_version=cfg_ver, - config_name=cfg_name, + identity=identity, life=life, ), encoding="utf-8", @@ -344,8 +358,7 @@ def export_help( ) (out_dir / "History.md").write_text( build_history( - config_version=cfg_ver, - config_name=cfg_name, + identity=identity, life=life, prev_exports=prev_exports, ), @@ -356,6 +369,9 @@ def export_help( exports = [ { "config_version": cfg_ver, + "config_name": identity.name, + "config_display": identity.display_name, + "config_vendor": identity.vendor, "exported_at": exported_at, "pages_written": stats.pages_written, "created": stats.created_count, @@ -370,7 +386,9 @@ def export_help( "tool": "export_1c_help", "version": __version__, "config_version": cfg_ver, - "config_name": cfg_name, + "config_name": identity.name, + "config_display": identity.display_name, + "config_vendor": identity.vendor, "source": label, "exported_at": exported_at, "pages_total": stats.pages_total, diff --git a/export_1c_help.py b/export_1c_help.py index dedfabc..59fde4b 100644 --- a/export_1c_help.py +++ b/export_1c_help.py @@ -13,7 +13,7 @@ if str(TOOL_ROOT) not in sys.path: from export1c_help import __status__, __version__ # noqa: E402 from export1c_help.config_version import ( # noqa: E402 - read_config_name, + read_config_identity, read_config_version, ) from export1c_help.export import export_help # noqa: E402 @@ -190,6 +190,7 @@ def cmd_info(args: argparse.Namespace) -> int: 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 @@ -198,8 +199,10 @@ def cmd_info(args: argparse.Namespace) -> int: 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"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}")