Include configuration name and vendor in generated wiki footers.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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[]` |
|
||||
|
||||
|
||||
@@ -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"<Version>([^<]+)</Version>")
|
||||
_NAME_TAG = re.compile(
|
||||
r"<Name>([^<]+)</Name>",
|
||||
_NAME_TAG = re.compile(r"<Name>([^<]+)</Name>")
|
||||
_VENDOR_TAG = re.compile(r"<Vendor>([^<]*)</Vendor>")
|
||||
_SYN_RE = re.compile(
|
||||
r"<Synonym>\s*<v8:item>\s*<v8:lang>ru</v8:lang>\s*<v8:content>(.*?)</v8:content>",
|
||||
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. <config_root>/VERSION
|
||||
2. <config_root>/src/Configuration.xml → <Version>
|
||||
3. <src_root>/Configuration.xml → <Version>
|
||||
2. Configuration.xml → <Version>
|
||||
"""
|
||||
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 "<Properties>" in raw and "</Properties>" in raw:
|
||||
props = raw.split("<Properties>", 1)[1].split("</Properties>", 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,
|
||||
)
|
||||
|
||||
+41
-23
@@ -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,
|
||||
|
||||
+6
-3
@@ -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}")
|
||||
|
||||
Reference in New Issue
Block a user