mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-09-18 15:45:52 +03:00
refactor(skd-compile,skd-edit,form-compile,role-*): одно правило поиска @файла
Чтение текста из файла жило тремя копиями под именем Resolve-QueryValue и вне реестра гарда, а role-* завели четвёртое правило — только от рабочего каталога. Теперь функция одна на пять навыков: Resolve-TextFromFile / resolve_text_from_file, заведена семья в check-inline-drift, эталон — skd-edit. Правило поиска общее: абсолютный путь как есть, относительный — рядом с DSL (у edit-навыков — рядом с редактируемым объектом), затем в текущем каталоге, не нашли — ошибка со списком проверенных мест. Имя без "query": в ролях из файла приходит условие RLS, а не запрос. Текст ошибки приведён к языку остальных сообщений. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FGkXwoXTuafcu1SXMsauFq
This commit is contained in:
co-authored by
Claude Opus 5
parent
121c9c8ad3
commit
d425833709
@@ -1,4 +1,4 @@
|
|||||||
# form-compile v1.196 — Compile 1C managed form from JSON or object metadata (гвард на группу additionalColumns без ключа columns)
|
# form-compile v1.197 — 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
|
||||||
[CmdletBinding(PositionalBinding=$false)]
|
[CmdletBinding(PositionalBinding=$false)]
|
||||||
param(
|
param(
|
||||||
@@ -1726,17 +1726,24 @@ if ($FromObject) {
|
|||||||
|
|
||||||
# Базовая директория для @file-ссылок в query динсписка (зеркало skd-compile)
|
# Базовая директория для @file-ссылок в query динсписка (зеркало skd-compile)
|
||||||
$script:queryBaseDir = if ($JsonPath) { [System.IO.Path]::GetDirectoryName((Resolve-Path $JsonPath).Path) } else { (Get-Location).Path }
|
$script:queryBaseDir = if ($JsonPath) { [System.IO.Path]::GetDirectoryName((Resolve-Path $JsonPath).Path) } else { (Get-Location).Path }
|
||||||
function Resolve-QueryValue {
|
function Resolve-TextFromFile {
|
||||||
param([string]$val, [string]$baseDir)
|
param([string]$val, [string]$baseDir)
|
||||||
if (-not $val.StartsWith("@")) { return $val }
|
if (-not $val.StartsWith("@")) { return $val }
|
||||||
$filePath = $val.Substring(1)
|
$filePath = $val.Substring(1)
|
||||||
if ([System.IO.Path]::IsPathRooted($filePath)) {
|
if ([System.IO.Path]::IsPathRooted($filePath)) {
|
||||||
$candidates = @($filePath)
|
$candidates = @($filePath)
|
||||||
} else {
|
} else {
|
||||||
$candidates = @((Join-Path $baseDir $filePath), (Join-Path (Get-Location).Path $filePath))
|
$candidates = @(
|
||||||
|
(Join-Path $baseDir $filePath),
|
||||||
|
(Join-Path (Get-Location).Path $filePath)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
foreach ($c in $candidates) { if (Test-Path $c) { return (Get-Content -Raw -Encoding UTF8 $c).TrimEnd() } }
|
foreach ($c in $candidates) {
|
||||||
Write-Error "Query file not found: $filePath (searched: $($candidates -join ', '))"
|
if (Test-Path $c) {
|
||||||
|
return (Get-Content -Raw -Encoding UTF8 $c).TrimEnd()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Write-Error "Файл значения не найден: $filePath (искали: $($candidates -join ', '))"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6035,7 +6042,7 @@ function Emit-Attributes {
|
|||||||
$ddr = if ($st.dynamicDataRead -eq $false) { "false" } else { "true" }
|
$ddr = if ($st.dynamicDataRead -eq $false) { "false" } else { "true" }
|
||||||
X "$si<DynamicDataRead>$ddr</DynamicDataRead>"
|
X "$si<DynamicDataRead>$ddr</DynamicDataRead>"
|
||||||
if ($hasQuery) {
|
if ($hasQuery) {
|
||||||
$qtext = Resolve-QueryValue "$($st.query)" $script:queryBaseDir
|
$qtext = Resolve-TextFromFile "$($st.query)" $script:queryBaseDir
|
||||||
X "$si<QueryText>$(Esc-XmlText $qtext)</QueryText>"
|
X "$si<QueryText>$(Esc-XmlText $qtext)</QueryText>"
|
||||||
}
|
}
|
||||||
# Явные поля набора (редко): override title/dataPath
|
# Явные поля набора (редко): override title/dataPath
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# form-compile v1.196 — Compile 1C managed form from JSON or object metadata (гвард на группу additionalColumns без ключа columns)
|
# form-compile v1.197 — 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
|
||||||
@@ -1571,19 +1571,22 @@ def di_attr(el):
|
|||||||
QUERY_BASE_DIR = None
|
QUERY_BASE_DIR = None
|
||||||
|
|
||||||
|
|
||||||
def resolve_query_value(val, base_dir):
|
def resolve_text_from_file(val, base_dir):
|
||||||
if not val.startswith('@'):
|
if not val.startswith("@"):
|
||||||
return val
|
return val
|
||||||
file_path = val[1:]
|
file_path = val[1:]
|
||||||
if os.path.isabs(file_path):
|
if os.path.isabs(file_path):
|
||||||
candidates = [file_path]
|
candidates = [file_path]
|
||||||
else:
|
else:
|
||||||
candidates = [os.path.join(base_dir or os.getcwd(), file_path), os.path.join(os.getcwd(), file_path)]
|
candidates = [
|
||||||
|
os.path.join(base_dir, file_path),
|
||||||
|
os.path.join(os.getcwd(), file_path),
|
||||||
|
]
|
||||||
for c in candidates:
|
for c in candidates:
|
||||||
if os.path.exists(c):
|
if os.path.exists(c):
|
||||||
with open(c, 'r', encoding='utf-8-sig') as f:
|
with open(c, 'r', encoding='utf-8-sig') as f:
|
||||||
return f.read().rstrip()
|
return f.read().rstrip()
|
||||||
print(f"Query file not found: {file_path} (searched: {', '.join(candidates)})", file=sys.stderr)
|
print(f"Файл значения не найден: {file_path} (искали: {', '.join(candidates)})", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
@@ -5877,7 +5880,7 @@ def emit_attributes(lines, attrs, indent, conditional_appearance=None):
|
|||||||
ddr = 'false' if s.get('dynamicDataRead') is False else 'true'
|
ddr = 'false' if s.get('dynamicDataRead') is False else 'true'
|
||||||
lines.append(f'{si}<DynamicDataRead>{ddr}</DynamicDataRead>')
|
lines.append(f'{si}<DynamicDataRead>{ddr}</DynamicDataRead>')
|
||||||
if has_query:
|
if has_query:
|
||||||
qtext = resolve_query_value(str(s['query']), QUERY_BASE_DIR)
|
qtext = resolve_text_from_file(str(s['query']), QUERY_BASE_DIR)
|
||||||
lines.append(f'{si}<QueryText>{esc_xml_text(qtext)}</QueryText>')
|
lines.append(f'{si}<QueryText>{esc_xml_text(qtext)}</QueryText>')
|
||||||
# Явные поля набора (редко): override title/dataPath
|
# Явные поля набора (редко): override title/dataPath
|
||||||
if s.get('fields'):
|
if s.get('fields'):
|
||||||
|
|||||||
@@ -91,7 +91,8 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/role-compile.ps1" -
|
|||||||
|
|
||||||
Ссылка в `rls`: `"#ДляОбъекта(\"\")"`. Символ `&` автоматически экранируется в XML.
|
Ссылка в `rls`: `"#ДляОбъекта(\"\")"`. Символ `&` автоматически экранируется в XML.
|
||||||
|
|
||||||
Длинное условие держи в файле — в значении пишется `@путь` (путь от текущего каталога):
|
Длинное условие держи в файле — в значении пишется `@путь`. Относительный путь ищется рядом
|
||||||
|
с JSON-описанием роли, затем в текущем каталоге:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"objects": [{"name": "Document.Продажа", "preset": "view", "rls": {"Read": "@условие.txt"}}],
|
"objects": [{"name": "Document.Продажа", "preset": "view", "rls": {"Read": "@условие.txt"}}],
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# role-compile v1.40 — Compile 1C role from JSON
|
# role-compile v1.41 — Compile 1C role from JSON
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
[CmdletBinding(PositionalBinding=$false)]
|
[CmdletBinding(PositionalBinding=$false)]
|
||||||
param(
|
param(
|
||||||
@@ -1073,17 +1073,27 @@ function Validate-RightName {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# "@путь" в значении условия — текст берётся из файла: условия RLS типовых занимают десятки
|
# "@путь" в значении условия — текст берётся из файла: условия RLS типовых занимают десятки
|
||||||
# строк с кавычками, и внутри JSON-строки это источник ошибок экранирования. Путь относительный —
|
# строк с кавычками, и внутри JSON-строки это источник ошибок экранирования. Относительный
|
||||||
# от текущего каталога, как в role-edit.
|
# путь ищется рядом с JSON-описанием роли, затем в текущем каталоге.
|
||||||
function Resolve-TextValue([string]$text) {
|
function Resolve-TextFromFile {
|
||||||
if (-not $text -or -not $text.StartsWith("@")) { return $text }
|
param([string]$val, [string]$baseDir)
|
||||||
$valueFile = $text.Substring(1).Trim()
|
if (-not $val.StartsWith("@")) { return $val }
|
||||||
if (-not [System.IO.Path]::IsPathRooted($valueFile)) { $valueFile = Join-Path (Get-Location).Path $valueFile }
|
$filePath = $val.Substring(1)
|
||||||
if (-not (Test-Path -LiteralPath $valueFile -PathType Leaf)) {
|
if ([System.IO.Path]::IsPathRooted($filePath)) {
|
||||||
Add-ValidationError "Файл значения не найден: $valueFile"
|
$candidates = @($filePath)
|
||||||
return $text
|
} else {
|
||||||
|
$candidates = @(
|
||||||
|
(Join-Path $baseDir $filePath),
|
||||||
|
(Join-Path (Get-Location).Path $filePath)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return [System.IO.File]::ReadAllText($valueFile).Trim()
|
foreach ($c in $candidates) {
|
||||||
|
if (Test-Path $c) {
|
||||||
|
return (Get-Content -Raw -Encoding UTF8 $c).TrimEnd()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Write-Error "Файл значения не найден: $filePath (искали: $($candidates -join ', '))"
|
||||||
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- 5a. Service roots: expand to leaves ---
|
# --- 5a. Service roots: expand to leaves ---
|
||||||
@@ -1304,7 +1314,7 @@ function Parse-ObjectEntry {
|
|||||||
foreach ($p in $entry.rls.PSObject.Properties) {
|
foreach ($p in $entry.rls.PSObject.Properties) {
|
||||||
$rlsRight = Translate-RightName $p.Name
|
$rlsRight = Translate-RightName $p.Name
|
||||||
if ($rightsMap.Contains($rlsRight)) {
|
if ($rightsMap.Contains($rlsRight)) {
|
||||||
$rightsMap[$rlsRight].Condition = Resolve-TextValue "$($p.Value)"
|
$rightsMap[$rlsRight].Condition = Resolve-TextFromFile "$($p.Value)" $script:textBaseDir
|
||||||
} else {
|
} else {
|
||||||
Write-Warning "${objName}: RLS for '$rlsRight' but this right is not in the rights list"
|
Write-Warning "${objName}: RLS for '$rlsRight' but this right is not in the rights list"
|
||||||
}
|
}
|
||||||
@@ -1332,6 +1342,9 @@ if (-not $def.objects -and $def.rights) { $def | Add-Member -NotePropertyName ob
|
|||||||
# Путь нужен уже здесь: раскрытие сервисного корня читает метаданные сервиса рядом с ролью.
|
# Путь нужен уже здесь: раскрытие сервисного корня читает метаданные сервиса рядом с ролью.
|
||||||
$resolvedOutputDir = if ([System.IO.Path]::IsPathRooted($OutputDir)) { $OutputDir } else { Join-Path (Get-Location) $OutputDir }
|
$resolvedOutputDir = if ([System.IO.Path]::IsPathRooted($OutputDir)) { $OutputDir } else { Join-Path (Get-Location) $OutputDir }
|
||||||
|
|
||||||
|
# Относительный путь @файла ищем сначала рядом с JSON-описанием роли.
|
||||||
|
$script:textBaseDir = [System.IO.Path]::GetDirectoryName((Resolve-Path $JsonPath).Path)
|
||||||
|
|
||||||
$parsedObjects = @()
|
$parsedObjects = @()
|
||||||
$seenObjectNames = @{}
|
$seenObjectNames = @{}
|
||||||
if ($def.objects) {
|
if ($def.objects) {
|
||||||
@@ -1492,7 +1505,7 @@ if ($def.templates) {
|
|||||||
foreach ($tpl in $def.templates) {
|
foreach ($tpl in $def.templates) {
|
||||||
X "`t<restrictionTemplate>"
|
X "`t<restrictionTemplate>"
|
||||||
X "`t`t<name>$(Esc-XmlText "$($tpl.name)")</name>"
|
X "`t`t<name>$(Esc-XmlText "$($tpl.name)")</name>"
|
||||||
X "`t`t<condition>$(Esc-XmlText (Resolve-TextValue "$($tpl.condition)"))</condition>"
|
X "`t`t<condition>$(Esc-XmlText (Resolve-TextFromFile "$($tpl.condition)" $script:textBaseDir))</condition>"
|
||||||
X "`t</restrictionTemplate>"
|
X "`t</restrictionTemplate>"
|
||||||
$templateCount++
|
$templateCount++
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# role-compile v1.40 — Compile 1C role from JSON
|
# role-compile v1.41 — 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
|
||||||
@@ -1166,19 +1166,28 @@ def validate_right_name(object_name, right_name):
|
|||||||
|
|
||||||
|
|
||||||
# "@путь" в значении условия — текст берётся из файла: условия RLS типовых занимают десятки
|
# "@путь" в значении условия — текст берётся из файла: условия RLS типовых занимают десятки
|
||||||
# строк с кавычками, и внутри JSON-строки это источник ошибок экранирования. Путь относительный —
|
# строк с кавычками, и внутри JSON-строки это источник ошибок экранирования. Относительный
|
||||||
# от текущего каталога, как в role-edit.
|
# путь ищется рядом с JSON-описанием роли, затем в текущем каталоге.
|
||||||
def resolve_text_value(text):
|
def resolve_text_from_file(val, base_dir):
|
||||||
if not text or not text.startswith("@"):
|
if not val.startswith("@"):
|
||||||
return text
|
return val
|
||||||
value_file = text[1:].strip()
|
file_path = val[1:]
|
||||||
if not os.path.isabs(value_file):
|
if os.path.isabs(file_path):
|
||||||
value_file = os.path.join(os.getcwd(), value_file)
|
candidates = [file_path]
|
||||||
if not os.path.isfile(value_file):
|
else:
|
||||||
add_validation_error(f"Файл значения не найден: {value_file}")
|
candidates = [
|
||||||
return text
|
os.path.join(base_dir, file_path),
|
||||||
with open(value_file, encoding="utf-8-sig") as f:
|
os.path.join(os.getcwd(), file_path),
|
||||||
return f.read().strip()
|
]
|
||||||
|
for c in candidates:
|
||||||
|
if os.path.exists(c):
|
||||||
|
with open(c, 'r', encoding='utf-8-sig') as f:
|
||||||
|
return f.read().rstrip()
|
||||||
|
print(f"Файл значения не найден: {file_path} (искали: {', '.join(candidates)})", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
TEXT_BASE_DIR = os.getcwd()
|
||||||
|
|
||||||
|
|
||||||
MD_NS = 'http://v8.1c.ru/8.3/MDClasses'
|
MD_NS = 'http://v8.1c.ru/8.3/MDClasses'
|
||||||
@@ -1380,7 +1389,7 @@ def parse_object_entry(entry):
|
|||||||
for p_name, p_value in entry['rls'].items():
|
for p_name, p_value in entry['rls'].items():
|
||||||
rls_right = translate_right_name(p_name)
|
rls_right = translate_right_name(p_name)
|
||||||
if rls_right in rights_map:
|
if rls_right in rights_map:
|
||||||
rights_map[rls_right]['Condition'] = resolve_text_value(str(p_value))
|
rights_map[rls_right]['Condition'] = resolve_text_from_file(str(p_value), TEXT_BASE_DIR)
|
||||||
else:
|
else:
|
||||||
print(f"WARNING: {obj_name}: RLS for '{rls_right}' but this right is not in the rights list", file=sys.stderr)
|
print(f"WARNING: {obj_name}: RLS for '{rls_right}' but this right is not in the rights list", file=sys.stderr)
|
||||||
|
|
||||||
@@ -1628,6 +1637,10 @@ def main():
|
|||||||
format_version = detect_format_version(out_dir_resolved)
|
format_version = detect_format_version(out_dir_resolved)
|
||||||
|
|
||||||
# --- 2. Parse all object entries ---
|
# --- 2. Parse all object entries ---
|
||||||
|
# Относительный путь @файла ищем сначала рядом с JSON-описанием роли.
|
||||||
|
global TEXT_BASE_DIR
|
||||||
|
TEXT_BASE_DIR = os.path.dirname(os.path.abspath(args.JsonPath))
|
||||||
|
|
||||||
parsed_objects = []
|
parsed_objects = []
|
||||||
seen_object_names = set()
|
seen_object_names = set()
|
||||||
if defn.get('objects'):
|
if defn.get('objects'):
|
||||||
@@ -1740,7 +1753,7 @@ def main():
|
|||||||
for tpl in defn['templates']:
|
for tpl in defn['templates']:
|
||||||
lines.append('\t<restrictionTemplate>')
|
lines.append('\t<restrictionTemplate>')
|
||||||
lines.append(f'\t\t<name>{esc_xml_text(str(tpl["name"]))}</name>')
|
lines.append(f'\t\t<name>{esc_xml_text(str(tpl["name"]))}</name>')
|
||||||
lines.append(f'\t\t<condition>{esc_xml_text(resolve_text_value(str(tpl["condition"])))}</condition>')
|
lines.append(f'\t\t<condition>{esc_xml_text(resolve_text_from_file(str(tpl["condition"]), TEXT_BASE_DIR))}</condition>')
|
||||||
lines.append('\t</restrictionTemplate>')
|
lines.append('\t</restrictionTemplate>')
|
||||||
template_count += 1
|
template_count += 1
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# role-edit v1.0 — Edit existing 1C role rights in place
|
# role-edit v1.1 — Edit existing 1C role rights in place
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
[CmdletBinding(PositionalBinding=$false)]
|
[CmdletBinding(PositionalBinding=$false)]
|
||||||
param(
|
param(
|
||||||
@@ -1025,6 +1025,27 @@ function Validate-RightName {
|
|||||||
return $true
|
return $true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Resolve-TextFromFile {
|
||||||
|
param([string]$val, [string]$baseDir)
|
||||||
|
if (-not $val.StartsWith("@")) { return $val }
|
||||||
|
$filePath = $val.Substring(1)
|
||||||
|
if ([System.IO.Path]::IsPathRooted($filePath)) {
|
||||||
|
$candidates = @($filePath)
|
||||||
|
} else {
|
||||||
|
$candidates = @(
|
||||||
|
(Join-Path $baseDir $filePath),
|
||||||
|
(Join-Path (Get-Location).Path $filePath)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
foreach ($c in $candidates) {
|
||||||
|
if (Test-Path $c) {
|
||||||
|
return (Get-Content -Raw -Encoding UTF8 $c).TrimEnd()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Write-Error "Файл значения не найден: $filePath (искали: $($candidates -join ', '))"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
# --- 5a. Service roots: expand to leaves ---
|
# --- 5a. Service roots: expand to leaves ---
|
||||||
|
|
||||||
# Метаданные сервиса читаются один раз на имя: раскрытие и проверка заимствования
|
# Метаданные сервиса читаются один раз на имя: раскрытие и проверка заимствования
|
||||||
@@ -1405,20 +1426,6 @@ $script:rightsPath = $script:paths.RightsPath
|
|||||||
$script:roleXmlPath = $script:paths.RoleXmlPath
|
$script:roleXmlPath = $script:paths.RoleXmlPath
|
||||||
$script:configRoot = $script:paths.ConfigRoot
|
$script:configRoot = $script:paths.ConfigRoot
|
||||||
|
|
||||||
# "@путь" в позиции ТЕКСТА (условие RLS, тело шаблона, синоним) — содержимое берётся из файла:
|
|
||||||
# многострочное условие инлайном ломается о кавычки и о разделитель пакета. Адрес при этом
|
|
||||||
# остаётся в команде: "Catalog.Товары.Read: @условие.txt".
|
|
||||||
function Resolve-TextValue([string]$text) {
|
|
||||||
if (-not $text -or -not $text.StartsWith("@")) { return $text }
|
|
||||||
$valueFile = $text.Substring(1).Trim()
|
|
||||||
if (-not [System.IO.Path]::IsPathRooted($valueFile)) { $valueFile = Join-Path (Get-Location).Path $valueFile }
|
|
||||||
if (-not (Test-Path -LiteralPath $valueFile -PathType Leaf)) {
|
|
||||||
Add-ValidationError "Файл значения не найден: $valueFile"
|
|
||||||
return $text
|
|
||||||
}
|
|
||||||
return [System.IO.File]::ReadAllText($valueFile).Trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($DefinitionFile -and $Operation) {
|
if ($DefinitionFile -and $Operation) {
|
||||||
[Console]::Error.WriteLine("[role-edit] Укажите либо -DefinitionFile, либо -Operation, но не оба сразу")
|
[Console]::Error.WriteLine("[role-edit] Укажите либо -DefinitionFile, либо -Operation, но не оба сразу")
|
||||||
exit 1
|
exit 1
|
||||||
@@ -1428,6 +1435,11 @@ if (-not $DefinitionFile -and -not $Operation) {
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# База относительного пути @файла: каталог списка операций, иначе каталог самой роли.
|
||||||
|
# Текущий каталог функция проверяет вторым кандидатом в любом случае.
|
||||||
|
$script:textBaseDir = if ($DefinitionFile) { [System.IO.Path]::GetDirectoryName((Resolve-Path $DefinitionFile).Path) }
|
||||||
|
else { [System.IO.Path]::GetDirectoryName($script:paths.RightsPath) }
|
||||||
|
|
||||||
$targetForGuard = if (Test-Path -LiteralPath $script:roleXmlPath) { $script:roleXmlPath } else { $script:rightsPath }
|
$targetForGuard = if (Test-Path -LiteralPath $script:roleXmlPath) { $script:roleXmlPath } else { $script:rightsPath }
|
||||||
Assert-EditAllowed $targetForGuard 'editable'
|
Assert-EditAllowed $targetForGuard 'editable'
|
||||||
|
|
||||||
@@ -1538,7 +1550,7 @@ function Parse-RlsAddress([string]$text, [switch]$ConditionRequired) {
|
|||||||
Add-ValidationError "$text : разобрано как объект '$objName' и право '$rightName'"
|
Add-ValidationError "$text : разобрано как объект '$objName' и право '$rightName'"
|
||||||
return $null
|
return $null
|
||||||
}
|
}
|
||||||
return @{ Object = $objName; Right = $rightName; Fields = $fields; Condition = (Resolve-TextValue $condition) }
|
return @{ Object = $objName; Right = $rightName; Fields = $fields; Condition = (Resolve-TextFromFile $condition $script:textBaseDir) }
|
||||||
}
|
}
|
||||||
|
|
||||||
# "Имя(Пар1, Пар2): условие" — скобки принадлежат имени шаблона, разделитель ищем вне них.
|
# "Имя(Пар1, Пар2): условие" — скобки принадлежат имени шаблона, разделитель ищем вне них.
|
||||||
@@ -1549,7 +1561,7 @@ function Parse-TemplateSpec([string]$text, [switch]$NameOnly) {
|
|||||||
Add-ValidationError "$text : ожидается 'Имя(Параметры): условие'"
|
Add-ValidationError "$text : ожидается 'Имя(Параметры): условие'"
|
||||||
return $null
|
return $null
|
||||||
}
|
}
|
||||||
return @{ Name = $split.Left; Condition = (Resolve-TextValue $split.Right) }
|
return @{ Name = $split.Left; Condition = (Resolve-TextFromFile $split.Right $script:textBaseDir) }
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Доступ к дереву прав ---
|
# --- Доступ к дереву прав ---
|
||||||
@@ -2221,8 +2233,8 @@ foreach ($op in $operations) {
|
|||||||
"set-template" { Do-SetTemplate $opValue }
|
"set-template" { Do-SetTemplate $opValue }
|
||||||
"remove-template" { Do-RemoveTemplate $opValue }
|
"remove-template" { Do-RemoveTemplate $opValue }
|
||||||
"modify-property" { Do-ModifyProperty $opValue }
|
"modify-property" { Do-ModifyProperty $opValue }
|
||||||
"set-synonym" { $script:pendingMeta += ,@{ Field = 'Synonym'; Text = (Resolve-TextValue $opValue) } }
|
"set-synonym" { $script:pendingMeta += ,@{ Field = 'Synonym'; Text = (Resolve-TextFromFile $opValue $script:textBaseDir) } }
|
||||||
"set-comment" { $script:pendingMeta += ,@{ Field = 'Comment'; Text = (Resolve-TextValue $opValue) } }
|
"set-comment" { $script:pendingMeta += ,@{ Field = 'Comment'; Text = (Resolve-TextFromFile $opValue $script:textBaseDir) } }
|
||||||
default {
|
default {
|
||||||
Add-ValidationError "Неизвестная операция: $opName"
|
Add-ValidationError "Неизвестная операция: $opName"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# role-edit v1.0 — Edit existing 1C role rights in place
|
# role-edit v1.1 — Edit existing 1C role rights in place
|
||||||
# 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
|
||||||
@@ -1120,8 +1120,30 @@ def validate_right_name(object_name, right_name):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
MD_NS = 'http://v8.1c.ru/8.3/MDClasses'
|
# "@путь" в значении условия — текст берётся из файла: условия RLS типовых занимают десятки
|
||||||
|
|
||||||
|
def resolve_text_from_file(val, base_dir):
|
||||||
|
if not val.startswith("@"):
|
||||||
|
return val
|
||||||
|
file_path = val[1:]
|
||||||
|
if os.path.isabs(file_path):
|
||||||
|
candidates = [file_path]
|
||||||
|
else:
|
||||||
|
candidates = [
|
||||||
|
os.path.join(base_dir, file_path),
|
||||||
|
os.path.join(os.getcwd(), file_path),
|
||||||
|
]
|
||||||
|
for c in candidates:
|
||||||
|
if os.path.exists(c):
|
||||||
|
with open(c, 'r', encoding='utf-8-sig') as f:
|
||||||
|
return f.read().rstrip()
|
||||||
|
print(f"Файл значения не найден: {file_path} (искали: {', '.join(candidates)})", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
TEXT_BASE_DIR = os.getcwd()
|
||||||
|
|
||||||
|
MD_NS = 'http://v8.1c.ru/8.3/MDClasses'
|
||||||
|
|
||||||
# Метаданные сервиса читаются один раз на имя: раскрытие и проверка заимствования
|
# Метаданные сервиса читаются один раз на имя: раскрытие и проверка заимствования
|
||||||
# спрашивают один и тот же файл.
|
# спрашивают один и тот же файл.
|
||||||
@@ -1461,8 +1483,11 @@ def resolve_role_paths(input_path):
|
|||||||
class Editor:
|
class Editor:
|
||||||
"""Состояние правки: дерево прав, счётчики, отложенные операции."""
|
"""Состояние правки: дерево прав, счётчики, отложенные операции."""
|
||||||
|
|
||||||
def __init__(self, paths):
|
def __init__(self, paths, text_base_dir):
|
||||||
self.paths = paths
|
self.paths = paths
|
||||||
|
# База относительного пути @файла: каталог списка операций, иначе каталог самой роли.
|
||||||
|
# Текущий каталог функция проверяет вторым кандидатом в любом случае.
|
||||||
|
self.text_base_dir = text_base_dir
|
||||||
parser = etree.XMLParser(remove_blank_text=False)
|
parser = etree.XMLParser(remove_blank_text=False)
|
||||||
self.tree = etree.parse(paths["RightsPath"], parser)
|
self.tree = etree.parse(paths["RightsPath"], parser)
|
||||||
self.root = self.tree.getroot()
|
self.root = self.tree.getroot()
|
||||||
@@ -1486,21 +1511,6 @@ class Editor:
|
|||||||
# Делим ДО чтения файлов, поэтому ';;' внутри условия из файла разделителем не становится.
|
# Делим ДО чтения файлов, поэтому ';;' внутри условия из файла разделителем не становится.
|
||||||
return [part.strip() for part in value.split(";;") if part.strip()]
|
return [part.strip() for part in value.split(";;") if part.strip()]
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def resolve_text_value(text):
|
|
||||||
""""@путь" в позиции ТЕКСТА (условие RLS, тело шаблона, синоним) — содержимое из файла:
|
|
||||||
многострочное условие инлайном ломается о кавычки и о разделитель пакета. Адрес при этом
|
|
||||||
остаётся в команде: "Catalog.Товары.Read: @условие.txt"."""
|
|
||||||
if not text or not text.startswith("@"):
|
|
||||||
return text
|
|
||||||
value_file = text[1:].strip()
|
|
||||||
if not os.path.isabs(value_file):
|
|
||||||
value_file = os.path.join(os.getcwd(), value_file)
|
|
||||||
if not os.path.isfile(value_file):
|
|
||||||
add_validation_error(f"Файл значения не найден: {value_file}")
|
|
||||||
return text
|
|
||||||
with open(value_file, encoding="utf-8-sig") as f:
|
|
||||||
return f.read().strip()
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def split_at_top_level_colon(text, open_char, close_char):
|
def split_at_top_level_colon(text, open_char, close_char):
|
||||||
@@ -1566,7 +1576,7 @@ class Editor:
|
|||||||
add_validation_error(f"{text} : разобрано как объект '{obj_name}' и право '{right_name}'")
|
add_validation_error(f"{text} : разобрано как объект '{obj_name}' и право '{right_name}'")
|
||||||
return None
|
return None
|
||||||
return {"Object": obj_name, "Right": right_name, "Fields": fields,
|
return {"Object": obj_name, "Right": right_name, "Fields": fields,
|
||||||
"Condition": self.resolve_text_value(condition)}
|
"Condition": resolve_text_from_file(condition, self.text_base_dir)}
|
||||||
|
|
||||||
def parse_template_spec(self, text, name_only=False):
|
def parse_template_spec(self, text, name_only=False):
|
||||||
left, right, found = self.split_at_top_level_colon(text, "(", ")")
|
left, right, found = self.split_at_top_level_colon(text, "(", ")")
|
||||||
@@ -1575,7 +1585,7 @@ class Editor:
|
|||||||
if not found:
|
if not found:
|
||||||
add_validation_error(f"{text} : ожидается 'Имя(Параметры): условие'")
|
add_validation_error(f"{text} : ожидается 'Имя(Параметры): условие'")
|
||||||
return None
|
return None
|
||||||
return {"Name": left, "Condition": self.resolve_text_value(right)}
|
return {"Name": left, "Condition": resolve_text_from_file(right, self.text_base_dir)}
|
||||||
|
|
||||||
# --- Доступ к дереву прав ---
|
# --- Доступ к дереву прав ---
|
||||||
|
|
||||||
@@ -2090,7 +2100,9 @@ def main():
|
|||||||
target_for_guard = paths["RoleXmlPath"] if os.path.isfile(paths["RoleXmlPath"]) else paths["RightsPath"]
|
target_for_guard = paths["RoleXmlPath"] if os.path.isfile(paths["RoleXmlPath"]) else paths["RightsPath"]
|
||||||
assert_edit_allowed(target_for_guard, "editable")
|
assert_edit_allowed(target_for_guard, "editable")
|
||||||
|
|
||||||
ed = Editor(paths)
|
text_base_dir = (os.path.dirname(os.path.abspath(args.DefinitionFile)) if args.DefinitionFile
|
||||||
|
else os.path.dirname(paths["RightsPath"]))
|
||||||
|
ed = Editor(paths, text_base_dir)
|
||||||
|
|
||||||
operations = []
|
operations = []
|
||||||
if args.DefinitionFile:
|
if args.DefinitionFile:
|
||||||
@@ -2149,9 +2161,9 @@ def main():
|
|||||||
continue
|
continue
|
||||||
pending.append((key, {"Name": canonical, "Value": val}))
|
pending.append((key, {"Name": canonical, "Value": val}))
|
||||||
elif key == "set-synonym":
|
elif key == "set-synonym":
|
||||||
pending.append((key, {"Field": "Synonym", "Text": ed.resolve_text_value(op_value)}))
|
pending.append((key, {"Field": "Synonym", "Text": resolve_text_from_file(op_value, ed.text_base_dir)}))
|
||||||
elif key == "set-comment":
|
elif key == "set-comment":
|
||||||
pending.append((key, {"Field": "Comment", "Text": ed.resolve_text_value(op_value)}))
|
pending.append((key, {"Field": "Comment", "Text": resolve_text_from_file(op_value, ed.text_base_dir)}))
|
||||||
else:
|
else:
|
||||||
add_validation_error(f"Неизвестная операция: {op_name}")
|
add_validation_error(f"Неизвестная операция: {op_name}")
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# skd-compile v1.121 — Compile 1C DCS from JSON (+write_xml_file/write_utf8_bom: общий эталон записи)
|
# skd-compile v1.122 — 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
|
||||||
[CmdletBinding(PositionalBinding=$false)]
|
[CmdletBinding(PositionalBinding=$false)]
|
||||||
param(
|
param(
|
||||||
@@ -305,7 +305,7 @@ function Esc-XmlText {
|
|||||||
return $s.Replace('&','&').Replace('<','<').Replace('>','>')
|
return $s.Replace('&','&').Replace('<','<').Replace('>','>')
|
||||||
}
|
}
|
||||||
|
|
||||||
function Resolve-QueryValue {
|
function Resolve-TextFromFile {
|
||||||
param([string]$val, [string]$baseDir)
|
param([string]$val, [string]$baseDir)
|
||||||
if (-not $val.StartsWith("@")) { return $val }
|
if (-not $val.StartsWith("@")) { return $val }
|
||||||
$filePath = $val.Substring(1)
|
$filePath = $val.Substring(1)
|
||||||
@@ -322,7 +322,7 @@ function Resolve-QueryValue {
|
|||||||
return (Get-Content -Raw -Encoding UTF8 $c).TrimEnd()
|
return (Get-Content -Raw -Encoding UTF8 $c).TrimEnd()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Write-Error "Query file not found: $filePath (searched: $($candidates -join ', '))"
|
Write-Error "Файл значения не найден: $filePath (искали: $($candidates -join ', '))"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1341,7 +1341,7 @@ function Emit-DataSet {
|
|||||||
|
|
||||||
# Type-specific content
|
# Type-specific content
|
||||||
if ($dsType -eq "DataSetQuery") {
|
if ($dsType -eq "DataSetQuery") {
|
||||||
$queryText = Resolve-QueryValue "$($ds.query)" $script:queryBaseDir
|
$queryText = Resolve-TextFromFile "$($ds.query)" $script:queryBaseDir
|
||||||
X "$indent`t<query>$(Esc-XmlText $queryText)</query>"
|
X "$indent`t<query>$(Esc-XmlText $queryText)</query>"
|
||||||
if ($ds.autoFillFields -eq $false) {
|
if ($ds.autoFillFields -eq $false) {
|
||||||
X "$indent`t<autoFillFields>false</autoFillFields>"
|
X "$indent`t<autoFillFields>false</autoFillFields>"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# skd-compile v1.121 — Compile 1C DCS from JSON (+write_xml_file/write_utf8_bom: общий эталон записи)
|
# skd-compile v1.122 — 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
|
||||||
@@ -321,7 +321,7 @@ def fmt_dec(v):
|
|||||||
return str(int(v)) if v == int(v) else str(v)
|
return str(int(v)) if v == int(v) else str(v)
|
||||||
|
|
||||||
|
|
||||||
def resolve_query_value(val, base_dir):
|
def resolve_text_from_file(val, base_dir):
|
||||||
if not val.startswith("@"):
|
if not val.startswith("@"):
|
||||||
return val
|
return val
|
||||||
file_path = val[1:]
|
file_path = val[1:]
|
||||||
@@ -336,7 +336,7 @@ def resolve_query_value(val, base_dir):
|
|||||||
if os.path.exists(c):
|
if os.path.exists(c):
|
||||||
with open(c, 'r', encoding='utf-8-sig') as f:
|
with open(c, 'r', encoding='utf-8-sig') as f:
|
||||||
return f.read().rstrip()
|
return f.read().rstrip()
|
||||||
print(f"Query file not found: {file_path} (searched: {', '.join(candidates)})", file=sys.stderr)
|
print(f"Файл значения не найден: {file_path} (искали: {', '.join(candidates)})", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
@@ -1190,7 +1190,7 @@ def emit_data_set(lines, ds, indent, default_source, tag_name='dataSet'):
|
|||||||
|
|
||||||
# Type-specific content
|
# Type-specific content
|
||||||
if ds_type == 'DataSetQuery':
|
if ds_type == 'DataSetQuery':
|
||||||
query_text = resolve_query_value(str(ds.get("query", "")), query_base_dir)
|
query_text = resolve_text_from_file(str(ds.get("query", "")), query_base_dir)
|
||||||
lines.append(f'{indent}\t<query>{esc_xml_text(query_text)}</query>')
|
lines.append(f'{indent}\t<query>{esc_xml_text(query_text)}</query>')
|
||||||
if ds.get('autoFillFields') is False:
|
if ds.get('autoFillFields') is False:
|
||||||
lines.append(f'{indent}\t<autoFillFields>false</autoFillFields>')
|
lines.append(f'{indent}\t<autoFillFields>false</autoFillFields>')
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# skd-edit v1.39 — Atomic 1C DCS editor
|
# skd-edit v1.40 — Atomic 1C DCS editor
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
# NB: парный .py собирает выражения автодат вне f-string ради совместимости с python 3.9 (PEP 701).
|
# NB: парный .py собирает выражения автодат вне f-string ради совместимости с python 3.9 (PEP 701).
|
||||||
[CmdletBinding(PositionalBinding=$false)]
|
[CmdletBinding(PositionalBinding=$false)]
|
||||||
@@ -194,7 +194,7 @@ function Esc-XmlText {
|
|||||||
return $s.Replace('&','&').Replace('<','<').Replace('>','>')
|
return $s.Replace('&','&').Replace('<','<').Replace('>','>')
|
||||||
}
|
}
|
||||||
|
|
||||||
function Resolve-QueryValue {
|
function Resolve-TextFromFile {
|
||||||
param([string]$val, [string]$baseDir)
|
param([string]$val, [string]$baseDir)
|
||||||
if (-not $val.StartsWith("@")) { return $val }
|
if (-not $val.StartsWith("@")) { return $val }
|
||||||
$filePath = $val.Substring(1)
|
$filePath = $val.Substring(1)
|
||||||
@@ -211,7 +211,7 @@ function Resolve-QueryValue {
|
|||||||
return (Get-Content -Raw -Encoding UTF8 $c).TrimEnd()
|
return (Get-Content -Raw -Encoding UTF8 $c).TrimEnd()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Write-Error "Query file not found: $filePath (searched: $($candidates -join ', '))"
|
Write-Error "Файл значения не найден: $filePath (искали: $($candidates -join ', '))"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3097,7 +3097,7 @@ switch ($Operation) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# InnerText setter handles XML escaping automatically
|
# InnerText setter handles XML escaping automatically
|
||||||
$queryEl.InnerText = Resolve-QueryValue $Value $script:queryBaseDir
|
$queryEl.InnerText = Resolve-TextFromFile $Value $script:queryBaseDir
|
||||||
|
|
||||||
$script:Dirty = $true; Write-Host "[OK] Query replaced in dataset `"$dsName`""
|
$script:Dirty = $true; Write-Host "[OK] Query replaced in dataset `"$dsName`""
|
||||||
}
|
}
|
||||||
@@ -3337,7 +3337,7 @@ switch ($Operation) {
|
|||||||
$childIndent = Get-ChildIndent $root
|
$childIndent = Get-ChildIndent $root
|
||||||
|
|
||||||
$parsed = Parse-DataSetShorthand $Value
|
$parsed = Parse-DataSetShorthand $Value
|
||||||
$parsed.query = Resolve-QueryValue $parsed.query $script:queryBaseDir
|
$parsed.query = Resolve-TextFromFile $parsed.query $script:queryBaseDir
|
||||||
|
|
||||||
# Auto-name if empty
|
# Auto-name if empty
|
||||||
if (-not $parsed.name) {
|
if (-not $parsed.name) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# skd-edit v1.39 — Atomic 1C DCS editor (Python port)
|
# skd-edit v1.40 — Atomic 1C DCS editor (Python port)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
import argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
@@ -111,7 +111,7 @@ def esc_xml_text(s):
|
|||||||
return s.replace('&', '&').replace('<', '<').replace('>', '>')
|
return s.replace('&', '&').replace('<', '<').replace('>', '>')
|
||||||
|
|
||||||
|
|
||||||
def resolve_query_value(val, base_dir):
|
def resolve_text_from_file(val, base_dir):
|
||||||
if not val.startswith("@"):
|
if not val.startswith("@"):
|
||||||
return val
|
return val
|
||||||
file_path = val[1:]
|
file_path = val[1:]
|
||||||
@@ -126,7 +126,7 @@ def resolve_query_value(val, base_dir):
|
|||||||
if os.path.exists(c):
|
if os.path.exists(c):
|
||||||
with open(c, 'r', encoding='utf-8-sig') as f:
|
with open(c, 'r', encoding='utf-8-sig') as f:
|
||||||
return f.read().rstrip()
|
return f.read().rstrip()
|
||||||
print(f"Query file not found: {file_path} (searched: {', '.join(candidates)})", file=sys.stderr)
|
print(f"Файл значения не найден: {file_path} (искали: {', '.join(candidates)})", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
@@ -2689,7 +2689,7 @@ elif operation == "set-query":
|
|||||||
if query_el is None:
|
if query_el is None:
|
||||||
print(f"No <query> element found in dataset '{ds_name}'", file=sys.stderr)
|
print(f"No <query> element found in dataset '{ds_name}'", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
query_el.text = resolve_query_value(value_arg, query_base_dir)
|
query_el.text = resolve_text_from_file(value_arg, query_base_dir)
|
||||||
dirty = True; print(f'[OK] Query replaced in dataset "{ds_name}"')
|
dirty = True; print(f'[OK] Query replaced in dataset "{ds_name}"')
|
||||||
|
|
||||||
elif operation == "patch-query":
|
elif operation == "patch-query":
|
||||||
@@ -2877,7 +2877,7 @@ elif operation == "add-dataSetLink":
|
|||||||
elif operation == "add-dataSet":
|
elif operation == "add-dataSet":
|
||||||
child_indent = get_child_indent(xml_doc)
|
child_indent = get_child_indent(xml_doc)
|
||||||
parsed = parse_data_set_shorthand(value_arg)
|
parsed = parse_data_set_shorthand(value_arg)
|
||||||
parsed["query"] = resolve_query_value(parsed["query"], query_base_dir)
|
parsed["query"] = resolve_text_from_file(parsed["query"], query_base_dir)
|
||||||
|
|
||||||
if not parsed["name"]:
|
if not parsed["name"]:
|
||||||
count = sum(1 for ch in xml_doc if isinstance(ch.tag, str) and local_name(ch) == "dataSet" and etree.QName(ch.tag).namespace == SCH_NS)
|
count = sum(1 for ch in xml_doc if isinstance(ch.tag, str) and local_name(ch) == "dataSet" and etree.QName(ch.tag).namespace == SCH_NS)
|
||||||
|
|||||||
@@ -409,6 +409,16 @@ const FAMILIES = [
|
|||||||
// Существует только в PY: PowerShell регистронезависим сам по себе (свойства PSObject, ключи
|
// Существует только в PY: PowerShell регистронезависим сам по себе (свойства PSObject, ключи
|
||||||
// Hashtable, -eq/-contains, имена параметров, ValidateSet), поэтому в .ps1 копии нет и быть
|
// Hashtable, -eq/-contains, имена параметров, ValidateSet), поэтому в .ps1 копии нет и быть
|
||||||
// не должно — ps1: null.
|
// не должно — ps1: null.
|
||||||
|
// Значение операции может быть "@путь" — текст читается из файла. Правило поиска общее:
|
||||||
|
// абсолютный путь как есть, относительный — рядом с DSL (или с редактируемым объектом),
|
||||||
|
// затем в текущем каталоге. Разъедется — и один навык начнёт искать не там, где другой.
|
||||||
|
{
|
||||||
|
name: 'значение из файла: resolve_text_from_file', py: 'resolve_text_from_file', ps1: 'Resolve-TextFromFile',
|
||||||
|
variants: [
|
||||||
|
{ id: 'base', authority: 'skd-edit',
|
||||||
|
consumers: ['form-compile', 'role-compile', 'role-edit', 'skd-compile'] },
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'case-insensitive input: CIDict', py: 'CIDict', ps1: null,
|
name: 'case-insensitive input: CIDict', py: 'CIDict', ps1: null,
|
||||||
variants: [
|
variants: [
|
||||||
|
|||||||
Reference in New Issue
Block a user