From 8b0bcf01947146de32a70abcc25702b99c65e021 Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Fri, 15 May 2026 14:14:19 +0300 Subject: [PATCH] =?UTF-8?q?feat(skd-edit):=20=D1=84=D0=BB=D0=B0=D0=B3?= =?UTF-8?q?=D0=B8=20@hidden=20=D0=B8=20@always=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=BF=D0=B0=D1=80=D0=B0=D0=BC=D0=B5=D1=82=D1=80=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - @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) --- .claude/skills/skd-edit/SKILL.md | 17 +++- .claude/skills/skd-edit/scripts/skd-edit.ps1 | 80 ++++++++++++++++++- .claude/skills/skd-edit/scripts/skd-edit.py | 59 +++++++++++++- .../skd-edit/add-parameter-hidden-always.json | 21 +++++ .../modify-parameter-hidden-idempotent.json | 29 +++++++ .../add-parameter-hidden-always/Template.xml | 54 +++++++++++++ .../Template.xml | 54 +++++++++++++ 7 files changed, 309 insertions(+), 5 deletions(-) create mode 100644 tests/skills/cases/skd-edit/add-parameter-hidden-always.json create mode 100644 tests/skills/cases/skd-edit/modify-parameter-hidden-idempotent.json create mode 100644 tests/skills/cases/skd-edit/snapshots/add-parameter-hidden-always/Template.xml create mode 100644 tests/skills/cases/skd-edit/snapshots/modify-parameter-hidden-idempotent/Template.xml diff --git a/.claude/skills/skd-edit/SKILL.md b/.claude/skills/skd-edit/SKILL.md index f867dda4..76086832 100644 --- a/.claude/skills/skd-edit/SKILL.md +++ b/.claude/skills/skd-edit/SKILL.md @@ -82,7 +82,16 @@ Shorthand: `"Имя [Заголовок]: тип = Выражение #noFilter Shorthand: `"Имя [Заголовок]: тип = значение @флаги"`. `[Заголовок]` опциональный — добавляет ``. -`@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` всех вариантов. Текст запроса не трогает — переименование строго в области параметров. diff --git a/.claude/skills/skd-edit/scripts/skd-edit.ps1 b/.claude/skills/skd-edit/scripts/skd-edit.ps1 index ac0dc892..0cf61b20 100644 --- a/.claude/skills/skd-edit/scripts/skd-edit.ps1 +++ b/.claude/skills/skd-edit/scripts/skd-edit.ps1 @@ -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" + } } } diff --git a/.claude/skills/skd-edit/scripts/skd-edit.py b/.claude/skills/skd-edit/scripts/skd-edit.py index 4218581b..c2ce2f52 100644 --- a/.claude/skills/skd-edit/scripts/skd-edit.py +++ b/.claude/skills/skd-edit/scripts/skd-edit.py @@ -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: diff --git a/tests/skills/cases/skd-edit/add-parameter-hidden-always.json b/tests/skills/cases/skd-edit/add-parameter-hidden-always.json new file mode 100644 index 00000000..6a692927 --- /dev/null +++ b/tests/skills/cases/skd-edit/add-parameter-hidden-always.json @@ -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" + } +} diff --git a/tests/skills/cases/skd-edit/modify-parameter-hidden-idempotent.json b/tests/skills/cases/skd-edit/modify-parameter-hidden-idempotent.json new file mode 100644 index 00000000..e31dd6b5 --- /dev/null +++ b/tests/skills/cases/skd-edit/modify-parameter-hidden-idempotent.json @@ -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" + } +} diff --git a/tests/skills/cases/skd-edit/snapshots/add-parameter-hidden-always/Template.xml b/tests/skills/cases/skd-edit/snapshots/add-parameter-hidden-always/Template.xml new file mode 100644 index 00000000..ba6fb068 --- /dev/null +++ b/tests/skills/cases/skd-edit/snapshots/add-parameter-hidden-always/Template.xml @@ -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> diff --git a/tests/skills/cases/skd-edit/snapshots/modify-parameter-hidden-idempotent/Template.xml b/tests/skills/cases/skd-edit/snapshots/modify-parameter-hidden-idempotent/Template.xml new file mode 100644 index 00000000..ba6fb068 --- /dev/null +++ b/tests/skills/cases/skd-edit/snapshots/modify-parameter-hidden-idempotent/Template.xml @@ -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>