Auto-build: opencode (powershell) from 6d119eb

This commit is contained in:
github-actions[bot]
2026-06-04 09:28:00 +00:00
commit 1350759977
263 changed files with 110444 additions and 0 deletions
@@ -0,0 +1,403 @@
# interface-validate v1.1 — Validate 1C CommandInterface.xml structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)][Alias('Path')][string]$CIPath,
[switch]$Detailed,
[int]$MaxErrors = 30,
[string]$OutFile
)
$ErrorActionPreference = "Stop"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Resolve path ---
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
}
$resolvedPath = (Resolve-Path $CIPath).Path
# --- Derive context name from path ---
$contextName = ""
$parentParts = $resolvedPath -split '[/\\]'
for ($i = 0; $i -lt $parentParts.Count; $i++) {
if ($parentParts[$i] -eq "Subsystems" -and ($i + 1) -lt $parentParts.Count) {
$contextName = $parentParts[$i + 1]
}
}
if (-not $contextName) { $contextName = "Root" }
# --- Output infrastructure ---
$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) {
$script:okCount++
if ($Detailed) { Out-Line "[OK] $msg" }
}
function Report-Error([string]$msg) {
$script:errors++
Out-Line "[ERROR] $msg"
if ($script:errors -ge $MaxErrors) { $script:stopped = $true }
}
function Report-Warn([string]$msg) {
$script:warnings++
Out-Line "[WARN] $msg"
}
Out-Line "=== Validation: CommandInterface ($contextName) ==="
Out-Line ""
# --- Valid section names and order ---
$validSections = @("CommandsVisibility","CommandsPlacement","CommandsOrder","SubsystemsOrder","GroupsOrder")
# Command reference patterns
$stdCmdPattern = '^[A-Za-z]+\.[^\s\.]+\.StandardCommand\.\w+$'
$customCmdPattern = '^[A-Za-z]+\.[^\s\.]+\.Command\.\w+$'
$commonCmdPattern = '^CommonCommand\.\w+$'
$uuidCmdPattern = '^0:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
# --- 1. XML well-formedness + root structure ---
$xmlDoc = $null
try {
[xml]$xmlDoc = Get-Content -Path $resolvedPath -Encoding UTF8
} catch {
Report-Error "1. XML parse error: $($_.Exception.Message)"
$script:stopped = $true
}
if (-not $script:stopped) {
$root = $xmlDoc.DocumentElement
if ($root.LocalName -ne "CommandInterface") {
Report-Error "1. Root element: expected <CommandInterface>, got <$($root.LocalName)>"
$script:stopped = $true
} else {
$nsUri = $root.NamespaceURI
$version = $root.GetAttribute("version")
$expectedNs = "http://v8.1c.ru/8.3/xcf/extrnprops"
if ($nsUri -ne $expectedNs) {
Report-Error "1. Root namespace: expected $expectedNs, got $nsUri"
} elseif (-not $version) {
Report-Warn "1. Root structure: CommandInterface, namespace valid, but no version attribute"
} else {
Report-OK "1. Root structure: CommandInterface, version $version, namespace valid"
}
}
}
# --- Setup namespace manager ---
$ns = $null
if (-not $script:stopped) {
$ns = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable)
$ns.AddNamespace("ci", "http://v8.1c.ru/8.3/xcf/extrnprops")
$ns.AddNamespace("xr", "http://v8.1c.ru/8.3/xcf/readable")
$ns.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance")
$ns.AddNamespace("xs", "http://www.w3.org/2001/XMLSchema")
}
# --- 2. Valid child elements ---
if (-not $script:stopped) {
$root = $xmlDoc.DocumentElement
$foundSections = @()
$invalidElements = @()
foreach ($child in $root.ChildNodes) {
if ($child.NodeType -ne 'Element') { continue }
if ($child.LocalName -in $validSections) {
$foundSections += $child.LocalName
} else {
$invalidElements += $child.LocalName
}
}
if ($invalidElements.Count -gt 0) {
Report-Error "2. Invalid child elements: $($invalidElements -join ', ')"
} else {
Report-OK "2. Child elements: $($foundSections.Count) valid sections"
}
}
# --- 3. Section order ---
if (-not $script:stopped) {
$orderOk = $true
$lastIdx = -1
foreach ($sec in $foundSections) {
$idx = [array]::IndexOf($validSections, $sec)
if ($idx -lt $lastIdx) {
Report-Error "3. Section order: '$sec' appears after a later section (expected: CommandsVisibility -> CommandsPlacement -> CommandsOrder -> SubsystemsOrder -> GroupsOrder)"
$orderOk = $false
break
}
$lastIdx = $idx
}
if ($orderOk) { Report-OK "3. Section order: correct" }
}
# --- 4. No duplicate sections ---
if (-not $script:stopped) {
$dupes = $foundSections | Group-Object | Where-Object { $_.Count -gt 1 }
if ($dupes) {
$dupeNames = ($dupes | ForEach-Object { $_.Name }) -join ", "
Report-Error "4. Duplicate sections: $dupeNames"
} else {
Report-OK "4. No duplicate sections"
}
}
# --- 5. CommandsVisibility ---
if (-not $script:stopped) {
$visSection = $root.SelectSingleNode("ci:CommandsVisibility", $ns)
if ($visSection) {
$visOk = $true
$visNames = @()
$visCount = 0
foreach ($cmd in $visSection.ChildNodes) {
if ($cmd.NodeType -ne 'Element') { continue }
$visCount++
$cmdName = $cmd.GetAttribute("name")
if (-not $cmdName) {
Report-Error "5. CommandsVisibility: Command element without 'name' attribute"
$visOk = $false; continue
}
$visNames += $cmdName
$script:allCommandNames += $cmdName
$visibility = $cmd.SelectSingleNode("ci:Visibility", $ns)
if (-not $visibility) {
Report-Error "5. CommandsVisibility[$cmdName]: missing <Visibility>"
$visOk = $false; continue
}
$common = $visibility.SelectSingleNode("xr:Common", $ns)
if (-not $common) {
Report-Error "5. CommandsVisibility[$cmdName]: missing <xr:Common>"
$visOk = $false; continue
}
$val = $common.InnerText.Trim()
if ($val -ne "true" -and $val -ne "false") {
Report-Error "5. CommandsVisibility[$cmdName]: xr:Common='$val' (expected true/false)"
$visOk = $false
}
}
if ($visOk) { Report-OK "5. CommandsVisibility: $visCount entries, all valid" }
} else {
Report-OK "5. CommandsVisibility: not present"
$visNames = @()
}
}
# --- 6. CommandsVisibility duplicates ---
if (-not $script:stopped) {
if ($visNames.Count -gt 0) {
$dupes = $visNames | Group-Object | Where-Object { $_.Count -gt 1 }
if ($dupes) {
$dupeNames = ($dupes | ForEach-Object { $_.Name }) -join ", "
Report-Warn "6. CommandsVisibility: duplicates: $dupeNames"
} else {
Report-OK "6. CommandsVisibility: no duplicates"
}
} else {
Report-OK "6. CommandsVisibility: no duplicates (empty)"
}
}
# --- 7. CommandsPlacement ---
if (-not $script:stopped) {
$plcSection = $root.SelectSingleNode("ci:CommandsPlacement", $ns)
if ($plcSection) {
$plcOk = $true
$plcCount = 0
foreach ($cmd in $plcSection.ChildNodes) {
if ($cmd.NodeType -ne 'Element') { continue }
$plcCount++
$cmdName = $cmd.GetAttribute("name")
if (-not $cmdName) {
Report-Error "7. CommandsPlacement: Command without 'name' attribute"
$plcOk = $false; continue
}
$script:allCommandNames += $cmdName
$grpEl = $cmd.SelectSingleNode("ci:CommandGroup", $ns)
if (-not $grpEl -or -not $grpEl.InnerText.Trim()) {
Report-Error "7. CommandsPlacement[$cmdName]: missing or empty <CommandGroup>"
$plcOk = $false; continue
}
$placementEl = $cmd.SelectSingleNode("ci:Placement", $ns)
if (-not $placementEl) {
Report-Error "7. CommandsPlacement[$cmdName]: missing <Placement>"
$plcOk = $false
} elseif ($placementEl.InnerText.Trim() -ne "Auto") {
Report-Warn "7. CommandsPlacement[$cmdName]: Placement='$($placementEl.InnerText.Trim())' (expected Auto)"
}
}
if ($plcOk) { Report-OK "7. CommandsPlacement: $plcCount entries, all valid" }
} else {
Report-OK "7. CommandsPlacement: not present"
}
}
# --- 8. CommandsOrder ---
if (-not $script:stopped) {
$ordSection = $root.SelectSingleNode("ci:CommandsOrder", $ns)
if ($ordSection) {
$ordOk = $true
$ordCount = 0
foreach ($cmd in $ordSection.ChildNodes) {
if ($cmd.NodeType -ne 'Element') { continue }
$ordCount++
$cmdName = $cmd.GetAttribute("name")
if (-not $cmdName) {
Report-Error "8. CommandsOrder: Command without 'name' attribute"
$ordOk = $false; continue
}
$script:allCommandNames += $cmdName
$grpEl = $cmd.SelectSingleNode("ci:CommandGroup", $ns)
if (-not $grpEl -or -not $grpEl.InnerText.Trim()) {
Report-Error "8. CommandsOrder[$cmdName]: missing or empty <CommandGroup>"
$ordOk = $false
}
}
if ($ordOk) { Report-OK "8. CommandsOrder: $ordCount entries, all valid" }
} else {
Report-OK "8. CommandsOrder: not present"
}
}
# --- 9. SubsystemsOrder format ---
if (-not $script:stopped) {
$subSection = $root.SelectSingleNode("ci:SubsystemsOrder", $ns)
$subNames = @()
if ($subSection) {
$subOk = $true
$subCount = 0
foreach ($sub in $subSection.ChildNodes) {
if ($sub.NodeType -ne 'Element') { continue }
$subCount++
$text = $sub.InnerText.Trim()
$subNames += $text
if (-not $text) {
Report-Error "9. SubsystemsOrder: empty <Subsystem> element"
$subOk = $false
} elseif ($text -notmatch '^Subsystem\.') {
Report-Error "9. SubsystemsOrder: '$text' - expected format Subsystem.X..."
$subOk = $false
}
}
if ($subOk) { Report-OK "9. SubsystemsOrder: $subCount entries, all valid format" }
} else {
Report-OK "9. SubsystemsOrder: not present"
}
}
# --- 10. SubsystemsOrder duplicates ---
if (-not $script:stopped) {
if ($subNames.Count -gt 0) {
$dupes = $subNames | Group-Object | Where-Object { $_.Count -gt 1 }
if ($dupes) {
$dupeNames = ($dupes | ForEach-Object { $_.Name }) -join ", "
Report-Warn "10. SubsystemsOrder: duplicates: $dupeNames"
} else {
Report-OK "10. SubsystemsOrder: no duplicates"
}
} else {
Report-OK "10. SubsystemsOrder: no duplicates (empty)"
}
}
# --- 11. GroupsOrder entries ---
if (-not $script:stopped) {
$grpSection = $root.SelectSingleNode("ci:GroupsOrder", $ns)
$grpNames = @()
if ($grpSection) {
$grpOk = $true
$grpCount = 0
foreach ($grp in $grpSection.ChildNodes) {
if ($grp.NodeType -ne 'Element') { continue }
$grpCount++
$text = $grp.InnerText.Trim()
$grpNames += $text
if (-not $text) {
Report-Error "11. GroupsOrder: empty <Group> element"
$grpOk = $false
}
}
if ($grpOk) { Report-OK "11. GroupsOrder: $grpCount entries, all valid" }
} else {
Report-OK "11. GroupsOrder: not present"
}
}
# --- 12. GroupsOrder duplicates ---
if (-not $script:stopped) {
if ($grpNames.Count -gt 0) {
$dupes = $grpNames | Group-Object | Where-Object { $_.Count -gt 1 }
if ($dupes) {
$dupeNames = ($dupes | ForEach-Object { $_.Name }) -join ", "
Report-Warn "12. GroupsOrder: duplicates: $dupeNames"
} else {
Report-OK "12. GroupsOrder: no duplicates"
}
} else {
Report-OK "12. GroupsOrder: no duplicates (empty)"
}
}
# --- 13. Command reference format ---
if (-not $script:stopped) {
if ($script:allCommandNames.Count -gt 0) {
$badRefs = @()
foreach ($ref in $script:allCommandNames) {
if ($ref -match $stdCmdPattern) { continue }
if ($ref -match $customCmdPattern) { continue }
if ($ref -match $commonCmdPattern) { continue }
if ($ref -match $uuidCmdPattern) { continue }
$badRefs += $ref
}
if ($badRefs.Count -eq 0) {
Report-OK "13. Command reference format: all $($script:allCommandNames.Count) valid"
} else {
$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){' ...'})"
}
}
}
# --- Finalize ---
$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()
}
Write-Host $result
if ($OutFile) {
if (-not [System.IO.Path]::IsPathRooted($OutFile)) {
$OutFile = Join-Path (Get-Location).Path $OutFile
}
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText($OutFile, $result, $utf8Bom)
Write-Host "Written to: $OutFile"
}
if ($script:errors -gt 0) { exit 1 } else { exit 0 }
@@ -0,0 +1,400 @@
#!/usr/bin/env python3
# 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
from lxml import etree
NS_CI = 'http://v8.1c.ru/8.3/xcf/extrnprops'
NS_XR = 'http://v8.1c.ru/8.3/xcf/readable'
NS_XSI = 'http://www.w3.org/2001/XMLSchema-instance'
NS_XS = 'http://www.w3.org/2001/XMLSchema'
NS = {
'ci': NS_CI,
'xr': NS_XR,
'xsi': NS_XSI,
'xs': NS_XS,
}
VALID_SECTIONS = [
'CommandsVisibility', 'CommandsPlacement', 'CommandsOrder',
'SubsystemsOrder', 'GroupsOrder'
]
STD_CMD_PATTERN = re.compile(r'^[A-Za-z]+\.[^\s\.]+\.StandardCommand\.\w+$')
CUSTOM_CMD_PATTERN = re.compile(r'^[A-Za-z]+\.[^\s\.]+\.Command\.\w+$')
COMMON_CMD_PATTERN = re.compile(r'^CommonCommand\.\w+$')
UUID_CMD_PATTERN = re.compile(
r'^0:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
)
class Reporter:
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.ok_count += 1
if self.detailed:
self.lines.append(f'[OK] {msg}')
def error(self, msg):
self.errors += 1
self.lines.append(f'[ERROR] {msg}')
if self.errors >= self.max_errors:
self.stopped = True
def warn(self, msg):
self.warnings += 1
self.lines.append(f'[WARN] {msg}')
def text(self):
return '\r\n'.join(self.lines) + '\r\n'
def find_duplicates(items):
seen = {}
dupes = []
for item in items:
seen[item] = seen.get(item, 0) + 1
for item, count in seen.items():
if count > 1 and item not in dupes:
dupes.append(item)
return dupes
def main():
sys.stdout.reconfigure(encoding="utf-8")
sys.stderr.reconfigure(encoding="utf-8")
parser = argparse.ArgumentParser(
description='Validate 1C CommandInterface.xml structure', allow_abbrev=False
)
parser.add_argument('-CIPath', '-Path', 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
# --- Resolve path ---
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)
resolved_path = os.path.abspath(ci_path)
# --- Derive context name from path ---
context_name = ''
parts = re.split(r'[/\\]', resolved_path)
for i in range(len(parts)):
if parts[i] == 'Subsystems' and (i + 1) < len(parts):
context_name = parts[i + 1]
if not context_name:
context_name = 'Root'
r = Reporter(max_errors, detailed)
all_command_names = []
r.out(f'=== Validation: CommandInterface ({context_name}) ===')
r.out('')
# --- 1. XML well-formedness + root structure ---
xml_doc = None
try:
xml_parser = etree.XMLParser(remove_blank_text=False)
xml_doc = etree.parse(resolved_path, xml_parser)
except etree.XMLSyntaxError as e:
r.error(f'1. XML parse error: {e}')
r.stopped = True
root = None
if not r.stopped:
root = xml_doc.getroot()
root_local = etree.QName(root.tag).localname
if root_local != 'CommandInterface':
r.error(f'1. Root element: expected <CommandInterface>, got <{root_local}>')
r.stopped = True
else:
ns_uri = etree.QName(root.tag).namespace or ''
version = root.get('version', '')
expected_ns = NS_CI
if ns_uri != expected_ns:
r.error(f'1. Root namespace: expected {expected_ns}, got {ns_uri}')
elif not version:
r.warn('1. Root structure: CommandInterface, namespace valid, but no version attribute')
else:
r.ok(f'1. Root structure: CommandInterface, version {version}, namespace valid')
# --- 2. Valid child elements ---
found_sections = []
if not r.stopped:
invalid_elements = []
for child in root:
if not isinstance(child.tag, str):
continue
local_name = etree.QName(child.tag).localname
if local_name in VALID_SECTIONS:
found_sections.append(local_name)
else:
invalid_elements.append(local_name)
if len(invalid_elements) > 0:
r.error(f'2. Invalid child elements: {", ".join(invalid_elements)}')
else:
r.ok(f'2. Child elements: {len(found_sections)} valid sections')
# --- 3. Section order ---
if not r.stopped:
order_ok = True
last_idx = -1
for sec in found_sections:
idx = VALID_SECTIONS.index(sec) if sec in VALID_SECTIONS else -1
if idx < last_idx:
r.error(f"3. Section order: '{sec}' appears after a later section (expected: CommandsVisibility -> CommandsPlacement -> CommandsOrder -> SubsystemsOrder -> GroupsOrder)")
order_ok = False
break
last_idx = idx
if order_ok:
r.ok('3. Section order: correct')
# --- 4. No duplicate sections ---
if not r.stopped:
dupes = find_duplicates(found_sections)
if dupes:
r.error(f'4. Duplicate sections: {", ".join(dupes)}')
else:
r.ok('4. No duplicate sections')
# --- 5. CommandsVisibility ---
vis_names = []
if not r.stopped:
vis_section = root.find(f'{{{NS_CI}}}CommandsVisibility')
if vis_section is not None:
vis_ok = True
vis_count = 0
for cmd in vis_section:
if not isinstance(cmd.tag, str):
continue
vis_count += 1
cmd_name = cmd.get('name', '')
if not cmd_name:
r.error("5. CommandsVisibility: Command element without 'name' attribute")
vis_ok = False
continue
vis_names.append(cmd_name)
all_command_names.append(cmd_name)
visibility = cmd.find(f'{{{NS_CI}}}Visibility')
if visibility is None:
r.error(f'5. CommandsVisibility[{cmd_name}]: missing <Visibility>')
vis_ok = False
continue
common = visibility.find(f'{{{NS_XR}}}Common')
if common is None:
r.error(f'5. CommandsVisibility[{cmd_name}]: missing <xr:Common>')
vis_ok = False
continue
val = (common.text or '').strip()
if val not in ('true', 'false'):
r.error(f"5. CommandsVisibility[{cmd_name}]: xr:Common='{val}' (expected true/false)")
vis_ok = False
if vis_ok:
r.ok(f'5. CommandsVisibility: {vis_count} entries, all valid')
# CommandsVisibility not present — no check needed
# --- 6. CommandsVisibility duplicates ---
if not r.stopped:
if len(vis_names) > 0:
dupes = find_duplicates(vis_names)
if dupes:
r.warn(f'6. CommandsVisibility: duplicates: {", ".join(dupes)}')
else:
r.ok('6. CommandsVisibility: no duplicates')
# --- 7. CommandsPlacement ---
if not r.stopped:
plc_section = root.find(f'{{{NS_CI}}}CommandsPlacement')
if plc_section is not None:
plc_ok = True
plc_count = 0
for cmd in plc_section:
if not isinstance(cmd.tag, str):
continue
plc_count += 1
cmd_name = cmd.get('name', '')
if not cmd_name:
r.error("7. CommandsPlacement: Command without 'name' attribute")
plc_ok = False
continue
all_command_names.append(cmd_name)
grp_el = cmd.find(f'{{{NS_CI}}}CommandGroup')
if grp_el is None or not (grp_el.text or '').strip():
r.error(f'7. CommandsPlacement[{cmd_name}]: missing or empty <CommandGroup>')
plc_ok = False
continue
placement_el = cmd.find(f'{{{NS_CI}}}Placement')
if placement_el is None:
r.error(f'7. CommandsPlacement[{cmd_name}]: missing <Placement>')
plc_ok = False
elif (placement_el.text or '').strip() != 'Auto':
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')
# CommandsPlacement not present — no check needed
# --- 8. CommandsOrder ---
if not r.stopped:
ord_section = root.find(f'{{{NS_CI}}}CommandsOrder')
if ord_section is not None:
ord_ok = True
ord_count = 0
for cmd in ord_section:
if not isinstance(cmd.tag, str):
continue
ord_count += 1
cmd_name = cmd.get('name', '')
if not cmd_name:
r.error("8. CommandsOrder: Command without 'name' attribute")
ord_ok = False
continue
all_command_names.append(cmd_name)
grp_el = cmd.find(f'{{{NS_CI}}}CommandGroup')
if grp_el is None or not (grp_el.text or '').strip():
r.error(f'8. CommandsOrder[{cmd_name}]: missing or empty <CommandGroup>')
ord_ok = False
if ord_ok:
r.ok(f'8. CommandsOrder: {ord_count} entries, all valid')
# CommandsOrder not present — no check needed
# --- 9. SubsystemsOrder format ---
sub_names = []
if not r.stopped:
sub_section = root.find(f'{{{NS_CI}}}SubsystemsOrder')
if sub_section is not None:
sub_ok = True
sub_count = 0
for sub_el in sub_section:
if not isinstance(sub_el.tag, str):
continue
sub_count += 1
text = (sub_el.text or '').strip()
sub_names.append(text)
if not text:
r.error('9. SubsystemsOrder: empty <Subsystem> element')
sub_ok = False
elif not text.startswith('Subsystem.'):
r.error(f"9. SubsystemsOrder: '{text}' - expected format Subsystem.X...")
sub_ok = False
if sub_ok:
r.ok(f'9. SubsystemsOrder: {sub_count} entries, all valid format')
# SubsystemsOrder not present — no check needed
# --- 10. SubsystemsOrder duplicates ---
if not r.stopped:
if len(sub_names) > 0:
dupes = find_duplicates(sub_names)
if dupes:
r.warn(f'10. SubsystemsOrder: duplicates: {", ".join(dupes)}')
else:
r.ok('10. SubsystemsOrder: no duplicates')
# --- 11. GroupsOrder entries ---
grp_names = []
if not r.stopped:
grp_section = root.find(f'{{{NS_CI}}}GroupsOrder')
if grp_section is not None:
grp_ok = True
grp_count = 0
for grp in grp_section:
if not isinstance(grp.tag, str):
continue
grp_count += 1
text = (grp.text or '').strip()
grp_names.append(text)
if not text:
r.error('11. GroupsOrder: empty <Group> element')
grp_ok = False
if grp_ok:
r.ok(f'11. GroupsOrder: {grp_count} entries, all valid')
# GroupsOrder not present — no check needed
# --- 12. GroupsOrder duplicates ---
if not r.stopped:
if len(grp_names) > 0:
dupes = find_duplicates(grp_names)
if dupes:
r.warn(f'12. GroupsOrder: duplicates: {", ".join(dupes)}')
else:
r.ok('12. GroupsOrder: no duplicates')
# --- 13. Command reference format ---
if not r.stopped:
if len(all_command_names) > 0:
bad_refs = []
for ref in all_command_names:
if STD_CMD_PATTERN.match(ref):
continue
if CUSTOM_CMD_PATTERN.match(ref):
continue
if COMMON_CMD_PATTERN.match(ref):
continue
if UUID_CMD_PATTERN.match(ref):
continue
bad_refs.append(ref)
if len(bad_refs) == 0:
r.ok(f'13. Command reference format: all {len(all_command_names)} valid')
else:
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}')
# --- Finalize ---
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'
print(result, end='')
if out_file:
if not os.path.isabs(out_file):
out_file = os.path.join(os.getcwd(), out_file)
with open(out_file, 'w', encoding='utf-8-sig', newline='') as f:
f.write(result)
print(f'Written to: {out_file}')
sys.exit(1 if r.errors > 0 else 0)
if __name__ == '__main__':
main()