98cbb88461
Use git commit -F and strip NULs from argv/version strings (0.3.7). Co-authored-by: Cursor <cursoragent@cursor.com>
133 lines
3.7 KiB
Python
133 lines
3.7 KiB
Python
"""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>")
|
|
_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()
|
|
if src_root.name == "src":
|
|
return src_root.parent
|
|
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. 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 _sanitize_text(s: str) -> str:
|
|
"""Drop NULs (UTF-16 mis-reads / dirty dumps) that break Windows APIs."""
|
|
return s.replace("\0", "").strip()
|
|
|
|
|
|
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 = _sanitize_text(
|
|
version_file.read_text(encoding="utf-8-sig", errors="ignore")
|
|
)
|
|
if text:
|
|
version = text.splitlines()[0].strip()
|
|
|
|
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")
|
|
# 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 = _sanitize_text(m.group(1)) or "unknown"
|
|
|
|
m = _NAME_TAG.search(props)
|
|
if m:
|
|
name = _sanitize_text(m.group(1)) or None
|
|
|
|
m = _SYN_RE.search(props)
|
|
if m:
|
|
synonym = _sanitize_text(_xml_unescape(m.group(1))) or None
|
|
|
|
m = _VENDOR_TAG.search(props)
|
|
if m:
|
|
vendor = _sanitize_text(_xml_unescape(m.group(1))) or None
|
|
|
|
return ConfigIdentity(
|
|
version=_sanitize_text(version) or "unknown",
|
|
name=name,
|
|
synonym=synonym,
|
|
vendor=vendor,
|
|
)
|