feat(skd-edit): флаги @hidden и @always для параметров

- @hidden — скрывает параметр от пользовательских настроек
  (useRestriction=true + availableAsField=false). Для констант-параметров.
- @always — параметр всегда подставляется в запрос (use=Always).
  Используется самостоятельно для видимых обязательных параметров.
- Композируются: @hidden @always одной строкой даёт типовой паттерн
  "скрытая константа всегда применяется".
- Поддержка в add-parameter и modify-parameter, идемпотентны.

Регресс: 25/25 PS, 25/25 PY, 25/25 платформенный verify.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-05-15 14:14:19 +03:00
parent 529a5cacae
commit 8b0bcf0194
7 changed files with 309 additions and 5 deletions
+16 -1
View File
@@ -82,7 +82,16 @@ Shorthand: `"Имя [Заголовок]: тип = Выражение #noFilter
Shorthand: `"Имя [Заголовок]: тип = значение @флаги"`. `[Заголовок]` опциональный — добавляет `<title>`.
`@autoDates` генерирует пару скрытых параметров `ДатаНачала`/`ДатаОкончания` для StandardPeriod-параметра — для БСП-отчётов, чтобы получить пару полей «Начало/Конец» в панели быстрых настроек.
Флаги:
- `@autoDates` — генерирует пару скрытых параметров `ДатаНачала`/`ДатаОкончания` для StandardPeriod-параметра.
- `@hidden` — скрывает параметр от пользовательских настроек (для параметров-констант, используемых в запросе).
- `@always` — параметр всегда подставляется в запрос. Часто вместе с `@hidden`, но используется и отдельно (для видимых обязательных параметров типа отчётного периода).
```
"ПС: CatalogRef.Контрагенты = Справочник.Контрагенты.ПустаяСсылка @hidden"
"Период: StandardPeriod = LastMonth @always"
"ПСчет: ChartOfAccountsRef.Хозрасчетный = ПланСчетов.Хозрасчетный.X @hidden @always"
```
### modify-parameter — изменить существующий параметр
@@ -93,12 +102,18 @@ Shorthand: `"Имя [Заголовок]: тип = значение @флаги"
"ПорядокОкругления [Округление сумм] denyIncompleteValues=true"
"ПериодОтчета [Отчетный период]" # только title
"ПорядокОкругления availableValue=Перечисление.Округления.Окр1 presentation=руб."
"СчетПС value=ПланСчетов.Хозрасчетный.КассаПредприятия"
"Контрагент @hidden @always"
```
`[Заголовок]` опциональный — устанавливает или заменяет `<title>`. Можно вызывать без других kv-пар, чтобы только обновить title.
`availableValue=` добавляет один элемент списка допустимых значений (можно несколько через `;;`). Тип значения определяется автоматически (DesignTimeValue для ссылок).
`value=` заменяет значение параметра (тип значения подбирается автоматически по объявленному типу параметра).
Флаги `@hidden` / `@always` — те же, что и в `add-parameter`. Идемпотентны.
### rename-parameter — переименовать параметр
Shorthand: `"OldName => NewName"`. Атомарно обновляет имя параметра, ссылки `&Имя` в выражениях других параметров (только полные совпадения, `&ПериодX` не задевается), и записи в `dataParameters` всех вариантов. Текст запроса не трогает — переименование строго в области параметров.
+78 -2
View File
@@ -1,4 +1,4 @@
# skd-edit v1.12 — Atomic 1C DCS editor
# skd-edit v1.13 — Atomic 1C DCS editor
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
@@ -297,13 +297,23 @@ function Parse-CalcShorthand {
function Parse-ParamShorthand {
param([string]$s)
$result = @{ name = ""; type = ""; value = $null; autoDates = $false; title = $null }
$result = @{ name = ""; type = ""; value = $null; autoDates = $false; title = $null; hidden = $false; always = $false }
if ($s -match '@autoDates') {
$result.autoDates = $true
$s = $s -replace '\s*@autoDates', ''
}
if ($s -match '@hidden\b') {
$result.hidden = $true
$s = $s -replace '\s*@hidden\b', ''
}
if ($s -match '@always\b') {
$result.always = $true
$s = $s -replace '\s*@always\b', ''
}
# Extract optional [Title] (mirrors Parse-FieldShorthand)
if ($s -match '\[([^\]]*)\]') {
$result.title = $Matches[1].Trim()
@@ -873,6 +883,15 @@ function Build-ParamFragment {
foreach ($vl in $valueLines) { $lines += $vl }
}
if ($parsed.hidden) {
$lines += "$i`t<useRestriction>true</useRestriction>"
$lines += "$i`t<availableAsField>false</availableAsField>"
}
if ($parsed.always) {
$lines += "$i`t<use>Always</use>"
}
$lines += "$i</parameter>"
$fragments += ($lines -join "`r`n")
@@ -1796,6 +1815,12 @@ switch ($Operation) {
$paramName = $parts[0].Trim()
$rest = if ($parts.Count -gt 1) { $parts[1].Trim() } else { "" }
# Extract @hidden / @always flags
$flagHidden = $false
$flagAlways = $false
if ($rest -match '@hidden\b') { $flagHidden = $true; $rest = ($rest -replace '\s*@hidden\b', '').Trim() }
if ($rest -match '@always\b') { $flagAlways = $true; $rest = ($rest -replace '\s*@always\b', '').Trim() }
# Find parameter element
$paramEl = Find-ElementByChildValue $xmlDoc.DocumentElement "parameter" "name" $paramName $schNs
if (-not $paramEl) {
@@ -1970,6 +1995,57 @@ switch ($Operation) {
}
Write-Host "[OK] Parameter `"$paramName`": availableValue added"
}
# Process @hidden / @always flags (idempotent)
if ($flagHidden) {
# useRestriction → true (insert after <value>, before <expression>/<availableAsField>/...)
$urEl = $null
foreach ($ch in $paramEl.ChildNodes) {
if ($ch.NodeType -eq 'Element' -and $ch.LocalName -eq 'useRestriction' -and $ch.NamespaceURI -eq $schNs) { $urEl = $ch; break }
}
if ($urEl) {
if ($urEl.InnerText.Trim() -ne 'true') { $urEl.InnerText = 'true' }
} else {
$refNode = $null
foreach ($child in $paramEl.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -in @('expression','availableAsField','availableValue','denyIncompleteValues','use')) { $refNode = $child; break }
}
$nodes = Import-Fragment $xmlDoc "$childIndent<useRestriction>true</useRestriction>"
foreach ($node in $nodes) { Insert-BeforeElement $paramEl $node $refNode $childIndent }
}
# availableAsField → false (insert after <expression>, before <availableValue>/<denyIncompleteValues>/<use>)
$afEl = $null
foreach ($ch in $paramEl.ChildNodes) {
if ($ch.NodeType -eq 'Element' -and $ch.LocalName -eq 'availableAsField' -and $ch.NamespaceURI -eq $schNs) { $afEl = $ch; break }
}
if ($afEl) {
if ($afEl.InnerText.Trim() -ne 'false') { $afEl.InnerText = 'false' }
} else {
$refNode = $null
foreach ($child in $paramEl.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -in @('availableValue','denyIncompleteValues','use')) { $refNode = $child; break }
}
$nodes = Import-Fragment $xmlDoc "$childIndent<availableAsField>false</availableAsField>"
foreach ($node in $nodes) { Insert-BeforeElement $paramEl $node $refNode $childIndent }
}
Write-Host "[OK] Parameter `"$paramName`": @hidden applied"
}
if ($flagAlways) {
$useEl = $null
foreach ($ch in $paramEl.ChildNodes) {
if ($ch.NodeType -eq 'Element' -and $ch.LocalName -eq 'use' -and $ch.NamespaceURI -eq $schNs) { $useEl = $ch; break }
}
if ($useEl) {
if ($useEl.InnerText.Trim() -ne 'Always') { $useEl.InnerText = 'Always' }
} else {
$nodes = Import-Fragment $xmlDoc "$childIndent<use>Always</use>"
foreach ($node in $nodes) { Insert-BeforeElement $paramEl $node $null $childIndent }
}
Write-Host "[OK] Parameter `"$paramName`": @always applied"
}
}
}
+57 -2
View File
@@ -1,4 +1,4 @@
# skd-edit v1.12 — Atomic 1C DCS editor (Python port)
# skd-edit v1.13 — Atomic 1C DCS editor (Python port)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import os
@@ -299,12 +299,20 @@ def parse_calc_shorthand(s):
def parse_param_shorthand(s):
result = {"name": "", "type": "", "value": None, "autoDates": False, "title": None}
result = {"name": "", "type": "", "value": None, "autoDates": False, "title": None, "hidden": False, "always": False}
if re.search(r'@autoDates', s):
result["autoDates"] = True
s = re.sub(r'\s*@autoDates', '', s)
if re.search(r'@hidden\b', s):
result["hidden"] = True
s = re.sub(r'\s*@hidden\b', '', s)
if re.search(r'@always\b', s):
result["always"] = True
s = re.sub(r'\s*@always\b', '', s)
# Extract optional [Title] (mirrors parse_field_shorthand)
m = re.search(r'\[([^\]]*)\]', s)
if m:
@@ -785,6 +793,13 @@ def build_param_fragment(parsed, indent):
for vl in build_param_value_xml(parsed.get("type", ""), parsed["value"], f"{i}\t"):
lines.append(vl)
if parsed.get("hidden"):
lines.append(f"{i}\t<useRestriction>true</useRestriction>")
lines.append(f"{i}\t<availableAsField>false</availableAsField>")
if parsed.get("always"):
lines.append(f"{i}\t<use>Always</use>")
lines.append(f"{i}</parameter>")
fragments.append("\r\n".join(lines))
@@ -1567,6 +1582,15 @@ elif operation == "modify-parameter":
param_name = parts[0].strip()
rest = parts[1].strip() if len(parts) > 1 else ""
flag_hidden = False
flag_always = False
if re.search(r'@hidden\b', rest):
flag_hidden = True
rest = re.sub(r'\s*@hidden\b', '', rest).strip()
if re.search(r'@always\b', rest):
flag_always = True
rest = re.sub(r'\s*@always\b', '', rest).strip()
param_el = find_element_by_child_value(xml_doc, "parameter", "name", param_name, SCH_NS)
if param_el is None:
print(f'[WARN] Parameter "{param_name}" not found -- skipped')
@@ -1683,6 +1707,37 @@ elif operation == "modify-parameter":
insert_before_element(param_el, node, ref_node, child_indent)
print(f'[OK] Parameter "{param_name}": availableValue added')
if flag_hidden:
ur_el = next((ch for ch in param_el if isinstance(ch.tag, str) and local_name(ch) == "useRestriction" and etree.QName(ch.tag).namespace == SCH_NS), None)
if ur_el is not None:
if (ur_el.text or "").strip() != "true":
ur_el.text = "true"
else:
ref_node = next((ch for ch in param_el if isinstance(ch.tag, str) and local_name(ch) in ("expression", "availableAsField", "availableValue", "denyIncompleteValues", "use")), None)
for node in import_fragment(xml_doc, f"{child_indent}<useRestriction>true</useRestriction>"):
insert_before_element(param_el, node, ref_node, child_indent)
af_el = next((ch for ch in param_el if isinstance(ch.tag, str) and local_name(ch) == "availableAsField" and etree.QName(ch.tag).namespace == SCH_NS), None)
if af_el is not None:
if (af_el.text or "").strip() != "false":
af_el.text = "false"
else:
ref_node = next((ch for ch in param_el if isinstance(ch.tag, str) and local_name(ch) in ("availableValue", "denyIncompleteValues", "use")), None)
for node in import_fragment(xml_doc, f"{child_indent}<availableAsField>false</availableAsField>"):
insert_before_element(param_el, node, ref_node, child_indent)
print(f'[OK] Parameter "{param_name}": @hidden applied')
if flag_always:
use_el = next((ch for ch in param_el if isinstance(ch.tag, str) and local_name(ch) == "use" and etree.QName(ch.tag).namespace == SCH_NS), None)
if use_el is not None:
if (use_el.text or "").strip() != "Always":
use_el.text = "Always"
else:
for node in import_fragment(xml_doc, f"{child_indent}<use>Always</use>"):
insert_before_element(param_el, node, None, child_indent)
print(f'[OK] Parameter "{param_name}": @always applied')
elif operation == "rename-parameter":
root = xml_doc
for val in values:
@@ -0,0 +1,21 @@
{
"name": "add-parameter @hidden @always: useRestriction+availableAsField+use=Always",
"preRun": [
{
"script": "skd-compile/scripts/skd-compile",
"input": {
"dataSets": [{
"name": "Основной",
"query": "ВЫБРАТЬ Т.Поле ИЗ Регистр КАК Т",
"fields": ["Поле: string"]
}]
},
"args": { "-DefinitionFile": "{inputFile}", "-OutputPath": "{workDir}/Template.xml" }
}
],
"params": {
"templatePath": "Template.xml",
"operation": "add-parameter",
"value": "Контрагент: CatalogRef.Контрагенты = Справочник.Контрагенты.ПустаяСсылка @hidden @always"
}
}
@@ -0,0 +1,29 @@
{
"name": "modify-parameter @hidden @always: идемпотентность",
"preRun": [
{
"script": "skd-compile/scripts/skd-compile",
"input": {
"dataSets": [{
"name": "Основной",
"query": "ВЫБРАТЬ Т.Поле ИЗ Регистр КАК Т",
"fields": ["Поле: string"]
}]
},
"args": { "-DefinitionFile": "{inputFile}", "-OutputPath": "{workDir}/Template.xml" }
},
{
"script": "skd-edit/scripts/skd-edit",
"args": { "-TemplatePath": "{workDir}/Template.xml", "-Operation": "add-parameter", "-Value": "Контрагент: CatalogRef.Контрагенты = Справочник.Контрагенты.ПустаяСсылка" }
},
{
"script": "skd-edit/scripts/skd-edit",
"args": { "-TemplatePath": "{workDir}/Template.xml", "-Operation": "modify-parameter", "-Value": "Контрагент @hidden @always" }
}
],
"params": {
"templatePath": "Template.xml",
"operation": "modify-parameter",
"value": "Контрагент @hidden @always"
}
}
@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dataSource>
<name>ИсточникДанных1</name>
<dataSourceType>Local</dataSourceType>
</dataSource>
<dataSet xsi:type="DataSetQuery">
<name>Основной</name>
<field xsi:type="DataSetFieldField">
<dataPath>Поле</dataPath>
<field>Поле</field>
<valueType>
<v8:Type>xs:string</v8:Type>
<v8:StringQualifiers>
<v8:Length>0</v8:Length>
<v8:AllowedLength>Variable</v8:AllowedLength>
</v8:StringQualifiers>
</valueType>
</field>
<dataSource>ИсточникДанных1</dataSource>
<query>ВЫБРАТЬ Т.Поле ИЗ Регистр КАК Т</query>
</dataSet>
<parameter>
<name>Контрагент</name>
<valueType>
<v8:Type xmlns:d5p1="http://v8.1c.ru/8.1/data/enterprise/current-config">d5p1:CatalogRef.Контрагенты</v8:Type>
</valueType>
<value xsi:type="dcscor:DesignTimeValue">Справочник.Контрагенты.ПустаяСсылка</value>
<useRestriction>true</useRestriction>
<availableAsField>false</availableAsField>
<use>Always</use>
</parameter>
<settingsVariant>
<dcsset:name>Основной</dcsset:name>
<dcsset:presentation xsi:type="v8:LocalStringType">
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Основной</v8:content>
</v8:item>
</dcsset:presentation>
<dcsset:settings xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows">
<dcsset:selection>
</dcsset:selection>
<dcsset:item xsi:type="dcsset:StructureItemGroup">
<dcsset:order>
<dcsset:item xsi:type="dcsset:OrderItemAuto" />
</dcsset:order>
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto" />
</dcsset:selection>
</dcsset:item>
</dcsset:settings>
</settingsVariant>
</DataCompositionSchema>
@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dataSource>
<name>ИсточникДанных1</name>
<dataSourceType>Local</dataSourceType>
</dataSource>
<dataSet xsi:type="DataSetQuery">
<name>Основной</name>
<field xsi:type="DataSetFieldField">
<dataPath>Поле</dataPath>
<field>Поле</field>
<valueType>
<v8:Type>xs:string</v8:Type>
<v8:StringQualifiers>
<v8:Length>0</v8:Length>
<v8:AllowedLength>Variable</v8:AllowedLength>
</v8:StringQualifiers>
</valueType>
</field>
<dataSource>ИсточникДанных1</dataSource>
<query>ВЫБРАТЬ Т.Поле ИЗ Регистр КАК Т</query>
</dataSet>
<parameter>
<name>Контрагент</name>
<valueType>
<v8:Type xmlns:d5p1="http://v8.1c.ru/8.1/data/enterprise/current-config">d5p1:CatalogRef.Контрагенты</v8:Type>
</valueType>
<value xsi:type="dcscor:DesignTimeValue">Справочник.Контрагенты.ПустаяСсылка</value>
<useRestriction>true</useRestriction>
<availableAsField>false</availableAsField>
<use>Always</use>
</parameter>
<settingsVariant>
<dcsset:name>Основной</dcsset:name>
<dcsset:presentation xsi:type="v8:LocalStringType">
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Основной</v8:content>
</v8:item>
</dcsset:presentation>
<dcsset:settings xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows">
<dcsset:selection>
</dcsset:selection>
<dcsset:item xsi:type="dcsset:StructureItemGroup">
<dcsset:order>
<dcsset:item xsi:type="dcsset:OrderItemAuto" />
</dcsset:order>
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto" />
</dcsset:selection>
</dcsset:item>
</dcsset:settings>
</settingsVariant>
</DataCompositionSchema>