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