mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-07-24 21:51:02 +03:00
feat(cf-info,meta-info): вывод состояния поддержки из ParentConfigurations.bin
Декодер Ext/ParentConfigurations.bin (нативная копия в каждом скрипте, без общего модуля — по конвенции автономности навыков; single-pass, не падает на битом/пустом файле). cf-info (v1.3): блок «Поддержка:» в overview/full — на поддержке / возможность изменения вкл-выкл / счётчики на замке/редактируется/снято (только при G=0) / снята полностью / расширение (CFE). При G=1 показывает read-only без вводящих в заблуждение счётчиков; тяжёлый скан 7.4МБ пропускается, когда не нужен. При K>1 перечисляет конфигурации поставщика. meta-info (v1.3): строка «Поддержка:» под заголовком объекта — walk-up к корню конфигурации, статус с учётом G-vs-f1 и консервативной свёртки min(f1) по блокам поставщиков. Для на-замке/read-only — короткое последствие+действие (cfe-*), для остальных терсно. Проверено на корпусе и размеченных дампах (acc G=1, erp снято, ЧастьОбъектов, Корень, НесколькоПоддержек K=7 мультивендор, CFE) — оба порта байт-в-байт. Формат: docs/1c-support-state-spec.md. Тесты: cf-info 7/7, meta-info 17/17 на PowerShell и Python. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
75a97d51ea
commit
96660f4d9d
@@ -1,4 +1,4 @@
|
||||
# cf-info v1.2 — Compact summary of 1C configuration root
|
||||
# cf-info v1.3 — Compact summary of 1C configuration root
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][Alias('Path')][string]$ConfigPath,
|
||||
@@ -218,6 +218,78 @@ function Get-HomePageLayout {
|
||||
|
||||
$script:homePage = Get-HomePageLayout
|
||||
|
||||
# --- Support state (Ext/ParentConfigurations.bin) ---
|
||||
# Decodes the 1C support-state file. See docs/1c-support-state-spec.md.
|
||||
# Returns $null on absent/error; else hashtable: State='absent'|'removed'|'parsed',
|
||||
# G (0=editing on, 1=off), K (vendor configs), Vendors @(@{Vendor;Name;Version}),
|
||||
# Counts @(locked, editable, removed) by f1 — record tally (K>1 counts each
|
||||
# vendor block separately); only computed when G=0.
|
||||
function Read-SupportState([string]$binPath) {
|
||||
try {
|
||||
if (-not (Test-Path $binPath)) { return @{ State = 'absent' } }
|
||||
$bytes = [System.IO.File]::ReadAllBytes($binPath)
|
||||
if ($bytes.Length -le 32) { return @{ State = 'removed' } }
|
||||
$startIdx = 0
|
||||
if ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) { $startIdx = 3 }
|
||||
$text = [System.Text.Encoding]::UTF8.GetString($bytes, $startIdx, $bytes.Length - $startIdx)
|
||||
$h = [regex]::Match($text, '^\{6,(\d+),(\d+),')
|
||||
if (-not $h.Success) { return $null }
|
||||
$G = [int]$h.Groups[1].Value
|
||||
$K = [int]$h.Groups[2].Value
|
||||
if ($K -eq 0) { return @{ State = 'removed' } }
|
||||
# Vendor descriptors: ...,"ver","vendor","name",count,
|
||||
$vendors = @()
|
||||
$vRe = [regex]'"((?:[^"]|"")*)","((?:[^"]|"")*)","((?:[^"]|"")*)",\d+,'
|
||||
foreach ($m in $vRe.Matches($text)) {
|
||||
$vendors += @{
|
||||
Version = ($m.Groups[1].Value -replace '""','"')
|
||||
Vendor = ($m.Groups[2].Value -replace '""','"')
|
||||
Name = ($m.Groups[3].Value -replace '""','"')
|
||||
}
|
||||
}
|
||||
# Per-object counts only matter when editing is enabled (G=0); when G=1 the
|
||||
# whole config is read-only and stored f1 values are the inactive default.
|
||||
$counts = $null
|
||||
if ($G -eq 0) {
|
||||
$counts = @(0, 0, 0)
|
||||
# Object records: f1,0,uuidLocal[,uuidVendor] — flags precede the uuid.
|
||||
$rRe = [regex]'([0-2]),0,[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'
|
||||
foreach ($m in $rRe.Matches($text)) {
|
||||
$counts[[int]$m.Groups[1].Value]++
|
||||
}
|
||||
}
|
||||
return @{ State = 'parsed'; G = $G; K = $K; Vendors = $vendors; Counts = $counts }
|
||||
} catch { return $null }
|
||||
}
|
||||
|
||||
function Get-SupportLines {
|
||||
$configDir = [System.IO.Path]::GetDirectoryName($ConfigPath)
|
||||
$binPath = Join-Path (Join-Path $configDir "Ext") "ParentConfigurations.bin"
|
||||
$st = Read-SupportState $binPath
|
||||
$out = @()
|
||||
if (-not $st -or $st.State -eq 'absent') {
|
||||
if ($cfgExtPurpose) { $out += "Поддержка: расширение (CFE), правки свободны" }
|
||||
else { $out += "Поддержка: не на поддержке (своя конфигурация)" }
|
||||
return $out
|
||||
}
|
||||
if ($st.State -eq 'removed') {
|
||||
$out += "Поддержка: снята с поддержки полностью"
|
||||
return $out
|
||||
}
|
||||
$out += "Поддержка: на поддержке"
|
||||
if ($st.G -eq 0) {
|
||||
$out += " Возможность изменения: включена"
|
||||
$out += " Объектов: на замке $($st.Counts[0]) / редактируется $($st.Counts[1]) / снято $($st.Counts[2])"
|
||||
} else {
|
||||
$out += " Возможность изменения: выключена — вся конфигурация read-only (правки заблокированы)"
|
||||
}
|
||||
$out += " Конфигураций поставщика: $($st.K)"
|
||||
if ($st.K -gt 1) {
|
||||
foreach ($v in $st.Vendors) { $out += " Поставщик: $($v.Vendor) — $($v.Name) $($v.Version)" }
|
||||
}
|
||||
return $out
|
||||
}
|
||||
|
||||
function Format-HomePageItem($it, [bool]$detailed) {
|
||||
$badges = @()
|
||||
$badges += "h=$($it.height)"
|
||||
@@ -253,6 +325,7 @@ $cfgVersion = Get-PropText "Version"
|
||||
$cfgVendor = Get-PropText "Vendor"
|
||||
$cfgCompat = Get-PropText "CompatibilityMode"
|
||||
$cfgExtCompat = Get-PropText "ConfigurationExtensionCompatibilityMode"
|
||||
$cfgExtPurpose = Get-PropText "ConfigurationExtensionPurpose"
|
||||
$cfgDefaultRun = Get-PropText "DefaultRunMode"
|
||||
$cfgScript = Get-PropText "ScriptVariant"
|
||||
$cfgDefaultLang = Get-PropText "DefaultLanguage"
|
||||
@@ -284,6 +357,7 @@ if ($Mode -eq "overview" -and -not $Section) {
|
||||
Out "Формат: $version"
|
||||
if ($cfgVendor) { Out "Поставщик: $cfgVendor" }
|
||||
if ($cfgVersion) { Out "Версия: $cfgVersion" }
|
||||
foreach ($l in (Get-SupportLines)) { Out $l }
|
||||
Out "Совместимость: $cfgCompat"
|
||||
Out "Режим запуска: $cfgDefaultRun"
|
||||
Out "Язык скриптов: $cfgScript"
|
||||
@@ -386,6 +460,7 @@ if ($Mode -eq "full" -and -not $Section) {
|
||||
if ($cfgPrefix) { Out "Префикс: $cfgPrefix" }
|
||||
if ($cfgVendor) { Out "Поставщик: $cfgVendor" }
|
||||
if ($cfgVersion) { Out "Версия: $cfgVersion" }
|
||||
foreach ($l in (Get-SupportLines)) { Out $l }
|
||||
$cfgUpdateAddr = Get-PropText "UpdateCatalogAddress"
|
||||
if ($cfgUpdateAddr) { Out "Каталог обн.: $cfgUpdateAddr" }
|
||||
Out ""
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#!/usr/bin/env python3
|
||||
# cf-info v1.2 — Compact summary of 1C configuration root
|
||||
# cf-info v1.3 — Compact summary of 1C configuration root
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from collections import OrderedDict
|
||||
from lxml import etree
|
||||
@@ -219,6 +220,71 @@ def get_home_page_layout():
|
||||
|
||||
home_page = get_home_page_layout()
|
||||
|
||||
# --- Support state (Ext/ParentConfigurations.bin) ---
|
||||
# Decodes the 1C support-state file. See docs/1c-support-state-spec.md.
|
||||
# Returns None on absent/error; else dict: state='absent'|'removed'|'parsed',
|
||||
# g (0=editing on, 1=off), k (vendor configs), vendors [{vendor,name,version}],
|
||||
# counts [locked, editable, removed] by f1 — record tally (k>1 counts each
|
||||
# vendor block separately); only computed when g==0.
|
||||
def read_support_state(bin_path):
|
||||
try:
|
||||
if not os.path.isfile(bin_path):
|
||||
return {"state": "absent"}
|
||||
data = open(bin_path, "rb").read()
|
||||
if len(data) <= 32:
|
||||
return {"state": "removed"}
|
||||
if data[:3] == b"\xef\xbb\xbf":
|
||||
data = data[3:]
|
||||
text = data.decode("utf-8", "replace")
|
||||
h = re.match(r"\{6,(\d+),(\d+),", text)
|
||||
if not h:
|
||||
return None
|
||||
g = int(h.group(1))
|
||||
k = int(h.group(2))
|
||||
if k == 0:
|
||||
return {"state": "removed"}
|
||||
vendors = []
|
||||
for m in re.finditer(r'"((?:[^"]|"")*)","((?:[^"]|"")*)","((?:[^"]|"")*)",\d+,', text):
|
||||
vendors.append({
|
||||
"version": m.group(1).replace('""', '"'),
|
||||
"vendor": m.group(2).replace('""', '"'),
|
||||
"name": m.group(3).replace('""', '"'),
|
||||
})
|
||||
counts = None
|
||||
if g == 0:
|
||||
counts = [0, 0, 0]
|
||||
for m in re.finditer(r"([0-2]),0,[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", text):
|
||||
counts[int(m.group(1))] += 1
|
||||
return {"state": "parsed", "g": g, "k": k, "vendors": vendors, "counts": counts}
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def get_support_lines():
|
||||
config_dir = os.path.dirname(config_path)
|
||||
bin_path = os.path.join(config_dir, "Ext", "ParentConfigurations.bin")
|
||||
st = read_support_state(bin_path)
|
||||
res = []
|
||||
if not st or st["state"] == "absent":
|
||||
if cfg_ext_purpose:
|
||||
res.append("Поддержка: расширение (CFE), правки свободны")
|
||||
else:
|
||||
res.append("Поддержка: не на поддержке (своя конфигурация)")
|
||||
return res
|
||||
if st["state"] == "removed":
|
||||
res.append("Поддержка: снята с поддержки полностью")
|
||||
return res
|
||||
res.append("Поддержка: на поддержке")
|
||||
if st["g"] == 0:
|
||||
res.append(" Возможность изменения: включена")
|
||||
res.append(f" Объектов: на замке {st['counts'][0]} / редактируется {st['counts'][1]} / снято {st['counts'][2]}")
|
||||
else:
|
||||
res.append(" Возможность изменения: выключена — вся конфигурация read-only (правки заблокированы)")
|
||||
res.append(f" Конфигураций поставщика: {st['k']}")
|
||||
if st["k"] > 1:
|
||||
for v in st["vendors"]:
|
||||
res.append(f" Поставщик: {v['vendor']} — {v['name']} {v['version']}")
|
||||
return res
|
||||
|
||||
def format_home_page_item(it, detailed):
|
||||
badges = [f"h={it['height']}"]
|
||||
if not it["common"]:
|
||||
@@ -249,6 +315,7 @@ cfg_version = get_prop_text("Version")
|
||||
cfg_vendor = get_prop_text("Vendor")
|
||||
cfg_compat = get_prop_text("CompatibilityMode")
|
||||
cfg_ext_compat = get_prop_text("ConfigurationExtensionCompatibilityMode")
|
||||
cfg_ext_purpose = get_prop_text("ConfigurationExtensionPurpose")
|
||||
cfg_default_run = get_prop_text("DefaultRunMode")
|
||||
cfg_script = get_prop_text("ScriptVariant")
|
||||
cfg_default_lang = get_prop_text("DefaultLanguage")
|
||||
@@ -281,6 +348,8 @@ if args.Mode == "overview" and not args.Section:
|
||||
out(f"Поставщик: {cfg_vendor}")
|
||||
if cfg_version:
|
||||
out(f"Версия: {cfg_version}")
|
||||
for ln in get_support_lines():
|
||||
out(ln)
|
||||
out(f"Совместимость: {cfg_compat}")
|
||||
out(f"Режим запуска: {cfg_default_run}")
|
||||
out(f"Язык скриптов: {cfg_script}")
|
||||
@@ -369,6 +438,8 @@ if args.Mode == "full" and not args.Section:
|
||||
out(f"Поставщик: {cfg_vendor}")
|
||||
if cfg_version:
|
||||
out(f"Версия: {cfg_version}")
|
||||
for ln in get_support_lines():
|
||||
out(ln)
|
||||
cfg_update_addr = get_prop_text("UpdateCatalogAddress")
|
||||
if cfg_update_addr:
|
||||
out(f"Каталог обн.: {cfg_update_addr}")
|
||||
|
||||
Reference in New Issue
Block a user