mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-09-14 22:05:22 +03:00
feat(skd-edit): availableValue — список с replace-семантикой и в add-parameter
- Единый list-синтаксис: availableValue=v1[: p1], v2[: p2], ... Элементы через запятую, представление после двоеточия. - Запятые/двоеточия внутри значений и представлений — в одинарных кавычках: availableValue=Окр1: 'руб., коп.', Окр1000: руб. - add-parameter теперь принимает availableValue= и создаёт начальный список в одном вызове (раньше требовался последующий modify-parameter). - modify-parameter availableValue=... ЗАМЕНЯЕТ весь список (раньше append). Согласуется с остальными modify-* для одиночных свойств. - SKILL.md: добавлен shorthand-шаблон для modify-parameter, расширен для add-parameter [availableValue=список]. Существующие тесты мигрированы со старого ;;-batch на новый list-синтаксис. Снапшоты сохранились (тесты стартовали с пустого списка — semantics совпадает для greenfield). Регресс: 29/29 PS, 29/29 PY, 29/29 платформенный verify. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
610720334b
commit
e7cbf306a0
@@ -1,4 +1,4 @@
|
||||
# skd-edit v1.14 — Atomic 1C DCS editor (Python port)
|
||||
# skd-edit v1.15 — Atomic 1C DCS editor (Python port)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
import argparse
|
||||
import os
|
||||
@@ -299,7 +299,13 @@ def parse_calc_shorthand(s):
|
||||
|
||||
|
||||
def parse_param_shorthand(s):
|
||||
result = {"name": "", "type": "", "value": None, "autoDates": False, "title": None, "hidden": False, "always": False}
|
||||
result = {"name": "", "type": "", "value": None, "autoDates": False, "title": None, "hidden": False, "always": False, "availableValues": []}
|
||||
|
||||
# Extract availableValue=... (must be before main parse — captures to end of string)
|
||||
m_av = re.search(r'\s*availableValue=(.+)$', s)
|
||||
if m_av:
|
||||
result["availableValues"] = parse_available_value_list(m_av.group(1).strip())
|
||||
s = re.sub(r'\s*availableValue=.+$', '', s).strip()
|
||||
|
||||
if re.search(r'@autoDates', s):
|
||||
result["autoDates"] = True
|
||||
@@ -581,6 +587,80 @@ def parse_output_param_shorthand(s):
|
||||
return {"key": s.strip(), "value": ""}
|
||||
|
||||
|
||||
def parse_available_value_list(s):
|
||||
"""Returns list of {value, presentation} from comma-separated list.
|
||||
Items can use single/double quotes (stripped). Quoted spans preserve commas/colons."""
|
||||
if not s:
|
||||
return []
|
||||
|
||||
# Tokenize by ',' respecting quoted spans
|
||||
items = []
|
||||
buf = []
|
||||
in_quote = None
|
||||
for ch in s:
|
||||
if in_quote:
|
||||
buf.append(ch)
|
||||
if ch == in_quote:
|
||||
in_quote = None
|
||||
elif ch in ("'", '"'):
|
||||
in_quote = ch
|
||||
buf.append(ch)
|
||||
elif ch == ',':
|
||||
items.append("".join(buf))
|
||||
buf = []
|
||||
else:
|
||||
buf.append(ch)
|
||||
if buf:
|
||||
items.append("".join(buf))
|
||||
|
||||
def strip_quotes(t):
|
||||
t = t.strip()
|
||||
if len(t) >= 2 and ((t[0] == "'" and t[-1] == "'") or (t[0] == '"' and t[-1] == '"')):
|
||||
return t[1:-1]
|
||||
return t
|
||||
|
||||
result = []
|
||||
for raw in items:
|
||||
item = raw.strip()
|
||||
if not item:
|
||||
continue
|
||||
# Find first ':' outside quotes
|
||||
colon_idx = -1
|
||||
q = None
|
||||
for j, c in enumerate(item):
|
||||
if q:
|
||||
if c == q:
|
||||
q = None
|
||||
elif c in ("'", '"'):
|
||||
q = c
|
||||
elif c == ':':
|
||||
colon_idx = j
|
||||
break
|
||||
if colon_idx >= 0:
|
||||
val_part = item[:colon_idx]
|
||||
pres_part = item[colon_idx + 1:]
|
||||
result.append({"value": strip_quotes(val_part), "presentation": strip_quotes(pres_part)})
|
||||
else:
|
||||
result.append({"value": strip_quotes(item), "presentation": ""})
|
||||
return result
|
||||
|
||||
|
||||
def build_available_value_fragment(item, declared_type, indent):
|
||||
"""Return XML lines for a single <availableValue> block."""
|
||||
lines = [f"{indent}<availableValue>"]
|
||||
for vl in build_param_value_xml(declared_type, item["value"], f"{indent}\t"):
|
||||
lines.append(vl)
|
||||
if item.get("presentation"):
|
||||
lines.append(f'{indent}\t<presentation xsi:type="v8:LocalStringType">')
|
||||
lines.append(f"{indent}\t\t<v8:item>")
|
||||
lines.append(f"{indent}\t\t\t<v8:lang>ru</v8:lang>")
|
||||
lines.append(f"{indent}\t\t\t<v8:content>{esc_xml(item['presentation'])}</v8:content>")
|
||||
lines.append(f"{indent}\t\t</v8:item>")
|
||||
lines.append(f"{indent}\t</presentation>")
|
||||
lines.append(f"{indent}</availableValue>")
|
||||
return lines
|
||||
|
||||
|
||||
# ── 4. Build-* functions (XML fragment generators) ──────────
|
||||
|
||||
def build_value_type_xml(type_str, indent):
|
||||
@@ -797,6 +877,10 @@ def build_param_fragment(parsed, indent):
|
||||
lines.append(f"{i}\t<useRestriction>true</useRestriction>")
|
||||
lines.append(f"{i}\t<availableAsField>false</availableAsField>")
|
||||
|
||||
for av in parsed.get("availableValues", []) or []:
|
||||
for l in build_available_value_fragment(av, parsed.get("type", ""), f"{i}\t"):
|
||||
lines.append(l)
|
||||
|
||||
if parsed.get("always"):
|
||||
lines.append(f"{i}\t<use>Always</use>")
|
||||
|
||||
@@ -1664,11 +1748,8 @@ elif operation == "modify-parameter":
|
||||
|
||||
# Process availableValue
|
||||
if av_part:
|
||||
av_rest = av_part[len("availableValue="):]
|
||||
# Parse: "Перечисление...X presentation=текст с пробелами"
|
||||
av_parts = re.split(r'\s+presentation=', av_rest, 1)
|
||||
av_value = av_parts[0].strip()
|
||||
av_presentation = av_parts[1].strip() if len(av_parts) > 1 else ""
|
||||
av_rest = av_part[len("availableValue="):].strip()
|
||||
av_items = parse_available_value_list(av_rest)
|
||||
|
||||
# Prefer declared <valueType> of the parameter; fall back to value pattern
|
||||
declared_type = ""
|
||||
@@ -1683,29 +1764,24 @@ elif operation == "modify-parameter":
|
||||
declared_type = re.sub(r'^d\d+p\d+:', '', (tnode.text or "").strip())
|
||||
break
|
||||
|
||||
av_lines = [f"{child_indent}<availableValue>"]
|
||||
for vl in build_param_value_xml(declared_type, av_value, f"{child_indent}\t"):
|
||||
av_lines.append(vl)
|
||||
if av_presentation:
|
||||
av_lines.append(f'{child_indent}\t<presentation xsi:type="v8:LocalStringType">')
|
||||
av_lines.append(f"{child_indent}\t\t<v8:item>")
|
||||
av_lines.append(f"{child_indent}\t\t\t<v8:lang>ru</v8:lang>")
|
||||
av_lines.append(f"{child_indent}\t\t\t<v8:content>{esc_xml(av_presentation)}</v8:content>")
|
||||
av_lines.append(f"{child_indent}\t\t</v8:item>")
|
||||
av_lines.append(f"{child_indent}\t</presentation>")
|
||||
av_lines.append(f"{child_indent}</availableValue>")
|
||||
frag_xml = "\r\n".join(av_lines)
|
||||
# Remove all existing <availableValue>
|
||||
to_remove = [ch for ch in param_el if isinstance(ch.tag, str) and local_name(ch) == "availableValue" and etree.QName(ch.tag).namespace == SCH_NS]
|
||||
for el in to_remove:
|
||||
remove_node_with_whitespace(el)
|
||||
|
||||
# Insert before first of (denyIncompleteValues, use) in document order
|
||||
# Insert each new <availableValue> before (denyIncompleteValues, use)
|
||||
ref_node = None
|
||||
for child in param_el:
|
||||
if isinstance(child.tag, str) and local_name(child) in ("denyIncompleteValues", "use"):
|
||||
ref_node = child
|
||||
break
|
||||
nodes = import_fragment(xml_doc, frag_xml)
|
||||
for node in nodes:
|
||||
insert_before_element(param_el, node, ref_node, child_indent)
|
||||
print(f'[OK] Parameter "{param_name}": availableValue added')
|
||||
for av in av_items:
|
||||
av_lines = build_available_value_fragment(av, declared_type, child_indent)
|
||||
frag_xml = "\r\n".join(av_lines)
|
||||
nodes = import_fragment(xml_doc, frag_xml)
|
||||
for node in nodes:
|
||||
insert_before_element(param_el, node, ref_node, child_indent)
|
||||
print(f'[OK] Parameter "{param_name}": availableValue set to {len(av_items)} item(s)')
|
||||
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user