fix(skills): чтение входного JSON — кодировка из BOM, эхо только для inline-значения (#80)

Этажом ниже разбора, на чтении файла, жил тот же класс дефектов в худшей форме. Файл в cp1251
с кириллицей: PS1 `Get-Content -Encoding UTF8` менял имя на 12 символов U+FFFD, JSON после этого
разбирался УСПЕШНО, и навык создавал объект с именем из «замен» — молча. Py-порт на том же файле
падал traceback-ом. Файл в UTF-16 давал ту же пару: traceback против ложного «JSON must have
'type' field».

Новая семья read_json_file / Read-JsonInputFile: кодировка берётся из BOM (UTF-8, UTF-16 LE/BE),
без BOM — строгий UTF-8, при провале сообщение называет файл и байт. Кодовую страницу не
подбираем: угаданное имя уехало бы в метаданные так же молча.

Эхо полученного значения печатается теперь только для inline-входа. Для файла оно показывало
первые 60 символов первой строки независимо от того, что ошибка на 120-й, и спорило с позицией
от парсера.

Заодно уравнен пустой вход: PS 5.1 на пустой строке отдаёт $null, а не ошибку, и навык уходил
дальше с $null, тогда как py-порт падал.

Раннеру добавлен ключ inputEncoding (utf-16le / utf-16be / cp1251) — иначе кейс про кодировку
не выразить, writeFileSync пишет только UTF-8.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-08-21 16:41:54 +03:00
co-authored by Claude Opus 5
parent 0770889e47
commit 229e66b907
35 changed files with 1475 additions and 263 deletions
+43 -10
View File
@@ -1,4 +1,4 @@
# cf-edit v1.20 — Edit 1C configuration root (Configuration.xml) # cf-edit v1.21 — Edit 1C configuration root (Configuration.xml)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)][Alias('Path')][string]$ConfigPath, [Parameter(Mandatory)][Alias('Path')][string]$ConfigPath,
@@ -14,22 +14,55 @@ $ErrorActionPreference = "Stop"
# --- Разбор пользовательского JSON --- # --- Разбор пользовательского JSON ---
# Одна строка в stderr вместо дампа исключения ConvertFrom-Json (issue #80): агент по стектрейсу # Одна строка в stderr вместо дампа исключения ConvertFrom-Json (issue #80): агент по стектрейсу
# идёт чинить скрипт, а не свой вызов. $source — файл или параметр. $expected заполняем только # идёт чинить скрипт, а не свой вызов. $source — файл или параметр. $expected заполняем только
# для полиморфного входа: у файла подсказка была бы наполнителем. # для полиморфного входа: у файла подсказка была бы наполнителем. -Inline печатает ещё и то,
# что доехало: у файла такого вопроса нет — путь назван, позицию дал парсер, файл на диске.
# Возврат через -NoEnumerate: без него одноэлементный # Возврат через -NoEnumerate: без него одноэлементный
# JSON-массив разворачивался бы в скаляр вторым анруллингом. # JSON-массив разворачивался бы в скаляр вторым анруллингом.
function ConvertFrom-JsonInput([string]$text, [string]$source, [string]$expected) { function ConvertFrom-JsonInput([string]$text, [string]$source, [string]$expected, [switch]$Inline) {
try { try {
# PS 5.1 на пустой строке отдаёт $null, а не ошибку — навык уходил дальше с $null,
# тогда как py-порт падал. Проверяем сами, чтобы порты вели себя одинаково.
if ([string]::IsNullOrWhiteSpace($text)) { throw 'input is empty' }
$parsed = $text | ConvertFrom-Json $parsed = $text | ConvertFrom-Json
} catch { } catch {
$what = if ($expected) { "$source expects $expected" } else { "Invalid JSON in $source" } $what = if ($expected) { "$source expects $expected" } else { "Invalid JSON in $source" }
$got = ($text -replace '\s+', ' ').Trim() if ($Inline) {
$label = 'got' $got = ($text -replace '\s+', ' ').Trim()
if ($got.Length -gt 60) { $label = 'got (first 60 chars, whitespace collapsed)'; $got = $got.Substring(0, 60) } $label = 'got'
[Console]::Error.WriteLine("[ERROR] ${what}, ${label}: ${got} ($($_.Exception.Message))") if (-not $got) { $got = '(empty)' }
elseif ($got.Length -gt 60) { $label = 'got (first 60 chars)'; $got = $got.Substring(0, 60) }
$what = "${what}, ${label}: ${got}"
}
[Console]::Error.WriteLine("[ERROR] ${what} ($($_.Exception.Message))")
exit 1 exit 1
} }
Write-Output -NoEnumerate $parsed Write-Output -NoEnumerate $parsed
} }
# --- Чтение входного JSON-файла ---
# Кодировку берём из BOM — это объявление самого файла, а не догадка. Без BOM ждём строгий UTF-8:
# Get-Content -Encoding UTF8 на файле в cp1251 тихо меняет кириллицу на U+FFFD, JSON после этого
# разбирается успешно, и в конфигурацию уезжает имя из «замен». Кодовую страницу не подбираем:
# угаданное имя уйдёт в метаданные так же молча.
function Read-JsonInputFile([string]$path) {
$bytes = [System.IO.File]::ReadAllBytes($path)
if ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) {
return [System.Text.Encoding]::UTF8.GetString($bytes, 3, $bytes.Length - 3)
}
if ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFF -and $bytes[1] -eq 0xFE) {
return [System.Text.Encoding]::Unicode.GetString($bytes, 2, $bytes.Length - 2)
}
if ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFE -and $bytes[1] -eq 0xFF) {
return [System.Text.Encoding]::BigEndianUnicode.GetString($bytes, 2, $bytes.Length - 2)
}
try {
return (New-Object System.Text.UTF8Encoding($false, $true)).GetString($bytes)
} catch {
$detail = if ($_.Exception.InnerException) { $_.Exception.InnerException.Message } else { $_.Exception.Message }
[Console]::Error.WriteLine("[ERROR] ${path} is not valid UTF-8: ${detail} - save the file as UTF-8, or add a BOM if it is UTF-16")
exit 1
}
}
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Mode validation --- # --- Mode validation ---
@@ -659,7 +692,7 @@ function Do-SetPanels($valArg) {
# Accept string (JSON), PSCustomObject, or hashtable # Accept string (JSON), PSCustomObject, or hashtable
$layout = $valArg $layout = $valArg
if ($layout -is [string]) { if ($layout -is [string]) {
$layout = ConvertFrom-JsonInput $layout "-Value for operation 'set-panels'" "a JSON object with panel layout" $layout = ConvertFrom-JsonInput $layout "-Value for operation 'set-panels'" "a JSON object with panel layout" -Inline
} }
if (-not $layout) { if (-not $layout) {
Write-Error "set-panels value is empty" Write-Error "set-panels value is empty"
@@ -843,7 +876,7 @@ $indent</Item>
function Do-SetHomePage($valArg) { function Do-SetHomePage($valArg) {
$layout = $valArg $layout = $valArg
if ($layout -is [string]) { if ($layout -is [string]) {
$layout = ConvertFrom-JsonInput $layout "-Value for operation 'set-home-page'" "a JSON object with home page layout" $layout = ConvertFrom-JsonInput $layout "-Value for operation 'set-home-page'" "a JSON object with home page layout" -Inline
} }
if (-not $layout) { Write-Error "set-home-page value is empty"; exit 1 } if (-not $layout) { Write-Error "set-home-page value is empty"; exit 1 }
@@ -957,7 +990,7 @@ if ($DefinitionFile) {
if (-not [System.IO.Path]::IsPathRooted($DefinitionFile)) { if (-not [System.IO.Path]::IsPathRooted($DefinitionFile)) {
$DefinitionFile = Join-Path (Get-Location).Path $DefinitionFile $DefinitionFile = Join-Path (Get-Location).Path $DefinitionFile
} }
$jsonText = Get-Content -Raw -Encoding UTF8 $DefinitionFile $jsonText = Read-JsonInputFile $DefinitionFile
$ops = ConvertFrom-JsonInput $jsonText $DefinitionFile $ops = ConvertFrom-JsonInput $jsonText $DefinitionFile
if ($ops -is [System.Array]) { if ($ops -is [System.Array]) {
foreach ($op in $ops) { $operations += $op } foreach ($op in $ops) { $operations += $op }
+42 -13
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# cf-edit v1.20 — Edit 1C configuration root (Configuration.xml) # cf-edit v1.21 — Edit 1C configuration root (Configuration.xml)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
@@ -15,11 +15,12 @@ from lxml import etree
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet] # Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
# регистр не различают, в argparse совпадение точное. # регистр не различают, в argparse совпадение точное.
def parse_json_input(text, source, expected=None): def parse_json_input(text, source, expected=None, inline=False):
"""Разбор пользовательского JSON: одна строка в stderr вместо traceback (issue #80). """Разбор пользовательского JSON: одна строка в stderr вместо traceback (issue #80).
expected заполняем только для полиморфного входа: у файла подсказка expected заполняем только для полиморфного входа: у файла подсказка
была бы наполнителем — имя файла и текст парсера самодостаточны. была бы наполнителем — имя файла и текст парсера самодостаточны. inline печатает ещё и то,
что доехало: у файла такого вопроса нет, он лежит на диске и его видно целиком.
Импорты внутри тела: копия функции живёт в навыках с разными именами модулей Импорты внутри тела: копия функции живёт в навыках с разными именами модулей
(skd-decompile импортирует json локально как _json), а тело обязано быть одинаковым. (skd-decompile импортирует json локально как _json), а тело обязано быть одинаковым.
@@ -27,15 +28,44 @@ def parse_json_input(text, source, expected=None):
import json as _pj import json as _pj
import sys as _psys import sys as _psys
try: try:
if not str(text).strip():
raise ValueError("input is empty")
return _pj.loads(text) return _pj.loads(text)
except ValueError as exc: except ValueError as exc:
what = "%s expects %s" % (source, expected) if expected else "Invalid JSON in %s" % source what = "%s expects %s" % (source, expected) if expected else "Invalid JSON in %s" % source
got = " ".join(str(text).split()) if inline:
label = "got" got = " ".join(str(text).split())
if len(got) > 60: label = "got"
label = "got (first 60 chars, whitespace collapsed)" if not got:
got = got[:60] got = "(empty)"
print("[ERROR] %s, %s: %s (%s)" % (what, label, got, exc), file=_psys.stderr) elif len(got) > 60:
label = "got (first 60 chars)"
got = got[:60]
what = "%s, %s: %s" % (what, label, got)
print("[ERROR] %s (%s)" % (what, exc), file=_psys.stderr)
_psys.exit(1)
def read_json_file(path):
"""Чтение входного JSON-файла с кодировкой из BOM (issue #80).
BOM — объявление самого файла, поэтому ему верим; без BOM ждём строгий UTF-8. Кодовую
страницу не подбираем: угаданное имя уехало бы в метаданные молча.
"""
import sys as _psys
with open(path, "rb") as _fh:
data = _fh.read()
if data[:3] == b"\xef\xbb\xbf":
return data[3:].decode("utf-8")
if data[:2] == b"\xff\xfe":
return data[2:].decode("utf-16-le")
if data[:2] == b"\xfe\xff":
return data[2:].decode("utf-16-be")
try:
return data.decode("utf-8")
except UnicodeDecodeError as exc:
print("[ERROR] %s is not valid UTF-8: %s - save the file as UTF-8, or add a BOM if it is UTF-16"
% (path, exc), file=_psys.stderr)
_psys.exit(1) _psys.exit(1)
@@ -847,7 +877,7 @@ def main():
layout = value layout = value
if isinstance(layout, str): if isinstance(layout, str):
layout = ci_json(parse_json_input( layout = ci_json(parse_json_input(
layout, "-Value for operation 'set-panels'", "a JSON object with panel layout")) layout, "-Value for operation 'set-panels'", "a JSON object with panel layout", inline=True))
if not isinstance(layout, dict) or not layout: if not isinstance(layout, dict) or not layout:
print("set-panels value must be non-empty object", file=sys.stderr) print("set-panels value must be non-empty object", file=sys.stderr)
sys.exit(1) sys.exit(1)
@@ -999,7 +1029,7 @@ def main():
layout = value layout = value
if isinstance(layout, str): if isinstance(layout, str):
layout = ci_json(parse_json_input( layout = ci_json(parse_json_input(
layout, "-Value for operation 'set-home-page'", "a JSON object with home page layout")) layout, "-Value for operation 'set-home-page'", "a JSON object with home page layout", inline=True))
if not isinstance(layout, dict) or not layout: if not isinstance(layout, dict) or not layout:
print("set-home-page value must be non-empty object", file=sys.stderr) print("set-home-page value must be non-empty object", file=sys.stderr)
sys.exit(1) sys.exit(1)
@@ -1063,8 +1093,7 @@ def main():
def_file = args.DefinitionFile def_file = args.DefinitionFile
if not os.path.isabs(def_file): if not os.path.isabs(def_file):
def_file = os.path.join(os.getcwd(), def_file) def_file = os.path.join(os.getcwd(), def_file)
with open(def_file, "r", encoding="utf-8-sig") as fh: ops = ci_json(parse_json_input(read_json_file(def_file), def_file))
ops = ci_json(parse_json_input(fh.read(), def_file))
if isinstance(ops, list): if isinstance(ops, list):
operations = ops operations = ops
else: else:
@@ -1,4 +1,4 @@
# form-compile v1.193 — Compile 1C managed form from JSON or object metadata (гвард на группу additionalColumns без ключа columns) # form-compile v1.194 — Compile 1C managed form from JSON or object metadata (гвард на группу additionalColumns без ключа columns)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[string]$JsonPath, [string]$JsonPath,
@@ -18,22 +18,55 @@ $ErrorActionPreference = "Stop"
# --- Разбор пользовательского JSON --- # --- Разбор пользовательского JSON ---
# Одна строка в stderr вместо дампа исключения ConvertFrom-Json (issue #80): агент по стектрейсу # Одна строка в stderr вместо дампа исключения ConvertFrom-Json (issue #80): агент по стектрейсу
# идёт чинить скрипт, а не свой вызов. $source — файл или параметр. $expected заполняем только # идёт чинить скрипт, а не свой вызов. $source — файл или параметр. $expected заполняем только
# для полиморфного входа: у файла подсказка была бы наполнителем. # для полиморфного входа: у файла подсказка была бы наполнителем. -Inline печатает ещё и то,
# что доехало: у файла такого вопроса нет — путь назван, позицию дал парсер, файл на диске.
# Возврат через -NoEnumerate: без него одноэлементный # Возврат через -NoEnumerate: без него одноэлементный
# JSON-массив разворачивался бы в скаляр вторым анруллингом. # JSON-массив разворачивался бы в скаляр вторым анруллингом.
function ConvertFrom-JsonInput([string]$text, [string]$source, [string]$expected) { function ConvertFrom-JsonInput([string]$text, [string]$source, [string]$expected, [switch]$Inline) {
try { try {
# PS 5.1 на пустой строке отдаёт $null, а не ошибку — навык уходил дальше с $null,
# тогда как py-порт падал. Проверяем сами, чтобы порты вели себя одинаково.
if ([string]::IsNullOrWhiteSpace($text)) { throw 'input is empty' }
$parsed = $text | ConvertFrom-Json $parsed = $text | ConvertFrom-Json
} catch { } catch {
$what = if ($expected) { "$source expects $expected" } else { "Invalid JSON in $source" } $what = if ($expected) { "$source expects $expected" } else { "Invalid JSON in $source" }
$got = ($text -replace '\s+', ' ').Trim() if ($Inline) {
$label = 'got' $got = ($text -replace '\s+', ' ').Trim()
if ($got.Length -gt 60) { $label = 'got (first 60 chars, whitespace collapsed)'; $got = $got.Substring(0, 60) } $label = 'got'
[Console]::Error.WriteLine("[ERROR] ${what}, ${label}: ${got} ($($_.Exception.Message))") if (-not $got) { $got = '(empty)' }
elseif ($got.Length -gt 60) { $label = 'got (first 60 chars)'; $got = $got.Substring(0, 60) }
$what = "${what}, ${label}: ${got}"
}
[Console]::Error.WriteLine("[ERROR] ${what} ($($_.Exception.Message))")
exit 1 exit 1
} }
Write-Output -NoEnumerate $parsed Write-Output -NoEnumerate $parsed
} }
# --- Чтение входного JSON-файла ---
# Кодировку берём из BOM — это объявление самого файла, а не догадка. Без BOM ждём строгий UTF-8:
# Get-Content -Encoding UTF8 на файле в cp1251 тихо меняет кириллицу на U+FFFD, JSON после этого
# разбирается успешно, и в конфигурацию уезжает имя из «замен». Кодовую страницу не подбираем:
# угаданное имя уйдёт в метаданные так же молча.
function Read-JsonInputFile([string]$path) {
$bytes = [System.IO.File]::ReadAllBytes($path)
if ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) {
return [System.Text.Encoding]::UTF8.GetString($bytes, 3, $bytes.Length - 3)
}
if ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFF -and $bytes[1] -eq 0xFE) {
return [System.Text.Encoding]::Unicode.GetString($bytes, 2, $bytes.Length - 2)
}
if ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFE -and $bytes[1] -eq 0xFF) {
return [System.Text.Encoding]::BigEndianUnicode.GetString($bytes, 2, $bytes.Length - 2)
}
try {
return (New-Object System.Text.UTF8Encoding($false, $true)).GetString($bytes)
} catch {
$detail = if ($_.Exception.InnerException) { $_.Exception.InnerException.Message } else { $_.Exception.Message }
[Console]::Error.WriteLine("[ERROR] ${path} is not valid UTF-8: ${detail} - save the file as UTF-8, or add a BOM if it is UTF-16")
exit 1
}
}
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# ═══════════════════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════════════════
@@ -320,7 +353,7 @@ function Load-Preset([string]$PresetName, [string]$ScriptDir) {
$presetDir = Join-Path (Split-Path $ScriptDir -Parent) "presets" $presetDir = Join-Path (Split-Path $ScriptDir -Parent) "presets"
$builtInPath = Join-Path $presetDir "$PresetName.json" $builtInPath = Join-Path $presetDir "$PresetName.json"
if (Test-Path $builtInPath) { if (Test-Path $builtInPath) {
$presetJson = ConvertFrom-JsonInput (Get-Content -Raw -Encoding UTF8 $builtInPath) $builtInPath $presetJson = ConvertFrom-JsonInput (Read-JsonInputFile $builtInPath) $builtInPath
# Convert PSCustomObject to hashtable recursively # Convert PSCustomObject to hashtable recursively
$toHash = { $toHash = {
param($obj) param($obj)
@@ -347,7 +380,7 @@ function Load-Preset([string]$PresetName, [string]$ScriptDir) {
while ($scanDir) { while ($scanDir) {
$projPreset = Join-Path (Join-Path (Join-Path (Join-Path $scanDir "presets") "skills") "form") "$PresetName.json" $projPreset = Join-Path (Join-Path (Join-Path (Join-Path $scanDir "presets") "skills") "form") "$PresetName.json"
if (Test-Path $projPreset) { if (Test-Path $projPreset) {
$projJson = ConvertFrom-JsonInput (Get-Content -Raw -Encoding UTF8 $projPreset) $projPreset $projJson = ConvertFrom-JsonInput (Read-JsonInputFile $projPreset) $projPreset
$projHash = & $toHash $projJson $projHash = & $toHash $projJson
foreach ($k in @($projHash.Keys)) { foreach ($k in @($projHash.Keys)) {
$defaults[$k] = & $deepMerge $defaults[$k] $projHash[$k] $defaults[$k] = & $deepMerge $defaults[$k] $projHash[$k]
@@ -1675,7 +1708,7 @@ if ($FromObject) {
exit 1 exit 1
} }
$json = Get-Content -Raw -Encoding UTF8 $JsonPath $json = Read-JsonInputFile $JsonPath
$def = ConvertFrom-JsonInput $json $JsonPath $def = ConvertFrom-JsonInput $json $JsonPath
} }
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# form-compile v1.193 — Compile 1C managed form from JSON or object metadata (гвард на группу additionalColumns без ключа columns) # form-compile v1.194 — Compile 1C managed form from JSON or object metadata (гвард на группу additionalColumns без ключа columns)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import copy import copy
@@ -16,11 +16,12 @@ from lxml import etree
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet] # Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
# регистр не различают, в argparse совпадение точное. # регистр не различают, в argparse совпадение точное.
def parse_json_input(text, source, expected=None): def parse_json_input(text, source, expected=None, inline=False):
"""Разбор пользовательского JSON: одна строка в stderr вместо traceback (issue #80). """Разбор пользовательского JSON: одна строка в stderr вместо traceback (issue #80).
expected заполняем только для полиморфного входа: у файла подсказка expected заполняем только для полиморфного входа: у файла подсказка
была бы наполнителем имя файла и текст парсера самодостаточны. была бы наполнителем имя файла и текст парсера самодостаточны. inline печатает ещё и то,
что доехало: у файла такого вопроса нет, он лежит на диске и его видно целиком.
Импорты внутри тела: копия функции живёт в навыках с разными именами модулей Импорты внутри тела: копия функции живёт в навыках с разными именами модулей
(skd-decompile импортирует json локально как _json), а тело обязано быть одинаковым. (skd-decompile импортирует json локально как _json), а тело обязано быть одинаковым.
@@ -28,15 +29,44 @@ def parse_json_input(text, source, expected=None):
import json as _pj import json as _pj
import sys as _psys import sys as _psys
try: try:
if not str(text).strip():
raise ValueError("input is empty")
return _pj.loads(text) return _pj.loads(text)
except ValueError as exc: except ValueError as exc:
what = "%s expects %s" % (source, expected) if expected else "Invalid JSON in %s" % source what = "%s expects %s" % (source, expected) if expected else "Invalid JSON in %s" % source
got = " ".join(str(text).split()) if inline:
label = "got" got = " ".join(str(text).split())
if len(got) > 60: label = "got"
label = "got (first 60 chars, whitespace collapsed)" if not got:
got = got[:60] got = "(empty)"
print("[ERROR] %s, %s: %s (%s)" % (what, label, got, exc), file=_psys.stderr) elif len(got) > 60:
label = "got (first 60 chars)"
got = got[:60]
what = "%s, %s: %s" % (what, label, got)
print("[ERROR] %s (%s)" % (what, exc), file=_psys.stderr)
_psys.exit(1)
def read_json_file(path):
"""Чтение входного JSON-файла с кодировкой из BOM (issue #80).
BOM объявление самого файла, поэтому ему верим; без BOM ждём строгий UTF-8. Кодовую
страницу не подбираем: угаданное имя уехало бы в метаданные молча.
"""
import sys as _psys
with open(path, "rb") as _fh:
data = _fh.read()
if data[:3] == b"\xef\xbb\xbf":
return data[3:].decode("utf-8")
if data[:2] == b"\xff\xfe":
return data[2:].decode("utf-16-le")
if data[:2] == b"\xfe\xff":
return data[2:].decode("utf-16-be")
try:
return data.decode("utf-8")
except UnicodeDecodeError as exc:
print("[ERROR] %s is not valid UTF-8: %s - save the file as UTF-8, or add a BOM if it is UTF-16"
% (path, exc), file=_psys.stderr)
_psys.exit(1) _psys.exit(1)
@@ -567,8 +597,7 @@ def load_preset(preset_name, script_dir, out_path_resolved):
preset_dir = os.path.join(os.path.dirname(script_dir), 'presets') preset_dir = os.path.join(os.path.dirname(script_dir), 'presets')
built_in_path = os.path.join(preset_dir, f'{preset_name}.json') built_in_path = os.path.join(preset_dir, f'{preset_name}.json')
if os.path.isfile(built_in_path): if os.path.isfile(built_in_path):
with open(built_in_path, 'r', encoding='utf-8-sig') as f: preset_data = ci_json(parse_json_input(read_json_file(built_in_path), built_in_path))
preset_data = ci_json(parse_json_input(f.read(), built_in_path))
for k in list(preset_data.keys()): for k in list(preset_data.keys()):
defaults[k] = _deep_merge(defaults.get(k), preset_data[k]) defaults[k] = _deep_merge(defaults.get(k), preset_data[k])
@@ -577,8 +606,7 @@ def load_preset(preset_name, script_dir, out_path_resolved):
while scan_dir: while scan_dir:
proj_preset = os.path.join(scan_dir, 'presets', 'skills', 'form', f'{preset_name}.json') proj_preset = os.path.join(scan_dir, 'presets', 'skills', 'form', f'{preset_name}.json')
if os.path.isfile(proj_preset): if os.path.isfile(proj_preset):
with open(proj_preset, 'r', encoding='utf-8-sig') as f: proj_data = parse_json_input(read_json_file(proj_preset), proj_preset)
proj_data = parse_json_input(f.read(), proj_preset)
for k in list(proj_data.keys()): for k in list(proj_data.keys()):
defaults[k] = _deep_merge(defaults.get(k), proj_data[k]) defaults[k] = _deep_merge(defaults.get(k), proj_data[k])
break break
@@ -6480,8 +6508,7 @@ def main():
print(f"File not found: {json_path}", file=sys.stderr) print(f"File not found: {json_path}", file=sys.stderr)
sys.exit(1) sys.exit(1)
with open(json_path, 'r', encoding='utf-8-sig') as f: defn = ci_json(parse_json_input(read_json_file(json_path), json_path))
defn = ci_json(parse_json_input(f.read(), json_path))
global QUERY_BASE_DIR global QUERY_BASE_DIR
QUERY_BASE_DIR = os.path.dirname(os.path.abspath(json_path)) QUERY_BASE_DIR = os.path.dirname(os.path.abspath(json_path))
+41 -8
View File
@@ -1,4 +1,4 @@
# form-edit v1.15 — Edit 1C managed form elements (+esc_xml/esc_xml_text: разное экранирование атрибута и текста) # form-edit v1.16 — Edit 1C managed form elements (+esc_xml/esc_xml_text: разное экранирование атрибута и текста)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
@@ -14,22 +14,55 @@ $ErrorActionPreference = "Stop"
# --- Разбор пользовательского JSON --- # --- Разбор пользовательского JSON ---
# Одна строка в stderr вместо дампа исключения ConvertFrom-Json (issue #80): агент по стектрейсу # Одна строка в stderr вместо дампа исключения ConvertFrom-Json (issue #80): агент по стектрейсу
# идёт чинить скрипт, а не свой вызов. $source — файл или параметр. $expected заполняем только # идёт чинить скрипт, а не свой вызов. $source — файл или параметр. $expected заполняем только
# для полиморфного входа: у файла подсказка была бы наполнителем. # для полиморфного входа: у файла подсказка была бы наполнителем. -Inline печатает ещё и то,
# что доехало: у файла такого вопроса нет — путь назван, позицию дал парсер, файл на диске.
# Возврат через -NoEnumerate: без него одноэлементный # Возврат через -NoEnumerate: без него одноэлементный
# JSON-массив разворачивался бы в скаляр вторым анруллингом. # JSON-массив разворачивался бы в скаляр вторым анруллингом.
function ConvertFrom-JsonInput([string]$text, [string]$source, [string]$expected) { function ConvertFrom-JsonInput([string]$text, [string]$source, [string]$expected, [switch]$Inline) {
try { try {
# PS 5.1 на пустой строке отдаёт $null, а не ошибку — навык уходил дальше с $null,
# тогда как py-порт падал. Проверяем сами, чтобы порты вели себя одинаково.
if ([string]::IsNullOrWhiteSpace($text)) { throw 'input is empty' }
$parsed = $text | ConvertFrom-Json $parsed = $text | ConvertFrom-Json
} catch { } catch {
$what = if ($expected) { "$source expects $expected" } else { "Invalid JSON in $source" } $what = if ($expected) { "$source expects $expected" } else { "Invalid JSON in $source" }
$got = ($text -replace '\s+', ' ').Trim() if ($Inline) {
$label = 'got' $got = ($text -replace '\s+', ' ').Trim()
if ($got.Length -gt 60) { $label = 'got (first 60 chars, whitespace collapsed)'; $got = $got.Substring(0, 60) } $label = 'got'
[Console]::Error.WriteLine("[ERROR] ${what}, ${label}: ${got} ($($_.Exception.Message))") if (-not $got) { $got = '(empty)' }
elseif ($got.Length -gt 60) { $label = 'got (first 60 chars)'; $got = $got.Substring(0, 60) }
$what = "${what}, ${label}: ${got}"
}
[Console]::Error.WriteLine("[ERROR] ${what} ($($_.Exception.Message))")
exit 1 exit 1
} }
Write-Output -NoEnumerate $parsed Write-Output -NoEnumerate $parsed
} }
# --- Чтение входного JSON-файла ---
# Кодировку берём из BOM — это объявление самого файла, а не догадка. Без BOM ждём строгий UTF-8:
# Get-Content -Encoding UTF8 на файле в cp1251 тихо меняет кириллицу на U+FFFD, JSON после этого
# разбирается успешно, и в конфигурацию уезжает имя из «замен». Кодовую страницу не подбираем:
# угаданное имя уйдёт в метаданные так же молча.
function Read-JsonInputFile([string]$path) {
$bytes = [System.IO.File]::ReadAllBytes($path)
if ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) {
return [System.Text.Encoding]::UTF8.GetString($bytes, 3, $bytes.Length - 3)
}
if ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFF -and $bytes[1] -eq 0xFE) {
return [System.Text.Encoding]::Unicode.GetString($bytes, 2, $bytes.Length - 2)
}
if ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFE -and $bytes[1] -eq 0xFF) {
return [System.Text.Encoding]::BigEndianUnicode.GetString($bytes, 2, $bytes.Length - 2)
}
try {
return (New-Object System.Text.UTF8Encoding($false, $true)).GetString($bytes)
} catch {
$detail = if ($_.Exception.InnerException) { $_.Exception.InnerException.Message } else { $_.Exception.Message }
[Console]::Error.WriteLine("[ERROR] ${path} is not valid UTF-8: ${detail} - save the file as UTF-8, or add a BOM if it is UTF-16")
exit 1
}
}
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Support guard (Ext/ParentConfigurations.bin) --- # --- Support guard (Ext/ParentConfigurations.bin) ---
@@ -195,7 +228,7 @@ $root = $xmlDoc.DocumentElement
# === 2. Load JSON === # === 2. Load JSON ===
$def = ConvertFrom-JsonInput (Get-Content -Raw -Encoding UTF8 $JsonPath) $JsonPath $def = ConvertFrom-JsonInput (Read-JsonInputFile $JsonPath) $JsonPath
# === 3. Form name + header === # === 3. Form name + header ===
+40 -11
View File
@@ -1,4 +1,4 @@
# form-edit v1.15 — Edit 1C managed form elements (Python port) (+esc_xml/esc_xml_text: разное экранирование атрибута и текста) # form-edit v1.16 — Edit 1C managed form elements (Python port) (+esc_xml/esc_xml_text: разное экранирование атрибута и текста)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import json import json
@@ -14,11 +14,12 @@ sys.stderr.reconfigure(encoding="utf-8")
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet] # Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
# регистр не различают, в argparse совпадение точное. # регистр не различают, в argparse совпадение точное.
def parse_json_input(text, source, expected=None): def parse_json_input(text, source, expected=None, inline=False):
"""Разбор пользовательского JSON: одна строка в stderr вместо traceback (issue #80). """Разбор пользовательского JSON: одна строка в stderr вместо traceback (issue #80).
expected заполняем только для полиморфного входа: у файла подсказка expected заполняем только для полиморфного входа: у файла подсказка
была бы наполнителем — имя файла и текст парсера самодостаточны. была бы наполнителем — имя файла и текст парсера самодостаточны. inline печатает ещё и то,
что доехало: у файла такого вопроса нет, он лежит на диске и его видно целиком.
Импорты внутри тела: копия функции живёт в навыках с разными именами модулей Импорты внутри тела: копия функции живёт в навыках с разными именами модулей
(skd-decompile импортирует json локально как _json), а тело обязано быть одинаковым. (skd-decompile импортирует json локально как _json), а тело обязано быть одинаковым.
@@ -26,15 +27,44 @@ def parse_json_input(text, source, expected=None):
import json as _pj import json as _pj
import sys as _psys import sys as _psys
try: try:
if not str(text).strip():
raise ValueError("input is empty")
return _pj.loads(text) return _pj.loads(text)
except ValueError as exc: except ValueError as exc:
what = "%s expects %s" % (source, expected) if expected else "Invalid JSON in %s" % source what = "%s expects %s" % (source, expected) if expected else "Invalid JSON in %s" % source
got = " ".join(str(text).split()) if inline:
label = "got" got = " ".join(str(text).split())
if len(got) > 60: label = "got"
label = "got (first 60 chars, whitespace collapsed)" if not got:
got = got[:60] got = "(empty)"
print("[ERROR] %s, %s: %s (%s)" % (what, label, got, exc), file=_psys.stderr) elif len(got) > 60:
label = "got (first 60 chars)"
got = got[:60]
what = "%s, %s: %s" % (what, label, got)
print("[ERROR] %s (%s)" % (what, exc), file=_psys.stderr)
_psys.exit(1)
def read_json_file(path):
"""Чтение входного JSON-файла с кодировкой из BOM (issue #80).
BOM — объявление самого файла, поэтому ему верим; без BOM ждём строгий UTF-8. Кодовую
страницу не подбираем: угаданное имя уехало бы в метаданные молча.
"""
import sys as _psys
with open(path, "rb") as _fh:
data = _fh.read()
if data[:3] == b"\xef\xbb\xbf":
return data[3:].decode("utf-8")
if data[:2] == b"\xff\xfe":
return data[2:].decode("utf-16-le")
if data[:2] == b"\xfe\xff":
return data[2:].decode("utf-16-be")
try:
return data.decode("utf-8")
except UnicodeDecodeError as exc:
print("[ERROR] %s is not valid UTF-8: %s - save the file as UTF-8, or add a BOM if it is UTF-16"
% (path, exc), file=_psys.stderr)
_psys.exit(1) _psys.exit(1)
@@ -342,8 +372,7 @@ root = tree.getroot()
# ── 2. Load JSON ──────────────────────────────────────────── # ── 2. Load JSON ────────────────────────────────────────────
with open(json_path, "r", encoding="utf-8-sig") as f: defn = ci_json(parse_json_input(read_json_file(json_path), json_path))
defn = ci_json(parse_json_input(f.read(), json_path))
# ── 3. Form name + header ─────────────────────────────────── # ── 3. Form name + header ───────────────────────────────────
@@ -1,4 +1,4 @@
# interface-edit v1.19 — Edit 1C CommandInterface.xml (+русские алиасы типов: формы с ё и без) # interface-edit v1.20 — Edit 1C CommandInterface.xml (+русские алиасы типов: формы с ё и без)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)][Alias('Path')][string]$CIPath, [Parameter(Mandatory)][Alias('Path')][string]$CIPath,
@@ -20,23 +20,56 @@ if (-not $DefinitionFile -and -not $Operation) { Write-Error "Either -Definition
# --- Разбор пользовательского JSON --- # --- Разбор пользовательского JSON ---
# Одна строка в stderr вместо дампа исключения ConvertFrom-Json (issue #80): агент по стектрейсу # Одна строка в stderr вместо дампа исключения ConvertFrom-Json (issue #80): агент по стектрейсу
# идёт чинить скрипт, а не свой вызов. $source — файл или параметр. $expected заполняем только # идёт чинить скрипт, а не свой вызов. $source — файл или параметр. $expected заполняем только
# для полиморфного входа: у файла подсказка была бы наполнителем. # для полиморфного входа: у файла подсказка была бы наполнителем. -Inline печатает ещё и то,
# что доехало: у файла такого вопроса нет — путь назван, позицию дал парсер, файл на диске.
# Возврат через -NoEnumerate: без него одноэлементный # Возврат через -NoEnumerate: без него одноэлементный
# JSON-массив разворачивался бы в скаляр вторым анруллингом. # JSON-массив разворачивался бы в скаляр вторым анруллингом.
function ConvertFrom-JsonInput([string]$text, [string]$source, [string]$expected) { function ConvertFrom-JsonInput([string]$text, [string]$source, [string]$expected, [switch]$Inline) {
try { try {
# PS 5.1 на пустой строке отдаёт $null, а не ошибку — навык уходил дальше с $null,
# тогда как py-порт падал. Проверяем сами, чтобы порты вели себя одинаково.
if ([string]::IsNullOrWhiteSpace($text)) { throw 'input is empty' }
$parsed = $text | ConvertFrom-Json $parsed = $text | ConvertFrom-Json
} catch { } catch {
$what = if ($expected) { "$source expects $expected" } else { "Invalid JSON in $source" } $what = if ($expected) { "$source expects $expected" } else { "Invalid JSON in $source" }
$got = ($text -replace '\s+', ' ').Trim() if ($Inline) {
$label = 'got' $got = ($text -replace '\s+', ' ').Trim()
if ($got.Length -gt 60) { $label = 'got (first 60 chars, whitespace collapsed)'; $got = $got.Substring(0, 60) } $label = 'got'
[Console]::Error.WriteLine("[ERROR] ${what}, ${label}: ${got} ($($_.Exception.Message))") if (-not $got) { $got = '(empty)' }
elseif ($got.Length -gt 60) { $label = 'got (first 60 chars)'; $got = $got.Substring(0, 60) }
$what = "${what}, ${label}: ${got}"
}
[Console]::Error.WriteLine("[ERROR] ${what} ($($_.Exception.Message))")
exit 1 exit 1
} }
Write-Output -NoEnumerate $parsed Write-Output -NoEnumerate $parsed
} }
# --- Чтение входного JSON-файла ---
# Кодировку берём из BOM — это объявление самого файла, а не догадка. Без BOM ждём строгий UTF-8:
# Get-Content -Encoding UTF8 на файле в cp1251 тихо меняет кириллицу на U+FFFD, JSON после этого
# разбирается успешно, и в конфигурацию уезжает имя из «замен». Кодовую страницу не подбираем:
# угаданное имя уйдёт в метаданные так же молча.
function Read-JsonInputFile([string]$path) {
$bytes = [System.IO.File]::ReadAllBytes($path)
if ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) {
return [System.Text.Encoding]::UTF8.GetString($bytes, 3, $bytes.Length - 3)
}
if ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFF -and $bytes[1] -eq 0xFE) {
return [System.Text.Encoding]::Unicode.GetString($bytes, 2, $bytes.Length - 2)
}
if ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFE -and $bytes[1] -eq 0xFF) {
return [System.Text.Encoding]::BigEndianUnicode.GetString($bytes, 2, $bytes.Length - 2)
}
try {
return (New-Object System.Text.UTF8Encoding($false, $true)).GetString($bytes)
} catch {
$detail = if ($_.Exception.InnerException) { $_.Exception.InnerException.Message } else { $_.Exception.Message }
[Console]::Error.WriteLine("[ERROR] ${path} is not valid UTF-8: ${detail} - save the file as UTF-8, or add a BOM if it is UTF-16")
exit 1
}
}
# --- Resolve path --- # --- Resolve path ---
if (-not [System.IO.Path]::IsPathRooted($CIPath)) { if (-not [System.IO.Path]::IsPathRooted($CIPath)) {
$CIPath = Join-Path (Get-Location).Path $CIPath $CIPath = Join-Path (Get-Location).Path $CIPath
@@ -374,7 +407,7 @@ function Ensure-Section([string]$sectionName) {
function Parse-ValueList([string]$val, [string]$opName) { function Parse-ValueList([string]$val, [string]$opName) {
$val = $val.Trim() $val = $val.Trim()
if ($val.StartsWith("[")) { if ($val.StartsWith("[")) {
$arr = ConvertFrom-JsonInput $val "-Value for operation '$opName'" "a JSON array of command names" $arr = ConvertFrom-JsonInput $val "-Value for operation '$opName'" "a JSON array of command names" -Inline
$result = @(); foreach ($item in $arr) { $result += "$item" } $result = @(); foreach ($item in $arr) { $result += "$item" }
return ,$result return ,$result
} }
@@ -539,7 +572,7 @@ function Do-Show([string[]]$commands) {
} }
function Do-Place([string]$jsonVal) { function Do-Place([string]$jsonVal) {
$def = ConvertFrom-JsonInput $jsonVal "-Value for operation 'place'" "a JSON object {command, group}" $def = ConvertFrom-JsonInput $jsonVal "-Value for operation 'place'" "a JSON object {command, group}" -Inline
$cmdName = Normalize-CmdName "$($def.command)" $cmdName = Normalize-CmdName "$($def.command)"
$groupName = "$($def.group)" $groupName = "$($def.group)"
if (-not $cmdName -or -not $groupName) { Write-Error "place requires {command, group}"; exit 1 } if (-not $cmdName -or -not $groupName) { Write-Error "place requires {command, group}"; exit 1 }
@@ -572,7 +605,7 @@ function Do-Place([string]$jsonVal) {
} }
function Do-Order([string]$jsonVal) { function Do-Order([string]$jsonVal) {
$def = ConvertFrom-JsonInput $jsonVal "-Value for operation 'order'" "a JSON object {group, commands:[...]}" $def = ConvertFrom-JsonInput $jsonVal "-Value for operation 'order'" "a JSON object {group, commands:[...]}" -Inline
$groupName = "$($def.group)" $groupName = "$($def.group)"
$commands = @($def.commands | ForEach-Object { Normalize-CmdName "$_" }) $commands = @($def.commands | ForEach-Object { Normalize-CmdName "$_" })
if (-not $groupName -or $commands.Count -eq 0) { Write-Error "order requires {group, commands:[...]}"; exit 1 } if (-not $groupName -or $commands.Count -eq 0) { Write-Error "order requires {group, commands:[...]}"; exit 1 }
@@ -610,7 +643,7 @@ function Do-Order([string]$jsonVal) {
} }
function Do-SubsystemOrder([string]$jsonVal) { function Do-SubsystemOrder([string]$jsonVal) {
$parsed = ConvertFrom-JsonInput $jsonVal "-Value for operation 'subsystem-order'" "a JSON array of subsystem paths" $parsed = ConvertFrom-JsonInput $jsonVal "-Value for operation 'subsystem-order'" "a JSON array of subsystem paths" -Inline
$subsystems = @(); foreach ($s in $parsed) { $subsystems += "$s" } $subsystems = @(); foreach ($s in $parsed) { $subsystems += "$s" }
if ($subsystems.Count -eq 0) { Write-Error "subsystem-order requires array of subsystem paths"; exit 1 } if ($subsystems.Count -eq 0) { Write-Error "subsystem-order requires array of subsystem paths"; exit 1 }
@@ -638,7 +671,7 @@ function Do-SubsystemOrder([string]$jsonVal) {
} }
function Do-GroupOrder([string]$jsonVal) { function Do-GroupOrder([string]$jsonVal) {
$parsed = ConvertFrom-JsonInput $jsonVal "-Value for operation 'group-order'" "a JSON array of group names" $parsed = ConvertFrom-JsonInput $jsonVal "-Value for operation 'group-order'" "a JSON array of group names" -Inline
$groups = @(); foreach ($g in $parsed) { $groups += "$g" } $groups = @(); foreach ($g in $parsed) { $groups += "$g" }
if ($groups.Count -eq 0) { Write-Error "group-order requires array of group names"; exit 1 } if ($groups.Count -eq 0) { Write-Error "group-order requires array of group names"; exit 1 }
@@ -671,7 +704,7 @@ if ($DefinitionFile) {
if (-not [System.IO.Path]::IsPathRooted($DefinitionFile)) { if (-not [System.IO.Path]::IsPathRooted($DefinitionFile)) {
$DefinitionFile = Join-Path (Get-Location).Path $DefinitionFile $DefinitionFile = Join-Path (Get-Location).Path $DefinitionFile
} }
$jsonText = Get-Content -Raw -Encoding UTF8 $DefinitionFile $jsonText = Read-JsonInputFile $DefinitionFile
$ops = ConvertFrom-JsonInput $jsonText $DefinitionFile $ops = ConvertFrom-JsonInput $jsonText $DefinitionFile
if ($ops -is [System.Array]) { if ($ops -is [System.Array]) {
foreach ($op in $ops) { $operations += $op } foreach ($op in $ops) { $operations += $op }
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# interface-edit v1.19 — Edit 1C CommandInterface.xml (+русские алиасы типов: формы с ё и без) # interface-edit v1.20 — Edit 1C CommandInterface.xml (+русские алиасы типов: формы с ё и без)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
@@ -348,11 +348,12 @@ def import_ci_fragment(xml_string):
return nodes return nodes
def parse_json_input(text, source, expected=None): def parse_json_input(text, source, expected=None, inline=False):
"""Разбор пользовательского JSON: одна строка в stderr вместо traceback (issue #80). """Разбор пользовательского JSON: одна строка в stderr вместо traceback (issue #80).
expected заполняем только для полиморфного входа: у файла подсказка expected заполняем только для полиморфного входа: у файла подсказка
была бы наполнителем — имя файла и текст парсера самодостаточны. была бы наполнителем — имя файла и текст парсера самодостаточны. inline печатает ещё и то,
что доехало: у файла такого вопроса нет, он лежит на диске и его видно целиком.
Импорты внутри тела: копия функции живёт в навыках с разными именами модулей Импорты внутри тела: копия функции живёт в навыках с разными именами модулей
(skd-decompile импортирует json локально как _json), а тело обязано быть одинаковым. (skd-decompile импортирует json локально как _json), а тело обязано быть одинаковым.
@@ -360,22 +361,51 @@ def parse_json_input(text, source, expected=None):
import json as _pj import json as _pj
import sys as _psys import sys as _psys
try: try:
if not str(text).strip():
raise ValueError("input is empty")
return _pj.loads(text) return _pj.loads(text)
except ValueError as exc: except ValueError as exc:
what = "%s expects %s" % (source, expected) if expected else "Invalid JSON in %s" % source what = "%s expects %s" % (source, expected) if expected else "Invalid JSON in %s" % source
got = " ".join(str(text).split()) if inline:
label = "got" got = " ".join(str(text).split())
if len(got) > 60: label = "got"
label = "got (first 60 chars, whitespace collapsed)" if not got:
got = got[:60] got = "(empty)"
print("[ERROR] %s, %s: %s (%s)" % (what, label, got, exc), file=_psys.stderr) elif len(got) > 60:
label = "got (first 60 chars)"
got = got[:60]
what = "%s, %s: %s" % (what, label, got)
print("[ERROR] %s (%s)" % (what, exc), file=_psys.stderr)
_psys.exit(1)
def read_json_file(path):
"""Чтение входного JSON-файла с кодировкой из BOM (issue #80).
BOM — объявление самого файла, поэтому ему верим; без BOM ждём строгий UTF-8. Кодовую
страницу не подбираем: угаданное имя уехало бы в метаданные молча.
"""
import sys as _psys
with open(path, "rb") as _fh:
data = _fh.read()
if data[:3] == b"\xef\xbb\xbf":
return data[3:].decode("utf-8")
if data[:2] == b"\xff\xfe":
return data[2:].decode("utf-16-le")
if data[:2] == b"\xfe\xff":
return data[2:].decode("utf-16-be")
try:
return data.decode("utf-8")
except UnicodeDecodeError as exc:
print("[ERROR] %s is not valid UTF-8: %s - save the file as UTF-8, or add a BOM if it is UTF-16"
% (path, exc), file=_psys.stderr)
_psys.exit(1) _psys.exit(1)
def parse_value_list(val, op_name): def parse_value_list(val, op_name):
val = val.strip() val = val.strip()
if val.startswith("["): if val.startswith("["):
arr = ci_json(parse_json_input(val, "-Value for operation '%s'" % op_name, "a JSON array of command names")) arr = ci_json(parse_json_input(val, "-Value for operation '%s'" % op_name, "a JSON array of command names", inline=True))
return [str(item) for item in arr] return [str(item) for item in arr]
return [val] return [val]
@@ -672,7 +702,7 @@ def main():
def do_place(json_val): def do_place(json_val):
nonlocal add_count, modify_count nonlocal add_count, modify_count
defn = ci_json(json_val if isinstance(json_val, dict) else parse_json_input( defn = ci_json(json_val if isinstance(json_val, dict) else parse_json_input(
json_val, "-Value for operation 'place'", "a JSON object {command, group}")) json_val, "-Value for operation 'place'", "a JSON object {command, group}", inline=True))
cmd_name = normalize_cmd_name(str(defn["command"])) cmd_name = normalize_cmd_name(str(defn["command"]))
group_name = str(defn["group"]) group_name = str(defn["group"])
if not cmd_name or not group_name: if not cmd_name or not group_name:
@@ -701,7 +731,7 @@ def main():
def do_order(json_val): def do_order(json_val):
nonlocal add_count, remove_count nonlocal add_count, remove_count
defn = ci_json(json_val if isinstance(json_val, dict) else parse_json_input( defn = ci_json(json_val if isinstance(json_val, dict) else parse_json_input(
json_val, "-Value for operation 'order'", "a JSON object {group, commands:[...]}")) json_val, "-Value for operation 'order'", "a JSON object {group, commands:[...]}", inline=True))
group_name = str(defn["group"]) group_name = str(defn["group"])
commands = [normalize_cmd_name(str(c)) for c in defn["commands"]] commands = [normalize_cmd_name(str(c)) for c in defn["commands"]]
if not group_name or not commands: if not group_name or not commands:
@@ -736,7 +766,7 @@ def main():
def do_subsystem_order(json_val): def do_subsystem_order(json_val):
nonlocal add_count, remove_count nonlocal add_count, remove_count
parsed = ci_json(json_val if isinstance(json_val, list) else parse_json_input( parsed = ci_json(json_val if isinstance(json_val, list) else parse_json_input(
json_val, "-Value for operation 'subsystem-order'", "a JSON array of subsystem paths")) json_val, "-Value for operation 'subsystem-order'", "a JSON array of subsystem paths", inline=True))
subsystems = [str(s) for s in parsed] subsystems = [str(s) for s in parsed]
if not subsystems: if not subsystems:
print("subsystem-order requires array of subsystem paths", file=sys.stderr) print("subsystem-order requires array of subsystem paths", file=sys.stderr)
@@ -762,7 +792,7 @@ def main():
def do_group_order(json_val): def do_group_order(json_val):
nonlocal add_count, remove_count nonlocal add_count, remove_count
parsed = ci_json(json_val if isinstance(json_val, list) else parse_json_input( parsed = ci_json(json_val if isinstance(json_val, list) else parse_json_input(
json_val, "-Value for operation 'group-order'", "a JSON array of group names")) json_val, "-Value for operation 'group-order'", "a JSON array of group names", inline=True))
groups = [str(g) for g in parsed] groups = [str(g) for g in parsed]
if not groups: if not groups:
print("group-order requires array of group names", file=sys.stderr) print("group-order requires array of group names", file=sys.stderr)
@@ -791,8 +821,7 @@ def main():
def_file = args.DefinitionFile def_file = args.DefinitionFile
if not os.path.isabs(def_file): if not os.path.isabs(def_file):
def_file = os.path.join(os.getcwd(), def_file) def_file = os.path.join(os.getcwd(), def_file)
with open(def_file, "r", encoding="utf-8-sig") as fh: ops = ci_json(parse_json_input(read_json_file(def_file), def_file))
ops = ci_json(parse_json_input(fh.read(), def_file))
if isinstance(ops, list): if isinstance(ops, list):
operations = ops operations = ops
else: else:
@@ -1,4 +1,4 @@
# meta-compile v1.96 — Compile 1C metadata object from JSON # meta-compile v1.97 — Compile 1C metadata object from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
@@ -13,22 +13,55 @@ $ErrorActionPreference = "Stop"
# --- Разбор пользовательского JSON --- # --- Разбор пользовательского JSON ---
# Одна строка в stderr вместо дампа исключения ConvertFrom-Json (issue #80): агент по стектрейсу # Одна строка в stderr вместо дампа исключения ConvertFrom-Json (issue #80): агент по стектрейсу
# идёт чинить скрипт, а не свой вызов. $source — файл или параметр. $expected заполняем только # идёт чинить скрипт, а не свой вызов. $source — файл или параметр. $expected заполняем только
# для полиморфного входа: у файла подсказка была бы наполнителем. # для полиморфного входа: у файла подсказка была бы наполнителем. -Inline печатает ещё и то,
# что доехало: у файла такого вопроса нет — путь назван, позицию дал парсер, файл на диске.
# Возврат через -NoEnumerate: без него одноэлементный # Возврат через -NoEnumerate: без него одноэлементный
# JSON-массив разворачивался бы в скаляр вторым анруллингом. # JSON-массив разворачивался бы в скаляр вторым анруллингом.
function ConvertFrom-JsonInput([string]$text, [string]$source, [string]$expected) { function ConvertFrom-JsonInput([string]$text, [string]$source, [string]$expected, [switch]$Inline) {
try { try {
# PS 5.1 на пустой строке отдаёт $null, а не ошибку — навык уходил дальше с $null,
# тогда как py-порт падал. Проверяем сами, чтобы порты вели себя одинаково.
if ([string]::IsNullOrWhiteSpace($text)) { throw 'input is empty' }
$parsed = $text | ConvertFrom-Json $parsed = $text | ConvertFrom-Json
} catch { } catch {
$what = if ($expected) { "$source expects $expected" } else { "Invalid JSON in $source" } $what = if ($expected) { "$source expects $expected" } else { "Invalid JSON in $source" }
$got = ($text -replace '\s+', ' ').Trim() if ($Inline) {
$label = 'got' $got = ($text -replace '\s+', ' ').Trim()
if ($got.Length -gt 60) { $label = 'got (first 60 chars, whitespace collapsed)'; $got = $got.Substring(0, 60) } $label = 'got'
[Console]::Error.WriteLine("[ERROR] ${what}, ${label}: ${got} ($($_.Exception.Message))") if (-not $got) { $got = '(empty)' }
elseif ($got.Length -gt 60) { $label = 'got (first 60 chars)'; $got = $got.Substring(0, 60) }
$what = "${what}, ${label}: ${got}"
}
[Console]::Error.WriteLine("[ERROR] ${what} ($($_.Exception.Message))")
exit 1 exit 1
} }
Write-Output -NoEnumerate $parsed Write-Output -NoEnumerate $parsed
} }
# --- Чтение входного JSON-файла ---
# Кодировку берём из BOM — это объявление самого файла, а не догадка. Без BOM ждём строгий UTF-8:
# Get-Content -Encoding UTF8 на файле в cp1251 тихо меняет кириллицу на U+FFFD, JSON после этого
# разбирается успешно, и в конфигурацию уезжает имя из «замен». Кодовую страницу не подбираем:
# угаданное имя уйдёт в метаданные так же молча.
function Read-JsonInputFile([string]$path) {
$bytes = [System.IO.File]::ReadAllBytes($path)
if ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) {
return [System.Text.Encoding]::UTF8.GetString($bytes, 3, $bytes.Length - 3)
}
if ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFF -and $bytes[1] -eq 0xFE) {
return [System.Text.Encoding]::Unicode.GetString($bytes, 2, $bytes.Length - 2)
}
if ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFE -and $bytes[1] -eq 0xFF) {
return [System.Text.Encoding]::BigEndianUnicode.GetString($bytes, 2, $bytes.Length - 2)
}
try {
return (New-Object System.Text.UTF8Encoding($false, $true)).GetString($bytes)
} catch {
$detail = if ($_.Exception.InnerException) { $_.Exception.InnerException.Message } else { $_.Exception.Message }
[Console]::Error.WriteLine("[ERROR] ${path} is not valid UTF-8: ${detail} - save the file as UTF-8, or add a BOM if it is UTF-16")
exit 1
}
}
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- 1. Load and validate JSON --- # --- 1. Load and validate JSON ---
@@ -38,7 +71,7 @@ if (-not (Test-Path $JsonPath)) {
exit 1 exit 1
} }
$json = Get-Content -Raw -Encoding UTF8 $JsonPath $json = Read-JsonInputFile $JsonPath
$def = ConvertFrom-JsonInput $json $JsonPath $def = ConvertFrom-JsonInput $json $JsonPath
# --- Support guard (Ext/ParentConfigurations.bin) --- # --- Support guard (Ext/ParentConfigurations.bin) ---
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# meta-compile v1.96 — Compile 1C metadata object from JSON # meta-compile v1.97 — Compile 1C metadata object from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
@@ -24,11 +24,12 @@ sys.stderr.reconfigure(encoding="utf-8")
# ============================================================ # ============================================================
def parse_json_input(text, source, expected=None): def parse_json_input(text, source, expected=None, inline=False):
"""Разбор пользовательского JSON: одна строка в stderr вместо traceback (issue #80). """Разбор пользовательского JSON: одна строка в stderr вместо traceback (issue #80).
expected заполняем только для полиморфного входа: у файла подсказка expected заполняем только для полиморфного входа: у файла подсказка
была бы наполнителем имя файла и текст парсера самодостаточны. была бы наполнителем имя файла и текст парсера самодостаточны. inline печатает ещё и то,
что доехало: у файла такого вопроса нет, он лежит на диске и его видно целиком.
Импорты внутри тела: копия функции живёт в навыках с разными именами модулей Импорты внутри тела: копия функции живёт в навыках с разными именами модулей
(skd-decompile импортирует json локально как _json), а тело обязано быть одинаковым. (skd-decompile импортирует json локально как _json), а тело обязано быть одинаковым.
@@ -36,15 +37,44 @@ def parse_json_input(text, source, expected=None):
import json as _pj import json as _pj
import sys as _psys import sys as _psys
try: try:
if not str(text).strip():
raise ValueError("input is empty")
return _pj.loads(text) return _pj.loads(text)
except ValueError as exc: except ValueError as exc:
what = "%s expects %s" % (source, expected) if expected else "Invalid JSON in %s" % source what = "%s expects %s" % (source, expected) if expected else "Invalid JSON in %s" % source
got = " ".join(str(text).split()) if inline:
label = "got" got = " ".join(str(text).split())
if len(got) > 60: label = "got"
label = "got (first 60 chars, whitespace collapsed)" if not got:
got = got[:60] got = "(empty)"
print("[ERROR] %s, %s: %s (%s)" % (what, label, got, exc), file=_psys.stderr) elif len(got) > 60:
label = "got (first 60 chars)"
got = got[:60]
what = "%s, %s: %s" % (what, label, got)
print("[ERROR] %s (%s)" % (what, exc), file=_psys.stderr)
_psys.exit(1)
def read_json_file(path):
"""Чтение входного JSON-файла с кодировкой из BOM (issue #80).
BOM объявление самого файла, поэтому ему верим; без BOM ждём строгий UTF-8. Кодовую
страницу не подбираем: угаданное имя уехало бы в метаданные молча.
"""
import sys as _psys
with open(path, "rb") as _fh:
data = _fh.read()
if data[:3] == b"\xef\xbb\xbf":
return data[3:].decode("utf-8")
if data[:2] == b"\xff\xfe":
return data[2:].decode("utf-16-le")
if data[:2] == b"\xfe\xff":
return data[2:].decode("utf-16-be")
try:
return data.decode("utf-8")
except UnicodeDecodeError as exc:
print("[ERROR] %s is not valid UTF-8: %s - save the file as UTF-8, or add a BOM if it is UTF-16"
% (path, exc), file=_psys.stderr)
_psys.exit(1) _psys.exit(1)
@@ -399,8 +429,7 @@ if not os.path.isfile(json_path):
print(f'File not found: {json_path}', file=sys.stderr) print(f'File not found: {json_path}', file=sys.stderr)
sys.exit(1) sys.exit(1)
with open(json_path, 'r', encoding='utf-8-sig') as f: json_text = read_json_file(json_path)
json_text = f.read()
defn = ci_json(parse_json_input(json_text, json_path)) defn = ci_json(parse_json_input(json_text, json_path))
+41 -8
View File
@@ -1,4 +1,4 @@
# meta-edit v1.39 — Edit existing 1C metadata object XML # meta-edit v1.40 — Edit existing 1C metadata object XML
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[string]$DefinitionFile, [string]$DefinitionFile,
@@ -35,22 +35,55 @@ $ErrorActionPreference = "Stop"
# --- Разбор пользовательского JSON --- # --- Разбор пользовательского JSON ---
# Одна строка в stderr вместо дампа исключения ConvertFrom-Json (issue #80): агент по стектрейсу # Одна строка в stderr вместо дампа исключения ConvertFrom-Json (issue #80): агент по стектрейсу
# идёт чинить скрипт, а не свой вызов. $source — файл или параметр. $expected заполняем только # идёт чинить скрипт, а не свой вызов. $source — файл или параметр. $expected заполняем только
# для полиморфного входа: у файла подсказка была бы наполнителем. # для полиморфного входа: у файла подсказка была бы наполнителем. -Inline печатает ещё и то,
# что доехало: у файла такого вопроса нет — путь назван, позицию дал парсер, файл на диске.
# Возврат через -NoEnumerate: без него одноэлементный # Возврат через -NoEnumerate: без него одноэлементный
# JSON-массив разворачивался бы в скаляр вторым анруллингом. # JSON-массив разворачивался бы в скаляр вторым анруллингом.
function ConvertFrom-JsonInput([string]$text, [string]$source, [string]$expected) { function ConvertFrom-JsonInput([string]$text, [string]$source, [string]$expected, [switch]$Inline) {
try { try {
# PS 5.1 на пустой строке отдаёт $null, а не ошибку — навык уходил дальше с $null,
# тогда как py-порт падал. Проверяем сами, чтобы порты вели себя одинаково.
if ([string]::IsNullOrWhiteSpace($text)) { throw 'input is empty' }
$parsed = $text | ConvertFrom-Json $parsed = $text | ConvertFrom-Json
} catch { } catch {
$what = if ($expected) { "$source expects $expected" } else { "Invalid JSON in $source" } $what = if ($expected) { "$source expects $expected" } else { "Invalid JSON in $source" }
$got = ($text -replace '\s+', ' ').Trim() if ($Inline) {
$label = 'got' $got = ($text -replace '\s+', ' ').Trim()
if ($got.Length -gt 60) { $label = 'got (first 60 chars, whitespace collapsed)'; $got = $got.Substring(0, 60) } $label = 'got'
[Console]::Error.WriteLine("[ERROR] ${what}, ${label}: ${got} ($($_.Exception.Message))") if (-not $got) { $got = '(empty)' }
elseif ($got.Length -gt 60) { $label = 'got (first 60 chars)'; $got = $got.Substring(0, 60) }
$what = "${what}, ${label}: ${got}"
}
[Console]::Error.WriteLine("[ERROR] ${what} ($($_.Exception.Message))")
exit 1 exit 1
} }
Write-Output -NoEnumerate $parsed Write-Output -NoEnumerate $parsed
} }
# --- Чтение входного JSON-файла ---
# Кодировку берём из BOM — это объявление самого файла, а не догадка. Без BOM ждём строгий UTF-8:
# Get-Content -Encoding UTF8 на файле в cp1251 тихо меняет кириллицу на U+FFFD, JSON после этого
# разбирается успешно, и в конфигурацию уезжает имя из «замен». Кодовую страницу не подбираем:
# угаданное имя уйдёт в метаданные так же молча.
function Read-JsonInputFile([string]$path) {
$bytes = [System.IO.File]::ReadAllBytes($path)
if ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) {
return [System.Text.Encoding]::UTF8.GetString($bytes, 3, $bytes.Length - 3)
}
if ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFF -and $bytes[1] -eq 0xFE) {
return [System.Text.Encoding]::Unicode.GetString($bytes, 2, $bytes.Length - 2)
}
if ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFE -and $bytes[1] -eq 0xFF) {
return [System.Text.Encoding]::BigEndianUnicode.GetString($bytes, 2, $bytes.Length - 2)
}
try {
return (New-Object System.Text.UTF8Encoding($false, $true)).GetString($bytes)
} catch {
$detail = if ($_.Exception.InnerException) { $_.Exception.InnerException.Message } else { $_.Exception.Message }
[Console]::Error.WriteLine("[ERROR] ${path} is not valid UTF-8: ${detail} - save the file as UTF-8, or add a BOM if it is UTF-16")
exit 1
}
}
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# ============================================================ # ============================================================
@@ -141,7 +174,7 @@ if ($DefinitionFile) {
Write-Error "Definition file not found: $DefinitionFile" Write-Error "Definition file not found: $DefinitionFile"
exit 1 exit 1
} }
$jsonText = Get-Content -Raw -Encoding UTF8 $DefinitionFile $jsonText = Read-JsonInputFile $DefinitionFile
$def = ConvertFrom-JsonInput $jsonText $DefinitionFile $def = ConvertFrom-JsonInput $jsonText $DefinitionFile
} }
+40 -11
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# meta-edit v1.39 — Edit existing 1C metadata object XML # meta-edit v1.40 — Edit existing 1C metadata object XML
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
@@ -14,11 +14,12 @@ from lxml import etree
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet] # Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
# регистр не различают, в argparse совпадение точное. # регистр не различают, в argparse совпадение точное.
def parse_json_input(text, source, expected=None): def parse_json_input(text, source, expected=None, inline=False):
"""Разбор пользовательского JSON: одна строка в stderr вместо traceback (issue #80). """Разбор пользовательского JSON: одна строка в stderr вместо traceback (issue #80).
expected заполняем только для полиморфного входа: у файла подсказка expected заполняем только для полиморфного входа: у файла подсказка
была бы наполнителем — имя файла и текст парсера самодостаточны. была бы наполнителем — имя файла и текст парсера самодостаточны. inline печатает ещё и то,
что доехало: у файла такого вопроса нет, он лежит на диске и его видно целиком.
Импорты внутри тела: копия функции живёт в навыках с разными именами модулей Импорты внутри тела: копия функции живёт в навыках с разными именами модулей
(skd-decompile импортирует json локально как _json), а тело обязано быть одинаковым. (skd-decompile импортирует json локально как _json), а тело обязано быть одинаковым.
@@ -26,15 +27,44 @@ def parse_json_input(text, source, expected=None):
import json as _pj import json as _pj
import sys as _psys import sys as _psys
try: try:
if not str(text).strip():
raise ValueError("input is empty")
return _pj.loads(text) return _pj.loads(text)
except ValueError as exc: except ValueError as exc:
what = "%s expects %s" % (source, expected) if expected else "Invalid JSON in %s" % source what = "%s expects %s" % (source, expected) if expected else "Invalid JSON in %s" % source
got = " ".join(str(text).split()) if inline:
label = "got" got = " ".join(str(text).split())
if len(got) > 60: label = "got"
label = "got (first 60 chars, whitespace collapsed)" if not got:
got = got[:60] got = "(empty)"
print("[ERROR] %s, %s: %s (%s)" % (what, label, got, exc), file=_psys.stderr) elif len(got) > 60:
label = "got (first 60 chars)"
got = got[:60]
what = "%s, %s: %s" % (what, label, got)
print("[ERROR] %s (%s)" % (what, exc), file=_psys.stderr)
_psys.exit(1)
def read_json_file(path):
"""Чтение входного JSON-файла с кодировкой из BOM (issue #80).
BOM — объявление самого файла, поэтому ему верим; без BOM ждём строгий UTF-8. Кодовую
страницу не подбираем: угаданное имя уехало бы в метаданные молча.
"""
import sys as _psys
with open(path, "rb") as _fh:
data = _fh.read()
if data[:3] == b"\xef\xbb\xbf":
return data[3:].decode("utf-8")
if data[:2] == b"\xff\xfe":
return data[2:].decode("utf-16-le")
if data[:2] == b"\xfe\xff":
return data[2:].decode("utf-16-be")
try:
return data.decode("utf-8")
except UnicodeDecodeError as exc:
print("[ERROR] %s is not valid UTF-8: %s - save the file as UTF-8, or add a BOM if it is UTF-16"
% (path, exc), file=_psys.stderr)
_psys.exit(1) _psys.exit(1)
@@ -3279,8 +3309,7 @@ def main():
if args.DefinitionFile: if args.DefinitionFile:
if not os.path.exists(args.DefinitionFile): if not os.path.exists(args.DefinitionFile):
die(f"Definition file not found: {args.DefinitionFile}") die(f"Definition file not found: {args.DefinitionFile}")
with open(args.DefinitionFile, "r", encoding="utf-8-sig") as f: definition = ci_json(parse_json_input(read_json_file(args.DefinitionFile), args.DefinitionFile))
definition = ci_json(parse_json_input(f.read(), args.DefinitionFile))
# --- Resolve object path --- # --- Resolve object path ---
object_path = args.ObjectPath object_path = args.ObjectPath
@@ -1,4 +1,4 @@
# mxl-compile v1.52 — Compile 1C spreadsheet from JSON # mxl-compile v1.53 — Compile 1C spreadsheet from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
@@ -13,22 +13,55 @@ $ErrorActionPreference = "Stop"
# --- Разбор пользовательского JSON --- # --- Разбор пользовательского JSON ---
# Одна строка в stderr вместо дампа исключения ConvertFrom-Json (issue #80): агент по стектрейсу # Одна строка в stderr вместо дампа исключения ConvertFrom-Json (issue #80): агент по стектрейсу
# идёт чинить скрипт, а не свой вызов. $source — файл или параметр. $expected заполняем только # идёт чинить скрипт, а не свой вызов. $source — файл или параметр. $expected заполняем только
# для полиморфного входа: у файла подсказка была бы наполнителем. # для полиморфного входа: у файла подсказка была бы наполнителем. -Inline печатает ещё и то,
# что доехало: у файла такого вопроса нет — путь назван, позицию дал парсер, файл на диске.
# Возврат через -NoEnumerate: без него одноэлементный # Возврат через -NoEnumerate: без него одноэлементный
# JSON-массив разворачивался бы в скаляр вторым анруллингом. # JSON-массив разворачивался бы в скаляр вторым анруллингом.
function ConvertFrom-JsonInput([string]$text, [string]$source, [string]$expected) { function ConvertFrom-JsonInput([string]$text, [string]$source, [string]$expected, [switch]$Inline) {
try { try {
# PS 5.1 на пустой строке отдаёт $null, а не ошибку — навык уходил дальше с $null,
# тогда как py-порт падал. Проверяем сами, чтобы порты вели себя одинаково.
if ([string]::IsNullOrWhiteSpace($text)) { throw 'input is empty' }
$parsed = $text | ConvertFrom-Json $parsed = $text | ConvertFrom-Json
} catch { } catch {
$what = if ($expected) { "$source expects $expected" } else { "Invalid JSON in $source" } $what = if ($expected) { "$source expects $expected" } else { "Invalid JSON in $source" }
$got = ($text -replace '\s+', ' ').Trim() if ($Inline) {
$label = 'got' $got = ($text -replace '\s+', ' ').Trim()
if ($got.Length -gt 60) { $label = 'got (first 60 chars, whitespace collapsed)'; $got = $got.Substring(0, 60) } $label = 'got'
[Console]::Error.WriteLine("[ERROR] ${what}, ${label}: ${got} ($($_.Exception.Message))") if (-not $got) { $got = '(empty)' }
elseif ($got.Length -gt 60) { $label = 'got (first 60 chars)'; $got = $got.Substring(0, 60) }
$what = "${what}, ${label}: ${got}"
}
[Console]::Error.WriteLine("[ERROR] ${what} ($($_.Exception.Message))")
exit 1 exit 1
} }
Write-Output -NoEnumerate $parsed Write-Output -NoEnumerate $parsed
} }
# --- Чтение входного JSON-файла ---
# Кодировку берём из BOM — это объявление самого файла, а не догадка. Без BOM ждём строгий UTF-8:
# Get-Content -Encoding UTF8 на файле в cp1251 тихо меняет кириллицу на U+FFFD, JSON после этого
# разбирается успешно, и в конфигурацию уезжает имя из «замен». Кодовую страницу не подбираем:
# угаданное имя уйдёт в метаданные так же молча.
function Read-JsonInputFile([string]$path) {
$bytes = [System.IO.File]::ReadAllBytes($path)
if ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) {
return [System.Text.Encoding]::UTF8.GetString($bytes, 3, $bytes.Length - 3)
}
if ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFF -and $bytes[1] -eq 0xFE) {
return [System.Text.Encoding]::Unicode.GetString($bytes, 2, $bytes.Length - 2)
}
if ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFE -and $bytes[1] -eq 0xFF) {
return [System.Text.Encoding]::BigEndianUnicode.GetString($bytes, 2, $bytes.Length - 2)
}
try {
return (New-Object System.Text.UTF8Encoding($false, $true)).GetString($bytes)
} catch {
$detail = if ($_.Exception.InnerException) { $_.Exception.InnerException.Message } else { $_.Exception.Message }
[Console]::Error.WriteLine("[ERROR] ${path} is not valid UTF-8: ${detail} - save the file as UTF-8, or add a BOM if it is UTF-16")
exit 1
}
}
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Support guard (Ext/ParentConfigurations.bin) --- # --- Support guard (Ext/ParentConfigurations.bin) ---
@@ -209,7 +242,7 @@ if (-not (Test-Path $JsonPath)) {
exit 1 exit 1
} }
$json = Get-Content -Raw -Encoding UTF8 $JsonPath $json = Read-JsonInputFile $JsonPath
$def = ConvertFrom-JsonInput $json $JsonPath $def = ConvertFrom-JsonInput $json $JsonPath
# Проверяем НАЛИЧИЕ ключа, а не истинность значения: `columns: 0` — осмысленная величина # Проверяем НАЛИЧИЕ ключа, а не истинность значения: `columns: 0` — осмысленная величина
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# mxl-compile v1.52 — Compile 1C spreadsheet from JSON # mxl-compile v1.53 — Compile 1C spreadsheet from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import hashlib import hashlib
@@ -14,11 +14,12 @@ from lxml import etree
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet] # Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
# регистр не различают, в argparse совпадение точное. # регистр не различают, в argparse совпадение точное.
def parse_json_input(text, source, expected=None): def parse_json_input(text, source, expected=None, inline=False):
"""Разбор пользовательского JSON: одна строка в stderr вместо traceback (issue #80). """Разбор пользовательского JSON: одна строка в stderr вместо traceback (issue #80).
expected заполняем только для полиморфного входа: у файла подсказка expected заполняем только для полиморфного входа: у файла подсказка
была бы наполнителем имя файла и текст парсера самодостаточны. была бы наполнителем имя файла и текст парсера самодостаточны. inline печатает ещё и то,
что доехало: у файла такого вопроса нет, он лежит на диске и его видно целиком.
Импорты внутри тела: копия функции живёт в навыках с разными именами модулей Импорты внутри тела: копия функции живёт в навыках с разными именами модулей
(skd-decompile импортирует json локально как _json), а тело обязано быть одинаковым. (skd-decompile импортирует json локально как _json), а тело обязано быть одинаковым.
@@ -26,15 +27,44 @@ def parse_json_input(text, source, expected=None):
import json as _pj import json as _pj
import sys as _psys import sys as _psys
try: try:
if not str(text).strip():
raise ValueError("input is empty")
return _pj.loads(text) return _pj.loads(text)
except ValueError as exc: except ValueError as exc:
what = "%s expects %s" % (source, expected) if expected else "Invalid JSON in %s" % source what = "%s expects %s" % (source, expected) if expected else "Invalid JSON in %s" % source
got = " ".join(str(text).split()) if inline:
label = "got" got = " ".join(str(text).split())
if len(got) > 60: label = "got"
label = "got (first 60 chars, whitespace collapsed)" if not got:
got = got[:60] got = "(empty)"
print("[ERROR] %s, %s: %s (%s)" % (what, label, got, exc), file=_psys.stderr) elif len(got) > 60:
label = "got (first 60 chars)"
got = got[:60]
what = "%s, %s: %s" % (what, label, got)
print("[ERROR] %s (%s)" % (what, exc), file=_psys.stderr)
_psys.exit(1)
def read_json_file(path):
"""Чтение входного JSON-файла с кодировкой из BOM (issue #80).
BOM объявление самого файла, поэтому ему верим; без BOM ждём строгий UTF-8. Кодовую
страницу не подбираем: угаданное имя уехало бы в метаданные молча.
"""
import sys as _psys
with open(path, "rb") as _fh:
data = _fh.read()
if data[:3] == b"\xef\xbb\xbf":
return data[3:].decode("utf-8")
if data[:2] == b"\xff\xfe":
return data[2:].decode("utf-16-le")
if data[:2] == b"\xfe\xff":
return data[2:].decode("utf-16-be")
try:
return data.decode("utf-8")
except UnicodeDecodeError as exc:
print("[ERROR] %s is not valid UTF-8: %s - save the file as UTF-8, or add a BOM if it is UTF-16"
% (path, exc), file=_psys.stderr)
_psys.exit(1) _psys.exit(1)
@@ -829,8 +859,7 @@ def main():
print(f"File not found: {json_path}", file=sys.stderr) print(f"File not found: {json_path}", file=sys.stderr)
sys.exit(1) sys.exit(1)
with open(json_path, 'r', encoding='utf-8-sig') as f: defn = ci_json(parse_json_input(read_json_file(json_path), json_path))
defn = ci_json(parse_json_input(f.read(), json_path))
# Проверяем НАЛИЧИЕ ключа, а не истинность значения: `columns: 0` — осмысленная величина # Проверяем НАЛИЧИЕ ключа, а не истинность значения: `columns: 0` — осмысленная величина
# (раскладка по умолчанию пустая, все строки живут в именованных раскладках), а пустой # (раскладка по умолчанию пустая, все строки живут в именованных раскладках), а пустой
@@ -1,4 +1,4 @@
# role-compile v1.29 — Compile 1C role from JSON # role-compile v1.30 — Compile 1C role from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
@@ -13,22 +13,55 @@ $ErrorActionPreference = "Stop"
# --- Разбор пользовательского JSON --- # --- Разбор пользовательского JSON ---
# Одна строка в stderr вместо дампа исключения ConvertFrom-Json (issue #80): агент по стектрейсу # Одна строка в stderr вместо дампа исключения ConvertFrom-Json (issue #80): агент по стектрейсу
# идёт чинить скрипт, а не свой вызов. $source — файл или параметр. $expected заполняем только # идёт чинить скрипт, а не свой вызов. $source — файл или параметр. $expected заполняем только
# для полиморфного входа: у файла подсказка была бы наполнителем. # для полиморфного входа: у файла подсказка была бы наполнителем. -Inline печатает ещё и то,
# что доехало: у файла такого вопроса нет — путь назван, позицию дал парсер, файл на диске.
# Возврат через -NoEnumerate: без него одноэлементный # Возврат через -NoEnumerate: без него одноэлементный
# JSON-массив разворачивался бы в скаляр вторым анруллингом. # JSON-массив разворачивался бы в скаляр вторым анруллингом.
function ConvertFrom-JsonInput([string]$text, [string]$source, [string]$expected) { function ConvertFrom-JsonInput([string]$text, [string]$source, [string]$expected, [switch]$Inline) {
try { try {
# PS 5.1 на пустой строке отдаёт $null, а не ошибку — навык уходил дальше с $null,
# тогда как py-порт падал. Проверяем сами, чтобы порты вели себя одинаково.
if ([string]::IsNullOrWhiteSpace($text)) { throw 'input is empty' }
$parsed = $text | ConvertFrom-Json $parsed = $text | ConvertFrom-Json
} catch { } catch {
$what = if ($expected) { "$source expects $expected" } else { "Invalid JSON in $source" } $what = if ($expected) { "$source expects $expected" } else { "Invalid JSON in $source" }
$got = ($text -replace '\s+', ' ').Trim() if ($Inline) {
$label = 'got' $got = ($text -replace '\s+', ' ').Trim()
if ($got.Length -gt 60) { $label = 'got (first 60 chars, whitespace collapsed)'; $got = $got.Substring(0, 60) } $label = 'got'
[Console]::Error.WriteLine("[ERROR] ${what}, ${label}: ${got} ($($_.Exception.Message))") if (-not $got) { $got = '(empty)' }
elseif ($got.Length -gt 60) { $label = 'got (first 60 chars)'; $got = $got.Substring(0, 60) }
$what = "${what}, ${label}: ${got}"
}
[Console]::Error.WriteLine("[ERROR] ${what} ($($_.Exception.Message))")
exit 1 exit 1
} }
Write-Output -NoEnumerate $parsed Write-Output -NoEnumerate $parsed
} }
# --- Чтение входного JSON-файла ---
# Кодировку берём из BOM — это объявление самого файла, а не догадка. Без BOM ждём строгий UTF-8:
# Get-Content -Encoding UTF8 на файле в cp1251 тихо меняет кириллицу на U+FFFD, JSON после этого
# разбирается успешно, и в конфигурацию уезжает имя из «замен». Кодовую страницу не подбираем:
# угаданное имя уйдёт в метаданные так же молча.
function Read-JsonInputFile([string]$path) {
$bytes = [System.IO.File]::ReadAllBytes($path)
if ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) {
return [System.Text.Encoding]::UTF8.GetString($bytes, 3, $bytes.Length - 3)
}
if ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFF -and $bytes[1] -eq 0xFE) {
return [System.Text.Encoding]::Unicode.GetString($bytes, 2, $bytes.Length - 2)
}
if ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFE -and $bytes[1] -eq 0xFF) {
return [System.Text.Encoding]::BigEndianUnicode.GetString($bytes, 2, $bytes.Length - 2)
}
try {
return (New-Object System.Text.UTF8Encoding($false, $true)).GetString($bytes)
} catch {
$detail = if ($_.Exception.InnerException) { $_.Exception.InnerException.Message } else { $_.Exception.Message }
[Console]::Error.WriteLine("[ERROR] ${path} is not valid UTF-8: ${detail} - save the file as UTF-8, or add a BOM if it is UTF-16")
exit 1
}
}
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Support guard (Ext/ParentConfigurations.bin) --- # --- Support guard (Ext/ParentConfigurations.bin) ---
@@ -169,7 +202,7 @@ if (-not (Test-Path $JsonPath)) {
exit 1 exit 1
} }
$json = Get-Content -Raw -Encoding UTF8 $JsonPath $json = Read-JsonInputFile $JsonPath
$def = ConvertFrom-JsonInput $json $JsonPath $def = ConvertFrom-JsonInput $json $JsonPath
if (-not $def.name) { if (-not $def.name) {
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# role-compile v1.29 — Compile 1C role from JSON # role-compile v1.30 — Compile 1C role from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import json import json
@@ -14,11 +14,12 @@ from lxml import etree
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet] # Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
# регистр не различают, в argparse совпадение точное. # регистр не различают, в argparse совпадение точное.
def parse_json_input(text, source, expected=None): def parse_json_input(text, source, expected=None, inline=False):
"""Разбор пользовательского JSON: одна строка в stderr вместо traceback (issue #80). """Разбор пользовательского JSON: одна строка в stderr вместо traceback (issue #80).
expected заполняем только для полиморфного входа: у файла подсказка expected заполняем только для полиморфного входа: у файла подсказка
была бы наполнителем имя файла и текст парсера самодостаточны. была бы наполнителем имя файла и текст парсера самодостаточны. inline печатает ещё и то,
что доехало: у файла такого вопроса нет, он лежит на диске и его видно целиком.
Импорты внутри тела: копия функции живёт в навыках с разными именами модулей Импорты внутри тела: копия функции живёт в навыках с разными именами модулей
(skd-decompile импортирует json локально как _json), а тело обязано быть одинаковым. (skd-decompile импортирует json локально как _json), а тело обязано быть одинаковым.
@@ -26,15 +27,44 @@ def parse_json_input(text, source, expected=None):
import json as _pj import json as _pj
import sys as _psys import sys as _psys
try: try:
if not str(text).strip():
raise ValueError("input is empty")
return _pj.loads(text) return _pj.loads(text)
except ValueError as exc: except ValueError as exc:
what = "%s expects %s" % (source, expected) if expected else "Invalid JSON in %s" % source what = "%s expects %s" % (source, expected) if expected else "Invalid JSON in %s" % source
got = " ".join(str(text).split()) if inline:
label = "got" got = " ".join(str(text).split())
if len(got) > 60: label = "got"
label = "got (first 60 chars, whitespace collapsed)" if not got:
got = got[:60] got = "(empty)"
print("[ERROR] %s, %s: %s (%s)" % (what, label, got, exc), file=_psys.stderr) elif len(got) > 60:
label = "got (first 60 chars)"
got = got[:60]
what = "%s, %s: %s" % (what, label, got)
print("[ERROR] %s (%s)" % (what, exc), file=_psys.stderr)
_psys.exit(1)
def read_json_file(path):
"""Чтение входного JSON-файла с кодировкой из BOM (issue #80).
BOM объявление самого файла, поэтому ему верим; без BOM ждём строгий UTF-8. Кодовую
страницу не подбираем: угаданное имя уехало бы в метаданные молча.
"""
import sys as _psys
with open(path, "rb") as _fh:
data = _fh.read()
if data[:3] == b"\xef\xbb\xbf":
return data[3:].decode("utf-8")
if data[:2] == b"\xff\xfe":
return data[2:].decode("utf-16-le")
if data[:2] == b"\xfe\xff":
return data[2:].decode("utf-16-be")
try:
return data.decode("utf-8")
except UnicodeDecodeError as exc:
print("[ERROR] %s is not valid UTF-8: %s - save the file as UTF-8, or add a BOM if it is UTF-16"
% (path, exc), file=_psys.stderr)
_psys.exit(1) _psys.exit(1)
@@ -1106,8 +1136,7 @@ def main():
print(f"File not found: {json_path}", file=sys.stderr) print(f"File not found: {json_path}", file=sys.stderr)
sys.exit(1) sys.exit(1)
with open(json_path, 'r', encoding='utf-8-sig') as f: defn = ci_json(parse_json_input(read_json_file(json_path), json_path))
defn = ci_json(parse_json_input(f.read(), json_path))
if not defn.get('name'): if not defn.get('name'):
print("JSON must have 'name' field (role programmatic name)", file=sys.stderr) print("JSON must have 'name' field (role programmatic name)", file=sys.stderr)
@@ -1,4 +1,4 @@
# skd-compile v1.118 — Compile 1C DCS from JSON (+write_xml_file/write_utf8_bom: общий эталон записи) # skd-compile v1.119 — Compile 1C DCS from JSON (+write_xml_file/write_utf8_bom: общий эталон записи)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[string]$DefinitionFile, [string]$DefinitionFile,
@@ -12,22 +12,55 @@ $ErrorActionPreference = "Stop"
# --- Разбор пользовательского JSON --- # --- Разбор пользовательского JSON ---
# Одна строка в stderr вместо дампа исключения ConvertFrom-Json (issue #80): агент по стектрейсу # Одна строка в stderr вместо дампа исключения ConvertFrom-Json (issue #80): агент по стектрейсу
# идёт чинить скрипт, а не свой вызов. $source — файл или параметр. $expected заполняем только # идёт чинить скрипт, а не свой вызов. $source — файл или параметр. $expected заполняем только
# для полиморфного входа: у файла подсказка была бы наполнителем. # для полиморфного входа: у файла подсказка была бы наполнителем. -Inline печатает ещё и то,
# что доехало: у файла такого вопроса нет — путь назван, позицию дал парсер, файл на диске.
# Возврат через -NoEnumerate: без него одноэлементный # Возврат через -NoEnumerate: без него одноэлементный
# JSON-массив разворачивался бы в скаляр вторым анруллингом. # JSON-массив разворачивался бы в скаляр вторым анруллингом.
function ConvertFrom-JsonInput([string]$text, [string]$source, [string]$expected) { function ConvertFrom-JsonInput([string]$text, [string]$source, [string]$expected, [switch]$Inline) {
try { try {
# PS 5.1 на пустой строке отдаёт $null, а не ошибку — навык уходил дальше с $null,
# тогда как py-порт падал. Проверяем сами, чтобы порты вели себя одинаково.
if ([string]::IsNullOrWhiteSpace($text)) { throw 'input is empty' }
$parsed = $text | ConvertFrom-Json $parsed = $text | ConvertFrom-Json
} catch { } catch {
$what = if ($expected) { "$source expects $expected" } else { "Invalid JSON in $source" } $what = if ($expected) { "$source expects $expected" } else { "Invalid JSON in $source" }
$got = ($text -replace '\s+', ' ').Trim() if ($Inline) {
$label = 'got' $got = ($text -replace '\s+', ' ').Trim()
if ($got.Length -gt 60) { $label = 'got (first 60 chars, whitespace collapsed)'; $got = $got.Substring(0, 60) } $label = 'got'
[Console]::Error.WriteLine("[ERROR] ${what}, ${label}: ${got} ($($_.Exception.Message))") if (-not $got) { $got = '(empty)' }
elseif ($got.Length -gt 60) { $label = 'got (first 60 chars)'; $got = $got.Substring(0, 60) }
$what = "${what}, ${label}: ${got}"
}
[Console]::Error.WriteLine("[ERROR] ${what} ($($_.Exception.Message))")
exit 1 exit 1
} }
Write-Output -NoEnumerate $parsed Write-Output -NoEnumerate $parsed
} }
# --- Чтение входного JSON-файла ---
# Кодировку берём из BOM — это объявление самого файла, а не догадка. Без BOM ждём строгий UTF-8:
# Get-Content -Encoding UTF8 на файле в cp1251 тихо меняет кириллицу на U+FFFD, JSON после этого
# разбирается успешно, и в конфигурацию уезжает имя из «замен». Кодовую страницу не подбираем:
# угаданное имя уйдёт в метаданные так же молча.
function Read-JsonInputFile([string]$path) {
$bytes = [System.IO.File]::ReadAllBytes($path)
if ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) {
return [System.Text.Encoding]::UTF8.GetString($bytes, 3, $bytes.Length - 3)
}
if ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFF -and $bytes[1] -eq 0xFE) {
return [System.Text.Encoding]::Unicode.GetString($bytes, 2, $bytes.Length - 2)
}
if ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFE -and $bytes[1] -eq 0xFF) {
return [System.Text.Encoding]::BigEndianUnicode.GetString($bytes, 2, $bytes.Length - 2)
}
try {
return (New-Object System.Text.UTF8Encoding($false, $true)).GetString($bytes)
} catch {
$detail = if ($_.Exception.InnerException) { $_.Exception.InnerException.Message } else { $_.Exception.Message }
[Console]::Error.WriteLine("[ERROR] ${path} is not valid UTF-8: ${detail} - save the file as UTF-8, or add a BOM if it is UTF-16")
exit 1
}
}
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Support guard (Ext/ParentConfigurations.bin) --- # --- Support guard (Ext/ParentConfigurations.bin) ---
@@ -180,14 +213,16 @@ if ($DefinitionFile) {
Write-Error "Definition file not found: $DefinitionFile" Write-Error "Definition file not found: $DefinitionFile"
exit 1 exit 1
} }
$json = Get-Content -Raw -Encoding UTF8 $DefinitionFile $json = Read-JsonInputFile $DefinitionFile
$jsonSource = $DefinitionFile $jsonSource = $DefinitionFile
$jsonInline = $false
} else { } else {
$json = $Value $json = $Value
$jsonSource = "-Value" $jsonSource = "-Value"
$jsonInline = $true
} }
$def = ConvertFrom-JsonInput $json $jsonSource $def = ConvertFrom-JsonInput $json $jsonSource -Inline:$jsonInline
# --- Sentinel check: refuse to compile if JSON contains skd-decompile sentinels --- # --- Sentinel check: refuse to compile if JSON contains skd-decompile sentinels ---
# These mark places the decompiler couldn't reverse cleanly; user must resolve # These mark places the decompiler couldn't reverse cleanly; user must resolve
@@ -1828,7 +1863,7 @@ while ($scanDir) {
} }
foreach ($stylesFile in $searchPaths) { foreach ($stylesFile in $searchPaths) {
if (Test-Path $stylesFile) { if (Test-Path $stylesFile) {
$userStyles = ConvertFrom-JsonInput (Get-Content -Raw -Encoding UTF8 $stylesFile) $stylesFile $userStyles = ConvertFrom-JsonInput (Read-JsonInputFile $stylesFile) $stylesFile
foreach ($prop in $userStyles.PSObject.Properties) { foreach ($prop in $userStyles.PSObject.Properties) {
$preset = @{} $preset = @{}
# Start from 'data' defaults # Start from 'data' defaults
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# skd-compile v1.118 — Compile 1C DCS from JSON (+write_xml_file/write_utf8_bom: общий эталон записи) # skd-compile v1.119 — Compile 1C DCS from JSON (+write_xml_file/write_utf8_bom: общий эталон записи)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import json import json
@@ -13,11 +13,12 @@ from lxml import etree
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet] # Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
# регистр не различают, в argparse совпадение точное. # регистр не различают, в argparse совпадение точное.
def parse_json_input(text, source, expected=None): def parse_json_input(text, source, expected=None, inline=False):
"""Разбор пользовательского JSON: одна строка в stderr вместо traceback (issue #80). """Разбор пользовательского JSON: одна строка в stderr вместо traceback (issue #80).
expected заполняем только для полиморфного входа: у файла подсказка expected заполняем только для полиморфного входа: у файла подсказка
была бы наполнителем имя файла и текст парсера самодостаточны. была бы наполнителем имя файла и текст парсера самодостаточны. inline печатает ещё и то,
что доехало: у файла такого вопроса нет, он лежит на диске и его видно целиком.
Импорты внутри тела: копия функции живёт в навыках с разными именами модулей Импорты внутри тела: копия функции живёт в навыках с разными именами модулей
(skd-decompile импортирует json локально как _json), а тело обязано быть одинаковым. (skd-decompile импортирует json локально как _json), а тело обязано быть одинаковым.
@@ -25,15 +26,44 @@ def parse_json_input(text, source, expected=None):
import json as _pj import json as _pj
import sys as _psys import sys as _psys
try: try:
if not str(text).strip():
raise ValueError("input is empty")
return _pj.loads(text) return _pj.loads(text)
except ValueError as exc: except ValueError as exc:
what = "%s expects %s" % (source, expected) if expected else "Invalid JSON in %s" % source what = "%s expects %s" % (source, expected) if expected else "Invalid JSON in %s" % source
got = " ".join(str(text).split()) if inline:
label = "got" got = " ".join(str(text).split())
if len(got) > 60: label = "got"
label = "got (first 60 chars, whitespace collapsed)" if not got:
got = got[:60] got = "(empty)"
print("[ERROR] %s, %s: %s (%s)" % (what, label, got, exc), file=_psys.stderr) elif len(got) > 60:
label = "got (first 60 chars)"
got = got[:60]
what = "%s, %s: %s" % (what, label, got)
print("[ERROR] %s (%s)" % (what, exc), file=_psys.stderr)
_psys.exit(1)
def read_json_file(path):
"""Чтение входного JSON-файла с кодировкой из BOM (issue #80).
BOM объявление самого файла, поэтому ему верим; без BOM ждём строгий UTF-8. Кодовую
страницу не подбираем: угаданное имя уехало бы в метаданные молча.
"""
import sys as _psys
with open(path, "rb") as _fh:
data = _fh.read()
if data[:3] == b"\xef\xbb\xbf":
return data[3:].decode("utf-8")
if data[:2] == b"\xff\xfe":
return data[2:].decode("utf-16-le")
if data[:2] == b"\xfe\xff":
return data[2:].decode("utf-16-be")
try:
return data.decode("utf-8")
except UnicodeDecodeError as exc:
print("[ERROR] %s is not valid UTF-8: %s - save the file as UTF-8, or add a BOM if it is UTF-16"
% (path, exc), file=_psys.stderr)
_psys.exit(1) _psys.exit(1)
@@ -1653,8 +1683,7 @@ def load_user_styles(base_dir, output_path=None):
scan_dir = parent_dir scan_dir = parent_dir
for p in search_paths: for p in search_paths:
if os.path.isfile(p): if os.path.isfile(p):
with open(p, 'r', encoding='utf-8-sig') as f: user_styles = ci_json(parse_json_input(read_json_file(p), p))
user_styles = ci_json(parse_json_input(f.read(), p))
for name, overrides in user_styles.items(): for name, overrides in user_styles.items():
base = dict(AREA_STYLE_PRESETS.get(name, AREA_STYLE_PRESETS['data'])) base = dict(AREA_STYLE_PRESETS.get(name, AREA_STYLE_PRESETS['data']))
base.update(overrides) base.update(overrides)
@@ -3086,14 +3115,15 @@ def main():
if not os.path.exists(def_file): if not os.path.exists(def_file):
print(f"Definition file not found: {def_file}", file=sys.stderr) print(f"Definition file not found: {def_file}", file=sys.stderr)
sys.exit(1) sys.exit(1)
with open(def_file, 'r', encoding='utf-8-sig') as f: json_text = read_json_file(def_file)
json_text = f.read()
json_source = def_file json_source = def_file
json_inline = False
else: else:
json_text = args.Value json_text = args.Value
json_source = "-Value" json_source = "-Value"
json_inline = True
defn = ci_json(parse_json_input(json_text, json_source)) defn = ci_json(parse_json_input(json_text, json_source, inline=json_inline))
if not defn.get('dataSets') or len(defn['dataSets']) == 0: if not defn.get('dataSets') or len(defn['dataSets']) == 0:
print("JSON must have at least one entry in 'dataSets'", file=sys.stderr) print("JSON must have at least one entry in 'dataSets'", file=sys.stderr)
@@ -1,4 +1,4 @@
# skd-decompile v0.93 — Decompile 1C DCS Template.xml to JSON DSL (draft) # skd-decompile v0.94 — Decompile 1C DCS Template.xml to JSON DSL (draft)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
@@ -13,22 +13,55 @@ $ErrorActionPreference = "Stop"
# --- Разбор пользовательского JSON --- # --- Разбор пользовательского JSON ---
# Одна строка в stderr вместо дампа исключения ConvertFrom-Json (issue #80): агент по стектрейсу # Одна строка в stderr вместо дампа исключения ConvertFrom-Json (issue #80): агент по стектрейсу
# идёт чинить скрипт, а не свой вызов. $source — файл или параметр. $expected заполняем только # идёт чинить скрипт, а не свой вызов. $source — файл или параметр. $expected заполняем только
# для полиморфного входа: у файла подсказка была бы наполнителем. # для полиморфного входа: у файла подсказка была бы наполнителем. -Inline печатает ещё и то,
# что доехало: у файла такого вопроса нет — путь назван, позицию дал парсер, файл на диске.
# Возврат через -NoEnumerate: без него одноэлементный # Возврат через -NoEnumerate: без него одноэлементный
# JSON-массив разворачивался бы в скаляр вторым анруллингом. # JSON-массив разворачивался бы в скаляр вторым анруллингом.
function ConvertFrom-JsonInput([string]$text, [string]$source, [string]$expected) { function ConvertFrom-JsonInput([string]$text, [string]$source, [string]$expected, [switch]$Inline) {
try { try {
# PS 5.1 на пустой строке отдаёт $null, а не ошибку — навык уходил дальше с $null,
# тогда как py-порт падал. Проверяем сами, чтобы порты вели себя одинаково.
if ([string]::IsNullOrWhiteSpace($text)) { throw 'input is empty' }
$parsed = $text | ConvertFrom-Json $parsed = $text | ConvertFrom-Json
} catch { } catch {
$what = if ($expected) { "$source expects $expected" } else { "Invalid JSON in $source" } $what = if ($expected) { "$source expects $expected" } else { "Invalid JSON in $source" }
$got = ($text -replace '\s+', ' ').Trim() if ($Inline) {
$label = 'got' $got = ($text -replace '\s+', ' ').Trim()
if ($got.Length -gt 60) { $label = 'got (first 60 chars, whitespace collapsed)'; $got = $got.Substring(0, 60) } $label = 'got'
[Console]::Error.WriteLine("[ERROR] ${what}, ${label}: ${got} ($($_.Exception.Message))") if (-not $got) { $got = '(empty)' }
elseif ($got.Length -gt 60) { $label = 'got (first 60 chars)'; $got = $got.Substring(0, 60) }
$what = "${what}, ${label}: ${got}"
}
[Console]::Error.WriteLine("[ERROR] ${what} ($($_.Exception.Message))")
exit 1 exit 1
} }
Write-Output -NoEnumerate $parsed Write-Output -NoEnumerate $parsed
} }
# --- Чтение входного JSON-файла ---
# Кодировку берём из BOM — это объявление самого файла, а не догадка. Без BOM ждём строгий UTF-8:
# Get-Content -Encoding UTF8 на файле в cp1251 тихо меняет кириллицу на U+FFFD, JSON после этого
# разбирается успешно, и в конфигурацию уезжает имя из «замен». Кодовую страницу не подбираем:
# угаданное имя уйдёт в метаданные так же молча.
function Read-JsonInputFile([string]$path) {
$bytes = [System.IO.File]::ReadAllBytes($path)
if ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) {
return [System.Text.Encoding]::UTF8.GetString($bytes, 3, $bytes.Length - 3)
}
if ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFF -and $bytes[1] -eq 0xFE) {
return [System.Text.Encoding]::Unicode.GetString($bytes, 2, $bytes.Length - 2)
}
if ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFE -and $bytes[1] -eq 0xFF) {
return [System.Text.Encoding]::BigEndianUnicode.GetString($bytes, 2, $bytes.Length - 2)
}
try {
return (New-Object System.Text.UTF8Encoding($false, $true)).GetString($bytes)
} catch {
$detail = if ($_.Exception.InnerException) { $_.Exception.InnerException.Message } else { $_.Exception.Message }
[Console]::Error.WriteLine("[ERROR] ${path} is not valid UTF-8: ${detail} - save the file as UTF-8, or add a BOM if it is UTF-16")
exit 1
}
}
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- 0. Resolve and validate input --- # --- 0. Resolve and validate input ---
@@ -1160,7 +1193,7 @@ function Load-UserStyles {
if (-not $dirPath) { return } if (-not $dirPath) { return }
$stylesPath = Join-Path $dirPath 'skd-styles.json' $stylesPath = Join-Path $dirPath 'skd-styles.json'
if (-not (Test-Path $stylesPath)) { return } if (-not (Test-Path $stylesPath)) { return }
$raw = ConvertFrom-JsonInput (Get-Content -Raw -Encoding UTF8 $stylesPath) $stylesPath $raw = ConvertFrom-JsonInput (Read-JsonInputFile $stylesPath) $stylesPath
$script:existingUserPresetsRaw = $raw $script:existingUserPresetsRaw = $raw
foreach ($prop in $raw.PSObject.Properties) { foreach ($prop in $raw.PSObject.Properties) {
# Compile-логика: data defaults → built-in if name match → user keys # Compile-логика: data defaults → built-in if name match → user keys
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# skd-decompile v0.93 — Decompile 1C DCS Template.xml to JSON DSL (draft) # skd-decompile v0.94 — Decompile 1C DCS Template.xml to JSON DSL (draft)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import os import os
@@ -10,11 +10,12 @@ import xml.etree.ElementTree as ET
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet] # Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
# регистр не различают, в argparse совпадение точное. # регистр не различают, в argparse совпадение точное.
def parse_json_input(text, source, expected=None): def parse_json_input(text, source, expected=None, inline=False):
"""Разбор пользовательского JSON: одна строка в stderr вместо traceback (issue #80). """Разбор пользовательского JSON: одна строка в stderr вместо traceback (issue #80).
expected заполняем только для полиморфного входа: у файла подсказка expected заполняем только для полиморфного входа: у файла подсказка
была бы наполнителем имя файла и текст парсера самодостаточны. была бы наполнителем имя файла и текст парсера самодостаточны. inline печатает ещё и то,
что доехало: у файла такого вопроса нет, он лежит на диске и его видно целиком.
Импорты внутри тела: копия функции живёт в навыках с разными именами модулей Импорты внутри тела: копия функции живёт в навыках с разными именами модулей
(skd-decompile импортирует json локально как _json), а тело обязано быть одинаковым. (skd-decompile импортирует json локально как _json), а тело обязано быть одинаковым.
@@ -22,15 +23,44 @@ def parse_json_input(text, source, expected=None):
import json as _pj import json as _pj
import sys as _psys import sys as _psys
try: try:
if not str(text).strip():
raise ValueError("input is empty")
return _pj.loads(text) return _pj.loads(text)
except ValueError as exc: except ValueError as exc:
what = "%s expects %s" % (source, expected) if expected else "Invalid JSON in %s" % source what = "%s expects %s" % (source, expected) if expected else "Invalid JSON in %s" % source
got = " ".join(str(text).split()) if inline:
label = "got" got = " ".join(str(text).split())
if len(got) > 60: label = "got"
label = "got (first 60 chars, whitespace collapsed)" if not got:
got = got[:60] got = "(empty)"
print("[ERROR] %s, %s: %s (%s)" % (what, label, got, exc), file=_psys.stderr) elif len(got) > 60:
label = "got (first 60 chars)"
got = got[:60]
what = "%s, %s: %s" % (what, label, got)
print("[ERROR] %s (%s)" % (what, exc), file=_psys.stderr)
_psys.exit(1)
def read_json_file(path):
"""Чтение входного JSON-файла с кодировкой из BOM (issue #80).
BOM объявление самого файла, поэтому ему верим; без BOM ждём строгий UTF-8. Кодовую
страницу не подбираем: угаданное имя уехало бы в метаданные молча.
"""
import sys as _psys
with open(path, "rb") as _fh:
data = _fh.read()
if data[:3] == b"\xef\xbb\xbf":
return data[3:].decode("utf-8")
if data[:2] == b"\xff\xfe":
return data[2:].decode("utf-16-le")
if data[:2] == b"\xfe\xff":
return data[2:].decode("utf-16-be")
try:
return data.decode("utf-8")
except UnicodeDecodeError as exc:
print("[ERROR] %s is not valid UTF-8: %s - save the file as UTF-8, or add a BOM if it is UTF-16"
% (path, exc), file=_psys.stderr)
_psys.exit(1) _psys.exit(1)
@@ -1387,8 +1417,7 @@ def load_user_styles(dir_path):
styles_path = os.path.join(dir_path, 'skd-styles.json') styles_path = os.path.join(dir_path, 'skd-styles.json')
if not os.path.exists(styles_path): if not os.path.exists(styles_path):
return return
with open(styles_path, 'r', encoding='utf-8-sig') as f: raw = parse_json_input(read_json_file(styles_path), styles_path)
raw = parse_json_input(f.read(), styles_path)
existing_user_presets_raw = raw existing_user_presets_raw = raw
for prop_name, prop_value in raw.items(): for prop_name, prop_value in raw.items():
preset = {} preset = {}
@@ -1,4 +1,4 @@
# subsystem-compile v1.27 — Create 1C subsystem from JSON definition # subsystem-compile v1.28 — Create 1C subsystem from JSON definition
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[string]$DefinitionFile, [string]$DefinitionFile,
@@ -13,22 +13,55 @@ $ErrorActionPreference = "Stop"
# --- Разбор пользовательского JSON --- # --- Разбор пользовательского JSON ---
# Одна строка в stderr вместо дампа исключения ConvertFrom-Json (issue #80): агент по стектрейсу # Одна строка в stderr вместо дампа исключения ConvertFrom-Json (issue #80): агент по стектрейсу
# идёт чинить скрипт, а не свой вызов. $source — файл или параметр. $expected заполняем только # идёт чинить скрипт, а не свой вызов. $source — файл или параметр. $expected заполняем только
# для полиморфного входа: у файла подсказка была бы наполнителем. # для полиморфного входа: у файла подсказка была бы наполнителем. -Inline печатает ещё и то,
# что доехало: у файла такого вопроса нет — путь назван, позицию дал парсер, файл на диске.
# Возврат через -NoEnumerate: без него одноэлементный # Возврат через -NoEnumerate: без него одноэлементный
# JSON-массив разворачивался бы в скаляр вторым анруллингом. # JSON-массив разворачивался бы в скаляр вторым анруллингом.
function ConvertFrom-JsonInput([string]$text, [string]$source, [string]$expected) { function ConvertFrom-JsonInput([string]$text, [string]$source, [string]$expected, [switch]$Inline) {
try { try {
# PS 5.1 на пустой строке отдаёт $null, а не ошибку — навык уходил дальше с $null,
# тогда как py-порт падал. Проверяем сами, чтобы порты вели себя одинаково.
if ([string]::IsNullOrWhiteSpace($text)) { throw 'input is empty' }
$parsed = $text | ConvertFrom-Json $parsed = $text | ConvertFrom-Json
} catch { } catch {
$what = if ($expected) { "$source expects $expected" } else { "Invalid JSON in $source" } $what = if ($expected) { "$source expects $expected" } else { "Invalid JSON in $source" }
$got = ($text -replace '\s+', ' ').Trim() if ($Inline) {
$label = 'got' $got = ($text -replace '\s+', ' ').Trim()
if ($got.Length -gt 60) { $label = 'got (first 60 chars, whitespace collapsed)'; $got = $got.Substring(0, 60) } $label = 'got'
[Console]::Error.WriteLine("[ERROR] ${what}, ${label}: ${got} ($($_.Exception.Message))") if (-not $got) { $got = '(empty)' }
elseif ($got.Length -gt 60) { $label = 'got (first 60 chars)'; $got = $got.Substring(0, 60) }
$what = "${what}, ${label}: ${got}"
}
[Console]::Error.WriteLine("[ERROR] ${what} ($($_.Exception.Message))")
exit 1 exit 1
} }
Write-Output -NoEnumerate $parsed Write-Output -NoEnumerate $parsed
} }
# --- Чтение входного JSON-файла ---
# Кодировку берём из BOM — это объявление самого файла, а не догадка. Без BOM ждём строгий UTF-8:
# Get-Content -Encoding UTF8 на файле в cp1251 тихо меняет кириллицу на U+FFFD, JSON после этого
# разбирается успешно, и в конфигурацию уезжает имя из «замен». Кодовую страницу не подбираем:
# угаданное имя уйдёт в метаданные так же молча.
function Read-JsonInputFile([string]$path) {
$bytes = [System.IO.File]::ReadAllBytes($path)
if ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) {
return [System.Text.Encoding]::UTF8.GetString($bytes, 3, $bytes.Length - 3)
}
if ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFF -and $bytes[1] -eq 0xFE) {
return [System.Text.Encoding]::Unicode.GetString($bytes, 2, $bytes.Length - 2)
}
if ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFE -and $bytes[1] -eq 0xFF) {
return [System.Text.Encoding]::BigEndianUnicode.GetString($bytes, 2, $bytes.Length - 2)
}
try {
return (New-Object System.Text.UTF8Encoding($false, $true)).GetString($bytes)
} catch {
$detail = if ($_.Exception.InnerException) { $_.Exception.InnerException.Message } else { $_.Exception.Message }
[Console]::Error.WriteLine("[ERROR] ${path} is not valid UTF-8: ${detail} - save the file as UTF-8, or add a BOM if it is UTF-16")
exit 1
}
}
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- 1. Load JSON --- # --- 1. Load JSON ---
@@ -49,14 +82,16 @@ if ($DefinitionFile) {
Write-Error "Definition file not found: $DefinitionFile" Write-Error "Definition file not found: $DefinitionFile"
exit 1 exit 1
} }
$json = Get-Content -Raw -Encoding UTF8 $DefinitionFile $json = Read-JsonInputFile $DefinitionFile
$jsonSource = $DefinitionFile $jsonSource = $DefinitionFile
$jsonInline = $false
} else { } else {
$json = $Value $json = $Value
$jsonSource = "-Value" $jsonSource = "-Value"
$jsonInline = $true
} }
$def = ConvertFrom-JsonInput $json $jsonSource $def = ConvertFrom-JsonInput $json $jsonSource -Inline:$jsonInline
if (-not $def.name) { if (-not $def.name) {
Write-Error "JSON must have 'name' field" Write-Error "JSON must have 'name' field"
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# subsystem-compile v1.27 — Create 1C subsystem from JSON definition # subsystem-compile v1.28 — Create 1C subsystem from JSON definition
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import json import json
@@ -14,11 +14,12 @@ from lxml import etree
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet] # Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
# регистр не различают, в argparse совпадение точное. # регистр не различают, в argparse совпадение точное.
def parse_json_input(text, source, expected=None): def parse_json_input(text, source, expected=None, inline=False):
"""Разбор пользовательского JSON: одна строка в stderr вместо traceback (issue #80). """Разбор пользовательского JSON: одна строка в stderr вместо traceback (issue #80).
expected заполняем только для полиморфного входа: у файла подсказка expected заполняем только для полиморфного входа: у файла подсказка
была бы наполнителем имя файла и текст парсера самодостаточны. была бы наполнителем имя файла и текст парсера самодостаточны. inline печатает ещё и то,
что доехало: у файла такого вопроса нет, он лежит на диске и его видно целиком.
Импорты внутри тела: копия функции живёт в навыках с разными именами модулей Импорты внутри тела: копия функции живёт в навыках с разными именами модулей
(skd-decompile импортирует json локально как _json), а тело обязано быть одинаковым. (skd-decompile импортирует json локально как _json), а тело обязано быть одинаковым.
@@ -26,15 +27,44 @@ def parse_json_input(text, source, expected=None):
import json as _pj import json as _pj
import sys as _psys import sys as _psys
try: try:
if not str(text).strip():
raise ValueError("input is empty")
return _pj.loads(text) return _pj.loads(text)
except ValueError as exc: except ValueError as exc:
what = "%s expects %s" % (source, expected) if expected else "Invalid JSON in %s" % source what = "%s expects %s" % (source, expected) if expected else "Invalid JSON in %s" % source
got = " ".join(str(text).split()) if inline:
label = "got" got = " ".join(str(text).split())
if len(got) > 60: label = "got"
label = "got (first 60 chars, whitespace collapsed)" if not got:
got = got[:60] got = "(empty)"
print("[ERROR] %s, %s: %s (%s)" % (what, label, got, exc), file=_psys.stderr) elif len(got) > 60:
label = "got (first 60 chars)"
got = got[:60]
what = "%s, %s: %s" % (what, label, got)
print("[ERROR] %s (%s)" % (what, exc), file=_psys.stderr)
_psys.exit(1)
def read_json_file(path):
"""Чтение входного JSON-файла с кодировкой из BOM (issue #80).
BOM объявление самого файла, поэтому ему верим; без BOM ждём строгий UTF-8. Кодовую
страницу не подбираем: угаданное имя уехало бы в метаданные молча.
"""
import sys as _psys
with open(path, "rb") as _fh:
data = _fh.read()
if data[:3] == b"\xef\xbb\xbf":
return data[3:].decode("utf-8")
if data[:2] == b"\xff\xfe":
return data[2:].decode("utf-16-le")
if data[:2] == b"\xfe\xff":
return data[2:].decode("utf-16-be")
try:
return data.decode("utf-8")
except UnicodeDecodeError as exc:
print("[ERROR] %s is not valid UTF-8: %s - save the file as UTF-8, or add a BOM if it is UTF-16"
% (path, exc), file=_psys.stderr)
_psys.exit(1) _psys.exit(1)
@@ -493,14 +523,15 @@ def main():
if not os.path.exists(def_file): if not os.path.exists(def_file):
print(f"Definition file not found: {def_file}", file=sys.stderr) print(f"Definition file not found: {def_file}", file=sys.stderr)
sys.exit(1) sys.exit(1)
with open(def_file, 'r', encoding='utf-8-sig') as f: json_text = read_json_file(def_file)
json_text = f.read()
json_source = def_file json_source = def_file
json_inline = False
else: else:
json_text = args.Value json_text = args.Value
json_source = "-Value" json_source = "-Value"
json_inline = True
defn = ci_json(parse_json_input(json_text, json_source)) defn = ci_json(parse_json_input(json_text, json_source, inline=json_inline))
if not defn.get('name'): if not defn.get('name'):
print("JSON must have 'name' field", file=sys.stderr) print("JSON must have 'name' field", file=sys.stderr)
@@ -1,4 +1,4 @@
# subsystem-edit v1.22 — Edit existing 1C subsystem XML (+тип Bot; cfe-diff/cfe-borrow: недостающие типы) # subsystem-edit v1.23 — Edit existing 1C subsystem XML (+тип Bot; cfe-diff/cfe-borrow: недостающие типы)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)][Alias('Path')][string]$SubsystemPath, [Parameter(Mandatory)][Alias('Path')][string]$SubsystemPath,
@@ -14,22 +14,55 @@ $ErrorActionPreference = "Stop"
# --- Разбор пользовательского JSON --- # --- Разбор пользовательского JSON ---
# Одна строка в stderr вместо дампа исключения ConvertFrom-Json (issue #80): агент по стектрейсу # Одна строка в stderr вместо дампа исключения ConvertFrom-Json (issue #80): агент по стектрейсу
# идёт чинить скрипт, а не свой вызов. $source — файл или параметр. $expected заполняем только # идёт чинить скрипт, а не свой вызов. $source — файл или параметр. $expected заполняем только
# для полиморфного входа: у файла подсказка была бы наполнителем. # для полиморфного входа: у файла подсказка была бы наполнителем. -Inline печатает ещё и то,
# что доехало: у файла такого вопроса нет — путь назван, позицию дал парсер, файл на диске.
# Возврат через -NoEnumerate: без него одноэлементный # Возврат через -NoEnumerate: без него одноэлементный
# JSON-массив разворачивался бы в скаляр вторым анруллингом. # JSON-массив разворачивался бы в скаляр вторым анруллингом.
function ConvertFrom-JsonInput([string]$text, [string]$source, [string]$expected) { function ConvertFrom-JsonInput([string]$text, [string]$source, [string]$expected, [switch]$Inline) {
try { try {
# PS 5.1 на пустой строке отдаёт $null, а не ошибку — навык уходил дальше с $null,
# тогда как py-порт падал. Проверяем сами, чтобы порты вели себя одинаково.
if ([string]::IsNullOrWhiteSpace($text)) { throw 'input is empty' }
$parsed = $text | ConvertFrom-Json $parsed = $text | ConvertFrom-Json
} catch { } catch {
$what = if ($expected) { "$source expects $expected" } else { "Invalid JSON in $source" } $what = if ($expected) { "$source expects $expected" } else { "Invalid JSON in $source" }
$got = ($text -replace '\s+', ' ').Trim() if ($Inline) {
$label = 'got' $got = ($text -replace '\s+', ' ').Trim()
if ($got.Length -gt 60) { $label = 'got (first 60 chars, whitespace collapsed)'; $got = $got.Substring(0, 60) } $label = 'got'
[Console]::Error.WriteLine("[ERROR] ${what}, ${label}: ${got} ($($_.Exception.Message))") if (-not $got) { $got = '(empty)' }
elseif ($got.Length -gt 60) { $label = 'got (first 60 chars)'; $got = $got.Substring(0, 60) }
$what = "${what}, ${label}: ${got}"
}
[Console]::Error.WriteLine("[ERROR] ${what} ($($_.Exception.Message))")
exit 1 exit 1
} }
Write-Output -NoEnumerate $parsed Write-Output -NoEnumerate $parsed
} }
# --- Чтение входного JSON-файла ---
# Кодировку берём из BOM — это объявление самого файла, а не догадка. Без BOM ждём строгий UTF-8:
# Get-Content -Encoding UTF8 на файле в cp1251 тихо меняет кириллицу на U+FFFD, JSON после этого
# разбирается успешно, и в конфигурацию уезжает имя из «замен». Кодовую страницу не подбираем:
# угаданное имя уйдёт в метаданные так же молча.
function Read-JsonInputFile([string]$path) {
$bytes = [System.IO.File]::ReadAllBytes($path)
if ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) {
return [System.Text.Encoding]::UTF8.GetString($bytes, 3, $bytes.Length - 3)
}
if ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFF -and $bytes[1] -eq 0xFE) {
return [System.Text.Encoding]::Unicode.GetString($bytes, 2, $bytes.Length - 2)
}
if ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFE -and $bytes[1] -eq 0xFF) {
return [System.Text.Encoding]::BigEndianUnicode.GetString($bytes, 2, $bytes.Length - 2)
}
try {
return (New-Object System.Text.UTF8Encoding($false, $true)).GetString($bytes)
} catch {
$detail = if ($_.Exception.InnerException) { $_.Exception.InnerException.Message } else { $_.Exception.Message }
[Console]::Error.WriteLine("[ERROR] ${path} is not valid UTF-8: ${detail} - save the file as UTF-8, or add a BOM if it is UTF-16")
exit 1
}
}
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Content type normalization (plural→singular, Russian→English) --- # --- Content type normalization (plural→singular, Russian→English) ---
@@ -452,7 +485,7 @@ function Expand-SelfClosingElement($container, $parentIndent) {
function Parse-ValueList([string]$val, [string]$opName) { function Parse-ValueList([string]$val, [string]$opName) {
$val = $val.Trim() $val = $val.Trim()
if ($val.StartsWith("[")) { if ($val.StartsWith("[")) {
$arr = ConvertFrom-JsonInput $val "-Value for operation '$opName'" "a JSON array of object names" $arr = ConvertFrom-JsonInput $val "-Value for operation '$opName'" "a JSON array of object names" -Inline
$result = @(); foreach ($item in $arr) { $result += "$item" } $result = @(); foreach ($item in $arr) { $result += "$item" }
return ,$result return ,$result
} }
@@ -585,7 +618,7 @@ function Do-RemoveChild([string]$childName) {
} }
function Do-SetProperty([string]$jsonVal) { function Do-SetProperty([string]$jsonVal) {
$propDef = ConvertFrom-JsonInput $jsonVal "-Value for operation 'set-property'" "a JSON object {name, value}" $propDef = ConvertFrom-JsonInput $jsonVal "-Value for operation 'set-property'" "a JSON object {name, value}" -Inline
$propName = "$($propDef.name)" $propName = "$($propDef.name)"
$propValue = "$($propDef.value)" $propValue = "$($propDef.value)"
@@ -658,7 +691,7 @@ if ($DefinitionFile) {
if (-not [System.IO.Path]::IsPathRooted($DefinitionFile)) { if (-not [System.IO.Path]::IsPathRooted($DefinitionFile)) {
$DefinitionFile = Join-Path (Get-Location).Path $DefinitionFile $DefinitionFile = Join-Path (Get-Location).Path $DefinitionFile
} }
$jsonText = Get-Content -Raw -Encoding UTF8 $DefinitionFile $jsonText = Read-JsonInputFile $DefinitionFile
$ops = ConvertFrom-JsonInput $jsonText $DefinitionFile $ops = ConvertFrom-JsonInput $jsonText $DefinitionFile
if ($ops -is [System.Array]) { if ($ops -is [System.Array]) {
foreach ($op in $ops) { $operations += $op } foreach ($op in $ops) { $operations += $op }
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# subsystem-edit v1.22 — Edit existing 1C subsystem XML (+тип Bot; cfe-diff/cfe-borrow: недостающие типы) # subsystem-edit v1.23 — Edit existing 1C subsystem XML (+тип Bot; cfe-diff/cfe-borrow: недостающие типы)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
@@ -14,11 +14,12 @@ from lxml import etree
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet] # Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
# регистр не различают, в argparse совпадение точное. # регистр не различают, в argparse совпадение точное.
def parse_json_input(text, source, expected=None): def parse_json_input(text, source, expected=None, inline=False):
"""Разбор пользовательского JSON: одна строка в stderr вместо traceback (issue #80). """Разбор пользовательского JSON: одна строка в stderr вместо traceback (issue #80).
expected заполняем только для полиморфного входа: у файла подсказка expected заполняем только для полиморфного входа: у файла подсказка
была бы наполнителем имя файла и текст парсера самодостаточны. была бы наполнителем имя файла и текст парсера самодостаточны. inline печатает ещё и то,
что доехало: у файла такого вопроса нет, он лежит на диске и его видно целиком.
Импорты внутри тела: копия функции живёт в навыках с разными именами модулей Импорты внутри тела: копия функции живёт в навыках с разными именами модулей
(skd-decompile импортирует json локально как _json), а тело обязано быть одинаковым. (skd-decompile импортирует json локально как _json), а тело обязано быть одинаковым.
@@ -26,15 +27,44 @@ def parse_json_input(text, source, expected=None):
import json as _pj import json as _pj
import sys as _psys import sys as _psys
try: try:
if not str(text).strip():
raise ValueError("input is empty")
return _pj.loads(text) return _pj.loads(text)
except ValueError as exc: except ValueError as exc:
what = "%s expects %s" % (source, expected) if expected else "Invalid JSON in %s" % source what = "%s expects %s" % (source, expected) if expected else "Invalid JSON in %s" % source
got = " ".join(str(text).split()) if inline:
label = "got" got = " ".join(str(text).split())
if len(got) > 60: label = "got"
label = "got (first 60 chars, whitespace collapsed)" if not got:
got = got[:60] got = "(empty)"
print("[ERROR] %s, %s: %s (%s)" % (what, label, got, exc), file=_psys.stderr) elif len(got) > 60:
label = "got (first 60 chars)"
got = got[:60]
what = "%s, %s: %s" % (what, label, got)
print("[ERROR] %s (%s)" % (what, exc), file=_psys.stderr)
_psys.exit(1)
def read_json_file(path):
"""Чтение входного JSON-файла с кодировкой из BOM (issue #80).
BOM объявление самого файла, поэтому ему верим; без BOM ждём строгий UTF-8. Кодовую
страницу не подбираем: угаданное имя уехало бы в метаданные молча.
"""
import sys as _psys
with open(path, "rb") as _fh:
data = _fh.read()
if data[:3] == b"\xef\xbb\xbf":
return data[3:].decode("utf-8")
if data[:2] == b"\xff\xfe":
return data[2:].decode("utf-16-le")
if data[:2] == b"\xfe\xff":
return data[2:].decode("utf-16-be")
try:
return data.decode("utf-8")
except UnicodeDecodeError as exc:
print("[ERROR] %s is not valid UTF-8: %s - save the file as UTF-8, or add a BOM if it is UTF-16"
% (path, exc), file=_psys.stderr)
_psys.exit(1) _psys.exit(1)
@@ -544,7 +574,7 @@ def parse_value_list(val, op_name):
"""Parse a string or JSON array into a list of strings.""" """Parse a string or JSON array into a list of strings."""
val = val.strip() val = val.strip()
if val.startswith("["): if val.startswith("["):
arr = ci_json(parse_json_input(val, "-Value for operation '%s'" % op_name, "a JSON array of object names")) arr = ci_json(parse_json_input(val, "-Value for operation '%s'" % op_name, "a JSON array of object names", inline=True))
return [str(item) for item in arr] return [str(item) for item in arr]
return [val] return [val]
@@ -803,7 +833,7 @@ def main():
def do_set_property(json_val): def do_set_property(json_val):
nonlocal modify_count nonlocal modify_count
prop_def = ci_json(parse_json_input( prop_def = ci_json(parse_json_input(
json_val, "-Value for operation 'set-property'", "a JSON object {name, value}")) json_val, "-Value for operation 'set-property'", "a JSON object {name, value}", inline=True))
prop_name = str(prop_def["name"]) prop_name = str(prop_def["name"])
prop_value = str(prop_def.get("value", "")) prop_value = str(prop_def.get("value", ""))
@@ -899,8 +929,7 @@ def main():
def_file = args.DefinitionFile def_file = args.DefinitionFile
if not os.path.isabs(def_file): if not os.path.isabs(def_file):
def_file = os.path.join(os.getcwd(), def_file) def_file = os.path.join(os.getcwd(), def_file)
with open(def_file, "r", encoding="utf-8-sig") as fh: ops = ci_json(parse_json_input(read_json_file(def_file), def_file))
ops = ci_json(parse_json_input(fh.read(), def_file))
if isinstance(ops, list): if isinstance(ops, list):
operations = ops operations = ops
else: else:
+1
View File
@@ -296,6 +296,7 @@ ibcmd-проход автоматически `○ skipped`, если рядом
| `name` | да | Название теста (отображается в отчёте) | | `name` | да | Название теста (отображается в отчёте) |
| `input` | нет | JSON-объект, передаётся навыку через temp-файл | | `input` | нет | JSON-объект, передаётся навыку через temp-файл |
| `inputRaw` | нет | Строка, пишется во входной файл дословно. Нужна для негативных кейсов про битый JSON: через `input` такой вход невыразим — `JSON.stringify` всегда даёт валидный документ. Приоритетнее `input` | | `inputRaw` | нет | Строка, пишется во входной файл дословно. Нужна для негативных кейсов про битый JSON: через `input` такой вход невыразим — `JSON.stringify` всегда даёт валидный документ. Приоритетнее `input` |
| `inputEncoding` | нет | Кодировка входного файла: `utf-16le` / `utf-16be` (пишутся с BOM) / `cp1251`. Без ключа — UTF-8. Нужна для кейсов, где навык обязан принять кодировку, объявленную BOM, и отвергнуть не-UTF-8 без BOM. Работает и с `input`, и с `inputRaw` |
| `params` | нет | Параметры для `case.<field>` и `workPath` маппинга | | `params` | нет | Параметры для `case.<field>` и `workPath` маппинга |
| `setup` | нет | Переопределение setup из `_skill.json` | | `setup` | нет | Переопределение setup из `_skill.json` |
| `outputPath` | нет | Относительный путь для навыков с `-OutputPath` | | `outputPath` | нет | Относительный путь для навыков с `-OutputPath` |
@@ -0,0 +1,25 @@
{
"name": "Пустое значение операции: в сообщении (empty), а не висячее двоеточие",
"preRun": [
{
"script": "subsystem-compile/scripts/subsystem-compile",
"input": {
"name": "Продажи"
},
"args": {
"-DefinitionFile": "{inputFile}",
"-OutputDir": "{workDir}"
}
}
],
"params": {
"ciPath": "Subsystems/Продажи/CommandInterface"
},
"input": [
{
"operation": "place",
"value": ""
}
],
"expectError": "got: (empty)"
}
@@ -0,0 +1,11 @@
{
"name": "Файл в cp1251 отвергается, а не читается как U+FFFD (issue #80)",
"inputRaw": "{\"type\": \"Catalog\", \"name\": \"Номенклатура\"}",
"inputEncoding": "cp1251",
"expectError": "is not valid UTF-8",
"expect": {
"filesAbsent": [
"Catalogs"
]
}
}
@@ -0,0 +1,13 @@
{
"name": "Файл в UTF-16 с BOM читается: кодировка объявлена самим файлом",
"inputRaw": "{\"type\": \"Catalog\", \"name\": \"Номенклатура\"}",
"inputEncoding": "utf-16le",
"params": {
"validatePath": "Catalogs/Номенклатура.xml"
},
"expect": {
"files": [
"Catalogs/Номенклатура.xml"
]
}
}
@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Catalog uuid="UUID-001">
<InternalInfo>
<xr:GeneratedType name="CatalogObject.Номенклатура" category="Object">
<xr:TypeId>UUID-002</xr:TypeId>
<xr:ValueId>UUID-003</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="CatalogRef.Номенклатура" category="Ref">
<xr:TypeId>UUID-004</xr:TypeId>
<xr:ValueId>UUID-005</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="CatalogSelection.Номенклатура" category="Selection">
<xr:TypeId>UUID-006</xr:TypeId>
<xr:ValueId>UUID-007</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="CatalogList.Номенклатура" category="List">
<xr:TypeId>UUID-008</xr:TypeId>
<xr:ValueId>UUID-009</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="CatalogManager.Номенклатура" category="Manager">
<xr:TypeId>UUID-010</xr:TypeId>
<xr:ValueId>UUID-011</xr:ValueId>
</xr:GeneratedType>
</InternalInfo>
<Properties>
<Name>Номенклатура</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Номенклатура</v8:content>
</v8:item>
</Synonym>
<Comment/>
<Hierarchical>false</Hierarchical>
<HierarchyType>HierarchyFoldersAndItems</HierarchyType>
<LimitLevelCount>false</LimitLevelCount>
<LevelCount>2</LevelCount>
<FoldersOnTop>true</FoldersOnTop>
<UseStandardCommands>true</UseStandardCommands>
<Owners/>
<SubordinationUse>ToItems</SubordinationUse>
<CodeLength>9</CodeLength>
<DescriptionLength>25</DescriptionLength>
<CodeType>String</CodeType>
<CodeAllowedLength>Variable</CodeAllowedLength>
<CodeSeries>WholeCatalog</CodeSeries>
<CheckUnique>false</CheckUnique>
<Autonumbering>true</Autonumbering>
<DefaultPresentation>AsDescription</DefaultPresentation>
<Characteristics/>
<PredefinedDataUpdate>Auto</PredefinedDataUpdate>
<EditType>InDialog</EditType>
<QuickChoice>false</QuickChoice>
<ChoiceMode>BothWays</ChoiceMode>
<InputByString>
<xr:Field>Catalog.Номенклатура.StandardAttribute.Description</xr:Field>
<xr:Field>Catalog.Номенклатура.StandardAttribute.Code</xr:Field>
</InputByString>
<SearchStringModeOnInputByString>Begin</SearchStringModeOnInputByString>
<FullTextSearchOnInputByString>DontUse</FullTextSearchOnInputByString>
<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>
<DefaultObjectForm/>
<DefaultFolderForm/>
<DefaultListForm/>
<DefaultChoiceForm/>
<DefaultFolderChoiceForm/>
<AuxiliaryObjectForm/>
<AuxiliaryFolderForm/>
<AuxiliaryListForm/>
<AuxiliaryChoiceForm/>
<AuxiliaryFolderChoiceForm/>
<IncludeHelpInContents>false</IncludeHelpInContents>
<BasedOn/>
<DataLockFields/>
<DataLockControlMode>Managed</DataLockControlMode>
<FullTextSearch>Use</FullTextSearch>
<ObjectPresentation/>
<ExtendedObjectPresentation/>
<ListPresentation/>
<ExtendedListPresentation/>
<Explanation/>
<CreateOnInput>Use</CreateOnInput>
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
<DataHistory>DontUse</DataHistory>
<UpdateDataHistoryImmediatelyAfterWrite>false</UpdateDataHistoryImmediatelyAfterWrite>
<ExecuteAfterWriteDataHistoryVersionProcessing>false</ExecuteAfterWriteDataHistoryVersionProcessing>
</Properties>
<ChildObjects/>
</Catalog>
</MetaDataObject>
@@ -0,0 +1,252 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Configuration uuid="UUID-001">
<InternalInfo>
<xr:ContainedObject>
<xr:ClassId>UUID-002</xr:ClassId>
<xr:ObjectId>UUID-003</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-004</xr:ClassId>
<xr:ObjectId>UUID-005</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-006</xr:ClassId>
<xr:ObjectId>UUID-007</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-008</xr:ClassId>
<xr:ObjectId>UUID-009</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-010</xr:ClassId>
<xr:ObjectId>UUID-011</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-012</xr:ClassId>
<xr:ObjectId>UUID-013</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-014</xr:ClassId>
<xr:ObjectId>UUID-015</xr:ObjectId>
</xr:ContainedObject>
</InternalInfo>
<Properties>
<Name>TestConfig</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>TestConfig</v8:content>
</v8:item>
</Synonym>
<Comment/>
<NamePrefix/>
<ConfigurationExtensionCompatibilityMode>Version8_3_24</ConfigurationExtensionCompatibilityMode>
<DefaultRunMode>ManagedApplication</DefaultRunMode>
<UsePurposes>
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
</UsePurposes>
<ScriptVariant>Russian</ScriptVariant>
<DefaultRoles/>
<Vendor/>
<Version/>
<UpdateCatalogAddress/>
<IncludeHelpInContents>false</IncludeHelpInContents>
<UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication>
<UseOrdinaryFormInManagedApplication>false</UseOrdinaryFormInManagedApplication>
<AdditionalFullTextSearchDictionaries/>
<CommonSettingsStorage/>
<ReportsUserSettingsStorage/>
<ReportsVariantsStorage/>
<FormDataSettingsStorage/>
<DynamicListsUserSettingsStorage/>
<URLExternalDataStorage/>
<Content/>
<DefaultReportForm/>
<DefaultReportVariantForm/>
<DefaultReportSettingsForm/>
<DefaultReportAppearanceTemplate/>
<DefaultDynamicListSettingsForm/>
<DefaultSearchForm/>
<DefaultDataHistoryChangeHistoryForm/>
<DefaultDataHistoryVersionDataForm/>
<DefaultDataHistoryVersionDifferencesForm/>
<DefaultCollaborationSystemUsersChoiceForm/>
<RequiredMobileApplicationPermissions/>
<UsedMobileApplicationFunctionalities>
<app:functionality>
<app:functionality>Biometrics</app:functionality>
<app:use>true</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Location</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BackgroundLocation</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BluetoothPrinters</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>WiFiPrinters</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Contacts</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Calendars</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>PushNotifications</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>LocalNotifications</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>InAppPurchases</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>PersonalComputerFileExchange</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Ads</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>NumberDialing</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>CallProcessing</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>CallLog</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AutoSendSMS</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>ReceiveSMS</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>SMSLog</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Camera</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Microphone</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>MusicLibrary</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>PictureAndVideoLibraries</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AudioPlaybackAndVibration</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BackgroundAudioPlaybackAndVibration</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>InstallPackages</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>OSBackup</app:functionality>
<app:use>true</app:use>
</app:functionality>
<app:functionality>
<app:functionality>ApplicationUsageStatistics</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BarcodeScanning</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BackgroundAudioRecording</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AllFilesAccess</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Videoconferences</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>NFC</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>DocumentScanning</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>SpeechToText</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Geofences</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>IncomingShareRequests</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AllIncomingShareRequestsTypesProcessing</app:functionality>
<app:use>false</app:use>
</app:functionality>
</UsedMobileApplicationFunctionalities>
<StandaloneConfigurationRestrictionRoles/>
<MobileApplicationURLs/>
<AllowedIncomingShareRequestTypes/>
<MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode>
<DefaultInterface/>
<DefaultStyle/>
<DefaultLanguage>Language.Русский</DefaultLanguage>
<BriefInformation/>
<DetailedInformation/>
<Copyright/>
<VendorInformationAddress/>
<ConfigurationInformationAddress/>
<DataLockControlMode>Managed</DataLockControlMode>
<ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode>
<ModalityUseMode>DontUse</ModalityUseMode>
<SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode>
<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>
<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
<CompatibilityMode>Version8_3_24</CompatibilityMode>
<DefaultConstantsForm/>
</Properties>
<ChildObjects>
<Language>Русский</Language>
<Catalog>Номенклатура</Catalog>
</ChildObjects>
</Configuration>
</MetaDataObject>
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<ClientApplicationInterface xmlns="http://v8.1c.ru/8.2/managed-application/core" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="InterfaceLayouter">
<top>
<panel id="UUID-001">
<uuid>UUID-002</uuid>
</panel>
</top>
<left>
<panel id="UUID-003">
<uuid>UUID-004</uuid>
</panel>
</left>
<panelDef id="UUID-004"/>
<panelDef id="UUID-005"/>
<panelDef id="UUID-006"/>
<panelDef id="UUID-002"/>
<panelDef id="UUID-007"/>
</ClientApplicationInterface>
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Language uuid="UUID-001">
<Properties>
<Name>Русский</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Русский</v8:content>
</v8:item>
</Synonym>
<Comment/>
<LanguageCode>ru</LanguageCode>
</Properties>
</Language>
</MetaDataObject>
+8
View File
@@ -398,6 +398,14 @@ const FAMILIES = [
// Сообщение об ошибке разбора одинаково во всех навыках (issue #80): стектрейс парсера // Сообщение об ошибке разбора одинаково во всех навыках (issue #80): стектрейс парсера
// агент читает как «скрипт сломан» и идёт чинить не то место. Вся специфика навыка — // агент читает как «скрипт сломан» и идёт чинить не то место. Вся специфика навыка —
// в аргументах source/expected на месте вызова, тело функции общее. // в аргументах source/expected на месте вызова, тело функции общее.
{
name: 'read_json_file', py: 'read_json_file', ps1: 'Read-JsonInputFile',
variants: [
{ id: 'base', authority: 'interface-edit',
consumers: ['cf-edit', 'form-compile', 'form-edit', 'meta-compile', 'meta-edit', 'mxl-compile',
'role-compile', 'skd-compile', 'skd-decompile', 'subsystem-compile', 'subsystem-edit'] },
],
},
{ {
name: 'parse_json_input', py: 'parse_json_input', ps1: 'ConvertFrom-JsonInput', name: 'parse_json_input', py: 'parse_json_input', ps1: 'ConvertFrom-JsonInput',
variants: [ variants: [
+32 -4
View File
@@ -268,6 +268,34 @@ function cleanupWorkspace(ws) {
// ─── Arg building ─────────────────────────────────────────────────────────── // ─── Arg building ───────────────────────────────────────────────────────────
// Байты входного файла в заданной кодировке. Нужно для кейсов про кодировку: writeFileSync
// пишет только UTF-8, а навык обязан одинаково вести себя на UTF-16 с BOM (принять) и на
// cp1251 (отвергнуть, а не молча подменить кириллицу на U+FFFD). cp1251 в Node нет — кодируем
// формулой по диапазонам, которые встречаются в кейсах (ASCII + кириллица); прочее — ошибка кейса.
function encodeInput(text, encoding) {
if (!encoding || encoding === 'utf-8' || encoding === 'utf8') return Buffer.from(text, 'utf8');
if (encoding === 'utf-16le') return Buffer.concat([Buffer.from([0xff, 0xfe]), Buffer.from(text, 'utf16le')]);
if (encoding === 'utf-16be') {
const le = Buffer.from(text, 'utf16le');
const be = Buffer.alloc(le.length);
for (let i = 0; i < le.length; i += 2) { be[i] = le[i + 1]; be[i + 1] = le[i]; }
return Buffer.concat([Buffer.from([0xfe, 0xff]), be]);
}
if (encoding === 'cp1251') {
const out = Buffer.alloc(text.length);
for (let i = 0; i < text.length; i++) {
const c = text.codePointAt(i);
if (c < 0x80) out[i] = c;
else if (c >= 0x410 && c <= 0x44f) out[i] = c - 0x350;
else if (c === 0x401) out[i] = 0xa8;
else if (c === 0x451) out[i] = 0xb8;
else throw new Error(`inputEncoding cp1251: символ U+${c.toString(16)} вне поддержанного набора (ASCII + кириллица)`);
}
return out;
}
throw new Error(`inputEncoding: неизвестная кодировка "${encoding}"`);
}
function buildArgs(skillConfig, caseData, workDir, inputFilePath, runtime) { function buildArgs(skillConfig, caseData, workDir, inputFilePath, runtime) {
const args = []; const args = [];
const scriptPath = resolveScript(skillConfig.script, runtime); const scriptPath = resolveScript(skillConfig.script, runtime);
@@ -803,10 +831,10 @@ async function runCaseAsync(testCase, opts) {
// JSON.stringify всегда даёт валидный документ. // JSON.stringify всегда даёт валидный документ.
if (caseData.inputRaw !== undefined) { if (caseData.inputRaw !== undefined) {
inputFile = join(workDir, '__input.json'); inputFile = join(workDir, '__input.json');
writeFileSync(inputFile, caseData.inputRaw, 'utf8'); writeFileSync(inputFile, encodeInput(caseData.inputRaw, caseData.inputEncoding));
} else if (caseData.input !== undefined) { } else if (caseData.input !== undefined) {
inputFile = join(workDir, '__input.json'); inputFile = join(workDir, '__input.json');
writeFileSync(inputFile, JSON.stringify(caseData.input, null, 2), 'utf8'); writeFileSync(inputFile, encodeInput(JSON.stringify(caseData.input, null, 2), caseData.inputEncoding));
} }
// Execute // Execute
@@ -1028,10 +1056,10 @@ function runCase(testCase, opts) {
// 3. Write input JSON if needed (inputRaw — дословно, см. выше) // 3. Write input JSON if needed (inputRaw — дословно, см. выше)
if (caseData.inputRaw !== undefined) { if (caseData.inputRaw !== undefined) {
inputFile = join(workDir, '__input.json'); inputFile = join(workDir, '__input.json');
writeFileSync(inputFile, caseData.inputRaw, 'utf8'); writeFileSync(inputFile, encodeInput(caseData.inputRaw, caseData.inputEncoding));
} else if (caseData.input !== undefined) { } else if (caseData.input !== undefined) {
inputFile = join(workDir, '__input.json'); inputFile = join(workDir, '__input.json');
writeFileSync(inputFile, JSON.stringify(caseData.input, null, 2), 'utf8'); writeFileSync(inputFile, encodeInput(JSON.stringify(caseData.input, null, 2), caseData.inputEncoding));
} }
// 4. Build CLI args and execute // 4. Build CLI args and execute