feat(validate): brief output by default, -Detailed for verbose

All 10 validation skills (meta, epf, skd, cf, cfe, form, mxl, role,
subsystem, interface) now output a single summary line on success:
=== Validation OK: Type.Name (N checks) ===

Errors/warnings always shown. Full per-check [OK] output behind -Detailed flag.
Removed all N/A check lines. Unified role-validate output format.
Trimmed SKILL.md files from 42-119 to 51-67 lines.
Version bumps: meta-validate v1.2, all others v1.1.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-03-09 18:14:44 +03:00
parent 93e4130ff2
commit 422e397381
30 changed files with 685 additions and 816 deletions
+21 -39
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,31 @@ allowed-tools:
Проверяет Configuration.xml на структурные ошибки: XML well-formedness, InternalInfo, свойства, enum-значения, ChildObjects, DefaultLanguage, файлы языков, каталоги объектов. Проверяет Configuration.xml на структурные ошибки: XML well-formedness, InternalInfo, свойства, enum-значения, ChildObjects, DefaultLanguage, файлы языков, каталоги объектов.
## Параметры и команда ## Использование
| Параметр | Описание | ```
|----------|----------| /cf-validate <ConfigPath>
| `ConfigPath` | Путь к Configuration.xml или каталогу выгрузки | /cf-validate upload/cfempty — каталог выгрузки
| `MaxErrors` | Остановиться после N ошибок (default: 30) | ```
| `OutFile` | Записать результат в файл (UTF-8 BOM) |
`ConfigPath` авторезолв: если указана директория — ищет `Configuration.xml`.
## Параметры
| Параметр | Обяз. | Умолч. | Описание |
|------------|:-----:|---------|-------------------------------------------------|
| ConfigPath | да | — | Путь к Configuration.xml или каталогу выгрузки |
| Detailed | нет | — | Показывать [OK] для каждой проверки |
| 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 "<путь>"
``` ```
## Выполняемые проверки ## Проверки
| # | Проверка | Серьёзность | | # | Проверка | Серьёзность |
|---|----------|-------------| |---|----------|-------------|
@@ -37,34 +49,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)
+18 -16
View File
@@ -1,24 +1,34 @@
--- ---
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`, но для расширений.
## Использование
```
/cfe-validate <ExtensionPath>
/cfe-validate src — каталог расширения
```
`ExtensionPath` авторезолв: если указана директория — ищет `Configuration.xml`.
## Параметры ## Параметры
| Параметр | Описание | По умолчанию | | Параметр | Обяз. | Умолч. | Описание |
|----------|----------|--------------| |---------------|:-----:|---------|-------------------------------------------------|
| `ExtensionPath` | Путь к каталогу или Configuration.xml расширения (обязат.) | — | | ExtensionPath | да | — | Путь к каталогу или Configuration.xml расширения |
| `MaxErrors` | Лимит ошибок | 30 | | Detailed | нет | — | Показывать [OK] для каждой проверки |
| `OutFile` | Записать результат в файл | — | | MaxErrors | нет | 30 | Остановиться после N ошибок |
| OutFile | нет | — | Записать результат в файл |
## Команда ## Команда
@@ -40,12 +50,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')
+13 -47
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,25 +10,25 @@ allowed-tools:
# /epf-validate — валидация внешней обработки (EPF) # /epf-validate — валидация внешней обработки (EPF)
Проверяет структурную корректность XML-исходников внешней обработки: корневую структуру, InternalInfo, свойства, ChildObjects, реквизиты, табличные части, уникальность имён, наличие файлов форм и макетов. Проверяет структурную корректность XML-исходников внешней обработки: корневую структуру, InternalInfo, свойства, ChildObjects, реквизиты, табличные части, уникальность имён, наличие файлов форм и макетов. Также работает для внешних отчётов (ERF).
Скрипт также работает для внешних отчётов (ERF) — автоопределение по типу элемента. См. `/erf-validate`.
## Использование ## Использование
``` ```
/epf-validate <ObjectPath> /epf-validate <ObjectPath>
/epf-validate src/МояОбработка — авторезолв в <dir>/<dir>.xml
``` ```
`ObjectPath` авторезолв: если указана директория — ищет `<dirName>/<dirName>.xml`.
## Параметры ## Параметры
| Параметр | Обязательный | По умолчанию | Описание | | Параметр | Обяз. | Умолч. | Описание |
|------------|:------------:|--------------|-------------------------------------------------| |------------|:-----:|---------|-------------------------------------------------|
| ObjectPath | да | — | Путь к корневому XML или каталогу обработки | | ObjectPath | да | — | Путь к корневому XML или каталогу обработки |
| MaxErrors | нет | 30 | Остановиться после N ошибок | | Detailed | нет | — | Показывать [OK] для каждой проверки |
| OutFile | нет | — | Записать результат в файл (UTF-8 BOM) | | MaxErrors | нет | 30 | Остановиться после N ошибок |
| OutFile | нет | — | Записать результат в файл (UTF-8 BOM) |
`ObjectPath` авторезолв: если указана директория — ищет `<dirName>/<dirName>.xml`.
## Команда ## Команда
@@ -36,7 +36,7 @@ allowed-tools:
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 "<путь>"
``` ```
## Выполняемые проверки ## Проверки
| # | Проверка | Серьёзность | | # | Проверка | Серьёзность |
|----|-------------------------------------------------------|--------------| |----|-------------------------------------------------------|--------------|
@@ -51,38 +51,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()
+12 -48
View File
@@ -1,29 +1,31 @@
--- ---
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> /form-validate <FormPath>
/form-validate src/МояОбработка/Forms/Форма/Ext/Form.xml
``` ```
## Параметры ## Параметры
| Параметр | Обязательный | По умолчанию | Описание | | Параметр | Обяз. | Умолч. | Описание |
|-----------|:------------:|--------------|-----------------------------| |-----------|:-----:|---------|-----------------------------------------|
| FormPath | да | — | Путь к файлу Form.xml | | FormPath | да | — | Путь к файлу Form.xml |
| MaxErrors | нет | 30 | Остановиться после N ошибок | | Detailed | нет | — | Показывать [OK] для каждой проверки |
| MaxErrors | нет | 30 | Остановиться после N ошибок |
## Команда ## Команда
@@ -31,7 +33,7 @@ allowed-tools:
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 "<путь>"
``` ```
## Выполняемые проверки ## Проверки
| # | Проверка | Серьёзность | | # | Проверка | Серьёзность |
|---|---|---| |---|---|---|
@@ -51,42 +53,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,9 +1,11 @@
# 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
) )
@@ -40,10 +42,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 +77,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 +648,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,10 +23,12 @@ 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.isfile(form_path): if not os.path.isfile(form_path):
@@ -48,22 +50,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 +82,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 +326,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 +360,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 +399,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 +417,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 +567,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)
+16 -43
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,15 +10,23 @@ allowed-tools:
# /interface-validate — валидация CommandInterface.xml # /interface-validate — валидация CommandInterface.xml
Проверяет XML командного интерфейса из выгрузки конфигурации на структурные ошибки: корневой элемент, допустимые секции, порядок, формат ссылок на команды, дубликаты. Проверяет XML командного интерфейса на структурные ошибки: корневой элемент, допустимые секции, порядок, формат ссылок на команды, дубликаты.
## Использование
```
/interface-validate <CIPath>
/interface-validate Subsystems/Продажи/Ext/CommandInterface.xml
```
## Параметры ## Параметры
| Параметр | Обязательный | По умолчанию | Описание | | Параметр | Обяз. | Умолч. | Описание |
|-----------|:------------:|--------------|------------------------------------| |-----------|:-----:|---------|-----------------------------------------|
| CIPath | да | — | Путь к CommandInterface.xml | | CIPath | да | — | Путь к CommandInterface.xml |
| MaxErrors | нет | 30 | Остановиться после N ошибок | | Detailed | нет | — | Показывать [OK] для каждой проверки |
| OutFile | нет | — | Записать результат в файл (UTF-8 BOM) | | MaxErrors | нет | 30 | Остановиться после N ошибок |
| OutFile | нет | — | Записать результат в файл (UTF-8 BOM) |
## Команда ## Команда
@@ -44,39 +52,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
) )
@@ -33,11 +34,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 +363,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
@@ -103,7 +109,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 +216,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 +226,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 +256,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 +280,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 +303,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 +313,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 +332,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 +342,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 +363,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:
+17 -68
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,26 +10,25 @@ allowed-tools:
# /meta-validate — валидация объекта метаданных 1С # /meta-validate — валидация объекта метаданных 1С
Проверяет XML объекта метаданных из выгрузки конфигурации на структурные ошибки: корневую структуру, InternalInfo, свойства, допустимые значения, StandardAttributes, ChildObjects, уникальность имён, табличные части, кросс-свойства, вложенные структуры HTTP/Web-сервисов. Проверяет XML объекта метаданных из выгрузки конфигурации на структурные ошибки.
## Использование ## Использование
``` ```
/meta-validate <ObjectPath> /meta-validate <ObjectPath>
/meta-validate path1.xml|path2.xml|path3.xml — batch mode /meta-validate path1.xml|path2.xml — batch mode
``` ```
## Параметры
| Параметр | Обязательный | По умолчанию | Описание |
|------------|:------------:|--------------|-------------------------------------------------|
| ObjectPath | да | — | Путь к XML-файлу или каталогу объекта. Несколько путей через `\|` для batch |
| MaxErrors | нет | 30 | Остановиться после N ошибок (per object) |
| OutFile | нет | — | Записать результат в файл (UTF-8 BOM) |
`ObjectPath` авторезолв: если указана директория — ищет `<dirName>/<dirName>.xml`. `ObjectPath` авторезолв: если указана директория — ищет `<dirName>/<dirName>.xml`.
**Batch mode**: при нескольких путях через `|` каждый объект валидируется отдельно, в конце выводится сводка `=== Batch: N objects, X passed, Y failed ===`. ## Параметры
| Параметр | Обяз. | Умолч. | Описание |
|------------|:-----:|---------|-------------------------------------------------|
| ObjectPath | да | — | Путь к XML-файлу или каталогу. Через `\|` для batch |
| Detailed | нет | — | Показывать [OK] для каждой проверки |
| MaxErrors | нет | 30 | Остановиться после N ошибок (per object) |
| OutFile | нет | — | Записать результат в файл (UTF-8 BOM) |
## Команда ## Команда
@@ -45,7 +44,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 +55,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 ──────────────────────────────────────────────
+14 -41
View File
@@ -1,33 +1,34 @@
--- ---
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 <TemplatePath>
/mxl-validate <ProcessorName> <TemplateName> /mxl-validate -ProcessorName "МояОбработка" -TemplateName "Макет"
``` ```
## Параметры ## Параметры
| Параметр | Обязательный | По умолчанию | Описание | | Параметр | Обяз. | Умолч. | Описание |
|---------------|:------------:|--------------|------------------------------------------| |---------------|:-----:|---------|--------------------------------------------|
| TemplatePath | нет | — | Прямой путь к Template.xml | | TemplatePath | нет | — | Прямой путь к Template.xml |
| ProcessorName | нет | — | Имя обработки (альтернатива пути) | | ProcessorName | нет | — | Имя обработки (альтернатива пути) |
| TemplateName | нет | — | Имя макета (альтернатива пути) | | TemplateName | нет | — | Имя макета (альтернатива пути) |
| SrcDir | нет | `src` | Каталог исходников | | SrcDir | нет | `src` | Каталог исходников |
| MaxErrors | нет | 20 | Остановиться после N ошибок | | Detailed | нет | — | Показывать [OK] для каждой проверки |
| MaxErrors | нет | 20 | Остановиться после N ошибок |
Укажите либо `-TemplatePath`, либо оба `-ProcessorName` и `-TemplateName`. Укажите либо `-TemplatePath`, либо оба `-ProcessorName` и `-TemplateName`.
@@ -37,12 +38,7 @@ allowed-tools:
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 "<путь>"
``` ```
Или по имени обработки/макета: ## Проверки
```powershell
powershell.exe -NoProfile -File .claude/skills/mxl-validate/scripts/mxl-validate.ps1 -ProcessorName "<Имя>" -TemplateName "<Макет>" [-SrcDir "<каталог>"]
```
## Выполняемые проверки
| # | Проверка | Серьёзность | | # | Проверка | Серьёзность |
|---|---|---| |---|---|---|
@@ -59,27 +55,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
) )
@@ -44,10 +45,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 +69,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 +381,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 ---
@@ -85,13 +92,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 +183,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 +193,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 +353,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)
+27 -81
View File
@@ -1,7 +1,7 @@
--- ---
name: role-validate name: role-validate
description: Валидация роли 1С. Используй после создания или модификации роли для проверки корректности description: Валидация роли 1С. Используй после создания или модификации роли для проверки корректности
argument-hint: <RightsPath> argument-hint: <RightsPath> [-Detailed] [-MetadataPath <path>]
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
@@ -14,92 +14,38 @@ allowed-tools:
## Использование ## Использование
``` ```
/role-validate <RightsPath> [MetadataPath] /role-validate <RightsPath>
/role-validate Roles/МояРоль/Ext/Rights.xml Roles/МояРоль.xml
``` ```
## Запуск скрипта ## Параметры
```powershell | Параметр | Обяз. | Умолч. | Описание |
powershell.exe -NoProfile -File .claude/skills/role-validate/scripts/role-validate.ps1 -RightsPath <path> [-MetadataPath <path>] [-OutFile <output.txt>] |--------------|:-----:|---------|-------------------------------------------------|
``` | RightsPath | да | — | Путь к `Rights.xml` роли |
| MetadataPath | нет | — | Путь к метаданным роли (`Roles/ИмяРоли.xml`) |
### Параметры | Detailed | нет | — | Показывать [OK] для каждой проверки |
| OutFile | нет | — | Записать результат в файл (UTF-8 BOM) |
| Параметр | Обязательный | Описание |
|----------|:------------:|----------|
| `-RightsPath` | да | Путь к `Rights.xml` роли |
| `-MetadataPath` | нет | Путь к метаданным роли (`Roles/ИмяРоли.xml`) |
| `-OutFile` | нет | Записать результат в файл (UTF-8 BOM). Без этого — вывод в консоль |
**Важно:** Для кириллических путей используй `-OutFile` и читай результат через Read tool. **Важно:** Для кириллических путей используй `-OutFile` и читай результат через Read tool.
## Команда
```powershell
powershell.exe -NoProfile -File .claude/skills/role-validate/scripts/role-validate.ps1 -RightsPath <path> [-MetadataPath <path>]
```
## Проверки ## Проверки
### 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,4 +1,4 @@
# 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)]
@@ -6,7 +6,11 @@ param(
[string]$MetadataPath, [string]$MetadataPath,
[string]$OutFile [string]$OutFile,
[switch]$Detailed,
[int]$MaxErrors = 30
) )
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
@@ -132,25 +136,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 {
@@ -176,18 +191,17 @@ function Find-Similar {
# --- 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
} }
@@ -195,17 +209,18 @@ if (-not (Test-Path $RightsPath)) {
# 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 +230,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 +245,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 +272,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 +281,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 +304,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 +325,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 +341,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,56 +364,56 @@ 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 (optional) ---
if ($MetadataPath) { if ($MetadataPath) {
$script:lines += "" Out-Line ""
if (-not (Test-Path $MetadataPath)) { if (-not (Test-Path $MetadataPath)) {
Out-ERR "Metadata file not found: $MetadataPath" Report-Error "Metadata file not found: $MetadataPath"
} else { } else {
try { try {
[xml]$metaXml = Get-Content -Path $MetadataPath -Encoding UTF8 [xml]$metaXml = Get-Content -Path $MetadataPath -Encoding UTF8
$roleNode = $metaXml.DocumentElement.SelectSingleNode("//*[local-name()='Role']") $roleNode = $metaXml.DocumentElement.SelectSingleNode("//*[local-name()='Role']")
if (-not $roleNode) { if (-not $roleNode) {
Out-ERR "Metadata: <Role> element not found" Report-Error "Metadata: <Role> element not found"
} else { } else {
$uuid = $roleNode.GetAttribute("uuid") $uuid = $roleNode.GetAttribute("uuid")
if ($uuid -match '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$') { 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)" Report-OK "Metadata: UUID valid ($uuid)"
} else { } else {
Out-ERR "Metadata: invalid UUID format '$uuid'" Report-Error "Metadata: invalid UUID format '$uuid'"
} }
$nameNode = $roleNode.SelectSingleNode(".//*[local-name()='Name']") $nameNode = $roleNode.SelectSingleNode(".//*[local-name()='Name']")
if ($nameNode -and $nameNode.InnerText) { if ($nameNode -and $nameNode.InnerText) {
Out-OK "Metadata: Name = $($nameNode.InnerText)" Report-OK "Metadata: Name = $($nameNode.InnerText)"
} else { } else {
Out-ERR "Metadata: <Name> is empty or missing" Report-Error "Metadata: <Name> is empty or missing"
} }
$synNode = $roleNode.SelectSingleNode(".//*[local-name()='Synonym']") $synNode = $roleNode.SelectSingleNode(".//*[local-name()='Synonym']")
if ($synNode -and $synNode.InnerXml) { if ($synNode -and $synNode.InnerXml) {
Out-OK "Metadata: Synonym present" Report-OK "Metadata: Synonym present"
} else { } else {
Out-WARN "Metadata: <Synonym> is empty" Report-Warn "Metadata: <Synonym> is empty"
} }
} }
} catch { } catch {
Out-ERR "Metadata XML parse error: $($_.Exception.Message)" Report-Error "Metadata XML parse error: $($_.Exception.Message)"
} }
} }
} }
@@ -425,7 +440,7 @@ if ($MetadataPath -and (Test-Path $MetadataPath)) {
} }
if (Test-Path $configXmlPath2) { if (Test-Path $configXmlPath2) {
$script:lines += "" Out-Line ""
try { try {
[xml]$cfgXml = Get-Content -Path $configXmlPath2 -Encoding UTF8 [xml]$cfgXml = Get-Content -Path $configXmlPath2 -Encoding UTF8
$cfgNs = New-Object System.Xml.XmlNamespaceManager($cfgXml.NameTable) $cfgNs = New-Object System.Xml.XmlNamespaceManager($cfgXml.NameTable)
@@ -441,22 +456,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 +487,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
@@ -182,6 +182,8 @@ def main():
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('-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
@@ -195,41 +197,45 @@ def main():
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 +243,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 +256,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 +270,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 +293,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 +301,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 +332,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 +348,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,14 +385,14 @@ 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 = ''
@@ -396,7 +403,7 @@ def main():
metadata_path = os.path.join(os.getcwd(), metadata_path) metadata_path = os.path.join(os.getcwd(), metadata_path)
if not os.path.exists(metadata_path): if not os.path.exists(metadata_path):
out_err(f'Metadata file not found: {metadata_path}') report_error(f'Metadata file not found: {metadata_path}')
else: else:
try: try:
meta_parser = etree.XMLParser(remove_blank_text=False) meta_parser = etree.XMLParser(remove_blank_text=False)
@@ -410,13 +417,13 @@ def main():
break break
if role_node is None: if role_node is None:
out_err('Metadata: <Role> element not found') report_error('Metadata: <Role> element not found')
else: else:
uuid_val = role_node.get('uuid', '') uuid_val = role_node.get('uuid', '')
if GUID_PATTERN.match(uuid_val): if GUID_PATTERN.match(uuid_val):
out_ok(f'Metadata: UUID valid ({uuid_val})') report_ok(f'Metadata: UUID valid ({uuid_val})')
else: else:
out_err(f"Metadata: invalid UUID format '{uuid_val}'") report_error(f"Metadata: invalid UUID format '{uuid_val}'")
# Find Name # Find Name
name_node = None name_node = None
@@ -426,10 +433,10 @@ def main():
break break
if name_node is not None and name_node.text: if name_node is not None and name_node.text:
out_ok(f'Metadata: Name = {name_node.text}') report_ok(f'Metadata: Name = {name_node.text}')
inferred_role_name = name_node.text inferred_role_name = name_node.text
else: else:
out_err('Metadata: <Name> is empty or missing') report_error('Metadata: <Name> is empty or missing')
# Find Synonym # Find Synonym
syn_node = None syn_node = None
@@ -439,11 +446,11 @@ def main():
break break
if syn_node is not None and len(syn_node) > 0: if syn_node is not None and len(syn_node) > 0:
out_ok('Metadata: Synonym present') report_ok('Metadata: Synonym present')
else: else:
out_warn('Metadata: <Synonym> is empty') report_warn('Metadata: <Synonym> is empty')
except etree.XMLSyntaxError as e: except etree.XMLSyntaxError as e:
out_err(f'Metadata XML parse error: {e}') report_error(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) resolved_rights = os.path.abspath(rights_path)
@@ -487,14 +494,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)
+20 -38
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,13 +12,25 @@ allowed-tools:
Проверяет структурную корректность Template.xml схемы компоновки данных. Выявляет ошибки формата, битые ссылки, дубликаты имён. Проверяет структурную корректность Template.xml схемы компоновки данных. Выявляет ошибки формата, битые ссылки, дубликаты имён.
## Параметры и команда ## Использование
| Параметр | Описание | ```
|----------|----------| /skd-validate <TemplatePath>
| `TemplatePath` | Путь к Template.xml или каталогу макета (авто-резолв в `Ext/Template.xml`) | /skd-validate path/to/Ext/Template.xml
| `MaxErrors` | Макс. ошибок до остановки (по умолчанию 20) | ```
| `OutFile` | Записать результат в файл |
`TemplatePath` авторезолв: если указан каталог макета — ищет `Ext/Template.xml`.
## Параметры
| Параметр | Обяз. | Умолч. | Описание |
|--------------|:-----:|---------|---------------------------------------------------------|
| TemplatePath | да | — | Путь к Template.xml или каталогу макета |
| Detailed | нет | — | Показывать [OK] для каждой проверки |
| 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 "<путь>"
@@ -41,34 +53,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
@@ -33,6 +35,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 +46,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 +66,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,11 +13,13 @@ 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
@@ -39,6 +41,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 +51,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 +72,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:
+34 -20
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,13 +12,23 @@ allowed-tools:
Проверяет структурную корректность XML-файла подсистемы из выгрузки конфигурации. Проверяет структурную корректность XML-файла подсистемы из выгрузки конфигурации.
## Параметры и команда ## Использование
| Параметр | Описание | ```
|----------|----------| /subsystem-validate <SubsystemPath>
| `SubsystemPath` | Путь к XML-файлу подсистемы | /subsystem-validate Subsystems/Продажи.xml
| `MaxErrors` | Максимум ошибок до остановки (по умолчанию 30) | ```
| `OutFile` | Записать результат в файл |
## Параметры
| Параметр | Обяз. | Умолч. | Описание |
|---------------|:-----:|---------|--------------------------------------------|
| SubsystemPath | да | — | Путь к XML-файлу подсистемы |
| Detailed | нет | — | Показывать [OK] для каждой проверки |
| 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 '<путь>'
@@ -26,16 +36,20 @@ powershell.exe -NoProfile -File '.claude/skills/subsystem-validate/scripts/subsy
## Проверки (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: