Merge branch 'dev'

This commit is contained in:
Nick Shirokov
2026-03-09 20:43:43 +03:00
32 changed files with 898 additions and 1009 deletions
+14 -40
View File
@@ -1,7 +1,7 @@
--- ---
name: cf-validate name: cf-validate
description: Валидация конфигурации 1С. Используй после создания или модификации конфигурации для проверки корректности description: Валидация конфигурации 1С. Используй после создания или модификации конфигурации для проверки корректности
argument-hint: <ConfigPath> [-MaxErrors 30] argument-hint: <ConfigPath> [-Detailed] [-MaxErrors 30]
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
@@ -12,19 +12,23 @@ allowed-tools:
Проверяет Configuration.xml на структурные ошибки: XML well-formedness, InternalInfo, свойства, enum-значения, ChildObjects, DefaultLanguage, файлы языков, каталоги объектов. Проверяет Configuration.xml на структурные ошибки: XML well-formedness, InternalInfo, свойства, enum-значения, ChildObjects, DefaultLanguage, файлы языков, каталоги объектов.
## Параметры и команда ## Параметры
| Параметр | Описание | | Параметр | Обяз. | Умолч. | Описание |
|----------|----------| |------------|:-----:|---------|-------------------------------------------------|
| `ConfigPath` | Путь к Configuration.xml или каталогу выгрузки | | ConfigPath | да | — | Путь к Configuration.xml или каталогу выгрузки |
| `MaxErrors` | Остановиться после N ошибок (default: 30) | | Detailed | нет | — | Показывать [OK] для каждой проверки |
| `OutFile` | Записать результат в файл (UTF-8 BOM) | | MaxErrors | нет | 30 | Остановиться после N ошибок |
| OutFile | нет | — | Записать результат в файл (UTF-8 BOM) |
## Команда
```powershell ```powershell
powershell.exe -NoProfile -File .claude/skills/cf-validate/scripts/cf-validate.ps1 -ConfigPath "<путь>" powershell.exe -NoProfile -File .claude/skills/cf-validate/scripts/cf-validate.ps1 -ConfigPath "upload/cfempty"
powershell.exe -NoProfile -File .claude/skills/cf-validate/scripts/cf-validate.ps1 -ConfigPath "upload/cfempty/Configuration.xml"
``` ```
## Выполняемые проверки ## Проверки
| # | Проверка | Серьёзность | | # | Проверка | Серьёзность |
|---|----------|-------------| |---|----------|-------------|
@@ -37,34 +41,4 @@ powershell.exe -NoProfile -File .claude/skills/cf-validate/scripts/cf-validate.p
| 7 | Файлы языков Languages/<name>.xml существуют | WARN | | 7 | Файлы языков Languages/<name>.xml существуют | WARN |
| 8 | Каталоги объектов из ChildObjects существуют (spot-check) | WARN | | 8 | Каталоги объектов из ChildObjects существуют (spot-check) | WARN |
## Вывод Exit code: 0 = OK, 1 = есть ошибки. По умолчанию краткий вывод. `-Detailed` для поштучной детализации.
```
=== Validation: Configuration.МояКонфигурация ===
[OK] 1. Root structure: MetaDataObject/Configuration, version 2.17
[OK] 2. InternalInfo: 7 ContainedObject, all ClassIds valid
[OK] 3. Properties: Name="МояКонфигурация", Synonym present
[OK] 4. Property values: 11 enum properties checked
[OK] 5. ChildObjects: 1 types, 1 objects, order correct
[OK] 6. DefaultLanguage "Language.Русский" found in ChildObjects
[OK] 7. Language files: 1/1 exist
[OK] 8. Object directories: spot-check passed
=== Result: 0 errors, 0 warnings ===
```
Exit code: 0 = OK, 1 = errors.
## Примеры
```powershell
# Пустая конфигурация
... -ConfigPath upload/cfempty
# Реальная конфигурация
... -ConfigPath C:\WS\tasks\cfsrc\acc_8.3.24
# С лимитом ошибок
... -ConfigPath test-tmp/cf -MaxErrors 10
```
@@ -1,9 +1,11 @@
# cf-validate v1.0 — Validate 1C configuration root structure # cf-validate v1.1 — Validate 1C configuration root structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
[string]$ConfigPath, [string]$ConfigPath,
[switch]$Detailed,
[int]$MaxErrors = 30, [int]$MaxErrors = 30,
[string]$OutFile [string]$OutFile
@@ -38,6 +40,7 @@ $configDir = Split-Path $resolvedPath -Parent
# --- Output infrastructure --- # --- Output infrastructure ---
$script:errors = 0 $script:errors = 0
$script:warnings = 0 $script:warnings = 0
$script:okCount = 0
$script:stopped = $false $script:stopped = $false
$script:output = New-Object System.Text.StringBuilder 8192 $script:output = New-Object System.Text.StringBuilder 8192
@@ -48,7 +51,8 @@ function Out-Line {
function Report-OK { function Report-OK {
param([string]$msg) param([string]$msg)
Out-Line "[OK] $msg" $script:okCount++
if ($Detailed) { Out-Line "[OK] $msg" }
} }
function Report-Error { function Report-Error {
@@ -67,10 +71,14 @@ function Report-Warn {
} }
$finalize = { $finalize = {
Out-Line "" $checks = $script:okCount + $script:errors + $script:warnings
Out-Line "=== Result: $($script:errors) errors, $($script:warnings) warnings ===" if ($script:errors -eq 0 -and $script:warnings -eq 0 -and -not $Detailed) {
$result = "=== Validation OK: Configuration.$objName ($checks checks) ==="
$result = $script:output.ToString() } else {
Out-Line ""
Out-Line "=== Result: $($script:errors) errors, $($script:warnings) warnings ($checks checks) ==="
$result = $script:output.ToString()
}
Write-Host $result Write-Host $result
if ($OutFile) { if ($OutFile) {
@@ -525,8 +533,6 @@ if ($childObjNode) {
Report-Warn "8. Missing directory: $md" Report-Warn "8. Missing directory: $md"
} }
} }
} else {
Report-OK "8. Object directories: N/A"
} }
# --- Final output --- # --- Final output ---
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# cf-validate v1.0 — Validate 1C configuration XML structure # cf-validate v1.1 — Validate 1C configuration XML structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
"""Validates Configuration.xml: root structure, InternalInfo, properties, ChildObjects, languages.""" """Validates Configuration.xml: root structure, InternalInfo, properties, ChildObjects, languages."""
import sys, os, argparse, re import sys, os, argparse, re
@@ -108,18 +108,23 @@ EXPECTED_NS = 'http://v8.1c.ru/8.3/MDClasses'
class Reporter: class Reporter:
def __init__(self, max_errors): def __init__(self, max_errors, detailed=False):
self.errors = 0 self.errors = 0
self.warnings = 0 self.warnings = 0
self.ok_count = 0
self.stopped = False self.stopped = False
self.max_errors = max_errors self.max_errors = max_errors
self.detailed = detailed
self.lines = [] self.lines = []
self.obj_name = '(unknown)'
def out(self, msg=''): def out(self, msg=''):
self.lines.append(msg) self.lines.append(msg)
def ok(self, msg): def ok(self, msg):
self.lines.append(f'[OK] {msg}') self.ok_count += 1
if self.detailed:
self.lines.append(f'[OK] {msg}')
def error(self, msg): def error(self, msg):
self.errors += 1 self.errors += 1
@@ -135,11 +140,15 @@ class Reporter:
return '\r\n'.join(self.lines) + '\r\n' return '\r\n'.join(self.lines) + '\r\n'
def finalize(self, out_file): def finalize(self, out_file):
self.out('') checks = self.ok_count + self.errors + self.warnings
self.out(f'=== Result: {self.errors} errors, {self.warnings} warnings ===') if self.errors == 0 and self.warnings == 0 and not self.detailed:
result = f'=== Validation OK: Configuration.{self.obj_name} ({checks} checks) ==='
else:
self.out('')
self.out(f'=== Result: {self.errors} errors, {self.warnings} warnings ({checks} checks) ===')
result = self.text()
result = self.text() print(result, end='' if '\r\n' in result else '\n')
print(result, end='')
if out_file: if out_file:
with open(out_file, 'w', encoding='utf-8-sig', newline='') as f: with open(out_file, 'w', encoding='utf-8-sig', newline='') as f:
@@ -154,6 +163,7 @@ def main():
description='Validate 1C configuration XML structure', allow_abbrev=False description='Validate 1C configuration XML structure', allow_abbrev=False
) )
parser.add_argument('-ConfigPath', dest='ConfigPath', required=True) parser.add_argument('-ConfigPath', dest='ConfigPath', required=True)
parser.add_argument('-Detailed', action='store_true')
parser.add_argument('-MaxErrors', dest='MaxErrors', type=int, default=30) parser.add_argument('-MaxErrors', dest='MaxErrors', type=int, default=30)
parser.add_argument('-OutFile', dest='OutFile', default='') parser.add_argument('-OutFile', dest='OutFile', default='')
args = parser.parse_args() args = parser.parse_args()
@@ -184,7 +194,7 @@ def main():
if out_file and not os.path.isabs(out_file): if out_file and not os.path.isabs(out_file):
out_file = os.path.join(os.getcwd(), out_file) out_file = os.path.join(os.getcwd(), out_file)
r = Reporter(max_errors) r = Reporter(max_errors, detailed=args.Detailed)
r.out('') r.out('')
# --- 1. Parse XML --- # --- 1. Parse XML ---
@@ -248,6 +258,7 @@ def main():
props_node = cfg_node.find('md:Properties', NS) props_node = cfg_node.find('md:Properties', NS)
name_node = props_node.find('md:Name', NS) if props_node is not None else None name_node = props_node.find('md:Name', NS) if props_node is not None else None
obj_name = (name_node.text or '') if name_node is not None and name_node.text else '(unknown)' obj_name = (name_node.text or '') if name_node is not None and name_node.text else '(unknown)'
r.obj_name = obj_name
r.lines.insert(0, f'=== Validation: Configuration.{obj_name} ===') r.lines.insert(0, f'=== Validation: Configuration.{obj_name} ===')
@@ -521,7 +532,7 @@ def main():
for md in missing_dirs: for md in missing_dirs:
r.warn(f'8. Missing directory: {md}') r.warn(f'8. Missing directory: {md}')
else: else:
r.ok('8. Object directories: N/A') pass # no ChildObjects
# --- Final output --- # --- Final output ---
r.finalize(out_file) r.finalize(out_file)
+11 -17
View File
@@ -1,29 +1,31 @@
--- ---
name: cfe-validate name: cfe-validate
description: Валидация расширения конфигурации 1С (CFE). Используй после создания или модификации расширения для проверки корректности description: Валидация расширения конфигурации 1С (CFE). Используй после создания или модификации расширения для проверки корректности
argument-hint: <ExtensionPath> [-MaxErrors 30] argument-hint: <ExtensionPath> [-Detailed] [-MaxErrors 30]
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
- Glob - Glob
--- ---
# /cfe-validate — Валидация расширения конфигурации # /cfe-validate — валидация расширения конфигурации (CFE)
Проверяет структурную корректность расширения: XML-формат, свойства, состав, заимствованные объекты. Аналог `/cf-validate`, но для расширений. Проверяет структурную корректность расширения: XML-формат, свойства, состав, заимствованные объекты. Аналог `/cf-validate`, но для расширений.
## Параметры ## Параметры
| Параметр | Описание | По умолчанию | | Параметр | Обяз. | Умолч. | Описание |
|----------|----------|--------------| |---------------|:-----:|---------|-------------------------------------------------|
| `ExtensionPath` | Путь к каталогу или Configuration.xml расширения (обязат.) | — | | ExtensionPath | да | — | Путь к каталогу или Configuration.xml расширения |
| `MaxErrors` | Лимит ошибок | 30 | | Detailed | нет | — | Показывать [OK] для каждой проверки |
| `OutFile` | Записать результат в файл | — | | MaxErrors | нет | 30 | Остановиться после N ошибок |
| OutFile | нет | — | Записать результат в файл |
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File .claude/skills/cfe-validate/scripts/cfe-validate.ps1 -ExtensionPath src powershell.exe -NoProfile -File .claude/skills/cfe-validate/scripts/cfe-validate.ps1 -ExtensionPath "src"
powershell.exe -NoProfile -File .claude/skills/cfe-validate/scripts/cfe-validate.ps1 -ExtensionPath "src/Configuration.xml"
``` ```
## Проверки (9 шагов) ## Проверки (9 шагов)
@@ -40,12 +42,4 @@ powershell.exe -NoProfile -File .claude/skills/cfe-validate/scripts/cfe-validate
| 8 | Каталоги объектов существуют | WARN | | 8 | Каталоги объектов существуют | WARN |
| 9 | Заимствованные объекты: ObjectBelonging=Adopted, ExtendedConfigurationObject UUID | ERROR/WARN | | 9 | Заимствованные объекты: ObjectBelonging=Adopted, ExtendedConfigurationObject UUID | ERROR/WARN |
## Пример вывода Exit code: 0 = OK, 1 = есть ошибки. По умолчанию краткий вывод. `-Detailed` для поштучной детализации.
```
=== Validation: Extension.МоёРасширение ===
[OK] 1. Root structure: MetaDataObject/Configuration, version 2.17
[OK] 2. InternalInfo: 7 ContainedObject, all ClassIds valid
...
=== Result: 0 errors, 0 warnings ===
```
@@ -1,9 +1,11 @@
# cfe-validate v1.0 — Validate 1C configuration extension structure (CFE) # cfe-validate v1.1 — Validate 1C configuration extension structure (CFE)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
[string]$ExtensionPath, [string]$ExtensionPath,
[switch]$Detailed,
[int]$MaxErrors = 30, [int]$MaxErrors = 30,
[string]$OutFile [string]$OutFile
@@ -38,6 +40,7 @@ $configDir = Split-Path $resolvedPath -Parent
# --- Output infrastructure --- # --- Output infrastructure ---
$script:errors = 0 $script:errors = 0
$script:warnings = 0 $script:warnings = 0
$script:okCount = 0
$script:stopped = $false $script:stopped = $false
$script:output = New-Object System.Text.StringBuilder 8192 $script:output = New-Object System.Text.StringBuilder 8192
@@ -48,7 +51,8 @@ function Out-Line {
function Report-OK { function Report-OK {
param([string]$msg) param([string]$msg)
Out-Line "[OK] $msg" $script:okCount++
if ($Detailed) { Out-Line "[OK] $msg" }
} }
function Report-Error { function Report-Error {
@@ -67,10 +71,14 @@ function Report-Warn {
} }
$finalize = { $finalize = {
Out-Line "" $checks = $script:okCount + $script:errors + $script:warnings
Out-Line "=== Result: $($script:errors) errors, $($script:warnings) warnings ===" if ($script:errors -eq 0 -and $script:warnings -eq 0 -and -not $Detailed) {
$result = "=== Validation OK: Extension.$objName ($checks checks) ==="
$result = $script:output.ToString() } else {
Out-Line ""
Out-Line "=== Result: $($script:errors) errors, $($script:warnings) warnings ($checks checks) ==="
$result = $script:output.ToString()
}
Write-Host $result Write-Host $result
if ($OutFile) { if ($OutFile) {
@@ -521,8 +529,6 @@ if ($childObjNode) {
Report-Warn "8. Missing directory: $md" Report-Warn "8. Missing directory: $md"
} }
} }
} else {
Report-OK "8. Object directories: N/A"
} }
if ($script:stopped) { & $finalize; exit 1 } if ($script:stopped) { & $finalize; exit 1 }
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# cfe-validate v1.0 — Validate 1C configuration extension XML structure (CFE) # cfe-validate v1.1 — Validate 1C configuration extension XML structure (CFE)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
"""Validates extension Configuration.xml: root, InternalInfo, extension properties, ChildObjects, borrowed objects.""" """Validates extension Configuration.xml: root, InternalInfo, extension properties, ChildObjects, borrowed objects."""
import sys, os, argparse, re import sys, os, argparse, re
@@ -93,18 +93,23 @@ EXPECTED_NS = 'http://v8.1c.ru/8.3/MDClasses'
class Reporter: class Reporter:
def __init__(self, max_errors): def __init__(self, max_errors, detailed=False):
self.errors = 0 self.errors = 0
self.warnings = 0 self.warnings = 0
self.ok_count = 0
self.stopped = False self.stopped = False
self.max_errors = max_errors self.max_errors = max_errors
self.detailed = detailed
self.lines = [] self.lines = []
self.obj_name = '(unknown)'
def out(self, msg=''): def out(self, msg=''):
self.lines.append(msg) self.lines.append(msg)
def ok(self, msg): def ok(self, msg):
self.lines.append(f'[OK] {msg}') self.ok_count += 1
if self.detailed:
self.lines.append(f'[OK] {msg}')
def error(self, msg): def error(self, msg):
self.errors += 1 self.errors += 1
@@ -120,11 +125,15 @@ class Reporter:
return '\r\n'.join(self.lines) + '\r\n' return '\r\n'.join(self.lines) + '\r\n'
def finalize(self, out_file): def finalize(self, out_file):
self.out('') checks = self.ok_count + self.errors + self.warnings
self.out(f'=== Result: {self.errors} errors, {self.warnings} warnings ===') if self.errors == 0 and self.warnings == 0 and not self.detailed:
result = f'=== Validation OK: Extension.{self.obj_name} ({checks} checks) ==='
else:
self.out('')
self.out(f'=== Result: {self.errors} errors, {self.warnings} warnings ({checks} checks) ===')
result = self.text()
result = self.text() print(result, end='' if '\r\n' in result else '\n')
print(result, end='')
if out_file: if out_file:
with open(out_file, 'w', encoding='utf-8-sig', newline='') as f: with open(out_file, 'w', encoding='utf-8-sig', newline='') as f:
@@ -139,6 +148,7 @@ def main():
description='Validate 1C configuration extension XML structure (CFE)', allow_abbrev=False description='Validate 1C configuration extension XML structure (CFE)', allow_abbrev=False
) )
parser.add_argument('-ExtensionPath', dest='ExtensionPath', required=True) parser.add_argument('-ExtensionPath', dest='ExtensionPath', required=True)
parser.add_argument('-Detailed', action='store_true')
parser.add_argument('-MaxErrors', dest='MaxErrors', type=int, default=30) parser.add_argument('-MaxErrors', dest='MaxErrors', type=int, default=30)
parser.add_argument('-OutFile', dest='OutFile', default='') parser.add_argument('-OutFile', dest='OutFile', default='')
args = parser.parse_args() args = parser.parse_args()
@@ -169,7 +179,7 @@ def main():
if out_file and not os.path.isabs(out_file): if out_file and not os.path.isabs(out_file):
out_file = os.path.join(os.getcwd(), out_file) out_file = os.path.join(os.getcwd(), out_file)
r = Reporter(max_errors) r = Reporter(max_errors, detailed=args.Detailed)
r.out('') r.out('')
# --- 1. Parse XML --- # --- 1. Parse XML ---
@@ -233,6 +243,7 @@ def main():
props_node = cfg_node.find('md:Properties', NS) props_node = cfg_node.find('md:Properties', NS)
name_node = props_node.find('md:Name', NS) if props_node is not None else None name_node = props_node.find('md:Name', NS) if props_node is not None else None
obj_name = (name_node.text or '') if name_node is not None and name_node.text else '(unknown)' obj_name = (name_node.text or '') if name_node is not None and name_node.text else '(unknown)'
r.obj_name = obj_name
r.lines.insert(0, f'=== Validation: Extension.{obj_name} ===') r.lines.insert(0, f'=== Validation: Extension.{obj_name} ===')
@@ -512,7 +523,7 @@ def main():
for md in missing_dirs: for md in missing_dirs:
r.warn(f'8. Missing directory: {md}') r.warn(f'8. Missing directory: {md}')
else: else:
r.ok('8. Object directories: N/A') pass # no ChildObjects
if r.stopped: if r.stopped:
r.finalize(out_file) r.finalize(out_file)
@@ -583,7 +594,7 @@ def main():
break break
if borrowed_count == 0: if borrowed_count == 0:
r.ok('9. Borrowed objects: none found') pass # no borrowed objects
elif check9_ok: elif check9_ok:
r.ok(f'9. Borrowed objects: {borrowed_ok_count}/{borrowed_count} validated') r.ok(f'9. Borrowed objects: {borrowed_ok_count}/{borrowed_count} validated')
+12 -54
View File
@@ -1,7 +1,7 @@
--- ---
name: epf-validate name: epf-validate
description: Валидация внешней обработки 1С (EPF). Используй после создания или модификации обработки для проверки корректности description: Валидация внешней обработки 1С (EPF). Используй после создания или модификации обработки для проверки корректности
argument-hint: <ObjectPath> [-MaxErrors 30] argument-hint: <ObjectPath> [-Detailed] [-MaxErrors 30]
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
@@ -10,33 +10,25 @@ allowed-tools:
# /epf-validate — валидация внешней обработки (EPF) # /epf-validate — валидация внешней обработки (EPF)
Проверяет структурную корректность XML-исходников внешней обработки: корневую структуру, InternalInfo, свойства, ChildObjects, реквизиты, табличные части, уникальность имён, наличие файлов форм и макетов. Проверяет структурную корректность XML-исходников внешней обработки: корневую структуру, InternalInfo, свойства, ChildObjects, реквизиты, табличные части, уникальность имён, наличие файлов форм и макетов. Также работает для внешних отчётов (ERF).
Скрипт также работает для внешних отчётов (ERF) — автоопределение по типу элемента. См. `/erf-validate`.
## Использование
```
/epf-validate <ObjectPath>
```
## Параметры ## Параметры
| Параметр | Обязательный | По умолчанию | Описание | | Параметр | Обяз. | Умолч. | Описание |
|------------|:------------:|--------------|-------------------------------------------------| |------------|:-----:|---------|-------------------------------------------------|
| ObjectPath | да | — | Путь к корневому XML или каталогу обработки | | ObjectPath | да | — | Путь к корневому XML или каталогу обработки |
| MaxErrors | нет | 30 | Остановиться после N ошибок | | Detailed | нет | — | Показывать [OK] для каждой проверки |
| OutFile | нет | — | Записать результат в файл (UTF-8 BOM) | | MaxErrors | нет | 30 | Остановиться после N ошибок |
| OutFile | нет | — | Записать результат в файл (UTF-8 BOM) |
`ObjectPath` авторезолв: если указана директория — ищет `<dirName>/<dirName>.xml`.
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File .claude/skills/epf-validate/scripts/epf-validate.ps1 -ObjectPath "<путь>" powershell.exe -NoProfile -File .claude/skills/epf-validate/scripts/epf-validate.ps1 -ObjectPath "src/МояОбработка"
powershell.exe -NoProfile -File .claude/skills/epf-validate/scripts/epf-validate.ps1 -ObjectPath "src/МояОбработка/МояОбработка.xml"
``` ```
## Выполняемые проверки ## Проверки
| # | Проверка | Серьёзность | | # | Проверка | Серьёзность |
|----|-------------------------------------------------------|--------------| |----|-------------------------------------------------------|--------------|
@@ -51,38 +43,4 @@ powershell.exe -NoProfile -File .claude/skills/epf-validate/scripts/epf-validate
| 9 | Файлы: формы (.xml + Ext/Form.xml), макеты | ERROR | | 9 | Файлы: формы (.xml + Ext/Form.xml), макеты | ERROR |
| 10 | Дескрипторы форм: корневая структура, uuid, Name, FormType | ERROR / WARN | | 10 | Дескрипторы форм: корневая структура, uuid, Name, FormType | ERROR / WARN |
## Вывод Exit code: 0 = OK, 1 = есть ошибки. По умолчанию краткий вывод. `-Detailed` для поштучной детализации.
```
=== Validation: EPF.МояОбработка ===
[OK] 1. Root structure: MetaDataObject/ExternalDataProcessor, version 2.17
[OK] 2. InternalInfo: ClassId correct, 1 GeneratedType
[OK] 3. Properties: Name="МояОбработка", Synonym present, DefaultForm set
[OK] 4. ChildObjects: Attribute(3), TabularSection(1), Form(1)
[OK] 5. Cross-references: DefaultForm valid
[OK] 6. Attributes: 3 checked (UUID, Name, Type)
[OK] 7. TabularSections: 1 sections, 5 inner attributes
[OK] 8. Name uniqueness: 6 names, all unique
[OK] 9. File existence: 3 files verified
[OK] 10. Form descriptors: 1 checked
=== Result: 0 errors, 0 warnings ===
```
Код возврата: 0 = все проверки пройдены, 1 = есть ошибки.
## Верификация
```
/epf-init <Name> — создать обработку
/epf-validate src/<Name>.xml — проверить результат
/epf-build <Name> — собрать EPF
```
## Когда использовать
- **После `/epf-init`**: проверить scaffold
- **После добавления формы/макета**: убедиться что ChildObjects, файлы и ссылки корректны
- **После ручного редактирования XML**: выявить структурные ошибки до сборки
- **При отладке сборки**: найти причину ошибки Designer
@@ -1,10 +1,12 @@
# epf-validate v1.0 — Validate 1C external data processor / report structure # epf-validate v1.1 — Validate 1C external data processor / report structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
# Works for both EPF (ExternalDataProcessor) and ERF (ExternalReport) — auto-detects # Works for both EPF (ExternalDataProcessor) and ERF (ExternalReport) — auto-detects
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
[string]$ObjectPath, [string]$ObjectPath,
[switch]$Detailed,
[int]$MaxErrors = 30, [int]$MaxErrors = 30,
[string]$OutFile [string]$OutFile
@@ -60,6 +62,7 @@ $srcDir = Split-Path $resolvedPath -Parent
$script:errors = 0 $script:errors = 0
$script:warnings = 0 $script:warnings = 0
$script:okCount = 0
$script:stopped = $false $script:stopped = $false
$script:output = New-Object System.Text.StringBuilder 8192 $script:output = New-Object System.Text.StringBuilder 8192
@@ -70,7 +73,8 @@ function Out-Line {
function Report-OK { function Report-OK {
param([string]$msg) param([string]$msg)
Out-Line "[OK] $msg" $script:okCount++
if ($Detailed) { Out-Line "[OK] $msg" }
} }
function Report-Error { function Report-Error {
@@ -89,10 +93,14 @@ function Report-Warn {
} }
$finalize = { $finalize = {
Out-Line "" $checks = $script:okCount + $script:errors + $script:warnings
Out-Line "=== Result: $($script:errors) errors, $($script:warnings) warnings ===" if ($script:errors -eq 0 -and $script:warnings -eq 0 -and -not $Detailed) {
$result = "=== Validation OK: $shortType.$objName ($checks checks) ==="
$result = $script:output.ToString() } else {
Out-Line ""
Out-Line "=== Result: $($script:errors) errors, $($script:warnings) warnings ($checks checks) ==="
$result = $script:output.ToString()
}
Write-Host $result Write-Host $result
if ($OutFile) { if ($OutFile) {
@@ -554,8 +562,6 @@ if ($childObjNode) {
} else { } else {
Report-OK "6. Attributes: none" Report-OK "6. Attributes: none"
} }
} else {
Report-OK "6. Attributes: N/A"
} }
if ($script:stopped) { & $finalize; exit 1 } if ($script:stopped) { & $finalize; exit 1 }
@@ -631,8 +637,6 @@ if ($childObjNode) {
} else { } else {
Report-OK "7. TabularSections: none" Report-OK "7. TabularSections: none"
} }
} else {
Report-OK "7. TabularSections: N/A"
} }
if ($script:stopped) { & $finalize; exit 1 } if ($script:stopped) { & $finalize; exit 1 }
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# epf-validate v1.0 — Validate 1C external data processor / report structure # epf-validate v1.1 — Validate 1C external data processor / report structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
# Works for both EPF (ExternalDataProcessor) and ERF (ExternalReport) — auto-detects # Works for both EPF (ExternalDataProcessor) and ERF (ExternalReport) — auto-detects
@@ -47,6 +47,7 @@ def main():
sys.stderr.reconfigure(encoding="utf-8") sys.stderr.reconfigure(encoding="utf-8")
parser = argparse.ArgumentParser(description="Validate 1C external data processor/report structure", allow_abbrev=False) parser = argparse.ArgumentParser(description="Validate 1C external data processor/report structure", allow_abbrev=False)
parser.add_argument("-ObjectPath", required=True) parser.add_argument("-ObjectPath", required=True)
parser.add_argument("-Detailed", action="store_true")
parser.add_argument("-MaxErrors", type=int, default=30) parser.add_argument("-MaxErrors", type=int, default=30)
parser.add_argument("-OutFile", default=None) parser.add_argument("-OutFile", default=None)
args = parser.parse_args() args = parser.parse_args()
@@ -91,8 +92,10 @@ def main():
src_dir = os.path.dirname(resolved_path) src_dir = os.path.dirname(resolved_path)
# --- Output infrastructure --- # --- Output infrastructure ---
detailed = args.Detailed
errors = 0 errors = 0
warnings = 0 warnings = 0
ok_count = 0
stopped = False stopped = False
output_lines = [] output_lines = []
@@ -100,7 +103,10 @@ def main():
output_lines.append(msg) output_lines.append(msg)
def report_ok(msg): def report_ok(msg):
out_line(f"[OK] {msg}") nonlocal ok_count
ok_count += 1
if detailed:
out_line(f"[OK] {msg}")
def report_error(msg): def report_error(msg):
nonlocal errors, stopped nonlocal errors, stopped
@@ -115,9 +121,13 @@ def main():
out_line(f"[WARN] {msg}") out_line(f"[WARN] {msg}")
def finalize(): def finalize():
out_line("") checks = ok_count + errors + warnings
out_line(f"=== Result: {errors} errors, {warnings} warnings ===") if errors == 0 and warnings == 0 and not detailed:
result = "\n".join(output_lines) result = f"=== Validation OK: {short_type}.{obj_name} ({checks} checks) ==="
else:
out_line("")
out_line(f"=== Result: {errors} errors, {warnings} warnings ({checks} checks) ===")
result = "\n".join(output_lines)
print(result) print(result)
if args.OutFile: if args.OutFile:
with open(args.OutFile, "w", encoding="utf-8-sig") as fh: with open(args.OutFile, "w", encoding="utf-8-sig") as fh:
@@ -352,7 +362,7 @@ def main():
else: else:
report_ok("4. ChildObjects: empty") report_ok("4. ChildObjects: empty")
else: else:
report_ok("4. ChildObjects: absent") pass # no ChildObjects — nothing to check
if stopped: if stopped:
finalize() finalize()
@@ -400,7 +410,7 @@ def main():
if refs: if refs:
report_ok(f"5. Cross-references: {', '.join(refs)} valid") report_ok(f"5. Cross-references: {', '.join(refs)} valid")
else: else:
report_ok("5. Cross-references: none to check") pass # no cross-references to check
if stopped: if stopped:
finalize() finalize()
@@ -461,9 +471,9 @@ def main():
if check6_ok: if check6_ok:
report_ok(f"6. Attributes: {attr_count} checked (UUID, Name, Type)") report_ok(f"6. Attributes: {attr_count} checked (UUID, Name, Type)")
else: else:
report_ok("6. Attributes: none") pass # no attributes
else: else:
report_ok("6. Attributes: N/A") pass # no ChildObjects
if stopped: if stopped:
finalize() finalize()
@@ -527,9 +537,9 @@ def main():
if check7_ok: if check7_ok:
report_ok(f"7. TabularSections: {ts_count} sections, {ts_attr_total} inner attributes") report_ok(f"7. TabularSections: {ts_count} sections, {ts_attr_total} inner attributes")
else: else:
report_ok("7. TabularSections: none") pass # no tabular sections
else: else:
report_ok("7. TabularSections: N/A") pass # no ChildObjects
if stopped: if stopped:
finalize() finalize()
@@ -628,7 +638,7 @@ def main():
if files_checked > 0: if files_checked > 0:
report_ok(f"9. File existence: {files_checked} files verified") report_ok(f"9. File existence: {files_checked} files verified")
else: else:
report_ok("9. File existence: no forms/templates to check") pass # no forms/templates to check
if stopped: if stopped:
finalize() finalize()
@@ -684,7 +694,7 @@ def main():
if forms_checked > 0: if forms_checked > 0:
report_ok(f"10. Form descriptors: {forms_checked} checked") report_ok(f"10. Form descriptors: {forms_checked} checked")
else: else:
report_ok("10. Form descriptors: none to check") pass # no form descriptors to check
# --- Final output --- # --- Final output ---
finalize() finalize()
+11 -51
View File
@@ -1,7 +1,7 @@
--- ---
name: erf-validate name: erf-validate
description: Валидация внешнего отчёта 1С (ERF). Используй после создания или модификации отчёта для проверки корректности description: Валидация внешнего отчёта 1С (ERF). Используй после создания или модификации отчёта для проверки корректности
argument-hint: <ObjectPath> [-MaxErrors 30] argument-hint: <ObjectPath> [-Detailed] [-MaxErrors 30]
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
@@ -14,29 +14,23 @@ allowed-tools:
Использует тот же скрипт, что и `/epf-validate` — автоопределение по типу элемента (ExternalReport). Использует тот же скрипт, что и `/epf-validate` — автоопределение по типу элемента (ExternalReport).
## Использование
```
/erf-validate <ObjectPath>
```
## Параметры ## Параметры
| Параметр | Обязательный | По умолчанию | Описание | | Параметр | Обяз. | Умолч. | Описание |
|------------|:------------:|--------------|-------------------------------------------------| |------------|:-----:|---------|-------------------------------------------------|
| ObjectPath | да | — | Путь к корневому XML или каталогу отчёта | | ObjectPath | да | — | Путь к корневому XML или каталогу отчёта |
| MaxErrors | нет | 30 | Остановиться после N ошибок | | Detailed | нет | — | Показывать [OK] для каждой проверки |
| OutFile | нет | — | Записать результат в файл (UTF-8 BOM) | | MaxErrors | нет | 30 | Остановиться после N ошибок |
| OutFile | нет | — | Записать результат в файл (UTF-8 BOM) |
`ObjectPath` авторезолв: если указана директория — ищет `<dirName>/<dirName>.xml`.
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File .claude/skills/epf-validate/scripts/epf-validate.ps1 -ObjectPath "<путь>" powershell.exe -NoProfile -File .claude/skills/epf-validate/scripts/epf-validate.ps1 -ObjectPath "src/МойОтчёт"
powershell.exe -NoProfile -File .claude/skills/epf-validate/scripts/epf-validate.ps1 -ObjectPath "src/МойОтчёт/МойОтчёт.xml"
``` ```
## Выполняемые проверки ## Проверки
| # | Проверка | Серьёзность | | # | Проверка | Серьёзность |
|----|-------------------------------------------------------|--------------| |----|-------------------------------------------------------|--------------|
@@ -51,38 +45,4 @@ powershell.exe -NoProfile -File .claude/skills/epf-validate/scripts/epf-validate
| 9 | Файлы: формы (.xml + Ext/Form.xml), макеты | ERROR | | 9 | Файлы: формы (.xml + Ext/Form.xml), макеты | ERROR |
| 10 | Дескрипторы форм: корневая структура, uuid, Name, FormType | ERROR / WARN | | 10 | Дескрипторы форм: корневая структура, uuid, Name, FormType | ERROR / WARN |
## Вывод Exit code: 0 = OK, 1 = есть ошибки. По умолчанию краткий вывод. `-Detailed` для поштучной детализации.
```
=== Validation: ERF.МойОтчёт ===
[OK] 1. Root structure: MetaDataObject/ExternalReport, version 2.17
[OK] 2. InternalInfo: ClassId correct, 1 GeneratedType
[OK] 3. Properties: Name="МойОтчёт", Synonym present, MainDCS set
[OK] 4. ChildObjects: Form(1), Template(1)
[OK] 5. Cross-references: DefaultForm, MainDCS valid
[OK] 6. Attributes: none
[OK] 7. TabularSections: none
[OK] 8. Name uniqueness: 2 names, all unique
[OK] 9. File existence: 4 files verified
[OK] 10. Form descriptors: 1 checked
=== Result: 0 errors, 0 warnings ===
```
Код возврата: 0 = все проверки пройдены, 1 = есть ошибки.
## Верификация
```
/erf-init <Name> --with-skd — создать отчёт с СКД
/erf-validate src/<Name>.xml — проверить результат
/erf-build <Name> — собрать ERF
```
## Когда использовать
- **После `/erf-init`**: проверить scaffold
- **После добавления формы/макета/СКД**: убедиться что ChildObjects и MainDCS корректны
- **После ручного редактирования XML**: выявить структурные ошибки до сборки
- **При отладке сборки**: найти причину ошибки Designer
+13 -55
View File
@@ -1,37 +1,33 @@
--- ---
name: form-validate name: form-validate
description: Валидация управляемой формы 1С. Используй после создания или модификации формы для проверки корректности description: Валидация управляемой формы 1С. Используй после создания или модификации формы для проверки корректности. При наличии BaseForm автоматически проверяет callType и ID расширений
argument-hint: <FormPath> argument-hint: <FormPath> [-Detailed] [-MaxErrors 30]
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
- Glob - Glob
--- ---
# /form-validate — Валидатор формы # /form-validate — валидация управляемой формы 1С
Проверяет Form.xml управляемой формы на структурные ошибки: уникальность ID, наличие companion-элементов, корректность ссылок DataPath и команд. Проверяет Form.xml на структурные ошибки: уникальность ID, наличие companion-элементов, корректность ссылок DataPath и команд.
## Использование
```
/form-validate <FormPath>
```
## Параметры ## Параметры
| Параметр | Обязательный | По умолчанию | Описание | | Параметр | Обяз. | Умолч. | Описание |
|-----------|:------------:|--------------|-----------------------------| |-----------|:-----:|---------|-----------------------------------------|
| FormPath | да | — | Путь к файлу Form.xml | | FormPath | да | — | Путь к файлу Form.xml |
| MaxErrors | нет | 30 | Остановиться после N ошибок | | Detailed | нет | — | Показывать [OK] для каждой проверки |
| MaxErrors | нет | 30 | Остановиться после N ошибок |
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File .claude/skills/form-validate/scripts/form-validate.ps1 -FormPath "<путь>" powershell.exe -NoProfile -File .claude/skills/form-validate/scripts/form-validate.ps1 -FormPath "Catalogs/Номенклатура/Forms/ФормаЭлемента"
powershell.exe -NoProfile -File .claude/skills/form-validate/scripts/form-validate.ps1 -FormPath "src/МояОбработка/Forms/Форма/Ext/Form.xml"
``` ```
## Выполняемые проверки ## Проверки
| # | Проверка | Серьёзность | | # | Проверка | Серьёзность |
|---|---|---| |---|---|---|
@@ -51,42 +47,4 @@ powershell.exe -NoProfile -File .claude/skills/form-validate/scripts/form-valida
| 14 | ID расширения >= 1000000 для добавленных attrs/commands | WARN | | 14 | ID расширения >= 1000000 для добавленных attrs/commands | WARN |
| 15 | callType без BaseForm — некорректная структура | WARN | | 15 | callType без BaseForm — некорректная структура | WARN |
## Вывод Exit code: 0 = OK, 1 = есть ошибки. По умолчанию краткий вывод. `-Detailed` для поштучной детализации.
```
=== Validation: ФормаДокумента ===
[OK] Root element: Form version=2.17
[OK] AutoCommandBar: name='ФормаКоманднаяПанель', id=-1
[OK] Unique element IDs: 96 elements
[OK] Unique attribute IDs: 38 entries
[OK] Unique command IDs: 5 entries
[OK] Companion elements: 86 elements checked
[OK] DataPath references: 53 paths checked
[OK] Command references: 2 buttons checked
[OK] Event handlers: 41 events checked
[OK] Command actions: 5 commands checked
[OK] MainAttribute: 1 main attribute
---
Total: 96 elements, 38 attributes, 5 commands
All checks passed.
```
Код возврата: 0 = все проверки пройдены, 1 = есть ошибки.
### Расширения
При обнаружении `<BaseForm>` автоматически активируются дополнительные проверки:
- Валидность значений `callType` (Before/After/Override)
- ID расширения >= 1000000 для добавленных атрибутов и команд
- Наличие version на `<BaseForm>`
Формы без `<BaseForm>` проверяются только стандартными проверками.
## Когда использовать
- **После `/form-compile`**: проверить корректность сгенерированной формы
- **После `/form-edit`**: проверить добавленные элементы, особенно в extension-формах
- **После ручного редактирования Form.xml**: убедиться что ID уникальны, companions на месте, ссылки валидны
- **При отладке**: выявить ошибки в структуре формы до сборки EPF
@@ -1,15 +1,38 @@
# form-validate v1.0 — Validate 1C managed form # form-validate v1.1 — Validate 1C managed form
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
[string]$FormPath, [string]$FormPath,
[switch]$Detailed,
[int]$MaxErrors = 30 [int]$MaxErrors = 30
) )
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Resolve path ---
# A: Directory → Ext/Form.xml
if (Test-Path $FormPath -PathType Container) {
$FormPath = Join-Path (Join-Path $FormPath "Ext") "Form.xml"
}
# B1: Missing Ext/ (e.g. Forms/Форма/Form.xml → Forms/Форма/Ext/Form.xml)
if (-not (Test-Path $FormPath)) {
$fn = [System.IO.Path]::GetFileName($FormPath)
if ($fn -eq "Form.xml") {
$c = Join-Path (Join-Path (Split-Path $FormPath) "Ext") $fn
if (Test-Path $c) { $FormPath = $c }
}
}
# B2: Descriptor (Forms/Форма.xml → Forms/Форма/Ext/Form.xml)
if (-not (Test-Path $FormPath) -and $FormPath.EndsWith(".xml")) {
$stem = [System.IO.Path]::GetFileNameWithoutExtension($FormPath)
$dir = Split-Path $FormPath
$c = Join-Path (Join-Path (Join-Path $dir $stem) "Ext") "Form.xml"
if (Test-Path $c) { $FormPath = $c }
}
# --- Load XML --- # --- Load XML ---
if (-not (Test-Path $FormPath)) { if (-not (Test-Path $FormPath)) {
@@ -40,10 +63,12 @@ $root = $xmlDoc.DocumentElement
$errors = 0 $errors = 0
$warnings = 0 $warnings = 0
$stopped = $false $stopped = $false
$script:okCount = 0
function Report-OK { function Report-OK {
param([string]$msg) param([string]$msg)
Write-Host "[OK] $msg" $script:okCount++
if ($Detailed) { Write-Host "[OK] $msg" }
} }
function Report-Error { function Report-Error {
@@ -73,8 +98,10 @@ if ($parentDir) {
} }
} }
Write-Host "=== Validation: $formName ===" if ($Detailed) {
Write-Host "" Write-Host "=== Validation: $formName ==="
Write-Host ""
}
# Early BaseForm detection (used in Check 5 to skip base element DataPath validation) # Early BaseForm detection (used in Check 5 to skip base element DataPath validation)
$hasBaseForm = ($root.SelectSingleNode("f:BaseForm", $nsMgr) -ne $null) $hasBaseForm = ($root.SelectSingleNode("f:BaseForm", $nsMgr) -ne $null)
@@ -642,18 +669,22 @@ if (-not $stopped -and -not $isExtension) {
# --- Summary --- # --- Summary ---
Write-Host "" $checks = $script:okCount + $errors + $warnings
Write-Host "---"
Write-Host "Total: $($allElements.Count) elements, $($attrNodes.Count) attributes, $($cmdNodes.Count) commands"
if ($stopped) { if ($errors -eq 0 -and $warnings -eq 0 -and -not $Detailed) {
Write-Host "Stopped after $MaxErrors errors. Fix and re-run." Write-Host "=== Validation OK: Form.$formName ($checks checks) ==="
}
if ($errors -eq 0 -and $warnings -eq 0) {
Write-Host "All checks passed."
} else { } else {
Write-Host "Errors: $errors, Warnings: $warnings" Write-Host ""
if ($Detailed) {
Write-Host "---"
Write-Host "Total: $($allElements.Count) elements, $($attrNodes.Count) attributes, $($cmdNodes.Count) commands"
}
if ($stopped) {
Write-Host "Stopped after $MaxErrors errors. Fix and re-run."
}
Write-Host "=== Result: $errors errors, $warnings warnings ($checks checks) ==="
} }
if ($errors -gt 0) { if ($errors -gt 0) {
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# form-validate v1.0 — Validate 1C managed form # form-validate v1.1 — Validate 1C managed form
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
@@ -23,12 +23,35 @@ def main():
sys.stderr.reconfigure(encoding="utf-8") sys.stderr.reconfigure(encoding="utf-8")
parser = argparse.ArgumentParser(description="Validate 1C managed form", allow_abbrev=False) parser = argparse.ArgumentParser(description="Validate 1C managed form", allow_abbrev=False)
parser.add_argument("-FormPath", required=True) parser.add_argument("-FormPath", required=True)
parser.add_argument("-Detailed", action="store_true")
parser.add_argument("-MaxErrors", type=int, default=30) parser.add_argument("-MaxErrors", type=int, default=30)
args = parser.parse_args() args = parser.parse_args()
form_path = args.FormPath form_path = args.FormPath
detailed = args.Detailed
max_errors = args.MaxErrors max_errors = args.MaxErrors
if not os.path.isabs(form_path):
form_path = os.path.join(os.getcwd(), form_path)
# A: Directory → Ext/Form.xml
if os.path.isdir(form_path):
form_path = os.path.join(form_path, 'Ext', 'Form.xml')
# B1: Missing Ext/ (e.g. Forms/Форма/Form.xml → Forms/Форма/Ext/Form.xml)
if not os.path.exists(form_path):
fn = os.path.basename(form_path)
if fn == 'Form.xml':
c = os.path.join(os.path.dirname(form_path), 'Ext', fn)
if os.path.exists(c):
form_path = c
# B2: Descriptor (Forms/Форма.xml → Forms/Форма/Ext/Form.xml)
if not os.path.exists(form_path) and form_path.endswith('.xml'):
stem = os.path.splitext(os.path.basename(form_path))[0]
parent = os.path.dirname(form_path)
c = os.path.join(parent, stem, 'Ext', 'Form.xml')
if os.path.exists(c):
form_path = c
if not os.path.isfile(form_path): if not os.path.isfile(form_path):
print(f"File not found: {form_path}", file=sys.stderr) print(f"File not found: {form_path}", file=sys.stderr)
sys.exit(1) sys.exit(1)
@@ -48,22 +71,27 @@ def main():
errors = 0 errors = 0
warnings = 0 warnings = 0
ok_count = 0
stopped = False stopped = False
output_lines = []
def report_ok(msg): def report_ok(msg):
print(f"[OK] {msg}") nonlocal ok_count
ok_count += 1
if detailed:
output_lines.append(f"[OK] {msg}")
def report_error(msg): def report_error(msg):
nonlocal errors, stopped nonlocal errors, stopped
errors += 1 errors += 1
print(f"[ERROR] {msg}") output_lines.append(f"[ERROR] {msg}")
if errors >= max_errors: if errors >= max_errors:
stopped = True stopped = True
def report_warn(msg): def report_warn(msg):
nonlocal warnings nonlocal warnings
warnings += 1 warnings += 1
print(f"[WARN] {msg}") output_lines.append(f"[WARN] {msg}")
# --- Form name from path --- # --- Form name from path ---
form_name = os.path.splitext(os.path.basename(form_path))[0] form_name = os.path.splitext(os.path.basename(form_path))[0]
@@ -75,8 +103,8 @@ def main():
if form_dir: if form_dir:
form_name = os.path.basename(form_dir) form_name = os.path.basename(form_dir)
print(f"=== Validation: {form_name} ===") output_lines.append(f"=== Validation: Form.{form_name} ===")
print() output_lines.append("")
# Early BaseForm detection # Early BaseForm detection
has_base_form = root.find(f"{{{F_NS}}}BaseForm") is not None has_base_form = root.find(f"{{{F_NS}}}BaseForm") is not None
@@ -319,8 +347,6 @@ def main():
path_msg = f"{path_msg}, {skip_note}" if path_msg else skip_note path_msg = f"{path_msg}, {skip_note}" if path_msg else skip_note
if path_errors == 0 and path_msg: if path_errors == 0 and path_msg:
report_ok(f"DataPath references: {path_msg}") report_ok(f"DataPath references: {path_msg}")
elif path_errors == 0:
report_ok("DataPath references: none")
# --- Check 6: Button command references --- # --- Check 6: Button command references ---
if not stopped: if not stopped:
@@ -355,8 +381,6 @@ def main():
if cmd_errors == 0 and cmd_checked > 0: if cmd_errors == 0 and cmd_checked > 0:
report_ok(f"Command references: {cmd_checked} buttons checked") report_ok(f"Command references: {cmd_checked} buttons checked")
elif cmd_checked == 0:
report_ok("Command references: none")
# --- Check 7: Events have handler names --- # --- Check 7: Events have handler names ---
if not stopped: if not stopped:
@@ -396,8 +420,6 @@ def main():
if event_errors == 0 and event_checked > 0: if event_errors == 0 and event_checked > 0:
report_ok(f"Event handlers: {event_checked} events checked") report_ok(f"Event handlers: {event_checked} events checked")
elif event_checked == 0:
report_ok("Event handlers: none")
# --- Check 8: Command actions --- # --- Check 8: Command actions ---
if not stopped: if not stopped:
@@ -416,8 +438,6 @@ def main():
if action_errors == 0 and action_checked > 0: if action_errors == 0 and action_checked > 0:
report_ok(f"Command actions: {action_checked} commands checked") report_ok(f"Command actions: {action_checked} commands checked")
elif action_checked == 0:
report_ok("Command actions: none")
# --- Check 9: MainAttribute count --- # --- Check 9: MainAttribute count ---
if not stopped: if not stopped:
@@ -568,18 +588,16 @@ def main():
if call_type_without_base: if call_type_without_base:
report_warn("callType attributes found but no BaseForm \u2014 possible incorrect structure") report_warn("callType attributes found but no BaseForm \u2014 possible incorrect structure")
# --- Summary --- # --- Finalize ---
print() checks = ok_count + errors + warnings
print("---") if errors == 0 and warnings == 0 and not detailed:
print(f"Total: {len(all_elements)} elements, {len(attr_nodes)} attributes, {len(cmd_nodes)} commands") result = f"=== Validation OK: Form.{form_name} ({checks} checks) ==="
if stopped:
print(f"Stopped after {max_errors} errors. Fix and re-run.")
if errors == 0 and warnings == 0:
print("All checks passed.")
else: else:
print(f"Errors: {errors}, Warnings: {warnings}") output_lines.append("")
output_lines.append(f"=== Result: {errors} errors, {warnings} warnings ({checks} checks) ===")
result = "\n".join(output_lines)
print(result)
if errors > 0: if errors > 0:
sys.exit(1) sys.exit(1)
+11 -44
View File
@@ -1,7 +1,7 @@
--- ---
name: interface-validate name: interface-validate
description: Валидация командного интерфейса 1С. Используй после настройки командного интерфейса подсистемы для проверки корректности description: Валидация командного интерфейса 1С. Используй после настройки командного интерфейса подсистемы для проверки корректности
argument-hint: <CIPath> [-MaxErrors 30] argument-hint: <CIPath> [-Detailed] [-MaxErrors 30]
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
@@ -10,20 +10,22 @@ allowed-tools:
# /interface-validate — валидация CommandInterface.xml # /interface-validate — валидация CommandInterface.xml
Проверяет XML командного интерфейса из выгрузки конфигурации на структурные ошибки: корневой элемент, допустимые секции, порядок, формат ссылок на команды, дубликаты. Проверяет XML командного интерфейса на структурные ошибки: корневой элемент, допустимые секции, порядок, формат ссылок на команды, дубликаты.
## Параметры ## Параметры
| Параметр | Обязательный | По умолчанию | Описание | | Параметр | Обяз. | Умолч. | Описание |
|-----------|:------------:|--------------|------------------------------------| |-----------|:-----:|---------|-----------------------------------------|
| CIPath | да | — | Путь к CommandInterface.xml | | CIPath | да | — | Путь к CommandInterface.xml |
| MaxErrors | нет | 30 | Остановиться после N ошибок | | Detailed | нет | — | Показывать [OK] для каждой проверки |
| OutFile | нет | — | Записать результат в файл (UTF-8 BOM) | | MaxErrors | нет | 30 | Остановиться после N ошибок |
| OutFile | нет | — | Записать результат в файл (UTF-8 BOM) |
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File '.claude/skills/interface-validate/scripts/interface-validate.ps1' -CIPath '<path>' powershell.exe -NoProfile -File ".claude/skills/interface-validate/scripts/interface-validate.ps1" -CIPath "Subsystems/Продажи"
powershell.exe -NoProfile -File ".claude/skills/interface-validate/scripts/interface-validate.ps1" -CIPath "Subsystems/Продажи/Ext/CommandInterface.xml"
``` ```
## Проверки (13) ## Проверки (13)
@@ -44,39 +46,4 @@ powershell.exe -NoProfile -File '.claude/skills/interface-validate/scripts/inter
| 12 | GroupsOrder — нет дубликатов | WARN | | 12 | GroupsOrder — нет дубликатов | WARN |
| 13 | Формат ссылок на команды | WARN | | 13 | Формат ссылок на команды | WARN |
## Вывод Exit code: 0 = OK, 1 = есть ошибки. По умолчанию краткий вывод. `-Detailed` для поштучной детализации.
```
=== Validation: CommandInterface (Продажи) ===
[OK] 1. Root structure: CommandInterface, version 2.17, namespace valid
[OK] 2. Child elements: 5 valid sections
[OK] 3. Section order: correct
[OK] 4. No duplicate sections
[OK] 5. CommandsVisibility: 55 entries, all valid
[OK] 6. CommandsVisibility: no duplicates
[OK] 7. CommandsPlacement: 3 entries, all valid
[OK] 8. CommandsOrder: 12 entries, all valid
[OK] 9. SubsystemsOrder: 9 entries, all valid format
[OK] 10. SubsystemsOrder: no duplicates
[OK] 11. GroupsOrder: 7 entries, all valid
[OK] 12. GroupsOrder: no duplicates
[OK] 13. Command reference format: all valid
---
Errors: 0, Warnings: 0
```
Код возврата: 0 = все проверки пройдены, 1 = есть ошибки.
## Примеры
```powershell
# CommandInterface подсистемы
... -CIPath upload/acc_8.3.24/Subsystems/Продажи/Ext/CommandInterface.xml
# Корневой CommandInterface конфигурации
... -CIPath upload/acc_8.3.24/Ext/CommandInterface.xml
# С лимитом ошибок
... -CIPath <path> -MaxErrors 10
```
@@ -1,7 +1,8 @@
# interface-validate v1.0 — Validate 1C CommandInterface.xml structure # interface-validate v1.1 — Validate 1C CommandInterface.xml structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)][string]$CIPath, [Parameter(Mandatory)][string]$CIPath,
[switch]$Detailed,
[int]$MaxErrors = 30, [int]$MaxErrors = 30,
[string]$OutFile [string]$OutFile
) )
@@ -13,6 +14,18 @@ $ErrorActionPreference = "Stop"
if (-not [System.IO.Path]::IsPathRooted($CIPath)) { if (-not [System.IO.Path]::IsPathRooted($CIPath)) {
$CIPath = Join-Path (Get-Location).Path $CIPath $CIPath = Join-Path (Get-Location).Path $CIPath
} }
# A: Directory → Ext/CommandInterface.xml
if (Test-Path $CIPath -PathType Container) {
$CIPath = Join-Path (Join-Path $CIPath "Ext") "CommandInterface.xml"
}
# B1: Missing Ext/ (e.g. Subsystems/X/CommandInterface.xml → Subsystems/X/Ext/CommandInterface.xml)
if (-not (Test-Path $CIPath)) {
$fn = [System.IO.Path]::GetFileName($CIPath)
if ($fn -eq "CommandInterface.xml") {
$c = Join-Path (Join-Path (Split-Path $CIPath) "Ext") $fn
if (Test-Path $c) { $CIPath = $c }
}
}
if (-not (Test-Path $CIPath)) { if (-not (Test-Path $CIPath)) {
Write-Host "[ERROR] File not found: $CIPath" Write-Host "[ERROR] File not found: $CIPath"
exit 1 exit 1
@@ -33,11 +46,15 @@ if (-not $contextName) { $contextName = "Root" }
$script:errors = 0 $script:errors = 0
$script:warnings = 0 $script:warnings = 0
$script:stopped = $false $script:stopped = $false
$script:okCount = 0
$script:output = New-Object System.Text.StringBuilder 8192 $script:output = New-Object System.Text.StringBuilder 8192
$script:allCommandNames = @() $script:allCommandNames = @()
function Out-Line([string]$msg) { $script:output.AppendLine($msg) | Out-Null } function Out-Line([string]$msg) { $script:output.AppendLine($msg) | Out-Null }
function Report-OK([string]$msg) { Out-Line "[OK] $msg" } function Report-OK([string]$msg) {
$script:okCount++
if ($Detailed) { Out-Line "[OK] $msg" }
}
function Report-Error([string]$msg) { function Report-Error([string]$msg) {
$script:errors++ $script:errors++
Out-Line "[ERROR] $msg" Out-Line "[ERROR] $msg"
@@ -358,16 +375,20 @@ if (-not $script:stopped) {
$shown = $badRefs[0..([Math]::Min(4, $badRefs.Count - 1))] $shown = $badRefs[0..([Math]::Min(4, $badRefs.Count - 1))]
Report-Warn "13. Command reference format: $($badRefs.Count) unrecognized: $($shown -join ', ')$(if($badRefs.Count -gt 5){' ...'})" Report-Warn "13. Command reference format: $($badRefs.Count) unrecognized: $($shown -join ', ')$(if($badRefs.Count -gt 5){' ...'})"
} }
} else {
Report-OK "13. Command reference format: n/a (no commands)"
} }
} }
# --- Finalize --- # --- Finalize ---
Out-Line "---" $checks = $script:okCount + $script:errors + $script:warnings
Out-Line "Errors: $($script:errors), Warnings: $($script:warnings)"
if ($script:errors -eq 0 -and $script:warnings -eq 0 -and -not $Detailed) {
$result = "=== Validation OK: CommandInterface ($contextName) ($checks checks) ==="
} else {
Out-Line ""
Out-Line "=== Result: $($script:errors) errors, $($script:warnings) warnings ($checks checks) ==="
$result = $script:output.ToString()
}
$result = $script:output.ToString()
Write-Host $result Write-Host $result
if ($OutFile) { if ($OutFile) {
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# interface-validate v1.0 — Validate 1C CommandInterface.xml structure # interface-validate v1.1 — Validate 1C CommandInterface.xml structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
"""Validates CommandInterface.xml sections, command references, order, duplicates.""" """Validates CommandInterface.xml sections, command references, order, duplicates."""
import sys, os, argparse, re import sys, os, argparse, re
@@ -31,18 +31,22 @@ UUID_CMD_PATTERN = re.compile(
class Reporter: class Reporter:
def __init__(self, max_errors): def __init__(self, max_errors, detailed=False):
self.errors = 0 self.errors = 0
self.warnings = 0 self.warnings = 0
self.ok_count = 0
self.stopped = False self.stopped = False
self.max_errors = max_errors self.max_errors = max_errors
self.detailed = detailed
self.lines = [] self.lines = []
def out(self, msg=''): def out(self, msg=''):
self.lines.append(msg) self.lines.append(msg)
def ok(self, msg): def ok(self, msg):
self.lines.append(f'[OK] {msg}') self.ok_count += 1
if self.detailed:
self.lines.append(f'[OK] {msg}')
def error(self, msg): def error(self, msg):
self.errors += 1 self.errors += 1
@@ -76,11 +80,13 @@ def main():
description='Validate 1C CommandInterface.xml structure', allow_abbrev=False description='Validate 1C CommandInterface.xml structure', allow_abbrev=False
) )
parser.add_argument('-CIPath', dest='CIPath', required=True) parser.add_argument('-CIPath', dest='CIPath', required=True)
parser.add_argument('-Detailed', action='store_true')
parser.add_argument('-MaxErrors', dest='MaxErrors', type=int, default=30) parser.add_argument('-MaxErrors', dest='MaxErrors', type=int, default=30)
parser.add_argument('-OutFile', dest='OutFile', default='') parser.add_argument('-OutFile', dest='OutFile', default='')
args = parser.parse_args() args = parser.parse_args()
ci_path = args.CIPath ci_path = args.CIPath
detailed = args.Detailed
max_errors = args.MaxErrors max_errors = args.MaxErrors
out_file = args.OutFile out_file = args.OutFile
@@ -88,6 +94,17 @@ def main():
if not os.path.isabs(ci_path): if not os.path.isabs(ci_path):
ci_path = os.path.join(os.getcwd(), ci_path) ci_path = os.path.join(os.getcwd(), ci_path)
# A: Directory → Ext/CommandInterface.xml
if os.path.isdir(ci_path):
ci_path = os.path.join(ci_path, 'Ext', 'CommandInterface.xml')
# B1: Missing Ext/
if not os.path.exists(ci_path):
fn = os.path.basename(ci_path)
if fn == 'CommandInterface.xml':
c = os.path.join(os.path.dirname(ci_path), 'Ext', fn)
if os.path.exists(c):
ci_path = c
if not os.path.exists(ci_path): if not os.path.exists(ci_path):
print(f'[ERROR] File not found: {ci_path}') print(f'[ERROR] File not found: {ci_path}')
sys.exit(1) sys.exit(1)
@@ -103,7 +120,7 @@ def main():
if not context_name: if not context_name:
context_name = 'Root' context_name = 'Root'
r = Reporter(max_errors) r = Reporter(max_errors, detailed)
all_command_names = [] all_command_names = []
r.out(f'=== Validation: CommandInterface ({context_name}) ===') r.out(f'=== Validation: CommandInterface ({context_name}) ===')
@@ -210,8 +227,7 @@ def main():
vis_ok = False vis_ok = False
if vis_ok: if vis_ok:
r.ok(f'5. CommandsVisibility: {vis_count} entries, all valid') r.ok(f'5. CommandsVisibility: {vis_count} entries, all valid')
else: # CommandsVisibility not present — no check needed
r.ok('5. CommandsVisibility: not present')
# --- 6. CommandsVisibility duplicates --- # --- 6. CommandsVisibility duplicates ---
if not r.stopped: if not r.stopped:
@@ -221,8 +237,6 @@ def main():
r.warn(f'6. CommandsVisibility: duplicates: {", ".join(dupes)}') r.warn(f'6. CommandsVisibility: duplicates: {", ".join(dupes)}')
else: else:
r.ok('6. CommandsVisibility: no duplicates') r.ok('6. CommandsVisibility: no duplicates')
else:
r.ok('6. CommandsVisibility: no duplicates (empty)')
# --- 7. CommandsPlacement --- # --- 7. CommandsPlacement ---
if not r.stopped: if not r.stopped:
@@ -253,8 +267,7 @@ def main():
r.warn(f"7. CommandsPlacement[{cmd_name}]: Placement='{(placement_el.text or '').strip()}' (expected Auto)") r.warn(f"7. CommandsPlacement[{cmd_name}]: Placement='{(placement_el.text or '').strip()}' (expected Auto)")
if plc_ok: if plc_ok:
r.ok(f'7. CommandsPlacement: {plc_count} entries, all valid') r.ok(f'7. CommandsPlacement: {plc_count} entries, all valid')
else: # CommandsPlacement not present — no check needed
r.ok('7. CommandsPlacement: not present')
# --- 8. CommandsOrder --- # --- 8. CommandsOrder ---
if not r.stopped: if not r.stopped:
@@ -278,8 +291,7 @@ def main():
ord_ok = False ord_ok = False
if ord_ok: if ord_ok:
r.ok(f'8. CommandsOrder: {ord_count} entries, all valid') r.ok(f'8. CommandsOrder: {ord_count} entries, all valid')
else: # CommandsOrder not present — no check needed
r.ok('8. CommandsOrder: not present')
# --- 9. SubsystemsOrder format --- # --- 9. SubsystemsOrder format ---
sub_names = [] sub_names = []
@@ -302,8 +314,7 @@ def main():
sub_ok = False sub_ok = False
if sub_ok: if sub_ok:
r.ok(f'9. SubsystemsOrder: {sub_count} entries, all valid format') r.ok(f'9. SubsystemsOrder: {sub_count} entries, all valid format')
else: # SubsystemsOrder not present — no check needed
r.ok('9. SubsystemsOrder: not present')
# --- 10. SubsystemsOrder duplicates --- # --- 10. SubsystemsOrder duplicates ---
if not r.stopped: if not r.stopped:
@@ -313,8 +324,6 @@ def main():
r.warn(f'10. SubsystemsOrder: duplicates: {", ".join(dupes)}') r.warn(f'10. SubsystemsOrder: duplicates: {", ".join(dupes)}')
else: else:
r.ok('10. SubsystemsOrder: no duplicates') r.ok('10. SubsystemsOrder: no duplicates')
else:
r.ok('10. SubsystemsOrder: no duplicates (empty)')
# --- 11. GroupsOrder entries --- # --- 11. GroupsOrder entries ---
grp_names = [] grp_names = []
@@ -334,8 +343,7 @@ def main():
grp_ok = False grp_ok = False
if grp_ok: if grp_ok:
r.ok(f'11. GroupsOrder: {grp_count} entries, all valid') r.ok(f'11. GroupsOrder: {grp_count} entries, all valid')
else: # GroupsOrder not present — no check needed
r.ok('11. GroupsOrder: not present')
# --- 12. GroupsOrder duplicates --- # --- 12. GroupsOrder duplicates ---
if not r.stopped: if not r.stopped:
@@ -345,8 +353,6 @@ def main():
r.warn(f'12. GroupsOrder: duplicates: {", ".join(dupes)}') r.warn(f'12. GroupsOrder: duplicates: {", ".join(dupes)}')
else: else:
r.ok('12. GroupsOrder: no duplicates') r.ok('12. GroupsOrder: no duplicates')
else:
r.ok('12. GroupsOrder: no duplicates (empty)')
# --- 13. Command reference format --- # --- 13. Command reference format ---
if not r.stopped: if not r.stopped:
@@ -368,14 +374,16 @@ def main():
shown = bad_refs[:5] shown = bad_refs[:5]
suffix = ' ...' if len(bad_refs) > 5 else '' suffix = ' ...' if len(bad_refs) > 5 else ''
r.warn(f'13. Command reference format: {len(bad_refs)} unrecognized: {", ".join(shown)}{suffix}') r.warn(f'13. Command reference format: {len(bad_refs)} unrecognized: {", ".join(shown)}{suffix}')
else:
r.ok('13. Command reference format: n/a (no commands)')
# --- Finalize --- # --- Finalize ---
r.out('---') checks = r.ok_count + r.errors + r.warnings
r.out(f'Errors: {r.errors}, Warnings: {r.warnings}') if r.errors == 0 and r.warnings == 0 and not detailed:
result = f'=== Validation OK: CommandInterface ({context_name}) ({checks} checks) ==='
else:
r.out('')
r.out(f'=== Result: {r.errors} errors, {r.warnings} warnings ({checks} checks) ===')
result = '\r\n'.join(r.lines) + '\r\n'
result = r.text()
print(result, end='') print(result, end='')
if out_file: if out_file:
+16 -75
View File
@@ -1,7 +1,7 @@
--- ---
name: meta-validate name: meta-validate
description: Валидация объекта метаданных 1С. Используй после создания или модификации объекта конфигурации для проверки корректности description: Валидация объекта метаданных 1С. Используй после создания или модификации объекта конфигурации для проверки корректности
argument-hint: <ObjectPath> [-MaxErrors 30] — pipe-separated paths for batch argument-hint: <ObjectPath> [-Detailed] [-MaxErrors 30] — pipe-separated paths for batch
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
@@ -10,31 +10,22 @@ allowed-tools:
# /meta-validate — валидация объекта метаданных 1С # /meta-validate — валидация объекта метаданных 1С
Проверяет XML объекта метаданных из выгрузки конфигурации на структурные ошибки: корневую структуру, InternalInfo, свойства, допустимые значения, StandardAttributes, ChildObjects, уникальность имён, табличные части, кросс-свойства, вложенные структуры HTTP/Web-сервисов. Проверяет XML объекта метаданных из выгрузки конфигурации на структурные ошибки.
## Использование
```
/meta-validate <ObjectPath>
/meta-validate path1.xml|path2.xml|path3.xml — batch mode
```
## Параметры ## Параметры
| Параметр | Обязательный | По умолчанию | Описание | | Параметр | Обяз. | Умолч. | Описание |
|------------|:------------:|--------------|-------------------------------------------------| |------------|:-----:|---------|-------------------------------------------------|
| ObjectPath | да | — | Путь к XML-файлу или каталогу объекта. Несколько путей через `\|` для batch | | ObjectPath | да | — | Путь к XML-файлу или каталогу. Через `\|` для batch |
| MaxErrors | нет | 30 | Остановиться после N ошибок (per object) | | Detailed | нет | — | Показывать [OK] для каждой проверки |
| OutFile | нет | — | Записать результат в файл (UTF-8 BOM) | | MaxErrors | нет | 30 | Остановиться после N ошибок (per object) |
| OutFile | нет | — | Записать результат в файл (UTF-8 BOM) |
`ObjectPath` авторезолв: если указана директория — ищет `<dirName>/<dirName>.xml`.
**Batch mode**: при нескольких путях через `|` каждый объект валидируется отдельно, в конце выводится сводка `=== Batch: N objects, X passed, Y failed ===`.
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File .claude/skills/meta-validate/scripts/meta-validate.ps1 -ObjectPath "<путь>" powershell.exe -NoProfile -File .claude/skills/meta-validate/scripts/meta-validate.ps1 -ObjectPath "Catalogs/Номенклатура/Номенклатура.xml"
powershell.exe -NoProfile -File .claude/skills/meta-validate/scripts/meta-validate.ps1 -ObjectPath "Catalogs/Банки|Documents/Заказ"
``` ```
## Поддерживаемые типы (23) ## Поддерживаемые типы (23)
@@ -45,7 +36,7 @@ powershell.exe -NoProfile -File .claude/skills/meta-validate/scripts/meta-valida
**Сервисные:** CommonModule, ScheduledJob, EventSubscription, HTTPService, WebService **Сервисные:** CommonModule, ScheduledJob, EventSubscription, HTTPService, WebService
**Прочие:** Constant, DocumentJournal, DefinedType **Прочие:** Constant, DocumentJournal, DefinedType
## Выполняемые проверки ## Проверки
| # | Проверка | Серьёзность | | # | Проверка | Серьёзность |
|----|------------------------------------------|--------------| |----|------------------------------------------|--------------|
@@ -56,63 +47,13 @@ powershell.exe -NoProfile -File .claude/skills/meta-validate/scripts/meta-valida
| 5 | StandardAttributes | ERROR / WARN | | 5 | StandardAttributes | ERROR / WARN |
| 6 | ChildObjects — допустимые элементы | ERROR | | 6 | ChildObjects — допустимые элементы | ERROR |
| 7 | Attributes/Dimensions/Resources — UUID, Name, Type | ERROR | | 7 | Attributes/Dimensions/Resources — UUID, Name, Type | ERROR |
| 7b | Reserved attribute names | WARN |
| 8 | Уникальность имён | ERROR | | 8 | Уникальность имён | ERROR |
| 9 | TabularSections — внутренняя структура | ERROR / WARN | | 9 | TabularSections — внутренняя структура | ERROR / WARN |
| 10 | Кросс-свойства | ERROR / WARN | | 10 | Кросс-свойства | ERROR / WARN |
| 11 | HTTPService/WebService — вложенная структура | ERROR | | 11 | HTTPService/WebService — вложенная структура | ERROR |
| 12 | Forbidden properties per type | ERROR |
| 13 | Method reference (Handler/MethodName) | ERROR / WARN |
| 14 | DocumentJournal Columns | ERROR |
## Вывод Exit code: 0 = OK, 1 = есть ошибки. По умолчанию краткий вывод. `-Detailed` для поштучной детализации.
```
=== Validation: Catalog.Номенклатура ===
[OK] 1. Root structure: MetaDataObject/Catalog, version 2.17
[OK] 2. InternalInfo: 5 GeneratedType (Object, Ref, Selection, List, Manager)
[OK] 3. Properties: Name="Номенклатура", Synonym present
[OK] 4. Property values: 12 enum properties checked
[ERROR] 5. StandardAttributes: missing "PredefinedDataName"
[OK] 6. ChildObjects types: Attribute(15), TabularSection(3), Form(4)
[OK] 7. Attributes/Dimensions: all valid
[WARN] 8. Name uniqueness: duplicate attribute "Комментарий" at positions 5, 12
[OK] 9. TabularSections: 3 sections, structure valid
[OK] 10. Cross-property consistency
[OK] 11. N/A (not HTTPService/WebService)
---
Errors: 1, Warnings: 1
```
Код возврата: 0 = все проверки пройдены, 1 = есть ошибки.
## Примеры
```powershell
# Справочник из выгрузки конфигурации
... -ObjectPath upload/acc_8.3.24/Catalogs/Банки/Банки.xml
# Авторезолв из директории
... -ObjectPath upload/acc_8.3.24/Documents/АвансовыйОтчет
# С лимитом ошибок
... -ObjectPath Catalogs/Номенклатура.xml -MaxErrors 10
# С записью в файл
... -ObjectPath Catalogs/Номенклатура.xml -OutFile result.txt
# Batch: несколько объектов через |
... -ObjectPath "Catalogs/Банки.xml|Documents/Заказ.xml|Enums/ВидДоговора.xml"
```
## Верификация
```
/meta-compile <JsonPath> <OutputDir> — генерация XML
/meta-validate <OutputDir>/<Type>/<Name>.xml — проверка результата
/meta-info <OutputDir>/<Type>/<Name>.xml — визуальная сводка
```
## Когда использовать
- **После `/meta-compile`**: проверить корректность сгенерированного XML
- **После ручного редактирования**: убедиться что структура не нарушена
- **После merge/импорта**: выявить конфликты и битые ссылки
- **При отладке**: найти структурные ошибки до сборки EPF
@@ -1,9 +1,11 @@
# meta-validate v1.1 — Validate 1C metadata object structure # meta-validate v1.2 — Validate 1C metadata object structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
[string]$ObjectPath, [string]$ObjectPath,
[switch]$Detailed,
[int]$MaxErrors = 30, [int]$MaxErrors = 30,
[string]$OutFile [string]$OutFile
@@ -19,7 +21,7 @@ if ($pathList.Count -gt 1) {
$batchOk = 0 $batchOk = 0
$batchFail = 0 $batchFail = 0
foreach ($singlePath in $pathList) { foreach ($singlePath in $pathList) {
$callArgs = @{ ObjectPath = $singlePath; MaxErrors = $MaxErrors } $callArgs = @{ ObjectPath = $singlePath; MaxErrors = $MaxErrors; Verbose = $Detailed }
if ($OutFile) { if ($OutFile) {
$baseName = [System.IO.Path]::GetFileNameWithoutExtension($OutFile) $baseName = [System.IO.Path]::GetFileNameWithoutExtension($OutFile)
$ext = [System.IO.Path]::GetExtension($OutFile) $ext = [System.IO.Path]::GetExtension($OutFile)
@@ -96,6 +98,7 @@ for ($depth = 0; $depth -lt 4; $depth++) {
$script:errors = 0 $script:errors = 0
$script:warnings = 0 $script:warnings = 0
$script:okCount = 0
$script:stopped = $false $script:stopped = $false
$script:output = New-Object System.Text.StringBuilder 8192 $script:output = New-Object System.Text.StringBuilder 8192
@@ -106,7 +109,8 @@ function Out-Line {
function Report-OK { function Report-OK {
param([string]$msg) param([string]$msg)
Out-Line "[OK] $msg" $script:okCount++
if ($Detailed) { Out-Line "[OK] $msg" }
} }
function Report-Error { function Report-Error {
@@ -125,10 +129,14 @@ function Report-Warn {
} }
$finalize = { $finalize = {
Out-Line "" $checks = $script:okCount + $script:errors + $script:warnings
Out-Line "=== Result: $($script:errors) errors, $($script:warnings) warnings ===" if ($script:errors -eq 0 -and $script:warnings -eq 0 -and -not $Detailed) {
$result = "=== Validation OK: $mdType.$objName ($checks checks) ==="
$result = $script:output.ToString() } else {
Out-Line ""
Out-Line "=== Result: $($script:errors) errors, $($script:warnings) warnings ($checks checks) ==="
$result = $script:output.ToString()
}
Write-Host $result Write-Host $result
if ($OutFile) { if ($OutFile) {
@@ -449,8 +457,6 @@ if ($typesWithoutInternalInfo -contains $mdType) {
Report-OK "2. InternalInfo: $($genTypes.Count) GeneratedType ($catList)" Report-OK "2. InternalInfo: $($genTypes.Count) GeneratedType ($catList)"
} }
} }
} else {
Report-OK "2. InternalInfo: N/A for $mdType"
} }
if ($script:stopped) { & $finalize; exit 1 } if ($script:stopped) { & $finalize; exit 1 }
@@ -569,8 +575,6 @@ if ($typesWithStdAttrs -contains $mdType) {
Report-OK "5. StandardAttributes: $($stdAttrs.Count) entries" Report-OK "5. StandardAttributes: $($stdAttrs.Count) entries"
} }
} }
} else {
Report-OK "5. StandardAttributes: N/A for $mdType"
} }
if ($script:stopped) { & $finalize; exit 1 } if ($script:stopped) { & $finalize; exit 1 }
@@ -690,8 +694,6 @@ if ($childObjNode) {
} elseif ($check7Count -eq 0) { } elseif ($check7Count -eq 0) {
Report-OK "7. Child elements: none to check" Report-OK "7. Child elements: none to check"
} }
} else {
Report-OK "7. Child elements: N/A (no ChildObjects)"
} }
if ($script:stopped) { & $finalize; exit 1 } if ($script:stopped) { & $finalize; exit 1 }
@@ -725,8 +727,6 @@ if ($childObjNode) {
if ($check7bOk) { if ($check7bOk) {
Report-OK "7b. Reserved attribute names: no conflicts" Report-OK "7b. Reserved attribute names: no conflicts"
} }
} else {
Report-OK "7b. Reserved attribute names: N/A"
} }
if ($script:stopped) { & $finalize; exit 1 } if ($script:stopped) { & $finalize; exit 1 }
@@ -814,8 +814,6 @@ if ($childObjNode) {
if ($check8Ok) { if ($check8Ok) {
Report-OK "8. Name uniqueness: all names unique" Report-OK "8. Name uniqueness: all names unique"
} }
} else {
Report-OK "8. Name uniqueness: N/A"
} }
if ($script:stopped) { & $finalize; exit 1 } if ($script:stopped) { & $finalize; exit 1 }
@@ -901,8 +899,6 @@ if ($childObjNode) {
} else { } else {
Report-OK "9. TabularSections: none present" Report-OK "9. TabularSections: none present"
} }
} else {
Report-OK "9. TabularSections: N/A"
} }
if ($script:stopped) { & $finalize; exit 1 } if ($script:stopped) { & $finalize; exit 1 }
@@ -1160,8 +1156,6 @@ if ($mdType -eq "HTTPService" -and $childObjNode) {
if ($check11Ok) { if ($check11Ok) {
Report-OK "11. WebService: $($operations.Count) operation(s), $paramCount parameter(s)" Report-OK "11. WebService: $($operations.Count) operation(s), $paramCount parameter(s)"
} }
} else {
Report-OK "11. HTTPService/WebService: N/A"
} }
if ($script:stopped) { & $finalize; exit 1 } if ($script:stopped) { & $finalize; exit 1 }
@@ -1181,8 +1175,6 @@ if ($propsNode -and $forbiddenProperties.ContainsKey($mdType)) {
if ($check12Ok) { if ($check12Ok) {
Report-OK "12. Forbidden properties: none found" Report-OK "12. Forbidden properties: none found"
} }
} else {
Report-OK "12. Forbidden properties: N/A"
} }
if ($script:stopped) { & $finalize; exit 1 } if ($script:stopped) { & $finalize; exit 1 }
@@ -1245,8 +1237,6 @@ if ($propsNode -and $mdType -in @("EventSubscription","ScheduledJob") -and $scri
if ($check13Ok) { if ($check13Ok) {
Report-OK "13. Method reference: $propLabel = '$methodRef'" Report-OK "13. Method reference: $propLabel = '$methodRef'"
} }
} else {
Report-OK "13. Method reference: N/A"
} }
if ($script:stopped) { & $finalize; exit 1 } if ($script:stopped) { & $finalize; exit 1 }
@@ -1283,8 +1273,6 @@ if ($mdType -eq "DocumentJournal" -and $childObjNode) {
} elseif ($colCount -eq 0) { } elseif ($colCount -eq 0) {
Report-OK "14. DocumentJournal Columns: none" Report-OK "14. DocumentJournal Columns: none"
} }
} else {
Report-OK "14. DocumentJournal Columns: N/A"
} }
# --- Final output --- # --- Final output ---
@@ -1,4 +1,4 @@
# meta-validate v1.1 — Validate 1C metadata object structure (Python port) # meta-validate v1.2 — Validate 1C metadata object structure (Python port)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import os import os
@@ -15,10 +15,12 @@ sys.stderr.reconfigure(encoding="utf-8")
parser = argparse.ArgumentParser(allow_abbrev=False) parser = argparse.ArgumentParser(allow_abbrev=False)
parser.add_argument("-ObjectPath", required=True) parser.add_argument("-ObjectPath", required=True)
parser.add_argument("-Detailed", action="store_true")
parser.add_argument("-MaxErrors", type=int, default=30) parser.add_argument("-MaxErrors", type=int, default=30)
parser.add_argument("-OutFile", default="") parser.add_argument("-OutFile", default="")
args = parser.parse_args() args = parser.parse_args()
detailed = args.Detailed
max_errors = args.MaxErrors max_errors = args.MaxErrors
out_file = args.OutFile out_file = args.OutFile
@@ -30,6 +32,8 @@ if len(path_list) > 1:
batch_fail = 0 batch_fail = 0
for single_path in path_list: for single_path in path_list:
cmd = [sys.executable, __file__, "-ObjectPath", single_path, "-MaxErrors", str(max_errors)] cmd = [sys.executable, __file__, "-ObjectPath", single_path, "-MaxErrors", str(max_errors)]
if detailed:
cmd.append("-Detailed")
if out_file: if out_file:
base, ext = os.path.splitext(out_file) base, ext = os.path.splitext(out_file)
obj_leaf = os.path.splitext(os.path.basename(single_path))[0] obj_leaf = os.path.splitext(os.path.basename(single_path))[0]
@@ -98,6 +102,7 @@ for _ in range(4):
errors = 0 errors = 0
warnings = 0 warnings = 0
ok_count = 0
stopped = False stopped = False
output_lines = [] output_lines = []
@@ -107,7 +112,10 @@ def out_line(msg):
def report_ok(msg): def report_ok(msg):
out_line(f"[OK] {msg}") global ok_count
ok_count += 1
if detailed:
out_line(f"[OK] {msg}")
def report_error(msg): def report_error(msg):
@@ -125,9 +133,13 @@ def report_warn(msg):
def finalize(): def finalize():
out_line("") checks = ok_count + errors + warnings
out_line(f"=== Result: {errors} errors, {warnings} warnings ===") if errors == 0 and warnings == 0 and not detailed:
result = "\n".join(output_lines) result = f"=== Validation OK: {md_type}.{obj_name} ({checks} checks) ==="
else:
out_line("")
out_line(f"=== Result: {errors} errors, {warnings} warnings ({checks} checks) ===")
result = "\n".join(output_lines)
print(result) print(result)
if out_file: if out_file:
with open(out_file, "w", encoding="utf-8-sig") as f: with open(out_file, "w", encoding="utf-8-sig") as f:
@@ -453,8 +465,6 @@ elif md_type in generated_type_categories:
if check2_ok: if check2_ok:
cat_list = ", ".join(sorted(found_categories)) cat_list = ", ".join(sorted(found_categories))
report_ok(f"2. InternalInfo: {len(gen_types)} GeneratedType ({cat_list})") report_ok(f"2. InternalInfo: {len(gen_types)} GeneratedType ({cat_list})")
else:
report_ok(f"2. InternalInfo: N/A for {md_type}")
if stopped: if stopped:
finalize() finalize()
@@ -560,8 +570,6 @@ if md_type in types_with_std_attrs:
if check5_ok: if check5_ok:
report_ok(f"5. StandardAttributes: {len(std_attrs)} entries") report_ok(f"5. StandardAttributes: {len(std_attrs)} entries")
else:
report_ok(f"5. StandardAttributes: N/A for {md_type}")
if stopped: if stopped:
finalize() finalize()
@@ -663,8 +671,6 @@ if child_obj_node is not None:
report_ok(f"7. Child elements: {check7_count} items checked (UUID, Name, Type)") report_ok(f"7. Child elements: {check7_count} items checked (UUID, Name, Type)")
elif check7_count == 0: elif check7_count == 0:
report_ok("7. Child elements: none to check") report_ok("7. Child elements: none to check")
else:
report_ok("7. Child elements: N/A (no ChildObjects)")
if stopped: if stopped:
finalize() finalize()
@@ -694,8 +700,6 @@ if child_obj_node is not None:
check7b_ok = False check7b_ok = False
if check7b_ok: if check7b_ok:
report_ok("7b. Reserved attribute names: no conflicts") report_ok("7b. Reserved attribute names: no conflicts")
else:
report_ok("7b. Reserved attribute names: N/A")
if stopped: if stopped:
finalize() finalize()
@@ -776,8 +780,6 @@ if child_obj_node is not None:
if check8_ok: if check8_ok:
report_ok("8. Name uniqueness: all names unique") report_ok("8. Name uniqueness: all names unique")
else:
report_ok("8. Name uniqueness: N/A")
if stopped: if stopped:
finalize() finalize()
@@ -854,8 +856,6 @@ if child_obj_node is not None:
report_ok(f"9. TabularSections: {ts_count} sections, structure valid") report_ok(f"9. TabularSections: {ts_count} sections, structure valid")
else: else:
report_ok("9. TabularSections: none present") report_ok("9. TabularSections: none present")
else:
report_ok("9. TabularSections: N/A")
if stopped: if stopped:
finalize() finalize()
@@ -1083,8 +1083,6 @@ elif md_type == "WebService" and child_obj_node is not None:
if check11_ok: if check11_ok:
report_ok(f"11. WebService: {len(operations)} operation(s), {param_count} parameter(s)") report_ok(f"11. WebService: {len(operations)} operation(s), {param_count} parameter(s)")
else:
report_ok("11. HTTPService/WebService: N/A")
if stopped: if stopped:
finalize() finalize()
@@ -1102,8 +1100,6 @@ if props_node is not None and md_type in forbidden_properties:
check12_ok = False check12_ok = False
if check12_ok: if check12_ok:
report_ok("12. Forbidden properties: none found") report_ok("12. Forbidden properties: none found")
else:
report_ok("12. Forbidden properties: N/A")
if stopped: if stopped:
finalize() finalize()
@@ -1161,8 +1157,6 @@ if props_node is not None and md_type in ("EventSubscription", "ScheduledJob") a
if check13_ok: if check13_ok:
report_ok(f"13. Method reference: {prop_label} = '{method_ref}'") report_ok(f"13. Method reference: {prop_label} = '{method_ref}'")
else:
report_ok("13. Method reference: N/A")
if stopped: if stopped:
finalize() finalize()
@@ -1197,8 +1191,6 @@ if md_type == "DocumentJournal" and child_obj_node is not None:
report_ok(f"14. DocumentJournal Columns: {col_count} column(s), all have References") report_ok(f"14. DocumentJournal Columns: {col_count} column(s), all have References")
elif col_count == 0: elif col_count == 0:
report_ok("14. DocumentJournal Columns: none") report_ok("14. DocumentJournal Columns: none")
else:
report_ok("14. DocumentJournal Columns: N/A")
# ── Final output ────────────────────────────────────────────── # ── Final output ──────────────────────────────────────────────
+12 -50
View File
@@ -1,48 +1,33 @@
--- ---
name: mxl-validate name: mxl-validate
description: Валидация макета табличного документа (MXL). Используй после создания или модификации макета для проверки корректности description: Валидация макета табличного документа (MXL). Используй после создания или модификации макета для проверки корректности
argument-hint: <TemplatePath> или <ProcessorName> <TemplateName> argument-hint: <TemplatePath> [-Detailed] [-MaxErrors 20]
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
- Glob - Glob
--- ---
# /mxl-validate — Валидатор макета # /mxl-validate — валидация макета табличного документа (MXL)
Проверяет Template.xml на структурные ошибки, которые платформа 1С может молча проигнорировать (с возможной потерей данных или повреждением макета). Проверяет Template.xml на структурные ошибки: индексы, ссылки на палитры, диапазоны именованных областей и объединений.
## Использование
```
/mxl-validate <TemplatePath>
/mxl-validate <ProcessorName> <TemplateName>
```
## Параметры ## Параметры
| Параметр | Обязательный | По умолчанию | Описание | | Параметр | Обяз. | Умолч. | Описание |
|---------------|:------------:|--------------|------------------------------------------| |---------------|:-----:|---------|--------------------------------------------|
| TemplatePath | нет | — | Прямой путь к Template.xml | | TemplatePath | да | — | Путь к макету (директория или Template.xml) |
| ProcessorName | нет | — | Имя обработки (альтернатива пути) | | Detailed | нет | — | Показывать [OK] для каждой проверки |
| TemplateName | нет | — | Имя макета (альтернатива пути) | | MaxErrors | нет | 20 | Остановиться после N ошибок |
| SrcDir | нет | `src` | Каталог исходников |
| MaxErrors | нет | 20 | Остановиться после N ошибок |
Укажите либо `-TemplatePath`, либо оба `-ProcessorName` и `-TemplateName`.
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File .claude/skills/mxl-validate/scripts/mxl-validate.ps1 -TemplatePath "<путь>" powershell.exe -NoProfile -File .claude/skills/mxl-validate/scripts/mxl-validate.ps1 -TemplatePath "Catalogs/Номенклатура/Templates/Макет"
powershell.exe -NoProfile -File .claude/skills/mxl-validate/scripts/mxl-validate.ps1 -TemplatePath "src/МояОбработка/Templates/ПечатнаяФорма"
``` ```
Или по имени обработки/макета: ## Проверки
```powershell
powershell.exe -NoProfile -File .claude/skills/mxl-validate/scripts/mxl-validate.ps1 -ProcessorName "<Имя>" -TemplateName "<Макет>" [-SrcDir "<каталог>"]
```
## Выполняемые проверки
| # | Проверка | Серьёзность | | # | Проверка | Серьёзность |
|---|---|---| |---|---|---|
@@ -59,27 +44,4 @@ powershell.exe -NoProfile -File .claude/skills/mxl-validate/scripts/mxl-validate
| 11 | Индексы линий границ в форматах в пределах палитры линий | ERROR | | 11 | Индексы линий границ в форматах в пределах палитры линий | ERROR |
| 12 | `pictureIndex` рисунков ссылается на существующую картинку | ERROR | | 12 | `pictureIndex` рисунков ссылается на существующую картинку | ERROR |
## Вывод Exit code: 0 = OK, 1 = есть ошибки. По умолчанию краткий вывод. `-Detailed` для поштучной детализации.
```
=== Validation: ИмяМакета ===
[OK] height (40) >= max row index + 1 (40), rowsItem count=34
[OK] Font refs: max=3, palette size=4
[ERROR] Row 15: cell format index 38 > format palette size (37)
[OK] Column indices: max in default set=32, default column count=33
---
Errors: 1, Warnings: 0
```
Код возврата: 0 = все проверки пройдены, 1 = есть ошибки.
## Когда использовать
- **После генерации макета**: запустить валидатор для выявления структурных ошибок до сборки EPF
- **После редактирования Template.xml**: убедиться, что индексы и ссылки остались валидными
- **При ошибках**: исправить найденные проблемы и перезапустить до полного прохождения
## Защита от переполнения
Останавливается после 20 ошибок по умолчанию (настраивается через `-MaxErrors`). Итоговая строка с количеством ошибок/предупреждений выводится всегда.
@@ -1,10 +1,11 @@
# mxl-validate v1.0 — Validate 1C spreadsheet # mxl-validate v1.1 — Validate 1C spreadsheet
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[string]$TemplatePath, [string]$TemplatePath,
[string]$ProcessorName, [string]$ProcessorName,
[string]$TemplateName, [string]$TemplateName,
[string]$SrcDir = "src", [string]$SrcDir = "src",
[switch]$Detailed,
[int]$MaxErrors = 20 [int]$MaxErrors = 20
) )
@@ -21,6 +22,26 @@ if (-not $TemplatePath) {
$TemplatePath = Join-Path (Join-Path (Join-Path (Join-Path (Join-Path $SrcDir $ProcessorName) "Templates") $TemplateName) "Ext") "Template.xml" $TemplatePath = Join-Path (Join-Path (Join-Path (Join-Path (Join-Path $SrcDir $ProcessorName) "Templates") $TemplateName) "Ext") "Template.xml"
} }
# A: Directory → Ext/Template.xml
if (Test-Path $TemplatePath -PathType Container) {
$TemplatePath = Join-Path (Join-Path $TemplatePath "Ext") "Template.xml"
}
# B1: Missing Ext/ (e.g. Templates/Макет/Template.xml → Templates/Макет/Ext/Template.xml)
if (-not (Test-Path $TemplatePath)) {
$fn = [System.IO.Path]::GetFileName($TemplatePath)
if ($fn -eq "Template.xml") {
$c = Join-Path (Join-Path (Split-Path $TemplatePath) "Ext") $fn
if (Test-Path $c) { $TemplatePath = $c }
}
}
# B2: Descriptor (Templates/Макет.xml → Templates/Макет/Ext/Template.xml)
if (-not (Test-Path $TemplatePath) -and $TemplatePath.EndsWith(".xml")) {
$stem = [System.IO.Path]::GetFileNameWithoutExtension($TemplatePath)
$dir = Split-Path $TemplatePath
$c = Join-Path (Join-Path (Join-Path $dir $stem) "Ext") "Template.xml"
if (Test-Path $c) { $TemplatePath = $c }
}
if (-not (Test-Path $TemplatePath)) { if (-not (Test-Path $TemplatePath)) {
Write-Error "File not found: $TemplatePath" Write-Error "File not found: $TemplatePath"
exit 1 exit 1
@@ -44,10 +65,12 @@ $root = $xmlDoc.DocumentElement
$errors = 0 $errors = 0
$warnings = 0 $warnings = 0
$stopped = $false $stopped = $false
$script:okCount = 0
function Report-OK { function Report-OK {
param([string]$msg) param([string]$msg)
Write-Host "[OK] $msg" $script:okCount++
if ($Detailed) { Write-Host "[OK] $msg" }
} }
function Report-Error { function Report-Error {
@@ -66,8 +89,10 @@ function Report-Warn {
} }
$templateName = [System.IO.Path]::GetFileName([System.IO.Path]::GetDirectoryName([System.IO.Path]::GetDirectoryName($TemplatePath))) $templateName = [System.IO.Path]::GetFileName([System.IO.Path]::GetDirectoryName([System.IO.Path]::GetDirectoryName($TemplatePath)))
Write-Host "=== Validation: $templateName ===" if ($Detailed) {
Write-Host "" Write-Host "=== Validation: $templateName ==="
Write-Host ""
}
# --- Collect palettes --- # --- Collect palettes ---
@@ -376,18 +401,18 @@ foreach ($drawing in $root.SelectNodes("d:drawing", $nsMgr)) {
# --- Summary --- # --- Summary ---
# :finish label equivalent $checks = $script:okCount + $errors + $warnings
Write-Host ""
Write-Host "---"
if ($stopped) { if ($errors -eq 0 -and $warnings -eq 0 -and -not $Detailed) {
Write-Host "Stopped after $MaxErrors errors. Fix and re-run." Write-Host "=== Validation OK: Template.$templateName ($checks checks) ==="
}
if ($errors -eq 0 -and $warnings -eq 0) {
Write-Host "All checks passed."
} else { } else {
Write-Host "Errors: $errors, Warnings: $warnings" Write-Host ""
if ($stopped) {
Write-Host "Stopped after $MaxErrors errors. Fix and re-run."
}
Write-Host "=== Result: $errors errors, $warnings warnings ($checks checks) ==="
} }
if ($errors -gt 0) { if ($errors -gt 0) {
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# mxl-validate v1.0 — Validate 1C spreadsheet document Template.xml # mxl-validate v1.1 — Validate 1C spreadsheet document Template.xml
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
"""Validates spreadsheet Template.xml: height, palette refs, column/row indices, areas, merges.""" """Validates spreadsheet Template.xml: height, palette refs, column/row indices, areas, merges."""
import sys, os, argparse import sys, os, argparse
@@ -17,24 +17,29 @@ NS = {
class Reporter: class Reporter:
def __init__(self, max_errors): def __init__(self, max_errors, detailed=False):
self.errors = 0 self.errors = 0
self.warnings = 0 self.warnings = 0
self.ok_count = 0
self.stopped = False self.stopped = False
self.max_errors = max_errors self.max_errors = max_errors
self.detailed = detailed
self.lines = []
def ok(self, msg): def ok(self, msg):
print(f'[OK] {msg}') self.ok_count += 1
if self.detailed:
self.lines.append(f'[OK] {msg}')
def error(self, msg): def error(self, msg):
self.errors += 1 self.errors += 1
print(f'[ERROR] {msg}') self.lines.append(f'[ERROR] {msg}')
if self.errors >= self.max_errors: if self.errors >= self.max_errors:
self.stopped = True self.stopped = True
def warn(self, msg): def warn(self, msg):
self.warnings += 1 self.warnings += 1
print(f'[WARN] {msg}') self.lines.append(f'[WARN] {msg}')
def int_text(node): def int_text(node):
@@ -54,6 +59,7 @@ def main():
parser.add_argument('-ProcessorName', dest='ProcessorName', default='') parser.add_argument('-ProcessorName', dest='ProcessorName', default='')
parser.add_argument('-TemplateName', dest='TemplateName', default='') parser.add_argument('-TemplateName', dest='TemplateName', default='')
parser.add_argument('-SrcDir', dest='SrcDir', default='src') parser.add_argument('-SrcDir', dest='SrcDir', default='src')
parser.add_argument('-Detailed', action='store_true')
parser.add_argument('-MaxErrors', dest='MaxErrors', type=int, default=20) parser.add_argument('-MaxErrors', dest='MaxErrors', type=int, default=20)
args = parser.parse_args() args = parser.parse_args()
@@ -61,6 +67,7 @@ def main():
processor_name = args.ProcessorName processor_name = args.ProcessorName
template_name_arg = args.TemplateName template_name_arg = args.TemplateName
src_dir = args.SrcDir src_dir = args.SrcDir
detailed = args.Detailed
max_errors = args.MaxErrors max_errors = args.MaxErrors
# --- Resolve template path --- # --- Resolve template path ---
@@ -74,6 +81,24 @@ def main():
if not os.path.isabs(template_path): if not os.path.isabs(template_path):
template_path = os.path.join(os.getcwd(), template_path) template_path = os.path.join(os.getcwd(), template_path)
# A: Directory → Ext/Template.xml
if os.path.isdir(template_path):
template_path = os.path.join(template_path, 'Ext', 'Template.xml')
# B1: Missing Ext/ (e.g. Templates/Макет/Template.xml → Templates/Макет/Ext/Template.xml)
if not os.path.exists(template_path):
fn = os.path.basename(template_path)
if fn == 'Template.xml':
c = os.path.join(os.path.dirname(template_path), 'Ext', fn)
if os.path.exists(c):
template_path = c
# B2: Descriptor (Templates/Макет.xml → Templates/Макет/Ext/Template.xml)
if not os.path.exists(template_path) and template_path.endswith('.xml'):
stem = os.path.splitext(os.path.basename(template_path))[0]
parent = os.path.dirname(template_path)
c = os.path.join(parent, stem, 'Ext', 'Template.xml')
if os.path.exists(c):
template_path = c
if not os.path.exists(template_path): if not os.path.exists(template_path):
print(f'File not found: {template_path}', file=sys.stderr) print(f'File not found: {template_path}', file=sys.stderr)
sys.exit(1) sys.exit(1)
@@ -85,13 +110,13 @@ def main():
xml_doc = etree.parse(resolved_path, xml_parser) xml_doc = etree.parse(resolved_path, xml_parser)
root = xml_doc.getroot() root = xml_doc.getroot()
r = Reporter(max_errors) r = Reporter(max_errors, detailed)
# Derive template name from path: .../Templates/<Name>/Ext/Template.xml # Derive template name from path: .../Templates/<Name>/Ext/Template.xml
# Go up 2 levels from Template.xml -> Ext -> <Name> # Go up 2 levels from Template.xml -> Ext -> <Name>
template_display_name = os.path.basename(os.path.dirname(os.path.dirname(resolved_path))) template_display_name = os.path.basename(os.path.dirname(os.path.dirname(resolved_path)))
print(f'=== Validation: {template_display_name} ===') r.lines.append(f'=== Validation: Template.{template_display_name} ===')
print() r.lines.append('')
# --- Collect palettes --- # --- Collect palettes ---
line_nodes = root.findall(f'{{{NS_D}}}line') line_nodes = root.findall(f'{{{NS_D}}}line')
@@ -176,8 +201,7 @@ def main():
r.error(f'Font index {max_font_ref} exceeds palette size ({font_count})') r.error(f'Font index {max_font_ref} exceeds palette size ({font_count})')
elif max_font_ref > 0: elif max_font_ref > 0:
r.error(f'Font index {max_font_ref} referenced but no fonts defined') r.error(f'Font index {max_font_ref} referenced but no fonts defined')
else: # No font references — no check needed
r.ok('No font references')
# --- Check 11: line/border indices in formats --- # --- Check 11: line/border indices in formats ---
if line_count > 0: if line_count > 0:
@@ -187,8 +211,7 @@ def main():
r.error(f'Line index {max_line_ref} exceeds palette size ({line_count})') r.error(f'Line index {max_line_ref} exceeds palette size ({line_count})')
elif max_line_ref > 0: elif max_line_ref > 0:
r.error(f'Line index {max_line_ref} referenced but no lines defined') r.error(f'Line index {max_line_ref} referenced but no lines defined')
else: # No line/border references — no check needed
r.ok('No line/border references')
# --- Check 3, 4, 5, 6: row/cell checks --- # --- Check 3, 4, 5, 6: row/cell checks ---
max_cell_format_ref = 0 max_cell_format_ref = 0
@@ -348,17 +371,16 @@ def main():
draw_id = draw_id_node.text if draw_id_node is not None else '?' draw_id = draw_id_node.text if draw_id_node is not None else '?'
r.error(f'Drawing id={draw_id}: pictureIndex={pic_idx} > picture count ({picture_count})') r.error(f'Drawing id={draw_id}: pictureIndex={pic_idx} > picture count ({picture_count})')
# --- Summary --- # --- Finalize ---
print() checks = r.ok_count + r.errors + r.warnings
print('---') if r.errors == 0 and r.warnings == 0 and not detailed:
result = f'=== Validation OK: Template.{template_display_name} ({checks} checks) ==='
if r.stopped:
print(f'Stopped after {max_errors} errors. Fix and re-run.')
if r.errors == 0 and r.warnings == 0:
print('All checks passed.')
else: else:
print(f'Errors: {r.errors}, Warnings: {r.warnings}') r.lines.append('')
r.lines.append(f'=== Result: {r.errors} errors, {r.warnings} warnings ({checks} checks) ===')
result = '\n'.join(r.lines)
print(result)
sys.exit(1 if r.errors > 0 else 0) sys.exit(1 if r.errors > 0 else 0)
+21 -84
View File
@@ -1,7 +1,7 @@
--- ---
name: role-validate name: role-validate
description: Валидация роли 1С. Используй после создания или модификации роли для проверки корректности description: Валидация роли 1С. Используй после создания или модификации роли для проверки корректности
argument-hint: <RightsPath> argument-hint: <RightsPath> [-Detailed] [-MaxErrors 30]
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
@@ -11,95 +11,32 @@ allowed-tools:
Проверяет корректность `Rights.xml` роли: формат XML, namespace, глобальные флаги, типы объектов, имена прав, RLS-ограничения, шаблоны. Опционально проверяет метаданные роли (UUID, имя, синоним). Проверяет корректность `Rights.xml` роли: формат XML, namespace, глобальные флаги, типы объектов, имена прав, RLS-ограничения, шаблоны. Опционально проверяет метаданные роли (UUID, имя, синоним).
## Использование ## Параметры
``` | Параметр | Обяз. | Умолч. | Описание |
/role-validate <RightsPath> [MetadataPath] |--------------|:-----:|---------|-------------------------------------------------|
``` | RightsPath | да | — | Путь к роли (директория или `Rights.xml`) |
| Detailed | нет | — | Показывать [OK] для каждой проверки |
| MaxErrors | нет | 30 | Макс. ошибок до остановки (по умолчанию 30) |
| OutFile | нет | — | Записать результат в файл (UTF-8 BOM) |
## Запуск скрипта ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File .claude/skills/role-validate/scripts/role-validate.ps1 -RightsPath <path> [-MetadataPath <path>] [-OutFile <output.txt>] powershell.exe -NoProfile -File .claude/skills/role-validate/scripts/role-validate.ps1 -RightsPath "Roles/МояРоль"
``` ```
### Параметры
| Параметр | Обязательный | Описание |
|----------|:------------:|----------|
| `-RightsPath` | да | Путь к `Rights.xml` роли |
| `-MetadataPath` | нет | Путь к метаданным роли (`Roles/ИмяРоли.xml`) |
| `-OutFile` | нет | Записать результат в файл (UTF-8 BOM). Без этого — вывод в консоль |
**Важно:** Для кириллических путей используй `-OutFile` и читай результат через Read tool.
## Проверки ## Проверки
### Rights.xml | # | Проверка | Серьёзность |
1. XML well-formed — парсинг без ошибок |---|----------|-------------|
2. Корневой элемент `<Rights>` с namespace `http://v8.1c.ru/8.2/roles` | 1 | XML well-formed — парсинг без ошибок | ERROR |
3. Три глобальных флага: `setForNewObjects`, `setForAttributesByDefault`, `independentRightsOfChildObjects` | 2 | Корневой элемент `<Rights>` с namespace `http://v8.1c.ru/8.2/roles` | ERROR |
4. Для каждого `<object>`: | 3 | Три глобальных флага: setForNewObjects, setForAttributesByDefault, independentRightsOfChildObjects | ERROR |
- `<name>` не пуст | 4 | Объекты: name не пуст, тип распознан, права валидны для типа (с подсказкой при опечатке) | ERROR/WARN |
- Тип объекта распознан (Catalog, Document, InformationRegister и т.д.) | 5 | Вложенные объекты (3+ сегмента): допустимы только View, Edit (или Use для IntegrationServiceChannel) | ERROR |
- Каждое `<right>` имеет `<name>` и `<value>` (`true`/`false`) | 6 | RLS `<restrictionByCondition>`: condition не пуст | ERROR |
- Имя права валидно для данного типа объекта (с подсказкой при опечатке) | 7 | Шаблоны `<restrictionTemplate>`: name и condition не пусты | ERROR |
5. Вложенные объекты (3+ сегмента через `.`): допустимы только View, Edit (или Use для IntegrationServiceChannel) | 8 | Метаданные (если MetadataPath): UUID, Name, Synonym | ERROR/WARN |
6. RLS `<restrictionByCondition>`: `<condition>` не пуст
7. Шаблоны `<restrictionTemplate>`: `<name>` и `<condition>` не пусты
### Метаданные (опционально) Exit code: 0 = OK, 1 = есть ошибки. По умолчанию краткий вывод. `-Detailed` для поштучной детализации.
- Элемент `<Role>` найден
- UUID в корректном формате
- `<Name>` не пуст
- `<Synonym>` присутствует
## Формат вывода
```
Validating: Roles/МояРоль/Ext/Rights.xml
OK XML well-formed
OK Root element: <Rights> with correct namespace
OK 3 global flags present
WARN Document.Реализация: unknown right 'Rea'. Did you mean: Read?
OK 12 objects, 45 rights
OK 2 RLS restrictions
OK 1 templates: ДляОбъекта
OK Metadata: UUID valid (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
OK Metadata: Name = МояРоль
OK Metadata: Synonym present
---
Result: 0 error(s), 1 warning(s)
```
### Уровни сообщений
| Маркер | Значение |
|--------|----------|
| `OK` | Проверка пройдена |
| `WARN` | Предупреждение (неизвестный тип объекта, подозрительное имя права) |
| `ERR` | Ошибка (невалидный XML, отсутствие обязательных элементов) |
Код возврата: `0` — без ошибок, `1` — есть ошибки.
## Примеры
### Только Rights.xml
```
/role-validate upload/acc_8.3.20/Roles/БазовыеПраваБП/Ext/Rights.xml
```
### С проверкой метаданных
```
/role-validate Roles/МояРоль/Ext/Rights.xml Roles/МояРоль.xml
```
### Верификация после /role-compile
```
/role-compile role.json Roles/
/role-validate Roles/МояРоль/Ext/Rights.xml Roles/МояРоль.xml
```
@@ -1,12 +1,14 @@
# role-validate v1.0 — Validate 1C role structure # role-validate v1.1 — Validate 1C role structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
[string]$RightsPath, [string]$RightsPath,
[string]$MetadataPath, [string]$OutFile,
[string]$OutFile [switch]$Detailed,
[int]$MaxErrors = 30
) )
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
@@ -132,25 +134,36 @@ $script:commandRights = @("View")
# --- 2. Output helpers --- # --- 2. Output helpers ---
$script:lines = @()
$script:errors = 0 $script:errors = 0
$script:warnings = 0 $script:warnings = 0
$script:okCount = 0
$script:stopped = $false
$script:output = New-Object System.Text.StringBuilder 8192
function Out-OK { function Out-Line {
param([string]$msg) param([string]$msg)
$script:lines += " OK $msg" $script:output.AppendLine($msg) | Out-Null
} }
function Out-WARN { function Report-OK {
param([string]$msg) param([string]$msg)
$script:warnings++ $script:okCount++
$script:lines += " WARN $msg" if ($Detailed) { Out-Line "[OK] $msg" }
} }
function Out-ERR { function Report-Error {
param([string]$msg) param([string]$msg)
$script:errors++ $script:errors++
$script:lines += " ERR $msg" Out-Line "[ERROR] $msg"
if ($script:errors -ge $MaxErrors) {
$script:stopped = $true
}
}
function Report-Warn {
param([string]$msg)
$script:warnings++
Out-Line "[WARN] $msg"
} }
function Get-ObjectType { function Get-ObjectType {
@@ -174,38 +187,63 @@ function Find-Similar {
return $result return $result
} }
# --- Resolve path ---
if (-not [System.IO.Path]::IsPathRooted($RightsPath)) {
$RightsPath = Join-Path (Get-Location).Path $RightsPath
}
# A: Directory → Ext/Rights.xml
if (Test-Path $RightsPath -PathType Container) {
$RightsPath = Join-Path (Join-Path $RightsPath "Ext") "Rights.xml"
}
# B1: Missing Ext/ (e.g. Roles/МояРоль/Rights.xml → Roles/МояРоль/Ext/Rights.xml)
if (-not (Test-Path $RightsPath)) {
$fn = [System.IO.Path]::GetFileName($RightsPath)
if ($fn -eq "Rights.xml") {
$c = Join-Path (Join-Path (Split-Path $RightsPath) "Ext") $fn
if (Test-Path $c) { $RightsPath = $c }
}
}
# --- 3. Validate Rights.xml --- # --- 3. Validate Rights.xml ---
$script:lines += "Validating: $RightsPath"
if (-not (Test-Path $RightsPath)) { if (-not (Test-Path $RightsPath)) {
Out-ERR "File not found: $RightsPath" Report-Error "File not found: $RightsPath"
$script:lines += "---" $result = $script:output.ToString()
$script:lines += "Result: $($script:errors) error(s), $($script:warnings) warning(s)" Write-Host $result
$output = $script:lines -join "`n"
if ($OutFile) { if ($OutFile) {
$enc = New-Object System.Text.UTF8Encoding($true) $outPath = if ([System.IO.Path]::IsPathRooted($OutFile)) { $OutFile } else { Join-Path (Get-Location) $OutFile }
[System.IO.File]::WriteAllText($OutFile, $output, $enc) $outDir = [System.IO.Path]::GetDirectoryName($outPath)
} else { if (-not (Test-Path $outDir)) { New-Item -ItemType Directory -Path $outDir -Force | Out-Null }
Write-Host $output $utf8Bom = New-Object System.Text.UTF8Encoding $true
[System.IO.File]::WriteAllText($outPath, $result, $utf8Bom)
Write-Host "Written to: $outPath"
} }
exit 1 exit 1
} }
# Auto-detect metadata: Roles/Name/Ext/Rights.xml → Roles/Name.xml
$resolvedRights = (Resolve-Path $RightsPath).Path
$extDir = Split-Path $resolvedRights -Parent
$roleDir = Split-Path $extDir -Parent
$rolesDir = Split-Path $roleDir -Parent
$roleDirName = Split-Path $roleDir -Leaf
$MetadataPath = Join-Path $rolesDir "$roleDirName.xml"
# 3a. Parse XML # 3a. Parse XML
try { try {
[xml]$xml = Get-Content -Path $RightsPath -Encoding UTF8 [xml]$xml = Get-Content -Path $RightsPath -Encoding UTF8
Out-OK "XML well-formed" Report-OK "XML well-formed"
} catch { } catch {
Out-ERR "XML parse error: $($_.Exception.Message)" Report-Error "XML parse error: $($_.Exception.Message)"
$script:lines += "---" $result = $script:output.ToString()
$script:lines += "Result: $($script:errors) error(s), $($script:warnings) warning(s)" Write-Host $result
$output = $script:lines -join "`n"
if ($OutFile) { if ($OutFile) {
$enc = New-Object System.Text.UTF8Encoding($true) $outPath = if ([System.IO.Path]::IsPathRooted($OutFile)) { $OutFile } else { Join-Path (Get-Location) $OutFile }
[System.IO.File]::WriteAllText($OutFile, $output, $enc) $outDir = [System.IO.Path]::GetDirectoryName($outPath)
} else { if (-not (Test-Path $outDir)) { New-Item -ItemType Directory -Path $outDir -Force | Out-Null }
Write-Host $output $utf8Bom = New-Object System.Text.UTF8Encoding $true
[System.IO.File]::WriteAllText($outPath, $result, $utf8Bom)
Write-Host "Written to: $outPath"
} }
exit 1 exit 1
} }
@@ -215,11 +253,11 @@ $rightsNs = "http://v8.1c.ru/8.2/roles"
# 3b. Check root element # 3b. Check root element
if ($root.LocalName -ne "Rights") { if ($root.LocalName -ne "Rights") {
Out-ERR "Root element is '$($root.LocalName)', expected 'Rights'" Report-Error "Root element is '$($root.LocalName)', expected 'Rights'"
} elseif ($root.NamespaceURI -ne $rightsNs) { } elseif ($root.NamespaceURI -ne $rightsNs) {
Out-WARN "Namespace is '$($root.NamespaceURI)', expected '$rightsNs'" Report-Warn "Namespace is '$($root.NamespaceURI)', expected '$rightsNs'"
} else { } else {
Out-OK "Root element: <Rights> with correct namespace" Report-OK "Root element: <Rights> with correct namespace"
} }
# 3c. Global flags # 3c. Global flags
@@ -230,15 +268,15 @@ foreach ($fn in $flagNames) {
if ($node.Count -gt 0) { if ($node.Count -gt 0) {
$val = $node[0].InnerText $val = $node[0].InnerText
if ($val -ne "true" -and $val -ne "false") { if ($val -ne "true" -and $val -ne "false") {
Out-WARN "$fn = '$val' (expected 'true' or 'false')" Report-Warn "$fn = '$val' (expected 'true' or 'false')"
} }
$flagsFound++ $flagsFound++
} else { } else {
Out-WARN "Missing global flag: $fn" Report-Warn "Missing global flag: $fn"
} }
} }
if ($flagsFound -eq 3) { if ($flagsFound -eq 3) {
Out-OK "3 global flags present" Report-OK "3 global flags present"
} }
# 3d. Objects # 3d. Objects
@@ -257,7 +295,7 @@ foreach ($obj in $objects) {
} }
if (-not $objName) { if (-not $objName) {
Out-ERR "Object without <name>" Report-Error "Object without <name>"
continue continue
} }
@@ -266,7 +304,7 @@ foreach ($obj in $objects) {
# Check object type is known # Check object type is known
if (-not $isNested -and -not $script:knownRights.ContainsKey($objectType)) { if (-not $isNested -and -not $script:knownRights.ContainsKey($objectType)) {
Out-WARN "${objName}: unknown object type '$objectType'" Report-Warn "${objName}: unknown object type '$objectType'"
} }
# Check rights # Check rights
@@ -289,18 +327,18 @@ foreach ($obj in $objects) {
if ($rcc.LocalName -eq "condition") { $condNode = $rcc } if ($rcc.LocalName -eq "condition") { $condNode = $rcc }
} }
if (-not $condNode -or -not $condNode.InnerText) { if (-not $condNode -or -not $condNode.InnerText) {
Out-WARN "${objName}: RLS condition for '$rName' is empty" Report-Warn "${objName}: RLS condition for '$rName' is empty"
} }
} }
} }
if (-not $rName) { if (-not $rName) {
Out-ERR "${objName}: <right> without <name>" Report-Error "${objName}: <right> without <name>"
continue continue
} }
if ($rValue -ne "true" -and $rValue -ne "false") { if ($rValue -ne "true" -and $rValue -ne "false") {
Out-ERR "${objName}: right '$rName' has invalid value '$rValue'" Report-Error "${objName}: right '$rName' has invalid value '$rValue'"
continue continue
} }
@@ -310,15 +348,15 @@ foreach ($obj in $objects) {
if ($isNested) { if ($isNested) {
if ($objName -match '\.Command\.') { if ($objName -match '\.Command\.') {
if ($rName -notin $script:commandRights) { if ($rName -notin $script:commandRights) {
Out-WARN "${objName}: '$rName' not valid for commands (only: View)" Report-Warn "${objName}: '$rName' not valid for commands (only: View)"
} }
} elseif ($objName -match '\.IntegrationServiceChannel\.') { } elseif ($objName -match '\.IntegrationServiceChannel\.') {
if ($rName -notin $script:channelRights) { if ($rName -notin $script:channelRights) {
Out-WARN "${objName}: '$rName' not valid for channels (only: Use)" Report-Warn "${objName}: '$rName' not valid for channels (only: Use)"
} }
} else { } else {
if ($rName -notin $script:nestedRights) { if ($rName -notin $script:nestedRights) {
Out-WARN "${objName}: '$rName' not valid for nested objects (only: View, Edit)" Report-Warn "${objName}: '$rName' not valid for nested objects (only: View, Edit)"
} }
} }
} elseif ($script:knownRights.ContainsKey($objectType)) { } elseif ($script:knownRights.ContainsKey($objectType)) {
@@ -326,15 +364,15 @@ foreach ($obj in $objects) {
if ($rName -notin $validRights) { if ($rName -notin $validRights) {
$similar = Find-Similar -needle $rName -haystack $validRights $similar = Find-Similar -needle $rName -haystack $validRights
$sugStr = if ($similar.Count -gt 0) { " Did you mean: $($similar -join ', ')?" } else { "" } $sugStr = if ($similar.Count -gt 0) { " Did you mean: $($similar -join ', ')?" } else { "" }
Out-WARN "${objName}: unknown right '$rName'.$sugStr" Report-Warn "${objName}: unknown right '$rName'.$sugStr"
} }
} }
} }
} }
Out-OK "$objCount objects, $rightCount rights" Report-OK "$objCount objects, $rightCount rights"
if ($rlsCount -gt 0) { if ($rlsCount -gt 0) {
Out-OK "$rlsCount RLS restrictions" Report-OK "$rlsCount RLS restrictions"
} }
# 3e. Templates # 3e. Templates
@@ -349,72 +387,63 @@ if ($templates.Count -gt 0) {
if ($child.LocalName -eq "condition") { $tCond = $child.InnerText } if ($child.LocalName -eq "condition") { $tCond = $child.InnerText }
} }
if (-not $tName) { if (-not $tName) {
Out-WARN "Restriction template without <name>" Report-Warn "Restriction template without <name>"
} else { } else {
$parenIdx = $tName.IndexOf("(") $parenIdx = $tName.IndexOf("(")
$shortName = if ($parenIdx -gt 0) { $tName.Substring(0, $parenIdx) } else { $tName } $shortName = if ($parenIdx -gt 0) { $tName.Substring(0, $parenIdx) } else { $tName }
$tplNames += $shortName $tplNames += $shortName
} }
if (-not $tCond) { if (-not $tCond) {
Out-WARN "Template '$tName': empty <condition>" Report-Warn "Template '$tName': empty <condition>"
} }
} }
Out-OK "$($templates.Count) templates: $($tplNames -join ', ')" Report-OK "$($templates.Count) templates: $($tplNames -join ', ')"
} }
# --- 4. Validate metadata (optional) --- # --- 4. Validate metadata ---
if ($MetadataPath) { if (Test-Path $MetadataPath) {
$script:lines += "" Out-Line ""
try {
if (-not (Test-Path $MetadataPath)) { [xml]$metaXml = Get-Content -Path $MetadataPath -Encoding UTF8
Out-ERR "Metadata file not found: $MetadataPath" $roleNode = $metaXml.DocumentElement.SelectSingleNode("//*[local-name()='Role']")
} else { if (-not $roleNode) {
try { Report-Error "Metadata: <Role> element not found"
[xml]$metaXml = Get-Content -Path $MetadataPath -Encoding UTF8 } else {
$roleNode = $metaXml.DocumentElement.SelectSingleNode("//*[local-name()='Role']") $uuid = $roleNode.GetAttribute("uuid")
if (-not $roleNode) { if ($uuid -match '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$') {
Out-ERR "Metadata: <Role> element not found" Report-OK "Metadata: UUID valid ($uuid)"
} else { } else {
$uuid = $roleNode.GetAttribute("uuid") Report-Error "Metadata: invalid UUID format '$uuid'"
if ($uuid -match '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$') { }
Out-OK "Metadata: UUID valid ($uuid)"
} else { $nameNode = $roleNode.SelectSingleNode(".//*[local-name()='Name']")
Out-ERR "Metadata: invalid UUID format '$uuid'" if ($nameNode -and $nameNode.InnerText) {
} Report-OK "Metadata: Name = $($nameNode.InnerText)"
} else {
$nameNode = $roleNode.SelectSingleNode(".//*[local-name()='Name']") Report-Error "Metadata: <Name> is empty or missing"
if ($nameNode -and $nameNode.InnerText) { }
Out-OK "Metadata: Name = $($nameNode.InnerText)"
} else { $synNode = $roleNode.SelectSingleNode(".//*[local-name()='Synonym']")
Out-ERR "Metadata: <Name> is empty or missing" if ($synNode -and $synNode.InnerXml) {
} Report-OK "Metadata: Synonym present"
} else {
$synNode = $roleNode.SelectSingleNode(".//*[local-name()='Synonym']") Report-Warn "Metadata: <Synonym> is empty"
if ($synNode -and $synNode.InnerXml) {
Out-OK "Metadata: Synonym present"
} else {
Out-WARN "Metadata: <Synonym> is empty"
}
} }
} catch {
Out-ERR "Metadata XML parse error: $($_.Exception.Message)"
} }
} catch {
Report-Error "Metadata XML parse error: $($_.Exception.Message)"
} }
} }
# --- 5. Check registration in Configuration.xml --- # --- 5. Check registration in Configuration.xml ---
# Infer paths: RightsPath = .../Roles/Name/Ext/Rights.xml $configDir = Split-Path $rolesDir -Parent
$extDir2 = Split-Path (Resolve-Path $RightsPath).Path -Parent $configXmlPath = Join-Path $configDir "Configuration.xml"
$roleDir2 = Split-Path $extDir2 -Parent $inferredRoleName = $roleDirName
$rolesDir2 = Split-Path $roleDir2 -Parent
$configDir2 = Split-Path $rolesDir2 -Parent
$configXmlPath2 = Join-Path $configDir2 "Configuration.xml"
$inferredRoleName = Split-Path $roleDir2 -Leaf
# Use metadata name if available # Use metadata name if available
if ($MetadataPath -and (Test-Path $MetadataPath)) { if (Test-Path $MetadataPath) {
try { try {
[xml]$metaXml2 = Get-Content -Path $MetadataPath -Encoding UTF8 [xml]$metaXml2 = Get-Content -Path $MetadataPath -Encoding UTF8
$nameNode2 = $metaXml2.DocumentElement.SelectSingleNode("//*[local-name()='Role']//*[local-name()='Name']") $nameNode2 = $metaXml2.DocumentElement.SelectSingleNode("//*[local-name()='Role']//*[local-name()='Name']")
@@ -424,10 +453,10 @@ if ($MetadataPath -and (Test-Path $MetadataPath)) {
} catch { } } catch { }
} }
if (Test-Path $configXmlPath2) { if (Test-Path $configXmlPath) {
$script:lines += "" Out-Line ""
try { try {
[xml]$cfgXml = Get-Content -Path $configXmlPath2 -Encoding UTF8 [xml]$cfgXml = Get-Content -Path $configXmlPath -Encoding UTF8
$cfgNs = New-Object System.Xml.XmlNamespaceManager($cfgXml.NameTable) $cfgNs = New-Object System.Xml.XmlNamespaceManager($cfgXml.NameTable)
$cfgNs.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses") $cfgNs.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
$childObj = $cfgXml.SelectSingleNode("//md:Configuration/md:ChildObjects", $cfgNs) $childObj = $cfgXml.SelectSingleNode("//md:Configuration/md:ChildObjects", $cfgNs)
@@ -441,22 +470,30 @@ if (Test-Path $configXmlPath2) {
} }
} }
if ($found) { if ($found) {
Out-OK "Configuration.xml: <Role>$inferredRoleName</Role> registered" Report-OK "Configuration.xml: <Role>$inferredRoleName</Role> registered"
} else { } else {
Out-WARN "Configuration.xml: <Role>$inferredRoleName</Role> NOT found in ChildObjects" Report-Warn "Configuration.xml: <Role>$inferredRoleName</Role> NOT found in ChildObjects"
} }
} }
} catch { } catch {
Out-WARN "Configuration.xml: parse error — $($_.Exception.Message)" Report-Warn "Configuration.xml: parse error — $($_.Exception.Message)"
} }
} }
# --- 6. Summary --- # --- 6. Summary ---
$script:lines += "---" # Insert header
$script:lines += "Result: $($script:errors) error(s), $($script:warnings) warning(s)" $script:output.Insert(0, "=== Validation: Role.$inferredRoleName ===$([Environment]::NewLine)") | Out-Null
$output = $script:lines -join "`n" $checks = $script:okCount + $script:errors + $script:warnings
if ($script:errors -eq 0 -and $script:warnings -eq 0 -and -not $Detailed) {
$result = "=== Validation OK: Role.$inferredRoleName ($checks checks) ==="
} else {
Out-Line ""
Out-Line "=== Result: $($script:errors) errors, $($script:warnings) warnings ($checks checks) ==="
$result = $script:output.ToString()
}
Write-Host $result
if ($OutFile) { if ($OutFile) {
$outPath = if ([System.IO.Path]::IsPathRooted($OutFile)) { $OutFile } else { Join-Path (Get-Location) $OutFile } $outPath = if ([System.IO.Path]::IsPathRooted($OutFile)) { $OutFile } else { Join-Path (Get-Location) $OutFile }
@@ -464,11 +501,9 @@ if ($OutFile) {
if (-not (Test-Path $outDir)) { if (-not (Test-Path $outDir)) {
New-Item -ItemType Directory -Path $outDir -Force | Out-Null New-Item -ItemType Directory -Path $outDir -Force | Out-Null
} }
$enc = New-Object System.Text.UTF8Encoding($true) $utf8Bom = New-Object System.Text.UTF8Encoding $true
[System.IO.File]::WriteAllText($outPath, $output, $enc) [System.IO.File]::WriteAllText($outPath, $result, $utf8Bom)
Write-Host "[OK] Validation result written to: $outPath" Write-Host "Written to: $outPath"
} else {
Write-Host $output
} }
if ($script:errors -gt 0) { exit 1 } else { exit 0 } if ($script:errors -gt 0) { exit 1 } else { exit 0 }
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# role-validate v1.0 — Validate 1C role Rights.xml structure # role-validate v1.1 — Validate 1C role Rights.xml structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
"""Validates role Rights.xml: root element, global flags, objects, rights, RLS, templates.""" """Validates role Rights.xml: root element, global flags, objects, rights, RLS, templates."""
import sys, os, argparse, re import sys, os, argparse, re
@@ -180,56 +180,80 @@ def main():
description='Validate 1C role Rights.xml structure', allow_abbrev=False description='Validate 1C role Rights.xml structure', allow_abbrev=False
) )
parser.add_argument('-RightsPath', dest='RightsPath', required=True) parser.add_argument('-RightsPath', dest='RightsPath', required=True)
parser.add_argument('-MetadataPath', dest='MetadataPath', default='')
parser.add_argument('-OutFile', dest='OutFile', default='') parser.add_argument('-OutFile', dest='OutFile', default='')
parser.add_argument('-Detailed', dest='Detailed', action='store_true')
parser.add_argument('-MaxErrors', dest='MaxErrors', type=int, default=30)
args = parser.parse_args() args = parser.parse_args()
rights_path = args.RightsPath rights_path = args.RightsPath
metadata_path = args.MetadataPath
out_file = args.OutFile out_file = args.OutFile
if not os.path.isabs(rights_path): if not os.path.isabs(rights_path):
rights_path = os.path.join(os.getcwd(), rights_path) rights_path = os.path.join(os.getcwd(), rights_path)
# A: Directory → Ext/Rights.xml
if os.path.isdir(rights_path):
rights_path = os.path.join(rights_path, 'Ext', 'Rights.xml')
# B1: Missing Ext/
if not os.path.exists(rights_path):
fn = os.path.basename(rights_path)
if fn == 'Rights.xml':
c = os.path.join(os.path.dirname(rights_path), 'Ext', fn)
if os.path.exists(c):
rights_path = c
resolved_path = os.path.abspath(rights_path)
# Auto-detect metadata: Roles/Name/Ext/Rights.xml → Roles/Name.xml
ext_dir = os.path.dirname(resolved_path)
role_dir = os.path.dirname(ext_dir)
roles_dir = os.path.dirname(role_dir)
role_dir_name = os.path.basename(role_dir)
metadata_path = os.path.join(roles_dir, f'{role_dir_name}.xml')
# --- Output helpers --- # --- Output helpers ---
lines = [] lines = []
errors = 0 errors = 0
warnings = 0 warnings = 0
ok_count = 0
stopped = False
def out_ok(msg): def report_ok(msg):
lines.append(f' OK {msg}') nonlocal ok_count
ok_count += 1
if args.Detailed:
lines.append(f'[OK] {msg}')
def out_warn(msg): def report_warn(msg):
nonlocal warnings nonlocal warnings
warnings += 1 warnings += 1
lines.append(f' WARN {msg}') lines.append(f'[WARN] {msg}')
def out_err(msg): def report_error(msg):
nonlocal errors nonlocal errors, stopped
errors += 1 errors += 1
lines.append(f' ERR {msg}') lines.append(f'[ERROR] {msg}')
if errors >= args.MaxErrors:
stopped = True
# --- 3. Validate Rights.xml --- # --- 3. Validate Rights.xml ---
lines.append(f'Validating: {rights_path}')
def finalize(): def write_output(text):
lines.append('---')
lines.append(f'Result: {errors} error(s), {warnings} warning(s)')
output = '\n'.join(lines)
if out_file: if out_file:
out_path = out_file if os.path.isabs(out_file) else os.path.join(os.getcwd(), out_file) out_path = out_file if os.path.isabs(out_file) else os.path.join(os.getcwd(), out_file)
out_dir = os.path.dirname(out_path) out_dir = os.path.dirname(out_path)
if out_dir and not os.path.exists(out_dir): if out_dir and not os.path.exists(out_dir):
os.makedirs(out_dir, exist_ok=True) os.makedirs(out_dir, exist_ok=True)
with open(out_path, 'w', encoding='utf-8-sig', newline='') as f: with open(out_path, 'w', encoding='utf-8-sig', newline='') as f:
f.write(output) f.write(text)
print(f'[OK] Validation result written to: {out_path}') print(f'Written to: {out_path}')
else: else:
print(output) print(text)
if not os.path.exists(rights_path): if not os.path.exists(rights_path):
out_err(f'File not found: {rights_path}') report_error(f'File not found: {rights_path}')
finalize() result = '\n'.join(lines)
write_output(result)
sys.exit(1) sys.exit(1)
# 3a. Parse XML # 3a. Parse XML
@@ -237,10 +261,11 @@ def main():
try: try:
xml_parser = etree.XMLParser(remove_blank_text=False) xml_parser = etree.XMLParser(remove_blank_text=False)
xml_doc = etree.parse(rights_path, xml_parser) xml_doc = etree.parse(rights_path, xml_parser)
out_ok('XML well-formed') report_ok('XML well-formed')
except etree.XMLSyntaxError as e: except etree.XMLSyntaxError as e:
out_err(f'XML parse error: {e}') report_error(f'XML parse error: {e}')
finalize() result = '\n'.join(lines)
write_output(result)
sys.exit(1) sys.exit(1)
root = xml_doc.getroot() root = xml_doc.getroot()
@@ -249,11 +274,11 @@ def main():
# 3b. Check root element # 3b. Check root element
if root_local != 'Rights': if root_local != 'Rights':
out_err(f"Root element is '{root_local}', expected 'Rights'") report_error(f"Root element is '{root_local}', expected 'Rights'")
elif root_ns != RIGHTS_NS: elif root_ns != RIGHTS_NS:
out_warn(f"Namespace is '{root_ns}', expected '{RIGHTS_NS}'") report_warn(f"Namespace is '{root_ns}', expected '{RIGHTS_NS}'")
else: else:
out_ok('Root element: <Rights> with correct namespace') report_ok('Root element: <Rights> with correct namespace')
# 3c. Global flags # 3c. Global flags
flag_names = ['setForNewObjects', 'setForAttributesByDefault', 'independentRightsOfChildObjects'] flag_names = ['setForNewObjects', 'setForAttributesByDefault', 'independentRightsOfChildObjects']
@@ -263,12 +288,12 @@ def main():
if len(nodes) > 0: if len(nodes) > 0:
val = nodes[0].text or '' val = nodes[0].text or ''
if val not in ('true', 'false'): if val not in ('true', 'false'):
out_warn(f"{fn} = '{val}' (expected 'true' or 'false')") report_warn(f"{fn} = '{val}' (expected 'true' or 'false')")
flags_found += 1 flags_found += 1
else: else:
out_warn(f'Missing global flag: {fn}') report_warn(f'Missing global flag: {fn}')
if flags_found == 3: if flags_found == 3:
out_ok('3 global flags present') report_ok('3 global flags present')
# 3d. Objects # 3d. Objects
objects = root.findall(f'{{{RIGHTS_NS}}}object') objects = root.findall(f'{{{RIGHTS_NS}}}object')
@@ -286,7 +311,7 @@ def main():
break break
if not obj_name: if not obj_name:
out_err('Object without <name>') report_error('Object without <name>')
continue continue
object_type = get_object_type(obj_name) object_type = get_object_type(obj_name)
@@ -294,7 +319,7 @@ def main():
# Check object type is known # Check object type is known
if not is_nested and object_type not in KNOWN_RIGHTS: if not is_nested and object_type not in KNOWN_RIGHTS:
out_warn(f"{obj_name}: unknown object type '{object_type}'") report_warn(f"{obj_name}: unknown object type '{object_type}'")
# Check rights # Check rights
for child in obj: for child in obj:
@@ -325,14 +350,14 @@ def main():
# Check condition not empty # Check condition not empty
cond_node = get_child_el(rc, 'condition', RIGHTS_NS) cond_node = get_child_el(rc, 'condition', RIGHTS_NS)
if cond_node is None or not (cond_node.text or ''): if cond_node is None or not (cond_node.text or ''):
out_warn(f"{obj_name}: RLS condition for '{r_name}' is empty") report_warn(f"{obj_name}: RLS condition for '{r_name}' is empty")
if not r_name: if not r_name:
out_err(f'{obj_name}: <right> without <name>') report_error(f'{obj_name}: <right> without <name>')
continue continue
if r_value not in ('true', 'false'): if r_value not in ('true', 'false'):
out_err(f"{obj_name}: right '{r_name}' has invalid value '{r_value}'") report_error(f"{obj_name}: right '{r_name}' has invalid value '{r_value}'")
continue continue
right_count += 1 right_count += 1
@@ -341,23 +366,23 @@ def main():
if is_nested: if is_nested:
if '.Command.' in obj_name: if '.Command.' in obj_name:
if r_name not in COMMAND_RIGHTS: if r_name not in COMMAND_RIGHTS:
out_warn(f"{obj_name}: '{r_name}' not valid for commands (only: View)") report_warn(f"{obj_name}: '{r_name}' not valid for commands (only: View)")
elif '.IntegrationServiceChannel.' in obj_name: elif '.IntegrationServiceChannel.' in obj_name:
if r_name not in CHANNEL_RIGHTS: if r_name not in CHANNEL_RIGHTS:
out_warn(f"{obj_name}: '{r_name}' not valid for channels (only: Use)") report_warn(f"{obj_name}: '{r_name}' not valid for channels (only: Use)")
else: else:
if r_name not in NESTED_RIGHTS: if r_name not in NESTED_RIGHTS:
out_warn(f"{obj_name}: '{r_name}' not valid for nested objects (only: View, Edit)") report_warn(f"{obj_name}: '{r_name}' not valid for nested objects (only: View, Edit)")
elif object_type in KNOWN_RIGHTS: elif object_type in KNOWN_RIGHTS:
valid_rights = KNOWN_RIGHTS[object_type] valid_rights = KNOWN_RIGHTS[object_type]
if r_name not in valid_rights: if r_name not in valid_rights:
similar = find_similar(r_name, valid_rights) similar = find_similar(r_name, valid_rights)
sug_str = f' Did you mean: {", ".join(similar)}?' if similar else '' sug_str = f' Did you mean: {", ".join(similar)}?' if similar else ''
out_warn(f"{obj_name}: unknown right '{r_name}'.{sug_str}") report_warn(f"{obj_name}: unknown right '{r_name}'.{sug_str}")
out_ok(f'{obj_count} objects, {right_count} rights') report_ok(f'{obj_count} objects, {right_count} rights')
if rls_count > 0: if rls_count > 0:
out_ok(f'{rls_count} RLS restrictions') report_ok(f'{rls_count} RLS restrictions')
# 3e. Templates # 3e. Templates
templates = root.findall(f'{{{RIGHTS_NS}}}restrictionTemplate') templates = root.findall(f'{{{RIGHTS_NS}}}restrictionTemplate')
@@ -378,100 +403,74 @@ def main():
elif local == 'condition': elif local == 'condition':
t_cond = child.text or '' t_cond = child.text or ''
if not t_name: if not t_name:
out_warn('Restriction template without <name>') report_warn('Restriction template without <name>')
else: else:
paren_idx = t_name.find('(') paren_idx = t_name.find('(')
short_name = t_name[:paren_idx] if paren_idx > 0 else t_name short_name = t_name[:paren_idx] if paren_idx > 0 else t_name
tpl_names.append(short_name) tpl_names.append(short_name)
if not t_cond: if not t_cond:
out_warn(f"Template '{t_name}': empty <condition>") report_warn(f"Template '{t_name}': empty <condition>")
out_ok(f'{len(templates)} templates: {", ".join(tpl_names)}') report_ok(f'{len(templates)} templates: {", ".join(tpl_names)}')
# --- 4. Validate metadata (optional) --- # --- 4. Validate metadata (optional) ---
inferred_role_name = '' inferred_role_name = ''
if metadata_path: if os.path.isfile(metadata_path):
lines.append('') lines.append('')
if not os.path.isabs(metadata_path): try:
metadata_path = os.path.join(os.getcwd(), metadata_path) meta_parser = etree.XMLParser(remove_blank_text=False)
meta_xml = etree.parse(metadata_path, meta_parser)
meta_root = meta_xml.getroot()
# Find <Role> element anywhere
role_node = None
for el in meta_root.iter():
if isinstance(el.tag, str) and etree.QName(el.tag).localname == 'Role':
role_node = el
break
if not os.path.exists(metadata_path): if role_node is None:
out_err(f'Metadata file not found: {metadata_path}') report_error('Metadata: <Role> element not found')
else: else:
try: uuid_val = role_node.get('uuid', '')
meta_parser = etree.XMLParser(remove_blank_text=False) if GUID_PATTERN.match(uuid_val):
meta_xml = etree.parse(metadata_path, meta_parser) report_ok(f'Metadata: UUID valid ({uuid_val})')
meta_root = meta_xml.getroot() else:
# Find <Role> element anywhere report_error(f"Metadata: invalid UUID format '{uuid_val}'")
role_node = None
for el in meta_root.iter(): # Find Name
if isinstance(el.tag, str) and etree.QName(el.tag).localname == 'Role': name_node = None
role_node = el for el in role_node.iter():
if isinstance(el.tag, str) and etree.QName(el.tag).localname == 'Name':
name_node = el
break break
if role_node is None: if name_node is not None and name_node.text:
out_err('Metadata: <Role> element not found') report_ok(f'Metadata: Name = {name_node.text}')
inferred_role_name = name_node.text
else: else:
uuid_val = role_node.get('uuid', '') report_error('Metadata: <Name> is empty or missing')
if GUID_PATTERN.match(uuid_val):
out_ok(f'Metadata: UUID valid ({uuid_val})')
else:
out_err(f"Metadata: invalid UUID format '{uuid_val}'")
# Find Name # Find Synonym
name_node = None syn_node = None
for el in role_node.iter(): for el in role_node.iter():
if isinstance(el.tag, str) and etree.QName(el.tag).localname == 'Name': if isinstance(el.tag, str) and etree.QName(el.tag).localname == 'Synonym':
name_node = el syn_node = el
break break
if name_node is not None and name_node.text: if syn_node is not None and len(syn_node) > 0:
out_ok(f'Metadata: Name = {name_node.text}') report_ok('Metadata: Synonym present')
inferred_role_name = name_node.text else:
else: report_warn('Metadata: <Synonym> is empty')
out_err('Metadata: <Name> is empty or missing') except etree.XMLSyntaxError as e:
report_error(f'Metadata XML parse error: {e}')
# Find Synonym
syn_node = None
for el in role_node.iter():
if isinstance(el.tag, str) and etree.QName(el.tag).localname == 'Synonym':
syn_node = el
break
if syn_node is not None and len(syn_node) > 0:
out_ok('Metadata: Synonym present')
else:
out_warn('Metadata: <Synonym> is empty')
except etree.XMLSyntaxError as e:
out_err(f'Metadata XML parse error: {e}')
# --- 5. Check registration in Configuration.xml --- # --- 5. Check registration in Configuration.xml ---
resolved_rights = os.path.abspath(rights_path)
ext_dir = os.path.dirname(resolved_rights) # Ext
role_dir = os.path.dirname(ext_dir) # RoleName
roles_dir = os.path.dirname(role_dir) # Roles
config_dir = os.path.dirname(roles_dir) # config root config_dir = os.path.dirname(roles_dir) # config root
config_xml_path = os.path.join(config_dir, 'Configuration.xml') config_xml_path = os.path.join(config_dir, 'Configuration.xml')
if not inferred_role_name: if not inferred_role_name:
inferred_role_name = os.path.basename(role_dir) inferred_role_name = os.path.basename(role_dir)
# Use metadata name if available (already set above if metadata was parsed)
if metadata_path and os.path.exists(metadata_path) and not inferred_role_name:
try:
meta_parser2 = etree.XMLParser(remove_blank_text=False)
meta_xml2 = etree.parse(metadata_path, meta_parser2)
for el in meta_xml2.getroot().iter():
if isinstance(el.tag, str) and etree.QName(el.tag).localname == 'Role':
for el2 in el.iter():
if isinstance(el2.tag, str) and etree.QName(el2.tag).localname == 'Name':
if el2.text:
inferred_role_name = el2.text
break
break
except Exception:
pass
if os.path.exists(config_xml_path): if os.path.exists(config_xml_path):
lines.append('') lines.append('')
try: try:
@@ -487,14 +486,25 @@ def main():
found = True found = True
break break
if found: if found:
out_ok(f'Configuration.xml: <Role>{inferred_role_name}</Role> registered') report_ok(f'Configuration.xml: <Role>{inferred_role_name}</Role> registered')
else: else:
out_warn(f'Configuration.xml: <Role>{inferred_role_name}</Role> NOT found in ChildObjects') report_warn(f'Configuration.xml: <Role>{inferred_role_name}</Role> NOT found in ChildObjects')
except etree.XMLSyntaxError as e: except etree.XMLSyntaxError as e:
out_warn(f'Configuration.xml: parse error \u2014 {e}') report_warn(f'Configuration.xml: parse error \u2014 {e}')
# --- 6. Summary --- # --- 6. Summary ---
finalize()
# Insert header at position 0
lines.insert(0, f'=== Validation: Role.{inferred_role_name} ===')
checks = ok_count + errors + warnings
if errors == 0 and warnings == 0 and not args.Detailed:
result = f'=== Validation OK: Role.{inferred_role_name} ({checks} checks) ==='
else:
lines.append('')
lines.append(f'=== Result: {errors} errors, {warnings} warnings ({checks} checks) ===')
result = '\n'.join(lines)
write_output(result)
sys.exit(1 if errors > 0 else 0) sys.exit(1 if errors > 0 else 0)
+1 -1
View File
@@ -1,7 +1,7 @@
--- ---
name: skd-compile name: skd-compile
description: Компиляция схемы компоновки данных 1С (СКД) из компактного JSON-определения. Используй когда нужно создать СКД с нуля description: Компиляция схемы компоновки данных 1С (СКД) из компактного JSON-определения. Используй когда нужно создать СКД с нуля
argument-hint: [-DefinitionFile <json> | -Value <json-string>] -OutputPath <Template.xml> argument-hint: "[-DefinitionFile <json> | -Value <json-string>] -OutputPath <Template.xml>"
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
+13 -39
View File
@@ -1,7 +1,7 @@
--- ---
name: skd-validate name: skd-validate
description: Валидация схемы компоновки данных 1С (СКД). Используй после создания или модификации СКД для проверки корректности description: Валидация схемы компоновки данных 1С (СКД). Используй после создания или модификации СКД для проверки корректности
argument-hint: <TemplatePath> [-MaxErrors 20] argument-hint: <TemplatePath> [-Detailed] [-MaxErrors 20]
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
@@ -12,16 +12,20 @@ allowed-tools:
Проверяет структурную корректность Template.xml схемы компоновки данных. Выявляет ошибки формата, битые ссылки, дубликаты имён. Проверяет структурную корректность Template.xml схемы компоновки данных. Выявляет ошибки формата, битые ссылки, дубликаты имён.
## Параметры и команда ## Параметры
| Параметр | Описание | | Параметр | Обяз. | Умолч. | Описание |
|----------|----------| |--------------|:-----:|---------|---------------------------------------------------------|
| `TemplatePath` | Путь к Template.xml или каталогу макета (авто-резолв в `Ext/Template.xml`) | | TemplatePath | да | — | Путь к Template.xml или каталогу макета |
| `MaxErrors` | Макс. ошибок до остановки (по умолчанию 20) | | Detailed | нет | — | Показывать [OK] для каждой проверки |
| `OutFile` | Записать результат в файл | | MaxErrors | нет | 20 | Остановиться после N ошибок |
| OutFile | нет | — | Записать результат в файл |
## Команда
```powershell ```powershell
powershell.exe -NoProfile -File .claude/skills/skd-validate/scripts/skd-validate.ps1 -TemplatePath "<путь>" powershell.exe -NoProfile -File .claude/skills/skd-validate/scripts/skd-validate.ps1 -TemplatePath "src/МойОтчёт/Templates/ОсновнаяСхема"
powershell.exe -NoProfile -File .claude/skills/skd-validate/scripts/skd-validate.ps1 -TemplatePath "Catalogs/Номенклатура/Templates/СКД/Ext/Template.xml"
``` ```
## Проверки (~30) ## Проверки (~30)
@@ -41,34 +45,4 @@ powershell.exe -NoProfile -File .claude/skills/skd-validate/scripts/skd-validate
| **Variants** | Наличие, name не пуст, settings element присутствует | | **Variants** | Наличие, name не пуст, settings element присутствует |
| **Settings** | selection/filter/order ссылаются на известные поля, comparisonType валиден, structure items типизированы | | **Settings** | selection/filter/order ссылаются на известные поля, comparisonType валиден, structure items типизированы |
## Коды выхода Exit code: 0 = OK, 1 = есть ошибки. По умолчанию краткий вывод. `-Detailed` для поштучной детализации.
| Код | Значение |
|-----|----------|
| 0 | Ошибок нет (могут быть предупреждения) |
| 1 | Есть ошибки |
## Пример вывода
```
=== Validation: Template.xml ===
[OK] XML parsed successfully
[OK] Root element: DataCompositionSchema
[OK] Default namespace correct
[OK] 1 dataSource(s) found, names unique
[OK] 1 dataSet(s) found, names unique
[OK] DataSet "НаборДанных1": 2 fields, dataPath unique
[OK] 1 totalField(s): dataPath and expression present
[OK] 1 settingsVariant(s) found
=== Result: 0 errors, 0 warnings ===
```
## Верификация
```
/skd-compile <JsonPath> <OutputPath> — генерация XML
/skd-validate <OutputPath> — проверка результата
/skd-info <OutputPath> — визуальная сводка
```
@@ -1,9 +1,11 @@
# skd-validate v1.0 — Validate 1C DCS structure # skd-validate v1.1 — Validate 1C DCS structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
[string]$TemplatePath, [string]$TemplatePath,
[switch]$Detailed,
[int]$MaxErrors = 20, [int]$MaxErrors = 20,
[string]$OutFile [string]$OutFile
@@ -14,12 +16,28 @@ $ErrorActionPreference = "Stop"
# --- Resolve path --- # --- Resolve path ---
if (-not $TemplatePath.EndsWith(".xml")) { if (-not [System.IO.Path]::IsPathRooted($TemplatePath)) {
$candidate = Join-Path (Join-Path $TemplatePath "Ext") "Template.xml" $TemplatePath = Join-Path (Get-Location).Path $TemplatePath
if (Test-Path $candidate) { }
$TemplatePath = $candidate # A: Directory → Ext/Template.xml
if (Test-Path $TemplatePath -PathType Container) {
$TemplatePath = Join-Path (Join-Path $TemplatePath "Ext") "Template.xml"
}
# B1: Missing Ext/ (e.g. Templates/СКД/Template.xml → Templates/СКД/Ext/Template.xml)
if (-not (Test-Path $TemplatePath)) {
$fn = [System.IO.Path]::GetFileName($TemplatePath)
if ($fn -eq "Template.xml") {
$c = Join-Path (Join-Path (Split-Path $TemplatePath) "Ext") $fn
if (Test-Path $c) { $TemplatePath = $c }
} }
} }
# B2: Descriptor (Templates/СКД.xml → Templates/СКД/Ext/Template.xml)
if (-not (Test-Path $TemplatePath) -and $TemplatePath.EndsWith(".xml")) {
$stem = [System.IO.Path]::GetFileNameWithoutExtension($TemplatePath)
$dir = Split-Path $TemplatePath
$c = Join-Path (Join-Path (Join-Path $dir $stem) "Ext") "Template.xml"
if (Test-Path $c) { $TemplatePath = $c }
}
if (-not (Test-Path $TemplatePath)) { if (-not (Test-Path $TemplatePath)) {
Write-Error "File not found: $TemplatePath" Write-Error "File not found: $TemplatePath"
@@ -33,6 +51,7 @@ $fileName = [System.IO.Path]::GetFileName($resolvedPath)
$script:errors = 0 $script:errors = 0
$script:warnings = 0 $script:warnings = 0
$script:okCount = 0
$script:stopped = $false $script:stopped = $false
$script:output = New-Object System.Text.StringBuilder 4096 $script:output = New-Object System.Text.StringBuilder 4096
@@ -43,7 +62,8 @@ function Out-Line {
function Report-OK { function Report-OK {
param([string]$msg) param([string]$msg)
Out-Line "[OK] $msg" $script:okCount++
if ($Detailed) { Out-Line "[OK] $msg" }
} }
function Report-Error { function Report-Error {
@@ -62,10 +82,14 @@ function Report-Warn {
} }
$finalize = { $finalize = {
Out-Line "" $checks = $script:okCount + $script:errors + $script:warnings
Out-Line "=== Result: $($script:errors) errors, $($script:warnings) warnings ===" if ($script:errors -eq 0 -and $script:warnings -eq 0 -and -not $Detailed) {
$result = "=== Validation OK: $fileName ($checks checks) ==="
$result = $script:output.ToString() } else {
Out-Line ""
Out-Line "=== Result: $($script:errors) errors, $($script:warnings) warnings ($checks checks) ==="
$result = $script:output.ToString()
}
Write-Host $result Write-Host $result
if ($OutFile) { if ($OutFile) {
@@ -1,4 +1,4 @@
# skd-validate v1.0 — Validate 1C DCS structure (Python port) # skd-validate v1.1 — Validate 1C DCS structure (Python port)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import os import os
@@ -13,20 +13,35 @@ sys.stderr.reconfigure(encoding="utf-8")
parser = argparse.ArgumentParser(allow_abbrev=False) parser = argparse.ArgumentParser(allow_abbrev=False)
parser.add_argument("-TemplatePath", required=True) parser.add_argument("-TemplatePath", required=True)
parser.add_argument("-Detailed", action="store_true")
parser.add_argument("-MaxErrors", type=int, default=20) parser.add_argument("-MaxErrors", type=int, default=20)
parser.add_argument("-OutFile", default="") parser.add_argument("-OutFile", default="")
args = parser.parse_args() args = parser.parse_args()
template_path = args.TemplatePath template_path = args.TemplatePath
detailed = args.Detailed
max_errors = args.MaxErrors max_errors = args.MaxErrors
out_file = args.OutFile out_file = args.OutFile
# ── resolve path ───────────────────────────────────────────── # ── resolve path ─────────────────────────────────────────────
if not template_path.endswith(".xml"): # A: Directory → Ext/Template.xml
candidate = os.path.join(template_path, "Ext", "Template.xml") if os.path.isdir(template_path):
if os.path.exists(candidate): template_path = os.path.join(template_path, 'Ext', 'Template.xml')
template_path = candidate # B1: Missing Ext/ (e.g. Templates/СКД/Template.xml → Templates/СКД/Ext/Template.xml)
if not os.path.exists(template_path):
fn = os.path.basename(template_path)
if fn == 'Template.xml':
c = os.path.join(os.path.dirname(template_path), 'Ext', fn)
if os.path.exists(c):
template_path = c
# B2: Descriptor (.xml → dir/Ext/Template.xml)
if not os.path.exists(template_path) and template_path.endswith('.xml'):
stem = os.path.splitext(os.path.basename(template_path))[0]
parent = os.path.dirname(template_path)
c = os.path.join(parent, stem, 'Ext', 'Template.xml')
if os.path.exists(c):
template_path = c
if not os.path.exists(template_path): if not os.path.exists(template_path):
print(f"File not found: {template_path}", file=sys.stderr) print(f"File not found: {template_path}", file=sys.stderr)
@@ -39,6 +54,7 @@ file_name = os.path.basename(resolved_path)
errors = 0 errors = 0
warnings = 0 warnings = 0
ok_count = 0
stopped = False stopped = False
output_lines = [] output_lines = []
@@ -48,7 +64,10 @@ def out_line(msg):
def report_ok(msg): def report_ok(msg):
out_line(f"[OK] {msg}") global ok_count
ok_count += 1
if detailed:
out_line(f"[OK] {msg}")
def report_error(msg): def report_error(msg):
@@ -66,9 +85,13 @@ def report_warn(msg):
def finalize(): def finalize():
out_line("") checks = ok_count + errors + warnings
out_line(f"=== Result: {errors} errors, {warnings} warnings ===") if errors == 0 and warnings == 0 and not detailed:
result = "\n".join(output_lines) result = f"=== Validation OK: {file_name} ({checks} checks) ==="
else:
out_line("")
out_line(f"=== Result: {errors} errors, {warnings} warnings ({checks} checks) ===")
result = "\n".join(output_lines)
print(result) print(result)
if out_file: if out_file:
with open(out_file, "w", encoding="utf-8-sig") as f: with open(out_file, "w", encoding="utf-8-sig") as f:
+29 -21
View File
@@ -1,7 +1,7 @@
--- ---
name: subsystem-validate name: subsystem-validate
description: Валидация подсистемы 1С. Используй после создания или модификации подсистемы для проверки корректности description: Валидация подсистемы 1С. Используй после создания или модификации подсистемы для проверки корректности
argument-hint: <SubsystemPath> [-MaxErrors 30] argument-hint: <SubsystemPath> [-Detailed] [-MaxErrors 30]
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
@@ -12,30 +12,38 @@ allowed-tools:
Проверяет структурную корректность XML-файла подсистемы из выгрузки конфигурации. Проверяет структурную корректность XML-файла подсистемы из выгрузки конфигурации.
## Параметры и команда ## Параметры
| Параметр | Описание | | Параметр | Обяз. | Умолч. | Описание |
|----------|----------| |---------------|:-----:|---------|--------------------------------------------|
| `SubsystemPath` | Путь к XML-файлу подсистемы | | SubsystemPath | да | — | Путь к XML-файлу подсистемы |
| `MaxErrors` | Максимум ошибок до остановки (по умолчанию 30) | | Detailed | нет | — | Показывать [OK] для каждой проверки |
| `OutFile` | Записать результат в файл | | MaxErrors | нет | 30 | Остановиться после N ошибок |
| OutFile | нет | — | Записать результат в файл |
## Команда
```powershell ```powershell
powershell.exe -NoProfile -File '.claude/skills/subsystem-validate/scripts/subsystem-validate.ps1' -SubsystemPath '<путь>' powershell.exe -NoProfile -File ".claude/skills/subsystem-validate/scripts/subsystem-validate.ps1" -SubsystemPath "Subsystems/Продажи"
powershell.exe -NoProfile -File ".claude/skills/subsystem-validate/scripts/subsystem-validate.ps1" -SubsystemPath "Subsystems/Продажи.xml"
``` ```
## Проверки (13) ## Проверки (13)
1. XML well-formedness + root structure (MetaDataObject/Subsystem) | # | Проверка | Серьёзность |
2. Properties — 9 обязательных свойств |---|----------|-------------|
3. Name — непустой, валидный идентификатор | 1 | XML well-formedness + root structure (MetaDataObject/Subsystem) | ERROR |
4. Synonym — непустой (хотя бы один v8:item) | 2 | Properties — 9 обязательных свойств | ERROR |
5. Булевы свойства — содержат true/false | 3 | Name — непустой, валидный идентификатор | ERROR |
6. Content — формат xr:Item, xsi:type | 4 | Synonym — непустой (хотя бы один v8:item) | WARN |
7. Content — нет дубликатов | 5 | Булевы свойства — содержат true/false | ERROR |
8. ChildObjects — элементы непустые | 6 | Content — формат xr:Item, xsi:type | ERROR |
9. ChildObjects — нет дубликатов | 7 | Content — нет дубликатов | WARN |
10. ChildObjects → файлы существуют | 8 | ChildObjects — элементы непустые | ERROR |
11. CommandInterface.xml — well-formedness | 9 | ChildObjects — нет дубликатов | WARN |
12. Picture — формат ссылки | 10 | ChildObjects → файлы существуют | WARN |
13. UseOneCommand=true → ровно 1 элемент в Content | 11 | CommandInterface.xml — well-formedness | ERROR |
| 12 | Picture — формат ссылки | ERROR |
| 13 | UseOneCommand=true → ровно 1 элемент в Content | ERROR |
Exit code: 0 = OK, 1 = есть ошибки. По умолчанию краткий вывод. `-Detailed` для поштучной детализации.
@@ -1,7 +1,8 @@
# subsystem-validate v1.0 — Validate 1C subsystem XML structure # subsystem-validate v1.1 — Validate 1C subsystem XML structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)][string]$SubsystemPath, [Parameter(Mandatory)][string]$SubsystemPath,
[switch]$Detailed,
[int]$MaxErrors = 30, [int]$MaxErrors = 30,
[string]$OutFile [string]$OutFile
) )
@@ -43,10 +44,14 @@ $resolvedPath = (Resolve-Path $SubsystemPath).Path
$script:errors = 0 $script:errors = 0
$script:warnings = 0 $script:warnings = 0
$script:stopped = $false $script:stopped = $false
$script:okCount = 0
$script:output = New-Object System.Text.StringBuilder 8192 $script:output = New-Object System.Text.StringBuilder 8192
function Out-Line([string]$msg) { $script:output.AppendLine($msg) | Out-Null } function Out-Line([string]$msg) { $script:output.AppendLine($msg) | Out-Null }
function Report-OK([string]$msg) { Out-Line "[OK] $msg" } function Report-OK([string]$msg) {
$script:okCount++
if ($Detailed) { Out-Line "[OK] $msg" }
}
function Report-Error([string]$msg) { function Report-Error([string]$msg) {
$script:errors++ $script:errors++
Out-Line "[ERROR] $msg" Out-Line "[ERROR] $msg"
@@ -120,8 +125,6 @@ if (-not $script:stopped) {
# --- 3. Name --- # --- 3. Name ---
$nameEl = $props.SelectSingleNode("md:Name", $ns) $nameEl = $props.SelectSingleNode("md:Name", $ns)
$subName = if ($nameEl) { $nameEl.InnerText.Trim() } else { "" } $subName = if ($nameEl) { $nameEl.InnerText.Trim() } else { "" }
Out-Line ""
Out-Line "=== Validation: Subsystem.$subName ==="
# Re-insert header at position 0 # Re-insert header at position 0
$headerLine = "=== Validation: Subsystem.$subName ===" $headerLine = "=== Validation: Subsystem.$subName ==="
$script:output.Insert(0, "$headerLine`r`n`r`n") | Out-Null $script:output.Insert(0, "$headerLine`r`n`r`n") | Out-Null
@@ -256,8 +259,6 @@ if (-not $script:stopped) {
} else { } else {
Report-Warn "10. ChildObjects files: missing: $($missingFiles -join ', ')" Report-Warn "10. ChildObjects files: missing: $($missingFiles -join ', ')"
} }
} else {
Report-OK "10. ChildObjects files: n/a (no children)"
} }
# --- 11. CommandInterface.xml --- # --- 11. CommandInterface.xml ---
@@ -307,10 +308,16 @@ if (-not $script:stopped) {
} }
# --- Finalize --- # --- Finalize ---
Out-Line "---" $checks = $script:okCount + $script:errors + $script:warnings
Out-Line "Errors: $($script:errors), Warnings: $($script:warnings)"
if ($script:errors -eq 0 -and $script:warnings -eq 0 -and -not $Detailed) {
$result = "=== Validation OK: Subsystem.$subName ($checks checks) ==="
} else {
Out-Line ""
Out-Line "=== Result: $($script:errors) errors, $($script:warnings) warnings ($checks checks) ==="
$result = $script:output.ToString()
}
$result = $script:output.ToString()
Write-Host $result Write-Host $result
if ($OutFile) { if ($OutFile) {
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# subsystem-validate v1.0 — Validate 1C subsystem XML structure # subsystem-validate v1.1 — Validate 1C subsystem XML structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
"""Validates subsystem XML file structure, properties, content items, child objects.""" """Validates subsystem XML file structure, properties, content items, child objects."""
import sys, os, argparse, re import sys, os, argparse, re
@@ -22,18 +22,22 @@ IDENT_PATTERN = re.compile(
class Reporter: class Reporter:
def __init__(self, max_errors): def __init__(self, max_errors, detailed=False):
self.errors = 0 self.errors = 0
self.warnings = 0 self.warnings = 0
self.ok_count = 0
self.stopped = False self.stopped = False
self.max_errors = max_errors self.max_errors = max_errors
self.detailed = detailed
self.lines = [] self.lines = []
def out(self, msg=''): def out(self, msg=''):
self.lines.append(msg) self.lines.append(msg)
def ok(self, msg): def ok(self, msg):
self.lines.append(f'[OK] {msg}') self.ok_count += 1
if self.detailed:
self.lines.append(f'[OK] {msg}')
def error(self, msg): def error(self, msg):
self.errors += 1 self.errors += 1
@@ -67,11 +71,13 @@ def main():
description='Validate 1C subsystem XML structure', allow_abbrev=False description='Validate 1C subsystem XML structure', allow_abbrev=False
) )
parser.add_argument('-SubsystemPath', dest='SubsystemPath', required=True) parser.add_argument('-SubsystemPath', dest='SubsystemPath', required=True)
parser.add_argument('-Detailed', action='store_true')
parser.add_argument('-MaxErrors', dest='MaxErrors', type=int, default=30) parser.add_argument('-MaxErrors', dest='MaxErrors', type=int, default=30)
parser.add_argument('-OutFile', dest='OutFile', default='') parser.add_argument('-OutFile', dest='OutFile', default='')
args = parser.parse_args() args = parser.parse_args()
subsystem_path = args.SubsystemPath subsystem_path = args.SubsystemPath
detailed = args.Detailed
max_errors = args.MaxErrors max_errors = args.MaxErrors
out_file = args.OutFile out_file = args.OutFile
@@ -105,7 +111,7 @@ def main():
sys.exit(1) sys.exit(1)
resolved_path = os.path.abspath(subsystem_path) resolved_path = os.path.abspath(subsystem_path)
r = Reporter(max_errors) r = Reporter(max_errors, detailed)
# --- 1. XML well-formedness + root structure --- # --- 1. XML well-formedness + root structure ---
xml_doc = None xml_doc = None
@@ -240,8 +246,6 @@ def main():
r.warn(f'7. Content: duplicates found: {", ".join(dupes)}') r.warn(f'7. Content: duplicates found: {", ".join(dupes)}')
else: else:
r.ok('7. Content: no duplicates') r.ok('7. Content: no duplicates')
else:
r.ok('7. Content: no duplicates (empty)')
# --- 8. ChildObjects entries non-empty --- # --- 8. ChildObjects entries non-empty ---
child_objs = sub.find('md:ChildObjects', NS) child_objs = sub.find('md:ChildObjects', NS)
@@ -272,8 +276,6 @@ def main():
r.error(f'9. ChildObjects: duplicates: {", ".join(dupes)}') r.error(f'9. ChildObjects: duplicates: {", ".join(dupes)}')
else: else:
r.ok('9. ChildObjects: no duplicates') r.ok('9. ChildObjects: no duplicates')
else:
r.ok('9. ChildObjects: no duplicates (empty)')
# --- 10. ChildObjects files exist --- # --- 10. ChildObjects files exist ---
if len(child_names) > 0: if len(child_names) > 0:
@@ -289,8 +291,6 @@ def main():
r.ok(f'10. ChildObjects files: all {len(child_names)} files exist') r.ok(f'10. ChildObjects files: all {len(child_names)} files exist')
else: else:
r.warn(f'10. ChildObjects files: missing: {", ".join(missing_files)}') r.warn(f'10. ChildObjects files: missing: {", ".join(missing_files)}')
else:
r.ok('10. ChildObjects files: n/a (no children)')
# --- 11. CommandInterface.xml --- # --- 11. CommandInterface.xml ---
parent_dir2 = os.path.dirname(resolved_path) parent_dir2 = os.path.dirname(resolved_path)
@@ -331,10 +331,14 @@ def main():
r.ok('13. UseOneCommand: false (no constraint)') r.ok('13. UseOneCommand: false (no constraint)')
# --- Finalize --- # --- Finalize ---
r.out('---') checks = r.ok_count + r.errors + r.warnings
r.out(f'Errors: {r.errors}, Warnings: {r.warnings}') if r.errors == 0 and r.warnings == 0 and not detailed:
result = f'=== Validation OK: Subsystem.{sub_name} ({checks} checks) ==='
else:
r.out('')
r.out(f'=== Result: {r.errors} errors, {r.warnings} warnings ({checks} checks) ===')
result = '\r\n'.join(r.lines) + '\r\n'
result = r.text()
print(result, end='') print(result, end='')
if out_file: if out_file: