Files
cc-1c-skills/docs/python-porting-guide.md
T
Nick Shirokov 86a959a354 feat(crossplatform): add Python 3 ports for all 58 PS1 skill scripts
Add cross-platform Python alternatives alongside existing PowerShell
scripts. PS1 remains the default runtime; Python is opt-in via switch
scripts. All parameters are identical between runtimes.

New files:
- 58 Python scripts in .claude/skills/*/scripts/*.py
- scripts/switch-to-python.py and switch-to-powershell.py
- docs/python-porting-guide.md
- __pycache__/ added to .gitignore

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 16:16:07 +03:00

8.5 KiB
Raw Blame History

Python Porting Guide

Руководство по Python-портам навыков 1С (PS1 → Python).

Зачем Python рядом с PS1

PowerShell 5.1 доступен только на Windows. Python-порты обеспечивают кроссплатформенность (Linux, Mac). Модель opt-in: PS1 — по умолчанию, Python — переключается скриптами.

Переключение рантайма

# Переключить все .md в навыках на Python
python scripts/switch-to-python.py

# Вернуть на PowerShell
python scripts/switch-to-powershell.py

Скрипты обрабатывают все .md файлы в .claude/skills/*/ (SKILL.md, json-dsl.md и др.). Идемпотентны — повторный запуск безопасен. Python-only навыки (img-grid) пропускаются при переключении на PowerShell.

Принцип самодостаточности

Каждый .py — полностью автономен, как и его .ps1-аналог. Нет общих модулей. Это соответствует рекомендациям Anthropic и зеркалит существующую архитектуру PS1.

Общие утилиты (5-15 строк) дублируются в каждом скрипте:

def esc_xml(s):
    return s.replace('&','&amp;').replace('<','&lt;').replace('>','&gt;').replace('"','&quot;')

def emit_mltext(lines, indent, tag, text):
    if not text:
        lines.append(f"{indent}<{tag}/>")
        return
    lines.append(f"{indent}<{tag}>")
    lines.append(f"{indent}\t<v8:item>")
    lines.append(f"{indent}\t\t<v8:lang>ru</v8:lang>")
    lines.append(f"{indent}\t\t<v8:content>{esc_xml(text)}</v8:content>")
    lines.append(f"{indent}\t</v8:item>")
    lines.append(f"{indent}</{tag}>")

def new_uuid():
    import uuid
    return str(uuid.uuid4())

def read_utf8(path):
    with open(path, 'r', encoding='utf-8-sig') as f:
        return f.read()

def write_utf8_bom(path, content):
    with open(path, 'w', encoding='utf-8-sig', newline='') as f:
        f.write(content)

Большие словари данных (синонимы типов, карты объектов) тоже inline — как $script:typeSynonyms в PS1.

Конвенция параметров

Формат -ParamName сохранён для минимальных различий в SKILL.md:

parser = argparse.ArgumentParser(allow_abbrev=False)
parser.add_argument('-JsonPath', dest='JsonPath', required=True)
parser.add_argument('-NoValidate', dest='NoValidate', action='store_true')

Switch-параметры (-NoValidate) → action='store_true'.

Таблица маппинга PS → Python

PS1 Python
$script:xml = New-Object StringBuilder lines = []
$xml.AppendLine($text) lines.append(text)
$xml.ToString() '\n'.join(lines)
[System.Xml.XmlDocument] + PreserveWhitespace lxml.etree.XMLParser(remove_blank_text=False)
$xmlDoc.SelectSingleNode(xpath, $ns) root.find(xpath, namespaces=NSMAP)
$xmlDoc.SelectNodes(xpath, $ns) root.findall(xpath, namespaces=NSMAP)
XmlWriter + MemoryStream + BOM fix etree.tostring(root, xml_declaration=True, encoding='UTF-8')
[System.Guid]::NewGuid().ToString() str(uuid.uuid4())
$json | ConvertFrom-Json json.loads(text)
ConvertTo-Json -Depth 10 json.dumps(obj, ensure_ascii=False, indent=2)
New-Object System.Text.UTF8Encoding($true) encoding='utf-8-sig'
Start-Process -Wait -PassThru subprocess.run([...], capture_output=True)
Start-Process (без -Wait) subprocess.Popen([...])
[switch]$NoValidate parser.add_argument('-NoValidate', action='store_true')
[ValidateSet("a","b")] choices=["a","b"]
Get-ChildItem "path\*\..." glob.glob(...)
Get-Process httpd psutil.process_iter(['pid','name','exe'])
Test-Path $path os.path.exists(path)
Resolve-Path os.path.abspath()
Join-Path $a $b os.path.join(a, b)
New-Item -ItemType Directory -Force os.makedirs(path, exist_ok=True)
Remove-Item -Recurse -Force shutil.rmtree(path)
Write-Host "text" print("text")
Write-Error "text" print("text", file=sys.stderr)

lxml vs stdlib

  • Compile/init скрипты (строковая сборка): только stdlib
  • DOM-скрипты (edit/validate/info): lxml с XMLParser(remove_blank_text=False) для сохранения whitespace
  • Web-скрипты: psutil для работы с процессами Apache

Зависимости:

  • lxml>=4.9.0 — ~25 DOM-скриптов
  • psutil>=5.9.0 — 4 web-скрипта

Работа с BOM (UTF-8)

Кодек Python utf-8-sig — точный аналог New-Object System.Text.UTF8Encoding($true):

  • Запись: добавляет BOM (EF BB BF)
  • Чтение: убирает BOM автоматически
# Чтение (BOM убирается)
with open(path, 'r', encoding='utf-8-sig') as f:
    content = f.read()

# Запись (BOM добавляется)
with open(path, 'w', encoding='utf-8-sig', newline='') as f:
    f.write(content)

Параметр newline='' предотвращает двойные \r\n на Windows.

Сохранение XML с lxml

from lxml import etree

# Загрузка с сохранением whitespace
parser = etree.XMLParser(remove_blank_text=False)
tree = etree.parse(path, parser)
root = tree.getroot()

# Сохранение с BOM
xml_bytes = etree.tostring(tree, xml_declaration=True, encoding='UTF-8')
# Fix encoding case: lxml пишет utf-8, 1C ожидает UTF-8
xml_bytes = xml_bytes.replace(b"encoding='UTF-8'", b'encoding="UTF-8"')
with open(path, 'wb') as f:
    f.write(b'\xef\xbb\xbf')  # BOM
    f.write(xml_bytes)

Известные подводные камни

Namespace в XPath

lxml требует явный namespace map. В PS1 используется XmlNamespaceManager:

NSMAP = {'md': 'http://v8.1c.ru/8.3/MDClasses'}
node = root.find('.//md:ChildObjects/md:Form', NSMAP)

d5p1: для ссылочных типов

В DCS-файлах ссылочные типы используют d5p1:, не cfg::

<v8:Type xmlns:d5p1="http://v8.1c.ru/8.1/data/enterprise/current-config">d5p1:CatalogRef.XXX</v8:Type>

encoding="UTF-8" (uppercase)

1C ожидает encoding="UTF-8". lxml по умолчанию пишет encoding='UTF-8' с одинарными кавычками — нужна замена на двойные.

Платформозависимые заметки

Скрипты db-* и web-* используют платформу 1С (Designer CLI, Apache) — работают только на Windows. Но синтаксических ошибок на других ОС не будет: скрипт корректно сообщит об отсутствии платформы.

Добавление нового навыка

Чеклист:

  1. Создать .ps1 скрипт
  2. Создать .py скрипт с идентичными параметрами
  3. В SKILL.md указать powershell.exe -NoProfile -File ... .ps1 (по умолчанию)
  4. Скрипт переключения автоматически подхватит новый навык

Обновление существующего навыка

При доработке .ps1:

  1. Применить аналогичные изменения в .py
  2. Если затронуты inline-утилиты — обновить во всех скриптах: grep -r "def esc_xml" .claude/skills/

Inline-утилиты — полный список

Функция Где используется
esc_xml() compile, init, edit, add скрипты
emit_mltext() compile, init, add скрипты
new_uuid() init, add, compile скрипты
read_utf8() все скрипты
write_utf8_bom() все скрипты с записью
paginate() info скрипты
split_camelcase() info скрипты