mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-09-20 00:45:54 +03:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a98fed138 |
@@ -1,32 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "cc-1c-skills",
|
|
||||||
"interface": {
|
|
||||||
"displayName": "1C Skills"
|
|
||||||
},
|
|
||||||
"plugins": [
|
|
||||||
{
|
|
||||||
"name": "1c-skills",
|
|
||||||
"source": {
|
|
||||||
"source": "url",
|
|
||||||
"url": "https://github.com/Nikolay-Shirokov/cc-1c-skills.git",
|
|
||||||
"ref": "port-codex"
|
|
||||||
},
|
|
||||||
"policy": {
|
|
||||||
"installation": "AVAILABLE"
|
|
||||||
},
|
|
||||||
"category": "Development"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "1c-skills-py",
|
|
||||||
"source": {
|
|
||||||
"source": "url",
|
|
||||||
"url": "https://github.com/Nikolay-Shirokov/cc-1c-skills.git",
|
|
||||||
"ref": "port-codex-py"
|
|
||||||
},
|
|
||||||
"policy": {
|
|
||||||
"installation": "AVAILABLE"
|
|
||||||
},
|
|
||||||
"category": "Development"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://json.schemastore.org/claude-code-marketplace-manifest.json",
|
|
||||||
"name": "cc-1c-skills",
|
|
||||||
"description": "Маркетплейс навыков для разработки на платформе 1С:Предприятие",
|
|
||||||
"owner": {
|
|
||||||
"name": "Nikolay Shirokov"
|
|
||||||
},
|
|
||||||
"plugins": [
|
|
||||||
{
|
|
||||||
"name": "1c-skills",
|
|
||||||
"source": "./",
|
|
||||||
"description": "[PowerShell] Навыки для разработки на 1С:Предприятие 8.3 — абстракции над XML-форматами и CLI конфигуратора, плюс глаза и руки для тестирования через веб-клиент."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "1c-skills-py",
|
|
||||||
"source": {
|
|
||||||
"source": "github",
|
|
||||||
"repo": "Nikolay-Shirokov/cc-1c-skills",
|
|
||||||
"ref": "port-claude-code-py"
|
|
||||||
},
|
|
||||||
"description": "[Python] То же — для Linux/Mac или когда PowerShell недоступен."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
||||||
"name": "1c-skills",
|
|
||||||
"description": "[PowerShell] Навыки для разработки на 1С:Предприятие 8.3 — абстракции над XML-форматами и CLI конфигуратора, плюс глаза и руки для тестирования через веб-клиент.",
|
|
||||||
"author": {
|
|
||||||
"name": "Nikolay Shirokov"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/Nikolay-Shirokov/cc-1c-skills",
|
|
||||||
"repository": "https://github.com/Nikolay-Shirokov/cc-1c-skills",
|
|
||||||
"license": "MIT",
|
|
||||||
"keywords": [
|
|
||||||
"1c",
|
|
||||||
"1c-dev",
|
|
||||||
"cf",
|
|
||||||
"cfe",
|
|
||||||
"epf",
|
|
||||||
"erf",
|
|
||||||
"metadata",
|
|
||||||
"configuration",
|
|
||||||
"extension",
|
|
||||||
"form",
|
|
||||||
"report",
|
|
||||||
"skd",
|
|
||||||
"data-processor",
|
|
||||||
"mxl",
|
|
||||||
"web-client",
|
|
||||||
"testing",
|
|
||||||
"test-automation"
|
|
||||||
],
|
|
||||||
"skills": "./.claude/skills/"
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
# Коммиты, которые git blame должен «проскакивать» (механические правки —
|
|
||||||
# не меняют авторство содержимого). Включить локально:
|
|
||||||
# git config blame.ignoreRevsFile .git-blame-ignore-revs
|
|
||||||
# GitHub/GitLab уважают этот файл в blame-UI автоматически.
|
|
||||||
|
|
||||||
# chore(repo): нормализация EOL к LF + .gitattributes
|
|
||||||
26888a07d58351755fb8e487727c00d5b611eb95
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
# EOL policy
|
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
# Авторский контент нормализуем к LF: инструмент правки (Edit) всегда пишет LF,
|
|
||||||
# поэтому единый LF убирает EOL-шум в диффах и ловушку «не правь CRLF-файл».
|
|
||||||
# git с eol=lf конвертит ТОЛЬКО CR<->LF и не трогает BOM (BOM — байты контента),
|
|
||||||
# поэтому BOM на .ps1 сохраняется.
|
|
||||||
*.ps1 text eol=lf
|
|
||||||
*.psm1 text eol=lf
|
|
||||||
*.py text eol=lf
|
|
||||||
*.mjs text eol=lf
|
|
||||||
*.md text eol=lf
|
|
||||||
*.json text eol=lf
|
|
||||||
.gitignore text eol=lf
|
|
||||||
|
|
||||||
# .bsl уже целиком LF — пин фиксирует статус-кво от будущего дрейфа.
|
|
||||||
*.bsl text eol=lf
|
|
||||||
|
|
||||||
# Данные 1С НЕ трогаем. *.xml — реальные выгрузки 1С (EOL местами значим,
|
|
||||||
# правим не мы, а навыки): оставляем как есть, под управление не берём.
|
|
||||||
# autocrlf=false и отсутствие text-атрибута => git хранит их байты как есть.
|
|
||||||
|
|
||||||
# Бинарники 1С
|
|
||||||
*.bin binary
|
|
||||||
|
|
||||||
# Package.bin пакетов XDTO — текстовый XML, несмотря на расширение. Оставляем
|
|
||||||
# под правилом *.bin binary (байты не нормализуются), но включаем текстовый diff,
|
|
||||||
# иначе изменение модели пакета в истории выглядит как «Binary files differ».
|
|
||||||
XDTOPackages/**/Package.bin diff
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
# 1C Skills for {{PLATFORM_LABEL}} ({{RUNTIME_LABEL}})
|
|
||||||
|
|
||||||
Автоматическая сборка из [main]({{MAIN_REPO_URL}}) — навыки 1С:Предприятие 8.3 для AI-агента **{{PLATFORM_LABEL}}** с рантаймом **{{RUNTIME_LABEL}}**.
|
|
||||||
|
|
||||||
> Эта ветка генерируется CI на каждый push в main. **Не редактируйте напрямую** — все правки идут в [main]({{MAIN_REPO_URL}}).
|
|
||||||
|
|
||||||
## Установка
|
|
||||||
|
|
||||||
1. Скачайте ZIP этой ветки: **Code → Download ZIP** (или `git archive`).
|
|
||||||
2. Распакуйте в корень своего проекта — должна появиться папка `{{PLATFORM_DIR}}/`.
|
|
||||||
3. Запустите {{PLATFORM_LABEL}} из этого проекта — навыки станут доступны.
|
|
||||||
|
|
||||||
## Требования
|
|
||||||
|
|
||||||
{{RUNTIME_REQUIREMENTS}}
|
|
||||||
- **1С:Предприятие 8.3** — для сборки/разборки EPF/ERF и работы с базами.
|
|
||||||
- **Node.js 18+** — для `/web-test`.
|
|
||||||
|
|
||||||
## Документация
|
|
||||||
|
|
||||||
Полные гайды, спецификации и описание навыков — в [main]({{MAIN_REPO_URL}}).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Source: {{MAIN_REPO_URL}}
|
|
||||||
Build commit: `{{COMMIT_SHA}}`
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
||||||
"name": "{{PLUGIN_NAME}}",
|
|
||||||
"description": "[Python] Навыки для разработки на 1С:Предприятие 8.3 — абстракции над XML-форматами и CLI конфигуратора, плюс глаза и руки для тестирования через веб-клиент. Linux/Mac или когда PowerShell недоступен.",
|
|
||||||
"author": {
|
|
||||||
"name": "Nikolay Shirokov"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/Nikolay-Shirokov/cc-1c-skills",
|
|
||||||
"repository": "https://github.com/Nikolay-Shirokov/cc-1c-skills",
|
|
||||||
"license": "MIT",
|
|
||||||
"keywords": [
|
|
||||||
"1c",
|
|
||||||
"1c-dev",
|
|
||||||
"cf",
|
|
||||||
"cfe",
|
|
||||||
"epf",
|
|
||||||
"erf",
|
|
||||||
"metadata",
|
|
||||||
"configuration",
|
|
||||||
"extension",
|
|
||||||
"form",
|
|
||||||
"report",
|
|
||||||
"skd",
|
|
||||||
"data-processor",
|
|
||||||
"mxl",
|
|
||||||
"web-client",
|
|
||||||
"testing",
|
|
||||||
"test-automation"
|
|
||||||
],
|
|
||||||
"skills": "./.claude/skills/"
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "{{PLUGIN_NAME}}",
|
|
||||||
"version": "{{VERSION}}",
|
|
||||||
"description": "[{{RUNTIME_LABEL}}] Навыки для разработки на 1С:Предприятие 8.3 — абстракции над XML-форматами и CLI конфигуратора, плюс глаза и руки для тестирования через веб-клиент.",
|
|
||||||
"author": {
|
|
||||||
"name": "Nikolay Shirokov"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/Nikolay-Shirokov/cc-1c-skills",
|
|
||||||
"repository": "https://github.com/Nikolay-Shirokov/cc-1c-skills",
|
|
||||||
"license": "MIT",
|
|
||||||
"keywords": [
|
|
||||||
"1c",
|
|
||||||
"1c-dev",
|
|
||||||
"cf",
|
|
||||||
"cfe",
|
|
||||||
"epf",
|
|
||||||
"erf",
|
|
||||||
"metadata",
|
|
||||||
"configuration",
|
|
||||||
"extension",
|
|
||||||
"form",
|
|
||||||
"report",
|
|
||||||
"skd",
|
|
||||||
"data-processor",
|
|
||||||
"mxl",
|
|
||||||
"web-client",
|
|
||||||
"testing",
|
|
||||||
"test-automation"
|
|
||||||
],
|
|
||||||
"skills": "./.codex/skills/",
|
|
||||||
"interface": {
|
|
||||||
"displayName": "1C Skills ({{RUNTIME_LABEL}})",
|
|
||||||
"shortDescription": "{{SHORT_DESCRIPTION}}",
|
|
||||||
"category": "Development"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,246 +0,0 @@
|
|||||||
name: Build port branches
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
paths:
|
|
||||||
- '.claude/skills/**'
|
|
||||||
- 'scripts/switch.py'
|
|
||||||
- 'requirements.txt'
|
|
||||||
- '.github/templates/README.port.md.tmpl'
|
|
||||||
- '.github/templates/codex-plugin.json.tmpl'
|
|
||||||
- '.github/templates/claude-plugin.json.tmpl'
|
|
||||||
- '.github/workflows/build-ports.yml'
|
|
||||||
- 'LICENSE'
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- platform: claude-code
|
|
||||||
runtime: python
|
|
||||||
branch: port-claude-code-py
|
|
||||||
label: Claude Code
|
|
||||||
target_dir: .claude/skills
|
|
||||||
- platform: cursor
|
|
||||||
runtime: powershell
|
|
||||||
branch: port-cursor
|
|
||||||
label: Cursor
|
|
||||||
target_dir: .cursor/skills
|
|
||||||
- platform: cursor
|
|
||||||
runtime: python
|
|
||||||
branch: port-cursor-py
|
|
||||||
label: Cursor
|
|
||||||
target_dir: .cursor/skills
|
|
||||||
- platform: codex
|
|
||||||
runtime: powershell
|
|
||||||
branch: port-codex
|
|
||||||
label: Codex
|
|
||||||
target_dir: .codex/skills
|
|
||||||
- platform: codex
|
|
||||||
runtime: python
|
|
||||||
branch: port-codex-py
|
|
||||||
label: Codex
|
|
||||||
target_dir: .codex/skills
|
|
||||||
- platform: copilot
|
|
||||||
runtime: powershell
|
|
||||||
branch: port-copilot
|
|
||||||
label: GitHub Copilot
|
|
||||||
target_dir: .github/skills
|
|
||||||
- platform: copilot
|
|
||||||
runtime: python
|
|
||||||
branch: port-copilot-py
|
|
||||||
label: GitHub Copilot
|
|
||||||
target_dir: .github/skills
|
|
||||||
- platform: augment
|
|
||||||
runtime: powershell
|
|
||||||
branch: port-augment
|
|
||||||
label: Augment
|
|
||||||
target_dir: .augment/skills
|
|
||||||
- platform: augment
|
|
||||||
runtime: python
|
|
||||||
branch: port-augment-py
|
|
||||||
label: Augment
|
|
||||||
target_dir: .augment/skills
|
|
||||||
- platform: cline
|
|
||||||
runtime: powershell
|
|
||||||
branch: port-cline
|
|
||||||
label: Cline
|
|
||||||
target_dir: .cline/skills
|
|
||||||
- platform: cline
|
|
||||||
runtime: python
|
|
||||||
branch: port-cline-py
|
|
||||||
label: Cline
|
|
||||||
target_dir: .cline/skills
|
|
||||||
- platform: kilo
|
|
||||||
runtime: powershell
|
|
||||||
branch: port-kilo
|
|
||||||
label: Kilo Code
|
|
||||||
target_dir: .kilocode/skills
|
|
||||||
- platform: kilo
|
|
||||||
runtime: python
|
|
||||||
branch: port-kilo-py
|
|
||||||
label: Kilo Code
|
|
||||||
target_dir: .kilocode/skills
|
|
||||||
- platform: kiro
|
|
||||||
runtime: powershell
|
|
||||||
branch: port-kiro
|
|
||||||
label: Kiro
|
|
||||||
target_dir: .kiro/skills
|
|
||||||
- platform: kiro
|
|
||||||
runtime: python
|
|
||||||
branch: port-kiro-py
|
|
||||||
label: Kiro
|
|
||||||
target_dir: .kiro/skills
|
|
||||||
- platform: gemini
|
|
||||||
runtime: powershell
|
|
||||||
branch: port-gemini
|
|
||||||
label: Gemini CLI
|
|
||||||
target_dir: .gemini/skills
|
|
||||||
- platform: gemini
|
|
||||||
runtime: python
|
|
||||||
branch: port-gemini-py
|
|
||||||
label: Gemini CLI
|
|
||||||
target_dir: .gemini/skills
|
|
||||||
- platform: opencode
|
|
||||||
runtime: powershell
|
|
||||||
branch: port-opencode
|
|
||||||
label: OpenCode
|
|
||||||
target_dir: .opencode/skills
|
|
||||||
- platform: opencode
|
|
||||||
runtime: python
|
|
||||||
branch: port-opencode-py
|
|
||||||
label: OpenCode
|
|
||||||
target_dir: .opencode/skills
|
|
||||||
- platform: roo
|
|
||||||
runtime: powershell
|
|
||||||
branch: port-roo
|
|
||||||
label: Roo Code
|
|
||||||
target_dir: .roo/skills
|
|
||||||
- platform: roo
|
|
||||||
runtime: python
|
|
||||||
branch: port-roo-py
|
|
||||||
label: Roo Code
|
|
||||||
target_dir: .roo/skills
|
|
||||||
- platform: windsurf
|
|
||||||
runtime: powershell
|
|
||||||
branch: port-windsurf
|
|
||||||
label: Windsurf
|
|
||||||
target_dir: .windsurf/skills
|
|
||||||
- platform: windsurf
|
|
||||||
runtime: python
|
|
||||||
branch: port-windsurf-py
|
|
||||||
label: Windsurf
|
|
||||||
target_dir: .windsurf/skills
|
|
||||||
- platform: codeassistant
|
|
||||||
runtime: powershell
|
|
||||||
branch: port-codeassistant
|
|
||||||
label: Yandex Code Assistant
|
|
||||||
target_dir: .codeassistant/skills
|
|
||||||
- platform: codeassistant
|
|
||||||
runtime: python
|
|
||||||
branch: port-codeassistant-py
|
|
||||||
label: Yandex Code Assistant
|
|
||||||
target_dir: .codeassistant/skills
|
|
||||||
- platform: agents
|
|
||||||
runtime: powershell
|
|
||||||
branch: port-agents
|
|
||||||
label: Agent Skills
|
|
||||||
target_dir: .agents/skills
|
|
||||||
- platform: agents
|
|
||||||
runtime: python
|
|
||||||
branch: port-agents-py
|
|
||||||
label: Agent Skills
|
|
||||||
target_dir: .agents/skills
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout main
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
|
|
||||||
- name: Set up Python
|
|
||||||
uses: actions/setup-python@v6
|
|
||||||
with:
|
|
||||||
python-version: '3.11'
|
|
||||||
|
|
||||||
- name: Build skills tree for ${{ matrix.platform }} (${{ matrix.runtime }})
|
|
||||||
run: |
|
|
||||||
python scripts/switch.py "${{ matrix.platform }}" \
|
|
||||||
--project-dir build \
|
|
||||||
--runtime "${{ matrix.runtime }}"
|
|
||||||
|
|
||||||
- name: Render port README
|
|
||||||
env:
|
|
||||||
PLATFORM_LABEL: ${{ matrix.label }}
|
|
||||||
PLATFORM_DIR: ${{ matrix.target_dir }}
|
|
||||||
RUNTIME: ${{ matrix.runtime }}
|
|
||||||
RUNTIME_LABEL: ${{ matrix.runtime == 'powershell' && 'PowerShell' || 'Python' }}
|
|
||||||
COMMIT_SHA: ${{ github.sha }}
|
|
||||||
MAIN_REPO_URL: https://github.com/${{ github.repository }}
|
|
||||||
run: |
|
|
||||||
if [ "$RUNTIME" = "powershell" ]; then
|
|
||||||
RUNTIME_REQUIREMENTS='- **Windows** с PowerShell 5.1+ (входит в Windows).'
|
|
||||||
else
|
|
||||||
RUNTIME_REQUIREMENTS='- **Python 3.9+**. Установка зависимостей: `pip install -r requirements.txt` (lxml, Pillow, psutil).'
|
|
||||||
fi
|
|
||||||
sed \
|
|
||||||
-e "s|{{PLATFORM_LABEL}}|${PLATFORM_LABEL}|g" \
|
|
||||||
-e "s|{{PLATFORM_DIR}}|${PLATFORM_DIR}|g" \
|
|
||||||
-e "s|{{RUNTIME_LABEL}}|${RUNTIME_LABEL}|g" \
|
|
||||||
-e "s|{{RUNTIME_REQUIREMENTS}}|${RUNTIME_REQUIREMENTS}|g" \
|
|
||||||
-e "s|{{COMMIT_SHA}}|${COMMIT_SHA}|g" \
|
|
||||||
-e "s|{{MAIN_REPO_URL}}|${MAIN_REPO_URL}|g" \
|
|
||||||
.github/templates/README.port.md.tmpl > build/README.md
|
|
||||||
|
|
||||||
- name: Render Codex plugin manifest
|
|
||||||
if: matrix.platform == 'codex'
|
|
||||||
env:
|
|
||||||
PLUGIN_NAME: ${{ matrix.runtime == 'python' && '1c-skills-py' || '1c-skills' }}
|
|
||||||
RUNTIME_LABEL: ${{ matrix.runtime == 'powershell' && 'PowerShell' || 'Python' }}
|
|
||||||
SHORT_DESCRIPTION: ${{ matrix.runtime == 'python' && 'Python runtime (Linux/Mac/Windows)' || 'PowerShell runtime (Windows-first)' }}
|
|
||||||
COMMIT_SHA: ${{ github.sha }}
|
|
||||||
run: |
|
|
||||||
VERSION="$(date -u +%Y.%-m.%-d)+${COMMIT_SHA::7}"
|
|
||||||
mkdir -p build/.codex-plugin
|
|
||||||
sed \
|
|
||||||
-e "s|{{PLUGIN_NAME}}|${PLUGIN_NAME}|g" \
|
|
||||||
-e "s|{{VERSION}}|${VERSION}|g" \
|
|
||||||
-e "s|{{RUNTIME_LABEL}}|${RUNTIME_LABEL}|g" \
|
|
||||||
-e "s|{{SHORT_DESCRIPTION}}|${SHORT_DESCRIPTION}|g" \
|
|
||||||
.github/templates/codex-plugin.json.tmpl > build/.codex-plugin/plugin.json
|
|
||||||
|
|
||||||
- name: Render Claude plugin manifest (Py variant)
|
|
||||||
if: matrix.platform == 'claude-code' && matrix.runtime == 'python'
|
|
||||||
env:
|
|
||||||
PLUGIN_NAME: 1c-skills-py
|
|
||||||
run: |
|
|
||||||
mkdir -p build/.claude-plugin
|
|
||||||
sed -e "s|{{PLUGIN_NAME}}|${PLUGIN_NAME}|g" \
|
|
||||||
.github/templates/claude-plugin.json.tmpl > build/.claude-plugin/plugin.json
|
|
||||||
|
|
||||||
- name: Copy LICENSE
|
|
||||||
run: cp LICENSE build/LICENSE
|
|
||||||
|
|
||||||
- name: Copy requirements.txt (Python builds only)
|
|
||||||
if: matrix.runtime == 'python'
|
|
||||||
run: cp requirements.txt build/requirements.txt
|
|
||||||
|
|
||||||
- name: Force-push orphan snapshot to ${{ matrix.branch }}
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
|
||||||
cd build
|
|
||||||
git init -q -b master
|
|
||||||
git config user.name "github-actions[bot]"
|
|
||||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
||||||
git add -A
|
|
||||||
git commit -q -m "Auto-build: ${{ matrix.platform }} (${{ matrix.runtime }}) from ${GITHUB_SHA::7}"
|
|
||||||
git push --force \
|
|
||||||
"https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git" \
|
|
||||||
"master:${{ matrix.branch }}"
|
|
||||||
-58
@@ -1,58 +0,0 @@
|
|||||||
# Реальные выгрузки обработок (примеры, не для версионирования)
|
|
||||||
upload/
|
|
||||||
|
|
||||||
# Результаты сборки
|
|
||||||
build/
|
|
||||||
base/
|
|
||||||
*.epf
|
|
||||||
*.log
|
|
||||||
|
|
||||||
# Временные файлы тестов
|
|
||||||
test-tmp/
|
|
||||||
|
|
||||||
# Локальные настройки Claude Code
|
|
||||||
.claude/settings.local.json
|
|
||||||
|
|
||||||
# Инструменты (portable Apache и т.д.)
|
|
||||||
tools/
|
|
||||||
|
|
||||||
# Отладка навыков (eval, trigger-test, run_loop результаты)
|
|
||||||
debug/
|
|
||||||
|
|
||||||
# Кэш тестов навыков
|
|
||||||
tests/skills/.cache/
|
|
||||||
|
|
||||||
# Python кэш
|
|
||||||
__pycache__/
|
|
||||||
|
|
||||||
# Локальный реестр баз данных 1С
|
|
||||||
.v8-project.json
|
|
||||||
# ...но в кейсах он — вход теста (настройка newObjectPosition), и эталон без него
|
|
||||||
# на чистом клоне не сойдётся с результатом прогона.
|
|
||||||
!tests/skills/cases/**/.v8-project.json
|
|
||||||
|
|
||||||
# web-test: Node.js зависимости и runtime-артефакты
|
|
||||||
.claude/skills/web-test/scripts/node_modules/
|
|
||||||
.claude/skills/web-test/.browser-session.json
|
|
||||||
|
|
||||||
# Маркер отработавшего prepare() в фикстуре _suite-root
|
|
||||||
tests/web-test/_suite-root/prepare-ran.txt
|
|
||||||
|
|
||||||
# Скриншоты и видео (артефакты тестирования web-test)
|
|
||||||
*.png
|
|
||||||
*.mp4
|
|
||||||
|
|
||||||
# Навыки, скопированные для других AI-платформ (генерируются scripts/switch.py)
|
|
||||||
.agents/skills/
|
|
||||||
.augment/
|
|
||||||
.cline/
|
|
||||||
.codex/
|
|
||||||
.cursor/
|
|
||||||
.gemini/
|
|
||||||
.github/skills/
|
|
||||||
.kilocode/
|
|
||||||
.kiro/
|
|
||||||
.opencode/
|
|
||||||
.roo/
|
|
||||||
.windsurf/
|
|
||||||
debug-templates.txt
|
|
||||||
@@ -24,7 +24,7 @@ allowed-tools:
|
|||||||
| `NoValidate` | Пропустить авто-валидацию |
|
| `NoValidate` | Пропустить авто-валидацию |
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cf-edit.ps1" -ConfigPath '<path>' -Operation modify-property -Value 'Version=1.0.0.1'
|
python ".roo/skills/cf-edit/scripts/cf-edit.py" -ConfigPath '<path>' -Operation modify-property -Value 'Version=1.0.0.1'
|
||||||
```
|
```
|
||||||
|
|
||||||
## Операции
|
## Операции
|
||||||
@@ -23,7 +23,7 @@ allowed-tools:
|
|||||||
| `OutFile` | Записать результат в файл (UTF-8 BOM) |
|
| `OutFile` | Записать результат в файл (UTF-8 BOM) |
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cf-info.ps1" -ConfigPath "<путь>"
|
python ".roo/skills/cf-info/scripts/cf-info.py" -ConfigPath "<путь>"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Три режима
|
## Три режима
|
||||||
@@ -39,7 +39,7 @@ allowed-tools:
|
|||||||
не будет.
|
не будет.
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cf-init.ps1" -Name "МояКонфигурация"
|
python ".roo/skills/cf-init/scripts/cf-init.py" -Name "МояКонфигурация"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Примеры
|
## Примеры
|
||||||
@@ -24,6 +24,6 @@ allowed-tools:
|
|||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cf-validate.ps1" -ConfigPath "upload/cfempty"
|
python ".roo/skills/cf-validate/scripts/cf-validate.py" -ConfigPath "upload/cfempty"
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cf-validate.ps1" -ConfigPath "upload/cfempty/Configuration.xml"
|
python ".roo/skills/cf-validate/scripts/cf-validate.py" -ConfigPath "upload/cfempty/Configuration.xml"
|
||||||
```
|
```
|
||||||
@@ -71,7 +71,7 @@ allowed-tools:
|
|||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-borrow.ps1" -ExtensionPath src\cfe\extname -ConfigPath src\cf -Object "Catalog.Контрагенты"
|
python ".roo/skills/cfe-borrow/scripts/cfe-borrow.py" -ExtensionPath src\cfe\extname -ConfigPath src\cf -Object "Catalog.Контрагенты"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Примеры
|
## Примеры
|
||||||
@@ -23,7 +23,7 @@ allowed-tools:
|
|||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-diff.ps1" -ExtensionPath src\cfe\extname -ConfigPath src\cf -Mode A
|
python ".roo/skills/cfe-diff/scripts/cfe-diff.py" -ExtensionPath src\cfe\extname -ConfigPath src\cf -Mode A
|
||||||
```
|
```
|
||||||
|
|
||||||
## Mode A — обзор расширения
|
## Mode A — обзор расширения
|
||||||
+88
-19
@@ -1,4 +1,4 @@
|
|||||||
# cfe-diff v1.5 — Analyze and compare 1C configuration extension (CFE)
|
# cfe-diff v1.6 — Analyze and compare 1C configuration extension (CFE)
|
||||||
# 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(
|
||||||
@@ -162,24 +162,92 @@ function Get-BslFiles {
|
|||||||
return $bslFiles
|
return $bslFiles
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Helper: parse interceptors from .bsl ---
|
# Built-in language keywords in both spellings. The platform accepts either one in any module
|
||||||
|
# (pairs taken from the platform string tables), so a module written in English is ordinary
|
||||||
|
# source, not a broken one: we must read both and emit the spelling we read.
|
||||||
|
function Get-BslKeywords {
|
||||||
|
return @{
|
||||||
|
ru = @{
|
||||||
|
Async="Асинх"; Proc="Процедура"; EndProc="КонецПроцедуры"
|
||||||
|
Func="Функция"; EndFunc="КонецФункции"; Val="Знач"
|
||||||
|
Region="Область"; EndRegion="КонецОбласти"
|
||||||
|
If="Если"; Then="Тогда"; ElsIf="ИначеЕсли"; Else="Иначе"; EndIf="КонецЕсли"
|
||||||
|
# Platform spells the negation "Не"; we emit "НЕ" as this skill always has —
|
||||||
|
# the language is case-insensitive, and the lower case would churn every snapshot.
|
||||||
|
And="И"; Or="Или"; Not="НЕ"
|
||||||
|
Insert="Вставка"; EndInsert="КонецВставки"; Delete="Удаление"; EndDelete="КонецУдаления"
|
||||||
|
Before="Перед"; After="После"; Around="Вместо"; Control="ИзменениеИКонтроль"
|
||||||
|
Proceed="ПродолжитьВызов"; Return="Возврат"
|
||||||
|
# Not a keyword: name of the local the generated Instead-stub declares. Lives here so
|
||||||
|
# that the language of emitted text is decided in exactly one place.
|
||||||
|
ResultVar="Результат"
|
||||||
|
Directives=@("НаКлиенте", "НаСервере", "НаСервереБезКонтекста", "НаКлиентеНаСервереБезКонтекста", "НаКлиентеНаСервере")
|
||||||
|
}
|
||||||
|
en = @{
|
||||||
|
Async="Async"; Proc="Procedure"; EndProc="EndProcedure"
|
||||||
|
Func="Function"; EndFunc="EndFunction"; Val="Val"
|
||||||
|
Region="Region"; EndRegion="EndRegion"
|
||||||
|
If="If"; Then="Then"; ElsIf="ElsIf"; Else="Else"; EndIf="EndIf"
|
||||||
|
And="And"; Or="Or"; Not="Not"
|
||||||
|
Insert="Insert"; EndInsert="EndInsert"; Delete="Delete"; EndDelete="EndDelete"
|
||||||
|
Before="Before"; After="After"; Around="Around"; Control="ChangeAndValidate"
|
||||||
|
Proceed="ProceedWithCall"; Return="Return"
|
||||||
|
ResultVar="Result"
|
||||||
|
Directives=@("AtClient", "AtServer", "AtServerNoContext", "AtClientAtServerNoContext", "AtClientAtServer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$script:bslKw = Get-BslKeywords
|
||||||
|
|
||||||
|
# Edit marker kind at the start of a line: Insert/Delete/EndInsert/EndDelete, $null if none.
|
||||||
|
# The marker owns the start of the line only — a trailing comment does not bother the platform
|
||||||
|
# and must not bother us either.
|
||||||
|
function Get-BslMarkerKind {
|
||||||
|
param([string]$line)
|
||||||
|
$kw = $script:bslKw
|
||||||
|
foreach ($k in @("EndInsert", "EndDelete", "Insert", "Delete")) {
|
||||||
|
if ($line -match ('^\s*#(?:' + $kw.ru[$k] + '|' + $kw.en[$k] + ')\b')) { return $k }
|
||||||
|
}
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Parse interceptor annotations present in a module. Type is normalized to the Russian spelling
|
||||||
|
# so everything downstream stays single-language; Raw keeps what the file actually says.
|
||||||
function Get-Interceptors {
|
function Get-Interceptors {
|
||||||
|
param($lines)
|
||||||
|
$kw = $script:bslKw
|
||||||
|
$keys = @("Before", "After", "Control", "Around")
|
||||||
|
$alt = @()
|
||||||
|
$norm = @{}
|
||||||
|
foreach ($k in $keys) {
|
||||||
|
$alt += $kw.ru[$k]; $alt += $kw.en[$k]
|
||||||
|
$norm[$kw.ru[$k].ToLower()] = $kw.ru[$k]
|
||||||
|
$norm[$kw.en[$k].ToLower()] = $kw.ru[$k]
|
||||||
|
}
|
||||||
|
$re = '^&(' + ($alt -join '|') + ')\("([^"]+)"\)'
|
||||||
|
$result = @()
|
||||||
|
for ($i = 0; $i -lt $lines.Count; $i++) {
|
||||||
|
$t = $lines[$i].Trim()
|
||||||
|
if ($t -match $re) {
|
||||||
|
$raw = $Matches[1]
|
||||||
|
$result += @{ Type = $norm[$raw.ToLower()]; Raw = $raw; Method = $Matches[2]; Line = $i }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $result
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Helper: read a .bsl and parse its interceptors ---
|
||||||
|
function Get-FileInterceptors {
|
||||||
param([string]$bslPath)
|
param([string]$bslPath)
|
||||||
|
|
||||||
if (-not (Test-Path $bslPath)) { return @() }
|
if (-not (Test-Path $bslPath)) { return @() }
|
||||||
$lines = [System.IO.File]::ReadAllLines($bslPath, [System.Text.Encoding]::UTF8)
|
$lines = [System.IO.File]::ReadAllLines($bslPath, [System.Text.Encoding]::UTF8)
|
||||||
$interceptors = @()
|
$result = @()
|
||||||
$i = 0
|
foreach ($ic in (Get-Interceptors $lines)) {
|
||||||
while ($i -lt $lines.Count) {
|
$result += @{ Type = $ic.Type; Raw = $ic.Raw; Method = $ic.Method; Line = $ic.Line + 1; File = $bslPath }
|
||||||
$line = $lines[$i].Trim()
|
|
||||||
if ($line -match '^&(Перед|После|ИзменениеИКонтроль|Вместо)\("([^"]+)"\)') {
|
|
||||||
$type = $Matches[1]
|
|
||||||
$method = $Matches[2]
|
|
||||||
$interceptors += @{ Type = $type; Method = $method; Line = $i + 1; File = $bslPath }
|
|
||||||
}
|
}
|
||||||
$i++
|
return $result
|
||||||
}
|
|
||||||
return $interceptors
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Helper: extract #Вставка blocks from .bsl ---
|
# --- Helper: extract #Вставка blocks from .bsl ---
|
||||||
@@ -194,12 +262,13 @@ function Get-InsertionBlocks {
|
|||||||
$startLine = 0
|
$startLine = 0
|
||||||
|
|
||||||
for ($i = 0; $i -lt $lines.Count; $i++) {
|
for ($i = 0; $i -lt $lines.Count; $i++) {
|
||||||
$line = $lines[$i].Trim()
|
$line = $lines[$i]
|
||||||
if ($line -eq "#Вставка") {
|
$kind = Get-BslMarkerKind $line
|
||||||
|
if ($kind -eq "Insert") {
|
||||||
$inBlock = $true
|
$inBlock = $true
|
||||||
$blockLines = @()
|
$blockLines = @()
|
||||||
$startLine = $i + 1
|
$startLine = $i + 1
|
||||||
} elseif ($line -eq "#КонецВставки" -and $inBlock) {
|
} elseif ($kind -eq "EndInsert" -and $inBlock) {
|
||||||
$inBlock = $false
|
$inBlock = $false
|
||||||
$blocks += @{
|
$blocks += @{
|
||||||
StartLine = $startLine
|
StartLine = $startLine
|
||||||
@@ -298,10 +367,10 @@ if ($Mode -eq "A") {
|
|||||||
$bslFiles = Get-BslFiles $obj.Type $obj.Name
|
$bslFiles = Get-BslFiles $obj.Type $obj.Name
|
||||||
foreach ($bsl in $bslFiles) {
|
foreach ($bsl in $bslFiles) {
|
||||||
$relPath = $bsl.Replace($ExtensionPath, "").TrimStart("\", "/")
|
$relPath = $bsl.Replace($ExtensionPath, "").TrimStart("\", "/")
|
||||||
$interceptors = Get-Interceptors $bsl
|
$interceptors = Get-FileInterceptors $bsl
|
||||||
if ($interceptors.Count -gt 0) {
|
if ($interceptors.Count -gt 0) {
|
||||||
foreach ($ic in $interceptors) {
|
foreach ($ic in $interceptors) {
|
||||||
Write-Host " &$($ic.Type)(`"$($ic.Method)`") — line $($ic.Line) in $relPath"
|
Write-Host " &$($ic.Raw)(`"$($ic.Method)`") — line $($ic.Line) in $relPath"
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Write-Host " $relPath (no interceptors)"
|
Write-Host " $relPath (no interceptors)"
|
||||||
@@ -412,7 +481,7 @@ if ($Mode -eq "B") {
|
|||||||
# Find .bsl files with &ИзменениеИКонтроль
|
# Find .bsl files with &ИзменениеИКонтроль
|
||||||
$bslFiles = Get-BslFiles $obj.Type $obj.Name
|
$bslFiles = Get-BslFiles $obj.Type $obj.Name
|
||||||
foreach ($bsl in $bslFiles) {
|
foreach ($bsl in $bslFiles) {
|
||||||
$interceptors = Get-Interceptors $bsl
|
$interceptors = Get-FileInterceptors $bsl
|
||||||
$macInterceptors = @($interceptors | Where-Object { $_.Type -eq "ИзменениеИКонтроль" })
|
$macInterceptors = @($interceptors | Where-Object { $_.Type -eq "ИзменениеИКонтроль" })
|
||||||
|
|
||||||
if ($macInterceptors.Count -eq 0) { continue }
|
if ($macInterceptors.Count -eq 0) { continue }
|
||||||
+88
-27
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# cfe-diff v1.5 — Analyze and compare 1C configuration extension (CFE)
|
# cfe-diff v1.6 — Analyze and compare 1C configuration extension (CFE)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -165,34 +165,97 @@ def get_bsl_files(obj_type, obj_name, extension_path):
|
|||||||
return bsl_files
|
return bsl_files
|
||||||
|
|
||||||
|
|
||||||
# --- Helper: parse interceptors from .bsl ---
|
# Built-in language keywords in both spellings. The platform accepts either one in any module
|
||||||
|
# (pairs taken from the platform string tables), so a module written in English is ordinary
|
||||||
|
# source, not a broken one: we must read both and emit the spelling we read.
|
||||||
|
def bsl_keywords():
|
||||||
|
return {
|
||||||
|
"ru": {
|
||||||
|
"Async": "Асинх", "Proc": "Процедура", "EndProc": "КонецПроцедуры",
|
||||||
|
"Func": "Функция", "EndFunc": "КонецФункции", "Val": "Знач",
|
||||||
|
"Region": "Область", "EndRegion": "КонецОбласти",
|
||||||
|
"If": "Если", "Then": "Тогда", "ElsIf": "ИначеЕсли", "Else": "Иначе", "EndIf": "КонецЕсли",
|
||||||
|
# Platform spells the negation "Не"; we emit "НЕ" as this skill always has —
|
||||||
|
# the language is case-insensitive, and the lower case would churn every snapshot.
|
||||||
|
"And": "И", "Or": "Или", "Not": "НЕ",
|
||||||
|
"Insert": "Вставка", "EndInsert": "КонецВставки", "Delete": "Удаление", "EndDelete": "КонецУдаления",
|
||||||
|
"Before": "Перед", "After": "После", "Around": "Вместо", "Control": "ИзменениеИКонтроль",
|
||||||
|
"Proceed": "ПродолжитьВызов", "Return": "Возврат",
|
||||||
|
# Not a keyword: name of the local the generated Instead-stub declares. Lives here so
|
||||||
|
# that the language of emitted text is decided in exactly one place.
|
||||||
|
"ResultVar": "Результат",
|
||||||
|
"Directives": ["НаКлиенте", "НаСервере", "НаСервереБезКонтекста", "НаКлиентеНаСервереБезКонтекста", "НаКлиентеНаСервере"],
|
||||||
|
},
|
||||||
|
"en": {
|
||||||
|
"Async": "Async", "Proc": "Procedure", "EndProc": "EndProcedure",
|
||||||
|
"Func": "Function", "EndFunc": "EndFunction", "Val": "Val",
|
||||||
|
"Region": "Region", "EndRegion": "EndRegion",
|
||||||
|
"If": "If", "Then": "Then", "ElsIf": "ElsIf", "Else": "Else", "EndIf": "EndIf",
|
||||||
|
"And": "And", "Or": "Or", "Not": "Not",
|
||||||
|
"Insert": "Insert", "EndInsert": "EndInsert", "Delete": "Delete", "EndDelete": "EndDelete",
|
||||||
|
"Before": "Before", "After": "After", "Around": "Around", "Control": "ChangeAndValidate",
|
||||||
|
"Proceed": "ProceedWithCall", "Return": "Return",
|
||||||
|
"ResultVar": "Result",
|
||||||
|
"Directives": ["AtClient", "AtServer", "AtServerNoContext", "AtClientAtServerNoContext", "AtClientAtServer"],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
def get_interceptors(bsl_path):
|
|
||||||
|
BSL_KW = bsl_keywords()
|
||||||
|
|
||||||
|
|
||||||
|
# Edit marker kind at the start of a line: Insert/Delete/EndInsert/EndDelete, None if none.
|
||||||
|
# The marker owns the start of the line only — a trailing comment does not bother the platform
|
||||||
|
# and must not bother us either.
|
||||||
|
def bsl_marker_kind(line):
|
||||||
|
kw = BSL_KW
|
||||||
|
for k in ("EndInsert", "EndDelete", "Insert", "Delete"):
|
||||||
|
if re.match(r'^\s*#(?:' + kw["ru"][k] + '|' + kw["en"][k] + r')\b', line, re.IGNORECASE):
|
||||||
|
return k
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# Parse interceptor annotations present in a module. Type is normalized to the Russian spelling
|
||||||
|
# so everything downstream stays single-language; raw keeps what the file actually says.
|
||||||
|
def get_interceptors(lines):
|
||||||
|
kw = BSL_KW
|
||||||
|
keys = ("Before", "After", "Control", "Around")
|
||||||
|
alt = []
|
||||||
|
norm = {}
|
||||||
|
for k in keys:
|
||||||
|
alt.append(kw["ru"][k])
|
||||||
|
alt.append(kw["en"][k])
|
||||||
|
norm[kw["ru"][k].lower()] = kw["ru"][k]
|
||||||
|
norm[kw["en"][k].lower()] = kw["ru"][k]
|
||||||
|
pat = re.compile(r'^&(' + '|'.join(alt) + r')\("([^"]+)"\)', re.IGNORECASE)
|
||||||
|
result = []
|
||||||
|
for i in range(len(lines)):
|
||||||
|
m = pat.match(lines[i].strip())
|
||||||
|
if m:
|
||||||
|
raw = m.group(1)
|
||||||
|
result.append({"type": norm[raw.lower()], "raw": raw, "method": m.group(2), "line": i})
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
# --- Helper: read a .bsl and parse its interceptors ---
|
||||||
|
|
||||||
|
def get_file_interceptors(bsl_path):
|
||||||
if not os.path.isfile(bsl_path):
|
if not os.path.isfile(bsl_path):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
with open(bsl_path, "r", encoding="utf-8-sig") as fh:
|
with open(bsl_path, "r", encoding="utf-8-sig") as fh:
|
||||||
lines = fh.readlines()
|
lines = fh.readlines()
|
||||||
|
|
||||||
interceptors = []
|
return [{
|
||||||
pattern = re.compile(r'^&(\u041f\u0435\u0440\u0435\u0434|\u041f\u043e\u0441\u043b\u0435|\u0418\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435\u0418\u041a\u043e\u043d\u0442\u0440\u043e\u043b\u044c|\u0412\u043c\u0435\u0441\u0442\u043e)\("([^"]+)"\)')
|
"Type": ic["type"],
|
||||||
# The above is: ^&(Перед|После|ИзменениеИКонтроль|Вместо)\("([^"]+)"\)
|
"Raw": ic["raw"],
|
||||||
|
"Method": ic["method"],
|
||||||
for i, line in enumerate(lines):
|
"Line": ic["line"] + 1,
|
||||||
stripped = line.strip()
|
|
||||||
m = pattern.match(stripped)
|
|
||||||
if m:
|
|
||||||
interceptors.append({
|
|
||||||
"Type": m.group(1),
|
|
||||||
"Method": m.group(2),
|
|
||||||
"Line": i + 1,
|
|
||||||
"File": bsl_path,
|
"File": bsl_path,
|
||||||
})
|
} for ic in get_interceptors(lines)]
|
||||||
|
|
||||||
return interceptors
|
|
||||||
|
|
||||||
|
|
||||||
# --- Helper: extract #Вставка blocks from .bsl ---
|
# --- Helper: extract insert-marker blocks from .bsl ---
|
||||||
|
|
||||||
def get_insertion_blocks(bsl_path):
|
def get_insertion_blocks(bsl_path):
|
||||||
if not os.path.isfile(bsl_path):
|
if not os.path.isfile(bsl_path):
|
||||||
@@ -207,14 +270,12 @@ def get_insertion_blocks(bsl_path):
|
|||||||
start_line = 0
|
start_line = 0
|
||||||
|
|
||||||
for i, line in enumerate(lines):
|
for i, line in enumerate(lines):
|
||||||
stripped = line.strip()
|
kind = bsl_marker_kind(line)
|
||||||
if stripped == "\u0023\u0412\u0441\u0442\u0430\u0432\u043a\u0430":
|
if kind == "Insert":
|
||||||
# #Вставка
|
|
||||||
in_block = True
|
in_block = True
|
||||||
block_lines = []
|
block_lines = []
|
||||||
start_line = i + 1
|
start_line = i + 1
|
||||||
elif stripped == "\u0023\u041a\u043e\u043d\u0435\u0446\u0412\u0441\u0442\u0430\u0432\u043a\u0438" and in_block:
|
elif kind == "EndInsert" and in_block:
|
||||||
# #КонецВставки
|
|
||||||
in_block = False
|
in_block = False
|
||||||
blocks.append({
|
blocks.append({
|
||||||
"StartLine": start_line,
|
"StartLine": start_line,
|
||||||
@@ -319,10 +380,10 @@ def mode_a(objects, extension_path):
|
|||||||
bsl_files = get_bsl_files(obj["Type"], obj["Name"], extension_path)
|
bsl_files = get_bsl_files(obj["Type"], obj["Name"], extension_path)
|
||||||
for bsl in bsl_files:
|
for bsl in bsl_files:
|
||||||
rel_path = bsl.replace(extension_path, "").lstrip("\\/")
|
rel_path = bsl.replace(extension_path, "").lstrip("\\/")
|
||||||
interceptor_list = get_interceptors(bsl)
|
interceptor_list = get_file_interceptors(bsl)
|
||||||
if len(interceptor_list) > 0:
|
if len(interceptor_list) > 0:
|
||||||
for ic in interceptor_list:
|
for ic in interceptor_list:
|
||||||
print(f' &{ic["Type"]}("{ic["Method"]}") \u2014 line {ic["Line"]} in {rel_path}')
|
print(f' &{ic["Raw"]}("{ic["Method"]}") \u2014 line {ic["Line"]} in {rel_path}')
|
||||||
else:
|
else:
|
||||||
print(f" {rel_path} (no interceptors)")
|
print(f" {rel_path} (no interceptors)")
|
||||||
|
|
||||||
@@ -434,7 +495,7 @@ def mode_b(objects, extension_path, config_path):
|
|||||||
# Find .bsl files with &ИзменениеИКонтроль
|
# Find .bsl files with &ИзменениеИКонтроль
|
||||||
bsl_files = get_bsl_files(obj["Type"], obj["Name"], extension_path)
|
bsl_files = get_bsl_files(obj["Type"], obj["Name"], extension_path)
|
||||||
for bsl in bsl_files:
|
for bsl in bsl_files:
|
||||||
interceptor_list = get_interceptors(bsl)
|
interceptor_list = get_file_interceptors(bsl)
|
||||||
mac_interceptors = [ic for ic in interceptor_list if ic["Type"] == "\u0418\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435\u0418\u041a\u043e\u043d\u0442\u0440\u043e\u043b\u044c"]
|
mac_interceptors = [ic for ic in interceptor_list if ic["Type"] == "\u0418\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435\u0418\u041a\u043e\u043d\u0442\u0440\u043e\u043b\u044c"]
|
||||||
|
|
||||||
if len(mac_interceptors) == 0:
|
if len(mac_interceptors) == 0:
|
||||||
@@ -44,7 +44,7 @@ allowed-tools:
|
|||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-init.ps1" -Name "МоёРасширение" -OutputDir "src\cfe\МоёРасширение" -ConfigPath "src\cf"
|
python ".roo/skills/cfe-init/scripts/cfe-init.py" -Name "МоёРасширение" -OutputDir "src\cfe\МоёРасширение" -ConfigPath "src\cf"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Примеры
|
## Примеры
|
||||||
@@ -112,7 +112,7 @@ allowed-tools:
|
|||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-patch-method.ps1" -ExtensionPath src\cfe\extname -ConfigPath src\cf -ModulePath "Catalog.Контрагенты.ObjectModule" -MethodName "ПриЗаписи" -InterceptorType Before
|
python ".roo/skills/cfe-patch-method/scripts/cfe-patch-method.py" -ExtensionPath src\cfe\extname -ConfigPath src\cf -ModulePath "Catalog.Контрагенты.ObjectModule" -MethodName "ПриЗаписи" -InterceptorType Before
|
||||||
```
|
```
|
||||||
|
|
||||||
## Примеры
|
## Примеры
|
||||||
+202
-87
@@ -1,4 +1,4 @@
|
|||||||
# cfe-patch-method v2.11 — Source-aware method interceptor for 1C extension (CFE)
|
# cfe-patch-method v2.12 — Source-aware method interceptor for 1C extension (CFE)
|
||||||
# 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(
|
||||||
@@ -52,6 +52,105 @@ $script:decoratorMap = @{
|
|||||||
"ModificationAndControl"="ИзменениеИКонтроль"
|
"ModificationAndControl"="ИзменениеИКонтроль"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# InterceptorType -> keyword table key (emission goes through the table, the map above stays
|
||||||
|
# the normalized value the logic compares against)
|
||||||
|
$script:decoratorKey = @{
|
||||||
|
"Before"="Before"; "After"="After"; "Instead"="Around"
|
||||||
|
"ModificationAndControl"="Control"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Built-in language keywords in both spellings. The platform accepts either one in any module
|
||||||
|
# (pairs taken from the platform string tables), so a module written in English is ordinary
|
||||||
|
# source, not a broken one: we must read both and emit the spelling we read.
|
||||||
|
function Get-BslKeywords {
|
||||||
|
return @{
|
||||||
|
ru = @{
|
||||||
|
Async="Асинх"; Proc="Процедура"; EndProc="КонецПроцедуры"
|
||||||
|
Func="Функция"; EndFunc="КонецФункции"; Val="Знач"
|
||||||
|
Region="Область"; EndRegion="КонецОбласти"
|
||||||
|
If="Если"; Then="Тогда"; ElsIf="ИначеЕсли"; Else="Иначе"; EndIf="КонецЕсли"
|
||||||
|
# Platform spells the negation "Не"; we emit "НЕ" as this skill always has —
|
||||||
|
# the language is case-insensitive, and the lower case would churn every snapshot.
|
||||||
|
And="И"; Or="Или"; Not="НЕ"
|
||||||
|
Insert="Вставка"; EndInsert="КонецВставки"; Delete="Удаление"; EndDelete="КонецУдаления"
|
||||||
|
Before="Перед"; After="После"; Around="Вместо"; Control="ИзменениеИКонтроль"
|
||||||
|
Proceed="ПродолжитьВызов"; Return="Возврат"
|
||||||
|
# Not a keyword: name of the local the generated Instead-stub declares. Lives here so
|
||||||
|
# that the language of emitted text is decided in exactly one place.
|
||||||
|
ResultVar="Результат"
|
||||||
|
Directives=@("НаКлиенте", "НаСервере", "НаСервереБезКонтекста", "НаКлиентеНаСервереБезКонтекста", "НаКлиентеНаСервере")
|
||||||
|
}
|
||||||
|
en = @{
|
||||||
|
Async="Async"; Proc="Procedure"; EndProc="EndProcedure"
|
||||||
|
Func="Function"; EndFunc="EndFunction"; Val="Val"
|
||||||
|
Region="Region"; EndRegion="EndRegion"
|
||||||
|
If="If"; Then="Then"; ElsIf="ElsIf"; Else="Else"; EndIf="EndIf"
|
||||||
|
And="And"; Or="Or"; Not="Not"
|
||||||
|
Insert="Insert"; EndInsert="EndInsert"; Delete="Delete"; EndDelete="EndDelete"
|
||||||
|
Before="Before"; After="After"; Around="Around"; Control="ChangeAndValidate"
|
||||||
|
Proceed="ProceedWithCall"; Return="Return"
|
||||||
|
ResultVar="Result"
|
||||||
|
Directives=@("AtClient", "AtServer", "AtServerNoContext", "AtClientAtServerNoContext", "AtClientAtServer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$script:bslKw = Get-BslKeywords
|
||||||
|
|
||||||
|
# Regex alternation for a keyword in both spellings
|
||||||
|
function Get-KwAlt {
|
||||||
|
param([string]$key)
|
||||||
|
return '(?:' + $script:bslKw.ru[$key] + '|' + $script:bslKw.en[$key] + ')'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Spelling language of a recognized keyword. English is the discriminator: a Russian keyword
|
||||||
|
# never equals an English one, so anything else is 'ru'.
|
||||||
|
function Get-BslLang {
|
||||||
|
param([string]$word)
|
||||||
|
$w = $word.Trim().ToLower()
|
||||||
|
foreach ($k in @("Async", "Proc", "EndProc", "Func", "EndFunc", "Region", "If", "Insert", "Delete", "Before", "After", "Around", "Control")) {
|
||||||
|
if ($script:bslKw.en[$k].ToLower() -eq $w) { return "en" }
|
||||||
|
}
|
||||||
|
return "ru"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Edit marker kind at the start of a line: Insert/Delete/EndInsert/EndDelete, $null if none.
|
||||||
|
# The marker owns the start of the line only — a trailing comment does not bother the platform
|
||||||
|
# and must not bother us either.
|
||||||
|
function Get-BslMarkerKind {
|
||||||
|
param([string]$line)
|
||||||
|
$kw = $script:bslKw
|
||||||
|
foreach ($k in @("EndInsert", "EndDelete", "Insert", "Delete")) {
|
||||||
|
if ($line -match ('^\s*#(?:' + $kw.ru[$k] + '|' + $kw.en[$k] + ')\b')) { return $k }
|
||||||
|
}
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Parse interceptor annotations present in a module. Type is normalized to the Russian spelling
|
||||||
|
# so everything downstream stays single-language; Raw keeps what the file actually says.
|
||||||
|
function Get-Interceptors {
|
||||||
|
param($lines)
|
||||||
|
$kw = $script:bslKw
|
||||||
|
$keys = @("Before", "After", "Control", "Around")
|
||||||
|
$alt = @()
|
||||||
|
$norm = @{}
|
||||||
|
foreach ($k in $keys) {
|
||||||
|
$alt += $kw.ru[$k]; $alt += $kw.en[$k]
|
||||||
|
$norm[$kw.ru[$k].ToLower()] = $kw.ru[$k]
|
||||||
|
$norm[$kw.en[$k].ToLower()] = $kw.ru[$k]
|
||||||
|
}
|
||||||
|
$re = '^&(' + ($alt -join '|') + ')\("([^"]+)"\)'
|
||||||
|
$result = @()
|
||||||
|
for ($i = 0; $i -lt $lines.Count; $i++) {
|
||||||
|
$t = $lines[$i].Trim()
|
||||||
|
if ($t -match $re) {
|
||||||
|
$raw = $Matches[1]
|
||||||
|
$result += @{ Type = $norm[$raw.ToLower()]; Raw = $raw; Method = $Matches[2]; Line = $i }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $result
|
||||||
|
}
|
||||||
|
|
||||||
# Compute relative .bsl path segments from ModulePath (used for both ext and src)
|
# Compute relative .bsl path segments from ModulePath (used for both ext and src)
|
||||||
function Get-ModuleRelPath {
|
function Get-ModuleRelPath {
|
||||||
param([string]$modulePath)
|
param([string]$modulePath)
|
||||||
@@ -125,7 +224,8 @@ function Split-TopLevel {
|
|||||||
# Is a trimmed line a context-directive annotation?
|
# Is a trimmed line a context-directive annotation?
|
||||||
function Test-ContextDirective {
|
function Test-ContextDirective {
|
||||||
param([string]$trimmed)
|
param([string]$trimmed)
|
||||||
return $trimmed -match '^&(НаКлиенте|НаСервере|НаСервереБезКонтекста|НаКлиентеНаСервереБезКонтекста|НаКлиентеНаСервере)\s*$'
|
$alt = ($script:bslKw.ru.Directives + $script:bslKw.en.Directives) -join '|'
|
||||||
|
return $trimmed -match ('^&(' + $alt + ')\s*$')
|
||||||
}
|
}
|
||||||
|
|
||||||
# Read a method signature starting at declaration line; returns @{ ParamsText; EndLineIdx }
|
# Read a method signature starting at declaration line; returns @{ ParamsText; EndLineIdx }
|
||||||
@@ -163,46 +263,54 @@ function Read-Signature {
|
|||||||
|
|
||||||
# Compute effective condition of the current branch of an #Если frame
|
# Compute effective condition of the current branch of an #Если frame
|
||||||
function Get-EffectiveCondition {
|
function Get-EffectiveCondition {
|
||||||
param($frame)
|
param($frame, [string]$lang)
|
||||||
|
$kwNot = $script:bslKw[$lang].Not
|
||||||
|
$kwAnd = $script:bslKw[$lang].And
|
||||||
$conds = $frame.Conds
|
$conds = $frame.Conds
|
||||||
$n = $conds.Count
|
$n = $conds.Count
|
||||||
if ($frame.InElse) {
|
if ($frame.InElse) {
|
||||||
$parts = @()
|
$parts = @()
|
||||||
foreach ($c in $conds) { $parts += "НЕ ($c)" }
|
foreach ($c in $conds) { $parts += "$kwNot ($c)" }
|
||||||
return ($parts -join " И ")
|
return ($parts -join " $kwAnd ")
|
||||||
}
|
}
|
||||||
if ($n -eq 1) { return $conds[0] }
|
if ($n -eq 1) { return $conds[0] }
|
||||||
$parts = @()
|
$parts = @()
|
||||||
for ($j = 0; $j -lt ($n - 1); $j++) { $parts += "НЕ ($($conds[$j]))" }
|
for ($j = 0; $j -lt ($n - 1); $j++) { $parts += "$kwNot ($($conds[$j]))" }
|
||||||
$parts += $conds[$n - 1]
|
$parts += $conds[$n - 1]
|
||||||
return ($parts -join " И ")
|
return ($parts -join " $kwAnd ")
|
||||||
}
|
}
|
||||||
|
|
||||||
# Extract the enclosing wrapper chain (regions + preprocessor) at a target line.
|
# Extract the enclosing wrapper chain (regions + preprocessor) at a target line.
|
||||||
# Returns array outer->inner of @{ Kind='region'|'if'; Name=..; Cond=.. }
|
# Returns array outer->inner of @{ Kind='region'|'if'; Name=..; Cond=.. }
|
||||||
function Get-EnclosingChain {
|
function Get-EnclosingChain {
|
||||||
param($lines, [int]$targetIdx)
|
param($lines, [int]$targetIdx, [string]$lang)
|
||||||
|
$reRegion = '^#' + (Get-KwAlt 'Region') + '\s+(\S+)'
|
||||||
|
$reEndRegion = '^#' + (Get-KwAlt 'EndRegion')
|
||||||
|
$reIf = '^#' + (Get-KwAlt 'If') + '\s+(.+?)\s+' + (Get-KwAlt 'Then')
|
||||||
|
$reElsIf = '^#' + (Get-KwAlt 'ElsIf') + '\s+(.+?)\s+' + (Get-KwAlt 'Then')
|
||||||
|
$reElse = '^#' + (Get-KwAlt 'Else') + '(\s|$)'
|
||||||
|
$reEndIf = '^#' + (Get-KwAlt 'EndIf')
|
||||||
$stack = New-Object System.Collections.ArrayList
|
$stack = New-Object System.Collections.ArrayList
|
||||||
for ($i = 0; $i -lt $targetIdx; $i++) {
|
for ($i = 0; $i -lt $targetIdx; $i++) {
|
||||||
$t = $lines[$i].Trim()
|
$t = $lines[$i].Trim()
|
||||||
if ($t -match '^#Область\s+(\S+)') {
|
if ($t -match $reRegion) {
|
||||||
[void]$stack.Add(@{ Kind = 'region'; Name = $Matches[1] })
|
[void]$stack.Add(@{ Kind = 'region'; Name = $Matches[1] })
|
||||||
} elseif ($t -match '^#КонецОбласти') {
|
} elseif ($t -match $reEndRegion) {
|
||||||
for ($k = $stack.Count - 1; $k -ge 0; $k--) { if ($stack[$k].Kind -eq 'region') { $stack.RemoveAt($k); break } }
|
for ($k = $stack.Count - 1; $k -ge 0; $k--) { if ($stack[$k].Kind -eq 'region') { $stack.RemoveAt($k); break } }
|
||||||
} elseif ($t -match '^#Если\s+(.+?)\s+Тогда') {
|
} elseif ($t -match $reIf) {
|
||||||
[void]$stack.Add(@{ Kind = 'if'; Conds = @($Matches[1].Trim()); InElse = $false })
|
[void]$stack.Add(@{ Kind = 'if'; Conds = @($Matches[1].Trim()); InElse = $false })
|
||||||
} elseif ($t -match '^#ИначеЕсли\s+(.+?)\s+Тогда') {
|
} elseif ($t -match $reElsIf) {
|
||||||
for ($k = $stack.Count - 1; $k -ge 0; $k--) { if ($stack[$k].Kind -eq 'if') { $stack[$k].Conds += $Matches[1].Trim(); $stack[$k].InElse = $false; break } }
|
for ($k = $stack.Count - 1; $k -ge 0; $k--) { if ($stack[$k].Kind -eq 'if') { $stack[$k].Conds += $Matches[1].Trim(); $stack[$k].InElse = $false; break } }
|
||||||
} elseif ($t -match '^#Иначе(\s|$)') {
|
} elseif ($t -match $reElse) {
|
||||||
for ($k = $stack.Count - 1; $k -ge 0; $k--) { if ($stack[$k].Kind -eq 'if') { $stack[$k].InElse = $true; break } }
|
for ($k = $stack.Count - 1; $k -ge 0; $k--) { if ($stack[$k].Kind -eq 'if') { $stack[$k].InElse = $true; break } }
|
||||||
} elseif ($t -match '^#КонецЕсли') {
|
} elseif ($t -match $reEndIf) {
|
||||||
for ($k = $stack.Count - 1; $k -ge 0; $k--) { if ($stack[$k].Kind -eq 'if') { $stack.RemoveAt($k); break } }
|
for ($k = $stack.Count - 1; $k -ge 0; $k--) { if ($stack[$k].Kind -eq 'if') { $stack.RemoveAt($k); break } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$chain = @()
|
$chain = @()
|
||||||
foreach ($f in $stack) {
|
foreach ($f in $stack) {
|
||||||
if ($f.Kind -eq 'region') { $chain += @{ Kind = 'region'; Name = $f.Name } }
|
if ($f.Kind -eq 'region') { $chain += @{ Kind = 'region'; Name = $f.Name } }
|
||||||
else { $chain += @{ Kind = 'if'; Cond = (Get-EffectiveCondition $f) } }
|
else { $chain += @{ Kind = 'if'; Cond = (Get-EffectiveCondition $f $lang) } }
|
||||||
}
|
}
|
||||||
return ,$chain
|
return ,$chain
|
||||||
}
|
}
|
||||||
@@ -210,13 +318,14 @@ function Get-EnclosingChain {
|
|||||||
# Extract a method from source .bsl lines. Returns $null if not found.
|
# Extract a method from source .bsl lines. Returns $null if not found.
|
||||||
function Extract-Method {
|
function Extract-Method {
|
||||||
param($lines, [string]$methodName)
|
param($lines, [string]$methodName)
|
||||||
$declRe = '^\s*(Асинх\s+)?(Процедура|Функция)\s+(' + [regex]::Escape($methodName) + ')\s*\('
|
$declRe = '^\s*(' + (Get-KwAlt 'Async') + '\s+)?(' + (Get-KwAlt 'Proc') + '|' + (Get-KwAlt 'Func') + ')\s+(' + [regex]::Escape($methodName) + ')\s*\('
|
||||||
for ($i = 0; $i -lt $lines.Count; $i++) {
|
for ($i = 0; $i -lt $lines.Count; $i++) {
|
||||||
if ($lines[$i] -imatch $declRe) {
|
if ($lines[$i] -imatch $declRe) {
|
||||||
$isAsync = [bool]$Matches[1]
|
$isAsync = [bool]$Matches[1]
|
||||||
$keyword = $Matches[2]
|
$keyword = $Matches[2]
|
||||||
$canonical = $Matches[3]
|
$canonical = $Matches[3]
|
||||||
$isFunction = ($keyword -ieq "Функция")
|
$lang = Get-BslLang $keyword
|
||||||
|
$isFunction = ($keyword -ieq $script:bslKw.ru.Func -or $keyword -ieq $script:bslKw.en.Func)
|
||||||
|
|
||||||
$sig = Read-Signature $lines $i
|
$sig = Read-Signature $lines $i
|
||||||
if (-not $sig) { throw "Не удалось разобрать сигнатуру метода '$methodName'" }
|
if (-not $sig) { throw "Не удалось разобрать сигнатуру метода '$methodName'" }
|
||||||
@@ -227,13 +336,13 @@ function Extract-Method {
|
|||||||
$paramNames = @()
|
$paramNames = @()
|
||||||
if ($paramsText.Trim().Length -gt 0) {
|
if ($paramsText.Trim().Length -gt 0) {
|
||||||
foreach ($seg in (Split-TopLevel $paramsText)) {
|
foreach ($seg in (Split-TopLevel $paramsText)) {
|
||||||
$s = $seg.Trim() -replace '^Знач\s+', ''
|
$s = $seg.Trim() -replace ('^' + (Get-KwAlt 'Val') + '\s+'), ''
|
||||||
if ($s -match '^([\w]+)') { $paramNames += $Matches[1] }
|
if ($s -match '^([\w]+)') { $paramNames += $Matches[1] }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Body: from sigEnd+1 to matching Конец*
|
# Body: from sigEnd+1 to matching Конец*
|
||||||
$endRe = if ($isFunction) { '^\s*КонецФункции\b' } else { '^\s*КонецПроцедуры\b' }
|
$endRe = if ($isFunction) { '^\s*' + (Get-KwAlt 'EndFunc') + '\b' } else { '^\s*' + (Get-KwAlt 'EndProc') + '\b' }
|
||||||
$bodyStart = $sigEnd + 1
|
$bodyStart = $sigEnd + 1
|
||||||
$bodyEnd = -1
|
$bodyEnd = -1
|
||||||
for ($j = $bodyStart; $j -lt $lines.Count; $j++) {
|
for ($j = $bodyStart; $j -lt $lines.Count; $j++) {
|
||||||
@@ -251,10 +360,11 @@ function Extract-Method {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Enclosing chain (regions + preprocessor)
|
# Enclosing chain (regions + preprocessor)
|
||||||
$chain = Get-EnclosingChain $lines $i
|
$chain = Get-EnclosingChain $lines $i $lang
|
||||||
|
|
||||||
return @{
|
return @{
|
||||||
Canonical = $canonical
|
Canonical = $canonical
|
||||||
|
Lang = $lang
|
||||||
IsFunction = $isFunction
|
IsFunction = $isFunction
|
||||||
IsAsync = $isAsync
|
IsAsync = $isAsync
|
||||||
ParamsText = $paramsText
|
ParamsText = $paramsText
|
||||||
@@ -271,25 +381,13 @@ function Extract-Method {
|
|||||||
return $null
|
return $null
|
||||||
}
|
}
|
||||||
|
|
||||||
# Parse interceptors present in a module (type + method)
|
|
||||||
function Get-Interceptors {
|
|
||||||
param($lines)
|
|
||||||
$result = @()
|
|
||||||
for ($i = 0; $i -lt $lines.Count; $i++) {
|
|
||||||
$t = $lines[$i].Trim()
|
|
||||||
if ($t -match '^&(Перед|После|ИзменениеИКонтроль|Вместо)\("([^"]+)"\)') {
|
|
||||||
$result += @{ Type = $Matches[1]; Method = $Matches[2]; Line = $i }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $result
|
|
||||||
}
|
|
||||||
|
|
||||||
# Parse declared procedure/function names in a module
|
# Parse declared procedure/function names in a module
|
||||||
function Get-ProcNames {
|
function Get-ProcNames {
|
||||||
param($lines)
|
param($lines)
|
||||||
|
$re = '^\s*(?:' + (Get-KwAlt 'Async') + '\s+)?(?:' + (Get-KwAlt 'Proc') + '|' + (Get-KwAlt 'Func') + ')\s+([\w]+)\s*\('
|
||||||
$names = @()
|
$names = @()
|
||||||
foreach ($line in $lines) {
|
foreach ($line in $lines) {
|
||||||
if ($line -imatch '^\s*(?:Асинх\s+)?(?:Процедура|Функция)\s+([\w]+)\s*\(') { $names += $Matches[1] }
|
if ($line -imatch $re) { $names += $Matches[1] }
|
||||||
}
|
}
|
||||||
return $names
|
return $names
|
||||||
}
|
}
|
||||||
@@ -300,14 +398,15 @@ function Get-ProcNames {
|
|||||||
function Build-InterceptorCore {
|
function Build-InterceptorCore {
|
||||||
param($method, [string]$interceptorType, [string]$interceptorName)
|
param($method, [string]$interceptorType, [string]$interceptorName)
|
||||||
|
|
||||||
$decoratorRu = $script:decoratorMap[$interceptorType]
|
$kw = $script:bslKw[$method.Lang]
|
||||||
$asyncPrefix = if ($method.IsAsync) { "Асинх " } else { "" }
|
$decorator = $kw[$script:decoratorKey[$interceptorType]]
|
||||||
$keyword = if ($method.IsFunction) { "Функция" } else { "Процедура" }
|
$asyncPrefix = if ($method.IsAsync) { "$($kw.Async) " } else { "" }
|
||||||
$endKeyword = if ($method.IsFunction) { "КонецФункции" } else { "КонецПроцедуры" }
|
$keyword = if ($method.IsFunction) { $kw.Func } else { $kw.Proc }
|
||||||
|
$endKeyword = if ($method.IsFunction) { $kw.EndFunc } else { $kw.EndProc }
|
||||||
|
|
||||||
$lines = @()
|
$lines = @()
|
||||||
if ($method.Context) { $lines += $method.Context }
|
if ($method.Context) { $lines += $method.Context }
|
||||||
$lines += "&$decoratorRu(`"$($method.Canonical)`")"
|
$lines += "&$decorator(`"$($method.Canonical)`")"
|
||||||
$lines += "$asyncPrefix$keyword $interceptorName($($method.ParamsText))"
|
$lines += "$asyncPrefix$keyword $interceptorName($($method.ParamsText))"
|
||||||
|
|
||||||
switch ($interceptorType) {
|
switch ($interceptorType) {
|
||||||
@@ -320,11 +419,11 @@ function Build-InterceptorCore {
|
|||||||
"Instead" {
|
"Instead" {
|
||||||
$namesJoined = ($method.ParamNames -join ", ")
|
$namesJoined = ($method.ParamNames -join ", ")
|
||||||
if ($method.IsFunction) {
|
if ($method.IsFunction) {
|
||||||
$lines += "`tРезультат = ПродолжитьВызов($namesJoined);"
|
$lines += "`t$($kw.ResultVar) = $($kw.Proceed)($namesJoined);"
|
||||||
$lines += "`t// TODO: доработать поведение"
|
$lines += "`t// TODO: доработать поведение"
|
||||||
$lines += "`tВозврат Результат;"
|
$lines += "`t$($kw.Return) $($kw.ResultVar);"
|
||||||
} else {
|
} else {
|
||||||
$lines += "`tПродолжитьВызов($namesJoined);"
|
$lines += "`t$($kw.Proceed)($namesJoined);"
|
||||||
$lines += "`t// TODO: доработать поведение"
|
$lines += "`t// TODO: доработать поведение"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -339,16 +438,17 @@ function Build-InterceptorCore {
|
|||||||
# Wrap core with region/preprocessor lines, adding blank lines ("air") around
|
# Wrap core with region/preprocessor lines, adding blank lines ("air") around
|
||||||
# each structural boundary. Empty chain -> core as-is.
|
# each structural boundary. Empty chain -> core as-is.
|
||||||
function Build-WrappedBlock {
|
function Build-WrappedBlock {
|
||||||
param($chainArr, $core)
|
param($chainArr, $core, [string]$lang)
|
||||||
|
$kw = $script:bslKw[$lang]
|
||||||
$b = @()
|
$b = @()
|
||||||
foreach ($w in $chainArr) {
|
foreach ($w in $chainArr) {
|
||||||
if ($w.Kind -eq 'region') { $b += "#Область $($w.Name)" } else { $b += "#Если $($w.Cond) Тогда" }
|
if ($w.Kind -eq 'region') { $b += "#$($kw.Region) $($w.Name)" } else { $b += "#$($kw.If) $($w.Cond) $($kw.Then)" }
|
||||||
$b += ""
|
$b += ""
|
||||||
}
|
}
|
||||||
$b += $core
|
$b += $core
|
||||||
for ($c = $chainArr.Count - 1; $c -ge 0; $c--) {
|
for ($c = $chainArr.Count - 1; $c -ge 0; $c--) {
|
||||||
$b += ""
|
$b += ""
|
||||||
if ($chainArr[$c].Kind -eq 'region') { $b += "#КонецОбласти" } else { $b += "#КонецЕсли" }
|
if ($chainArr[$c].Kind -eq 'region') { $b += "#$($kw.EndRegion)" } else { $b += "#$($kw.EndIf)" }
|
||||||
}
|
}
|
||||||
return $b
|
return $b
|
||||||
}
|
}
|
||||||
@@ -385,20 +485,27 @@ function Parse-MarkedBody {
|
|||||||
$ops = @() # edit operations
|
$ops = @() # edit operations
|
||||||
$i = 0
|
$i = 0
|
||||||
while ($i -lt $bodyLines.Count) {
|
while ($i -lt $bodyLines.Count) {
|
||||||
$t = $bodyLines[$i].Trim()
|
$kind = Get-BslMarkerKind $bodyLines[$i]
|
||||||
if ($t -eq '#Вставка') {
|
if ($kind -eq 'Insert') {
|
||||||
|
# Marker lines are kept verbatim: they carry the author's spelling, indent and — since
|
||||||
|
# a tail comment is legal — the note explaining the edit. Re-emitting a bare keyword
|
||||||
|
# would silently drop that note on -Actualize.
|
||||||
|
$open = $bodyLines[$i]
|
||||||
$ins = @()
|
$ins = @()
|
||||||
$i++
|
$i++
|
||||||
while ($i -lt $bodyLines.Count -and $bodyLines[$i].Trim() -ne '#КонецВставки') { $ins += $bodyLines[$i]; $i++ }
|
while ($i -lt $bodyLines.Count -and (Get-BslMarkerKind $bodyLines[$i]) -ne 'EndInsert') { $ins += $bodyLines[$i]; $i++ }
|
||||||
|
$close = if ($i -lt $bodyLines.Count) { $bodyLines[$i] } else { $null }
|
||||||
$i++ # skip #КонецВставки
|
$i++ # skip #КонецВставки
|
||||||
$ops += @{ Kind = 'insert'; After = ($v1.Count - 1); Lines = $ins }
|
$ops += @{ Kind = 'insert'; After = ($v1.Count - 1); Lines = $ins; Open = $open; Close = $close }
|
||||||
} elseif ($t -eq '#Удаление') {
|
} elseif ($kind -eq 'Delete') {
|
||||||
|
$open = $bodyLines[$i]
|
||||||
$startIdx = $v1.Count
|
$startIdx = $v1.Count
|
||||||
$i++
|
$i++
|
||||||
$del = @()
|
$del = @()
|
||||||
while ($i -lt $bodyLines.Count -and $bodyLines[$i].Trim() -ne '#КонецУдаления') { $del += $bodyLines[$i]; $v1 += $bodyLines[$i]; $i++ }
|
while ($i -lt $bodyLines.Count -and (Get-BslMarkerKind $bodyLines[$i]) -ne 'EndDelete') { $del += $bodyLines[$i]; $v1 += $bodyLines[$i]; $i++ }
|
||||||
|
$close = if ($i -lt $bodyLines.Count) { $bodyLines[$i] } else { $null }
|
||||||
$i++ # skip #КонецУдаления
|
$i++ # skip #КонецУдаления
|
||||||
$ops += @{ Kind = 'delete'; Start = $startIdx; End = ($v1.Count - 1); Lines = $del }
|
$ops += @{ Kind = 'delete'; Start = $startIdx; End = ($v1.Count - 1); Lines = $del; Open = $open; Close = $close }
|
||||||
} else {
|
} else {
|
||||||
$v1 += $bodyLines[$i]
|
$v1 += $bodyLines[$i]
|
||||||
$i++
|
$i++
|
||||||
@@ -594,7 +701,8 @@ function Get-ResyncConflictReason {
|
|||||||
|
|
||||||
# Write per-method conflict folder: conflict.md + base/local/remote
|
# Write per-method conflict folder: conflict.md + base/local/remote
|
||||||
function Write-ConflictFolder {
|
function Write-ConflictFolder {
|
||||||
param($folder, $methodId, $extBsl, $existingName, $method, $v1, $markedBody, $v2, $v1norm, $v2norm, $disputed, $enc)
|
param($folder, $methodId, $extBsl, $existingName, $method, $v1, $markedBody, $v2, $v1norm, $v2norm, $disputed, [string]$lang, $enc)
|
||||||
|
$kw = $script:bslKw[$lang]
|
||||||
if (-not (Test-Path $folder)) { New-Item -ItemType Directory -Path $folder -Force | Out-Null }
|
if (-not (Test-Path $folder)) { New-Item -ItemType Directory -Path $folder -Force | Out-Null }
|
||||||
[IO.File]::WriteAllText((Join-Path $folder 'base.bsl'), (($v1 -join "`r`n") + "`r`n"), $enc)
|
[IO.File]::WriteAllText((Join-Path $folder 'base.bsl'), (($v1 -join "`r`n") + "`r`n"), $enc)
|
||||||
[IO.File]::WriteAllText((Join-Path $folder 'local.bsl'), (($markedBody -join "`r`n") + "`r`n"), $enc)
|
[IO.File]::WriteAllText((Join-Path $folder 'local.bsl'), (($markedBody -join "`r`n") + "`r`n"), $enc)
|
||||||
@@ -602,7 +710,7 @@ function Write-ConflictFolder {
|
|||||||
$md = @()
|
$md = @()
|
||||||
$md += "# $methodId"
|
$md += "# $methodId"
|
||||||
$md += "Править: $extBsl"
|
$md += "Править: $extBsl"
|
||||||
$md += "Метод: $existingName (&ИзменениеИКонтроль(`"$($method.Canonical)`"))"
|
$md += "Метод: $existingName (&$($kw.Control)(`"$($method.Canonical)`"))"
|
||||||
$md += "Причина: $(Get-ResyncConflictReason $disputed)"
|
$md += "Причина: $(Get-ResyncConflictReason $disputed)"
|
||||||
$md += ""
|
$md += ""
|
||||||
$md += "## Не размещено — перенести вручную"
|
$md += "## Не размещено — перенести вручную"
|
||||||
@@ -614,10 +722,12 @@ function Write-ConflictFolder {
|
|||||||
$md += "### Конфликт №$cn — вставка"
|
$md += "### Конфликт №$cn — вставка"
|
||||||
$md += "Как блок стоял в вашей версии (local):"
|
$md += "Как блок стоял в вашей версии (local):"
|
||||||
if ($d.Before -and $d.Before.Count -gt 0) { foreach ($l in $d.Before) { $md += $l } }
|
if ($d.Before -and $d.Before.Count -gt 0) { foreach ($l in $d.Before) { $md += $l } }
|
||||||
$md += "#Вставка"; foreach ($l in $d.Lines) { $md += $l }; $md += "#КонецВставки"
|
$openLine = if ([string]::IsNullOrEmpty($d.Open)) { "#$($kw.Insert)" } else { $d.Open }
|
||||||
|
$closeLine = if ([string]::IsNullOrEmpty($d.Close)) { "#$($kw.EndInsert)" } else { $d.Close }
|
||||||
|
$md += $openLine; foreach ($l in $d.Lines) { $md += $l }; $md += $closeLine
|
||||||
if ($d.After -and $d.After.Count -gt 0) { foreach ($l in $d.After) { $md += $l } }
|
if ($d.After -and $d.After.Count -gt 0) { foreach ($l in $d.After) { $md += $l } }
|
||||||
$md += ""
|
$md += ""
|
||||||
$md += "Якорь (строки вокруг #Вставка) изменился/исчез в новом оригинале — блок не лёг автоматически (см. дифф base→remote ниже)."
|
$md += "Якорь (строки вокруг #$($kw.Insert)) изменился/исчез в новом оригинале — блок не лёг автоматически (см. дифф base→remote ниже)."
|
||||||
$md += "В модуле расширения блок припаркован в конце метода под меткой // [РЕСИНК-КОНФЛИКТ №$cn] — найди по ней."
|
$md += "В модуле расширения блок припаркован в конце метода под меткой // [РЕСИНК-КОНФЛИКТ №$cn] — найди по ней."
|
||||||
$md += "Куда переносить: если якорного кода в новом методе больше нет — он, вероятно, вынесен/отрефакторен (ищите в диффе новый вызов/процедуру). Размести адаптацию по смыслу: например пост-обработкой после нового вызова, либо в заимствованной процедуре, куда переехал код. При правке файла сохрани кодировку (UTF-8 с BOM)."
|
$md += "Куда переносить: если якорного кода в новом методе больше нет — он, вероятно, вынесен/отрефакторен (ищите в диффе новый вызов/процедуру). Размести адаптацию по смыслу: например пост-обработкой после нового вызова, либо в заимствованной процедуре, куда переехал код. При правке файла сохрани кодировку (UTF-8 с BOM)."
|
||||||
} else {
|
} else {
|
||||||
@@ -644,15 +754,20 @@ function Invoke-Resync {
|
|||||||
$methodId = "$logicalModule.$($method.Canonical)"
|
$methodId = "$logicalModule.$($method.Canonical)"
|
||||||
$decLine = $dup.Line
|
$decLine = $dup.Line
|
||||||
$sigLineIdx = $decLine + 1
|
$sigLineIdx = $decLine + 1
|
||||||
if ($sigLineIdx -ge $extLines.Count -or $extLines[$sigLineIdx] -notmatch '^\s*(?:Асинх\s+)?(?:Процедура|Функция)\s+([\w]+)\s*\(') {
|
$declRe = '^\s*(?:' + (Get-KwAlt 'Async') + '\s+)?(' + (Get-KwAlt 'Proc') + '|' + (Get-KwAlt 'Func') + ')\s+([\w]+)\s*\('
|
||||||
|
if ($sigLineIdx -ge $extLines.Count -or $extLines[$sigLineIdx] -notmatch $declRe) {
|
||||||
return @{ Id = $methodId; Status = 'ОШИБКА'; ExtBsl = $extBsl; Reason = 'не разобрать сигнатуру перехватчика' }
|
return @{ Id = $methodId; Status = 'ОШИБКА'; ExtBsl = $extBsl; Reason = 'не разобрать сигнатуру перехватчика' }
|
||||||
}
|
}
|
||||||
$existingName = $Matches[1]
|
# The block we are about to rewrite is the interceptor already in the extension, so it is
|
||||||
|
# its spelling — not the source module's — that the rewritten block must keep.
|
||||||
|
$lang = Get-BslLang $Matches[1]
|
||||||
|
$kw = $script:bslKw[$lang]
|
||||||
|
$existingName = $Matches[2]
|
||||||
$sig = Read-Signature $extLines $sigLineIdx
|
$sig = Read-Signature $extLines $sigLineIdx
|
||||||
if (-not $sig) { return @{ Id = $methodId; Status = 'ОШИБКА'; ExtBsl = $extBsl; Reason = 'не разобрать сигнатуру' } }
|
if (-not $sig) { return @{ Id = $methodId; Status = 'ОШИБКА'; ExtBsl = $extBsl; Reason = 'не разобрать сигнатуру' } }
|
||||||
$sigEnd = $sig.EndLineIdx
|
$sigEnd = $sig.EndLineIdx
|
||||||
$isFunc = ($extLines[$sigLineIdx] -imatch '^\s*(?:Асинх\s+)?Функция\b')
|
$isFunc = ($extLines[$sigLineIdx] -imatch ('^\s*(?:' + (Get-KwAlt 'Async') + '\s+)?' + (Get-KwAlt 'Func') + '\b'))
|
||||||
$endRe = if ($isFunc) { '^\s*КонецФункции\b' } else { '^\s*КонецПроцедуры\b' }
|
$endRe = if ($isFunc) { '^\s*' + (Get-KwAlt 'EndFunc') + '\b' } else { '^\s*' + (Get-KwAlt 'EndProc') + '\b' }
|
||||||
$blockEnd = -1
|
$blockEnd = -1
|
||||||
for ($j = $sigEnd + 1; $j -lt $extLines.Count; $j++) { if ($extLines[$j] -imatch $endRe) { $blockEnd = $j; break } }
|
for ($j = $sigEnd + 1; $j -lt $extLines.Count; $j++) { if ($extLines[$j] -imatch $endRe) { $blockEnd = $j; break } }
|
||||||
if ($blockEnd -lt 0) { return @{ Id = $methodId; Status = 'ОШИБКА'; ExtBsl = $extBsl; Reason = 'не найден конец перехватчика' } }
|
if ($blockEnd -lt 0) { return @{ Id = $methodId; Status = 'ОШИБКА'; ExtBsl = $extBsl; Reason = 'не найден конец перехватчика' } }
|
||||||
@@ -703,14 +818,14 @@ function Invoke-Resync {
|
|||||||
elseif ($null -eq $k) {
|
elseif ($null -eq $k) {
|
||||||
$dbefore = @(); if ($op.After -ge 0) { $bz = [Math]::Max(0, $op.After - 2); for ($z = $bz; $z -le $op.After; $z++) { $dbefore += $v1[$z] } }
|
$dbefore = @(); if ($op.After -ge 0) { $bz = [Math]::Max(0, $op.After - 2); for ($z = $bz; $z -le $op.After; $z++) { $dbefore += $v1[$z] } }
|
||||||
$dafter = @(); $az = [Math]::Min($v1.Count - 1, $op.After + 3); for ($z = $op.After + 1; $z -le $az; $z++) { $dafter += $v1[$z] }
|
$dafter = @(); $az = [Math]::Min($v1.Count - 1, $op.After + 3); for ($z = $op.After + 1; $z -le $az; $z++) { $dafter += $v1[$z] }
|
||||||
$disputed += @{ Kind = 'insert'; Lines = $op.Lines; Before = $dbefore; After = $dafter }
|
$disputed += @{ Kind = 'insert'; Lines = $op.Lines; Open = $op.Open; Close = $op.Close; Before = $dbefore; After = $dafter }
|
||||||
}
|
}
|
||||||
elseif ($k -lt 0) { $insertTop += ,$op.Lines; $transferred += @{ Kind = 'insert' } }
|
elseif ($k -lt 0) { $insertTop += $op; $transferred += @{ Kind = 'insert' } }
|
||||||
else { if (-not $insertAfter.ContainsKey($k)) { $insertAfter[$k] = @() }; $insertAfter[$k] += ,$op.Lines; $transferred += @{ Kind = 'insert' } }
|
else { if (-not $insertAfter.ContainsKey($k)) { $insertAfter[$k] = @() }; $insertAfter[$k] += $op; $transferred += @{ Kind = 'insert' } }
|
||||||
} else {
|
} else {
|
||||||
$keys = @(); for ($m = $op.Start; $m -le $op.End; $m++) { $keys += $v1norm[$m] }
|
$keys = @(); for ($m = $op.Start; $m -le $op.End; $m++) { $keys += $v1norm[$m] }
|
||||||
$p = Find-UniqueRun $v2norm $keys
|
$p = Find-UniqueRun $v2norm $keys
|
||||||
if ($p -ge 0) { $delStart[$p] = $true; $delEnd[$p + $keys.Count - 1] = $true; $transferred += @{ Kind = 'delete' } }
|
if ($p -ge 0) { $delStart[$p] = $op.Open; $delEnd[$p + $keys.Count - 1] = $op.Close; $transferred += @{ Kind = 'delete' } }
|
||||||
else {
|
else {
|
||||||
# Nearest significant neighbours around the deleted block; adjacency in the significant
|
# Nearest significant neighbours around the deleted block; adjacency in the significant
|
||||||
# projection means the block is already cut (blanks/comments left behind don't matter).
|
# projection means the block is already cut (blanks/comments left behind don't matter).
|
||||||
@@ -730,14 +845,16 @@ function Invoke-Resync {
|
|||||||
return @{ Id = $methodId; Status = $st; ExtBsl = $extBsl; Transferred = $transferred.Count; Absorbed = $absorbed.Count; Disputed = $disputed.Count; Reason = $rsn; AbsorbedNotes = $absorbedNotes }
|
return @{ Id = $methodId; Status = $st; ExtBsl = $extBsl; Transferred = $transferred.Count; Absorbed = $absorbed.Count; Disputed = $disputed.Count; Reason = $rsn; AbsorbedNotes = $absorbedNotes }
|
||||||
}
|
}
|
||||||
|
|
||||||
# assemble new marked body
|
# assemble new marked body. Marker lines come back as they were read (Open/Close); the keyword
|
||||||
|
# form is only a fallback for a block whose closing marker the module never had.
|
||||||
|
$mk = { param($orig, [string]$key) if ([string]::IsNullOrEmpty($orig)) { "#$($kw[$key])" } else { $orig } }
|
||||||
$newBody = @()
|
$newBody = @()
|
||||||
foreach ($blk in $insertTop) { $newBody += "#Вставка"; foreach ($l in $blk) { $newBody += $l }; $newBody += "#КонецВставки" }
|
foreach ($blk in $insertTop) { $newBody += (& $mk $blk.Open 'Insert'); foreach ($l in $blk.Lines) { $newBody += $l }; $newBody += (& $mk $blk.Close 'EndInsert') }
|
||||||
for ($k = 0; $k -lt $v2.Count; $k++) {
|
for ($k = 0; $k -lt $v2.Count; $k++) {
|
||||||
if ($delStart.ContainsKey($k)) { $newBody += "#Удаление" }
|
if ($delStart.ContainsKey($k)) { $newBody += (& $mk $delStart[$k] 'Delete') }
|
||||||
$newBody += $v2[$k]
|
$newBody += $v2[$k]
|
||||||
if ($delEnd.ContainsKey($k)) { $newBody += "#КонецУдаления" }
|
if ($delEnd.ContainsKey($k)) { $newBody += (& $mk $delEnd[$k] 'EndDelete') }
|
||||||
if ($insertAfter.ContainsKey($k)) { foreach ($blk in $insertAfter[$k]) { $newBody += "#Вставка"; foreach ($l in $blk) { $newBody += $l }; $newBody += "#КонецВставки" } }
|
if ($insertAfter.ContainsKey($k)) { foreach ($blk in $insertAfter[$k]) { $newBody += (& $mk $blk.Open 'Insert'); foreach ($l in $blk.Lines) { $newBody += $l }; $newBody += (& $mk $blk.Close 'EndInsert') } }
|
||||||
}
|
}
|
||||||
if ($disputed.Count -gt 0) {
|
if ($disputed.Count -gt 0) {
|
||||||
$newBody += "`t// [РЕСИНК-КОНФЛИКТ] блоки ниже не легли автоматически — перенесите вручную (по № см. conflict.md / index.md в merge-воркспейсе, путь в выводе)."
|
$newBody += "`t// [РЕСИНК-КОНФЛИКТ] блоки ниже не легли автоматически — перенесите вручную (по № см. conflict.md / index.md в merge-воркспейсе, путь в выводе)."
|
||||||
@@ -746,7 +863,7 @@ function Invoke-Resync {
|
|||||||
$cn++
|
$cn++
|
||||||
if ($d.Kind -eq 'insert') {
|
if ($d.Kind -eq 'insert') {
|
||||||
$newBody += "`t// [РЕСИНК-КОНФЛИКТ №$cn] вставка — исходный якорь изменён в новом оригинале."
|
$newBody += "`t// [РЕСИНК-КОНФЛИКТ №$cn] вставка — исходный якорь изменён в новом оригинале."
|
||||||
$newBody += "#Вставка"; foreach ($l in $d.Lines) { $newBody += $l }; $newBody += "#КонецВставки"
|
$newBody += (& $mk $d.Open 'Insert'); foreach ($l in $d.Lines) { $newBody += $l }; $newBody += (& $mk $d.Close 'EndInsert')
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$newBody += "`t// [РЕСИНК-КОНФЛИКТ №$cn] удаление — строки не найдены в новом оригинале:"
|
$newBody += "`t// [РЕСИНК-КОНФЛИКТ №$cn] удаление — строки не найдены в новом оригинале:"
|
||||||
@@ -754,12 +871,12 @@ function Invoke-Resync {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$asyncPrefix = if ($method.IsAsync) { "Асинх " } else { "" }
|
$asyncPrefix = if ($method.IsAsync) { "$($kw.Async) " } else { "" }
|
||||||
$keyword = if ($method.IsFunction) { "Функция" } else { "Процедура" }
|
$keyword = if ($method.IsFunction) { $kw.Func } else { $kw.Proc }
|
||||||
$endKeyword = if ($method.IsFunction) { "КонецФункции" } else { "КонецПроцедуры" }
|
$endKeyword = if ($method.IsFunction) { $kw.EndFunc } else { $kw.EndProc }
|
||||||
$newBlock = @()
|
$newBlock = @()
|
||||||
if ($method.Context) { $newBlock += $method.Context }
|
if ($method.Context) { $newBlock += $method.Context }
|
||||||
$newBlock += "&ИзменениеИКонтроль(`"$($method.Canonical)`")"
|
$newBlock += "&$($kw.Control)(`"$($method.Canonical)`")"
|
||||||
$newBlock += "$asyncPrefix$keyword $existingName($($method.ParamsText))"
|
$newBlock += "$asyncPrefix$keyword $existingName($($method.ParamsText))"
|
||||||
$newBlock += $newBody
|
$newBlock += $newBody
|
||||||
$newBlock += $endKeyword
|
$newBlock += $endKeyword
|
||||||
@@ -775,7 +892,7 @@ function Invoke-Resync {
|
|||||||
$conflictDir = $null
|
$conflictDir = $null
|
||||||
if ($disputed.Count -gt 0) {
|
if ($disputed.Count -gt 0) {
|
||||||
$conflictDir = $conflictFolder
|
$conflictDir = $conflictFolder
|
||||||
Write-ConflictFolder $conflictFolder $methodId $extBsl $existingName $method $v1 $markedBody $v2 $v1norm $v2norm $disputed $enc
|
Write-ConflictFolder $conflictFolder $methodId $extBsl $existingName $method $v1 $markedBody $v2 $v1norm $v2norm $disputed $lang $enc
|
||||||
}
|
}
|
||||||
$status = if ($disputed.Count -gt 0) { 'ЧАСТИЧНО' } elseif ($transferred.Count -eq 0 -and $absorbed.Count -gt 0) { 'ПЕРЕНЕСЕНО В ОСНОВНУЮ' } else { 'АКТУАЛИЗИРОВАН' }
|
$status = if ($disputed.Count -gt 0) { 'ЧАСТИЧНО' } elseif ($transferred.Count -eq 0 -and $absorbed.Count -gt 0) { 'ПЕРЕНЕСЕНО В ОСНОВНУЮ' } else { 'АКТУАЛИЗИРОВАН' }
|
||||||
$rsn = if ($disputed.Count -gt 0) { Get-ResyncConflictReason $disputed } elseif ($status -eq 'ПЕРЕНЕСЕНО В ОСНОВНУЮ') { 'все правки уже в основной конфигурации — перехватчик можно удалить' } else { '' }
|
$rsn = if ($disputed.Count -gt 0) { Get-ResyncConflictReason $disputed } elseif ($status -eq 'ПЕРЕНЕСЕНО В ОСНОВНУЮ') { 'все правки уже в основной конфигурации — перехватчик можно удалить' } else { '' }
|
||||||
@@ -1062,6 +1179,8 @@ if ($method.IsFunction -and ($InterceptorType -eq "Before" -or $InterceptorType
|
|||||||
}
|
}
|
||||||
|
|
||||||
$decoratorRu = $script:decoratorMap[$InterceptorType]
|
$decoratorRu = $script:decoratorMap[$InterceptorType]
|
||||||
|
# What the annotation will actually look like in the module: the source's spelling
|
||||||
|
$decoratorOut = $script:bslKw[$method.Lang][$script:decoratorKey[$InterceptorType]]
|
||||||
|
|
||||||
# --- Read existing extension module (if any) ---
|
# --- Read existing extension module (if any) ---
|
||||||
$extLines = @()
|
$extLines = @()
|
||||||
@@ -1078,7 +1197,7 @@ $enc = New-Object System.Text.UTF8Encoding($true)
|
|||||||
|
|
||||||
if ($dup) {
|
if ($dup) {
|
||||||
if ($InterceptorType -ne "ModificationAndControl") {
|
if ($InterceptorType -ne "ModificationAndControl") {
|
||||||
Write-Host "[ПРОПУЩЕН] Перехватчик &$decoratorRu(`"$MethodName`") уже есть в модуле — дубль не создаётся."
|
Write-Host "[ПРОПУЩЕН] Перехватчик &$($dup.Raw)(`"$MethodName`") уже есть в модуле — дубль не создаётся."
|
||||||
Write-Host " Файл: $extBsl"
|
Write-Host " Файл: $extBsl"
|
||||||
exit 0
|
exit 0
|
||||||
}
|
}
|
||||||
@@ -1123,11 +1242,7 @@ $candidate = "${namePrefix}$($method.Canonical)"
|
|||||||
$taken = @($existingProcNames | ForEach-Object { $_.ToLower() })
|
$taken = @($existingProcNames | ForEach-Object { $_.ToLower() })
|
||||||
$interceptorName = $candidate
|
$interceptorName = $candidate
|
||||||
if ($taken -contains $candidate.ToLower()) {
|
if ($taken -contains $candidate.ToLower()) {
|
||||||
if ($InterceptorType -eq "ModificationAndControl") {
|
$interceptorName = "${candidate}_$($script:bslKw[$method.Lang][$script:decoratorKey[$InterceptorType]])"
|
||||||
$interceptorName = "${candidate}_ИзменениеИКонтроль"
|
|
||||||
} else {
|
|
||||||
$interceptorName = "${candidate}_$decoratorRu"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$core = Build-InterceptorCore $method $InterceptorType $interceptorName
|
$core = Build-InterceptorCore $method $InterceptorType $interceptorName
|
||||||
@@ -1142,7 +1257,7 @@ if ($extExists) {
|
|||||||
if ($chain[$c].Kind -eq 'region') {
|
if ($chain[$c].Kind -eq 'region') {
|
||||||
$rname = $chain[$c].Name
|
$rname = $chain[$c].Name
|
||||||
for ($li = 0; $li -lt $extLines.Count; $li++) {
|
for ($li = 0; $li -lt $extLines.Count; $li++) {
|
||||||
if ($extLines[$li].Trim() -match ('^#Область\s+' + [regex]::Escape($rname) + '\s*$')) {
|
if ($extLines[$li].Trim() -match ('^#' + (Get-KwAlt 'Region') + '\s+' + [regex]::Escape($rname) + '\s*$')) {
|
||||||
$reuseRegionIdx = $c; $reuseLineIdx = $li; break
|
$reuseRegionIdx = $c; $reuseLineIdx = $li; break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1157,14 +1272,14 @@ if ($reuseRegionIdx -ge 0) {
|
|||||||
$innerChain = @()
|
$innerChain = @()
|
||||||
for ($c = $reuseRegionIdx + 1; $c -lt $chain.Count; $c++) { $innerChain += $chain[$c] }
|
for ($c = $reuseRegionIdx + 1; $c -lt $chain.Count; $c++) { $innerChain += $chain[$c] }
|
||||||
|
|
||||||
$block = Build-WrappedBlock $innerChain $core
|
$block = Build-WrappedBlock $innerChain $core $method.Lang
|
||||||
|
|
||||||
# find matching #КонецОбласти for the reused region
|
# find matching #КонецОбласти for the reused region
|
||||||
$depth = 0; $closeIdx = -1
|
$depth = 0; $closeIdx = -1
|
||||||
for ($li = $reuseLineIdx; $li -lt $extLines.Count; $li++) {
|
for ($li = $reuseLineIdx; $li -lt $extLines.Count; $li++) {
|
||||||
$t = $extLines[$li].Trim()
|
$t = $extLines[$li].Trim()
|
||||||
if ($t -match '^#Область\s') { $depth++ }
|
if ($t -match ('^#' + (Get-KwAlt 'Region') + '\s')) { $depth++ }
|
||||||
elseif ($t -match '^#КонецОбласти') { $depth--; if ($depth -eq 0) { $closeIdx = $li; break } }
|
elseif ($t -match ('^#' + (Get-KwAlt 'EndRegion'))) { $depth--; if ($depth -eq 0) { $closeIdx = $li; break } }
|
||||||
}
|
}
|
||||||
if ($closeIdx -lt 0) { Write-Error "Не найден #КонецОбласти для региона (переиспользование)"; exit 1 }
|
if ($closeIdx -lt 0) { Write-Error "Не найден #КонецОбласти для региона (переиспользование)"; exit 1 }
|
||||||
|
|
||||||
@@ -1181,7 +1296,7 @@ if ($reuseRegionIdx -ge 0) {
|
|||||||
$placement = "в существующий регион '$($chain[$reuseRegionIdx].Name)'"
|
$placement = "в существующий регион '$($chain[$reuseRegionIdx].Name)'"
|
||||||
} else {
|
} else {
|
||||||
# Build full wrapper chain (source order) and append (or create file).
|
# Build full wrapper chain (source order) and append (or create file).
|
||||||
$block = Build-WrappedBlock $chain $core
|
$block = Build-WrappedBlock $chain $core $method.Lang
|
||||||
$blockText = ($block -join "`r`n") + "`r`n"
|
$blockText = ($block -join "`r`n") + "`r`n"
|
||||||
|
|
||||||
$bslDir = Split-Path $extBsl -Parent
|
$bslDir = Split-Path $extBsl -Parent
|
||||||
@@ -1210,7 +1325,7 @@ if ($flagTarget) {
|
|||||||
Set-PropertyStateFlag $flagTarget.File $flagTarget.Property (Detect-FormatVersion $ExtensionPath)
|
Set-PropertyStateFlag $flagTarget.File $flagTarget.Property (Detect-FormatVersion $ExtensionPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Host "[OK] Перехватчик &$decoratorRu(`"$MethodName`") — $placement"
|
Write-Host "[OK] Перехватчик &$decoratorOut(`"$MethodName`") — $placement"
|
||||||
Write-Host " Файл: $extBsl"
|
Write-Host " Файл: $extBsl"
|
||||||
Write-Host " Процедура: $interceptorName($($method.ParamsText -replace '\s+', ' '))"
|
Write-Host " Процедура: $interceptorName($($method.ParamsText -replace '\s+', ' '))"
|
||||||
if ($method.Context) { Write-Host " Контекст: $($method.Context)" }
|
if ($method.Context) { Write-Host " Контекст: $($method.Context)" }
|
||||||
+203
-76
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# cfe-patch-method v2.11 — Source-aware method interceptor for 1C extension (CFE)
|
# cfe-patch-method v2.12 — Source-aware method interceptor for 1C extension (CFE)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -70,8 +70,83 @@ DECORATOR_MAP = {
|
|||||||
"ModificationAndControl": "ИзменениеИКонтроль",
|
"ModificationAndControl": "ИзменениеИКонтроль",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# InterceptorType -> keyword table key (emission goes through the table, the map above stays
|
||||||
|
# the normalized value the logic compares against)
|
||||||
|
DECORATOR_KEY = {
|
||||||
|
"Before": "Before", "After": "After", "Instead": "Around",
|
||||||
|
"ModificationAndControl": "Control",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Built-in language keywords in both spellings. The platform accepts either one in any module
|
||||||
|
# (pairs taken from the platform string tables), so a module written in English is ordinary
|
||||||
|
# source, not a broken one: we must read both and emit the spelling we read.
|
||||||
|
def bsl_keywords():
|
||||||
|
return {
|
||||||
|
"ru": {
|
||||||
|
"Async": "Асинх", "Proc": "Процедура", "EndProc": "КонецПроцедуры",
|
||||||
|
"Func": "Функция", "EndFunc": "КонецФункции", "Val": "Знач",
|
||||||
|
"Region": "Область", "EndRegion": "КонецОбласти",
|
||||||
|
"If": "Если", "Then": "Тогда", "ElsIf": "ИначеЕсли", "Else": "Иначе", "EndIf": "КонецЕсли",
|
||||||
|
# Platform spells the negation "Не"; we emit "НЕ" as this skill always has —
|
||||||
|
# the language is case-insensitive, and the lower case would churn every snapshot.
|
||||||
|
"And": "И", "Or": "Или", "Not": "НЕ",
|
||||||
|
"Insert": "Вставка", "EndInsert": "КонецВставки", "Delete": "Удаление", "EndDelete": "КонецУдаления",
|
||||||
|
"Before": "Перед", "After": "После", "Around": "Вместо", "Control": "ИзменениеИКонтроль",
|
||||||
|
"Proceed": "ПродолжитьВызов", "Return": "Возврат",
|
||||||
|
# Not a keyword: name of the local the generated Instead-stub declares. Lives here so
|
||||||
|
# that the language of emitted text is decided in exactly one place.
|
||||||
|
"ResultVar": "Результат",
|
||||||
|
"Directives": ["НаКлиенте", "НаСервере", "НаСервереБезКонтекста", "НаКлиентеНаСервереБезКонтекста", "НаКлиентеНаСервере"],
|
||||||
|
},
|
||||||
|
"en": {
|
||||||
|
"Async": "Async", "Proc": "Procedure", "EndProc": "EndProcedure",
|
||||||
|
"Func": "Function", "EndFunc": "EndFunction", "Val": "Val",
|
||||||
|
"Region": "Region", "EndRegion": "EndRegion",
|
||||||
|
"If": "If", "Then": "Then", "ElsIf": "ElsIf", "Else": "Else", "EndIf": "EndIf",
|
||||||
|
"And": "And", "Or": "Or", "Not": "Not",
|
||||||
|
"Insert": "Insert", "EndInsert": "EndInsert", "Delete": "Delete", "EndDelete": "EndDelete",
|
||||||
|
"Before": "Before", "After": "After", "Around": "Around", "Control": "ChangeAndValidate",
|
||||||
|
"Proceed": "ProceedWithCall", "Return": "Return",
|
||||||
|
"ResultVar": "Result",
|
||||||
|
"Directives": ["AtClient", "AtServer", "AtServerNoContext", "AtClientAtServerNoContext", "AtClientAtServer"],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BSL_KW = bsl_keywords()
|
||||||
|
|
||||||
|
|
||||||
|
# Regex alternation for a keyword in both spellings
|
||||||
|
def kw_alt(key):
|
||||||
|
return '(?:' + BSL_KW["ru"][key] + '|' + BSL_KW["en"][key] + ')'
|
||||||
|
|
||||||
|
|
||||||
|
# Spelling language of a recognized keyword. English is the discriminator: a Russian keyword
|
||||||
|
# never equals an English one, so anything else is 'ru'.
|
||||||
|
def bsl_lang(word):
|
||||||
|
w = word.strip().lower()
|
||||||
|
for k in ("Async", "Proc", "EndProc", "Func", "EndFunc", "Region", "If", "Insert", "Delete",
|
||||||
|
"Before", "After", "Around", "Control"):
|
||||||
|
if BSL_KW["en"][k].lower() == w:
|
||||||
|
return "en"
|
||||||
|
return "ru"
|
||||||
|
|
||||||
|
|
||||||
|
# Edit marker kind at the start of a line: Insert/Delete/EndInsert/EndDelete, None if none.
|
||||||
|
# The marker owns the start of the line only — a trailing comment does not bother the platform
|
||||||
|
# and must not bother us either.
|
||||||
|
def bsl_marker_kind(line):
|
||||||
|
kw = BSL_KW
|
||||||
|
for k in ("EndInsert", "EndDelete", "Insert", "Delete"):
|
||||||
|
if re.match(r'^\s*#(?:' + kw["ru"][k] + '|' + kw["en"][k] + r')\b', line, re.IGNORECASE):
|
||||||
|
return k
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
CONTEXT_RE = re.compile(
|
CONTEXT_RE = re.compile(
|
||||||
r'^&(НаКлиенте|НаСервере|НаСервереБезКонтекста|НаКлиентеНаСервереБезКонтекста|НаКлиентеНаСервере)\s*$'
|
r'^&(' + '|'.join(BSL_KW["ru"]["Directives"] + BSL_KW["en"]["Directives"]) + r')\s*$',
|
||||||
|
re.IGNORECASE
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -300,37 +375,45 @@ def read_signature(lines, start_idx):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def effective_condition(frame):
|
def effective_condition(frame, lang):
|
||||||
|
kw_not = BSL_KW[lang]["Not"]
|
||||||
|
kw_and = BSL_KW[lang]["And"]
|
||||||
conds = frame["conds"]
|
conds = frame["conds"]
|
||||||
n = len(conds)
|
n = len(conds)
|
||||||
if frame["in_else"]:
|
if frame["in_else"]:
|
||||||
return " И ".join("НЕ (%s)" % c for c in conds)
|
return (" %s " % kw_and).join("%s (%s)" % (kw_not, c) for c in conds)
|
||||||
if n == 1:
|
if n == 1:
|
||||||
return conds[0]
|
return conds[0]
|
||||||
parts = ["НЕ (%s)" % conds[j] for j in range(n - 1)]
|
parts = ["%s (%s)" % (kw_not, conds[j]) for j in range(n - 1)]
|
||||||
parts.append(conds[n - 1])
|
parts.append(conds[n - 1])
|
||||||
return " И ".join(parts)
|
return (" %s " % kw_and).join(parts)
|
||||||
|
|
||||||
|
|
||||||
def get_enclosing_chain(lines, target_idx):
|
def get_enclosing_chain(lines, target_idx, lang):
|
||||||
|
re_region = re.compile(r'^#' + kw_alt("Region") + r'\s+(\S+)', re.IGNORECASE)
|
||||||
|
re_end_region = re.compile(r'^#' + kw_alt("EndRegion"), re.IGNORECASE)
|
||||||
|
re_if = re.compile(r'^#' + kw_alt("If") + r'\s+(.+?)\s+' + kw_alt("Then"), re.IGNORECASE)
|
||||||
|
re_elsif = re.compile(r'^#' + kw_alt("ElsIf") + r'\s+(.+?)\s+' + kw_alt("Then"), re.IGNORECASE)
|
||||||
|
re_else = re.compile(r'^#' + kw_alt("Else") + r'(\s|$)', re.IGNORECASE)
|
||||||
|
re_endif = re.compile(r'^#' + kw_alt("EndIf"), re.IGNORECASE)
|
||||||
stack = []
|
stack = []
|
||||||
for i in range(target_idx):
|
for i in range(target_idx):
|
||||||
t = lines[i].strip()
|
t = lines[i].strip()
|
||||||
m = re.match(r'^#Область\s+(\S+)', t)
|
m = re_region.match(t)
|
||||||
if m:
|
if m:
|
||||||
stack.append({"kind": "region", "name": m.group(1)})
|
stack.append({"kind": "region", "name": m.group(1)})
|
||||||
continue
|
continue
|
||||||
if re.match(r'^#КонецОбласти', t):
|
if re_end_region.match(t):
|
||||||
for k in range(len(stack) - 1, -1, -1):
|
for k in range(len(stack) - 1, -1, -1):
|
||||||
if stack[k]["kind"] == "region":
|
if stack[k]["kind"] == "region":
|
||||||
del stack[k]
|
del stack[k]
|
||||||
break
|
break
|
||||||
continue
|
continue
|
||||||
m = re.match(r'^#Если\s+(.+?)\s+Тогда', t)
|
m = re_if.match(t)
|
||||||
if m:
|
if m:
|
||||||
stack.append({"kind": "if", "conds": [m.group(1).strip()], "in_else": False})
|
stack.append({"kind": "if", "conds": [m.group(1).strip()], "in_else": False})
|
||||||
continue
|
continue
|
||||||
m = re.match(r'^#ИначеЕсли\s+(.+?)\s+Тогда', t)
|
m = re_elsif.match(t)
|
||||||
if m:
|
if m:
|
||||||
for k in range(len(stack) - 1, -1, -1):
|
for k in range(len(stack) - 1, -1, -1):
|
||||||
if stack[k]["kind"] == "if":
|
if stack[k]["kind"] == "if":
|
||||||
@@ -338,13 +421,13 @@ def get_enclosing_chain(lines, target_idx):
|
|||||||
stack[k]["in_else"] = False
|
stack[k]["in_else"] = False
|
||||||
break
|
break
|
||||||
continue
|
continue
|
||||||
if re.match(r'^#Иначе(\s|$)', t):
|
if re_else.match(t):
|
||||||
for k in range(len(stack) - 1, -1, -1):
|
for k in range(len(stack) - 1, -1, -1):
|
||||||
if stack[k]["kind"] == "if":
|
if stack[k]["kind"] == "if":
|
||||||
stack[k]["in_else"] = True
|
stack[k]["in_else"] = True
|
||||||
break
|
break
|
||||||
continue
|
continue
|
||||||
if re.match(r'^#КонецЕсли', t):
|
if re_endif.match(t):
|
||||||
for k in range(len(stack) - 1, -1, -1):
|
for k in range(len(stack) - 1, -1, -1):
|
||||||
if stack[k]["kind"] == "if":
|
if stack[k]["kind"] == "if":
|
||||||
del stack[k]
|
del stack[k]
|
||||||
@@ -355,13 +438,14 @@ def get_enclosing_chain(lines, target_idx):
|
|||||||
if f["kind"] == "region":
|
if f["kind"] == "region":
|
||||||
chain.append({"kind": "region", "name": f["name"]})
|
chain.append({"kind": "region", "name": f["name"]})
|
||||||
else:
|
else:
|
||||||
chain.append({"kind": "if", "cond": effective_condition(f)})
|
chain.append({"kind": "if", "cond": effective_condition(f, lang)})
|
||||||
return chain
|
return chain
|
||||||
|
|
||||||
|
|
||||||
def extract_method(lines, method_name):
|
def extract_method(lines, method_name):
|
||||||
decl_re = re.compile(
|
decl_re = re.compile(
|
||||||
r'^\s*(Асинх\s+)?(Процедура|Функция)\s+(' + re.escape(method_name) + r')\s*\(',
|
r'^\s*(' + kw_alt("Async") + r'\s+)?(' + kw_alt("Proc") + '|' + kw_alt("Func") + r')\s+('
|
||||||
|
+ re.escape(method_name) + r')\s*\(',
|
||||||
re.IGNORECASE,
|
re.IGNORECASE,
|
||||||
)
|
)
|
||||||
for i in range(len(lines)):
|
for i in range(len(lines)):
|
||||||
@@ -371,7 +455,8 @@ def extract_method(lines, method_name):
|
|||||||
is_async = bool(m.group(1))
|
is_async = bool(m.group(1))
|
||||||
keyword = m.group(2)
|
keyword = m.group(2)
|
||||||
canonical = m.group(3)
|
canonical = m.group(3)
|
||||||
is_function = keyword.lower() == "функция"
|
lang = bsl_lang(keyword)
|
||||||
|
is_function = keyword.lower() in (BSL_KW["ru"]["Func"].lower(), BSL_KW["en"]["Func"].lower())
|
||||||
|
|
||||||
sig = read_signature(lines, i)
|
sig = read_signature(lines, i)
|
||||||
if not sig:
|
if not sig:
|
||||||
@@ -381,12 +466,12 @@ def extract_method(lines, method_name):
|
|||||||
param_names = []
|
param_names = []
|
||||||
if params_text.strip():
|
if params_text.strip():
|
||||||
for seg in split_top_level(params_text):
|
for seg in split_top_level(params_text):
|
||||||
s = re.sub(r'^Знач\s+', '', seg.strip())
|
s = re.sub(r'^' + kw_alt("Val") + r'\s+', '', seg.strip(), flags=re.IGNORECASE)
|
||||||
mm = re.match(r'^([\w]+)', s)
|
mm = re.match(r'^([\w]+)', s)
|
||||||
if mm:
|
if mm:
|
||||||
param_names.append(mm.group(1))
|
param_names.append(mm.group(1))
|
||||||
|
|
||||||
end_re = re.compile(r'^\s*КонецФункции\b' if is_function else r'^\s*КонецПроцедуры\b',
|
end_re = re.compile(r'^\s*' + (kw_alt("EndFunc") if is_function else kw_alt("EndProc")) + r'\b',
|
||||||
re.IGNORECASE)
|
re.IGNORECASE)
|
||||||
body_start = sig_end + 1
|
body_start = sig_end + 1
|
||||||
body_end = -1
|
body_end = -1
|
||||||
@@ -404,10 +489,11 @@ def extract_method(lines, method_name):
|
|||||||
if is_context_directive(prev):
|
if is_context_directive(prev):
|
||||||
context = prev
|
context = prev
|
||||||
|
|
||||||
chain = get_enclosing_chain(lines, i)
|
chain = get_enclosing_chain(lines, i, lang)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"canonical": canonical,
|
"canonical": canonical,
|
||||||
|
"lang": lang,
|
||||||
"is_function": is_function,
|
"is_function": is_function,
|
||||||
"is_async": is_async,
|
"is_async": is_async,
|
||||||
"params_text": params_text,
|
"params_text": params_text,
|
||||||
@@ -422,19 +508,32 @@ def extract_method(lines, method_name):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# Parse interceptor annotations present in a module. Type is normalized to the Russian spelling
|
||||||
|
# so everything downstream stays single-language; raw keeps what the file actually says.
|
||||||
def get_interceptors(lines):
|
def get_interceptors(lines):
|
||||||
|
kw = BSL_KW
|
||||||
|
keys = ("Before", "After", "Control", "Around")
|
||||||
|
alt = []
|
||||||
|
norm = {}
|
||||||
|
for k in keys:
|
||||||
|
alt.append(kw["ru"][k])
|
||||||
|
alt.append(kw["en"][k])
|
||||||
|
norm[kw["ru"][k].lower()] = kw["ru"][k]
|
||||||
|
norm[kw["en"][k].lower()] = kw["ru"][k]
|
||||||
|
pat = re.compile(r'^&(' + '|'.join(alt) + r')\("([^"]+)"\)', re.IGNORECASE)
|
||||||
result = []
|
result = []
|
||||||
pat = re.compile(r'^&(Перед|После|ИзменениеИКонтроль|Вместо)\("([^"]+)"\)')
|
|
||||||
for i in range(len(lines)):
|
for i in range(len(lines)):
|
||||||
m = pat.match(lines[i].strip())
|
m = pat.match(lines[i].strip())
|
||||||
if m:
|
if m:
|
||||||
result.append({"type": m.group(1), "method": m.group(2), "line": i})
|
raw = m.group(1)
|
||||||
|
result.append({"type": norm[raw.lower()], "raw": raw, "method": m.group(2), "line": i})
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def get_proc_names(lines):
|
def get_proc_names(lines):
|
||||||
names = []
|
names = []
|
||||||
pat = re.compile(r'^\s*(?:Асинх\s+)?(?:Процедура|Функция)\s+([\w]+)\s*\(', re.IGNORECASE)
|
pat = re.compile(r'^\s*(?:' + kw_alt("Async") + r'\s+)?(?:' + kw_alt("Proc") + '|' + kw_alt("Func")
|
||||||
|
+ r')\s+([\w]+)\s*\(', re.IGNORECASE)
|
||||||
for line in lines:
|
for line in lines:
|
||||||
m = pat.match(line)
|
m = pat.match(line)
|
||||||
if m:
|
if m:
|
||||||
@@ -443,15 +542,16 @@ def get_proc_names(lines):
|
|||||||
|
|
||||||
|
|
||||||
def build_interceptor_core(method, interceptor_type, interceptor_name):
|
def build_interceptor_core(method, interceptor_type, interceptor_name):
|
||||||
decorator_ru = DECORATOR_MAP[interceptor_type]
|
kw = BSL_KW[method["lang"]]
|
||||||
async_prefix = "Асинх " if method["is_async"] else ""
|
decorator = kw[DECORATOR_KEY[interceptor_type]]
|
||||||
keyword = "Функция" if method["is_function"] else "Процедура"
|
async_prefix = (kw["Async"] + " ") if method["is_async"] else ""
|
||||||
end_keyword = "КонецФункции" if method["is_function"] else "КонецПроцедуры"
|
keyword = kw["Func"] if method["is_function"] else kw["Proc"]
|
||||||
|
end_keyword = kw["EndFunc"] if method["is_function"] else kw["EndProc"]
|
||||||
|
|
||||||
lines = []
|
lines = []
|
||||||
if method["context"]:
|
if method["context"]:
|
||||||
lines.append(method["context"])
|
lines.append(method["context"])
|
||||||
lines.append('&%s("%s")' % (decorator_ru, method["canonical"]))
|
lines.append('&%s("%s")' % (decorator, method["canonical"]))
|
||||||
lines.append("%s%s %s(%s)" % (async_prefix, keyword, interceptor_name, method["params_text"]))
|
lines.append("%s%s %s(%s)" % (async_prefix, keyword, interceptor_name, method["params_text"]))
|
||||||
|
|
||||||
if interceptor_type == "Before":
|
if interceptor_type == "Before":
|
||||||
@@ -461,11 +561,11 @@ def build_interceptor_core(method, interceptor_type, interceptor_name):
|
|||||||
elif interceptor_type == "Instead":
|
elif interceptor_type == "Instead":
|
||||||
names_joined = ", ".join(method["param_names"])
|
names_joined = ", ".join(method["param_names"])
|
||||||
if method["is_function"]:
|
if method["is_function"]:
|
||||||
lines.append("\tРезультат = ПродолжитьВызов(%s);" % names_joined)
|
lines.append("\t%s = %s(%s);" % (kw["ResultVar"], kw["Proceed"], names_joined))
|
||||||
lines.append("\t// TODO: доработать поведение")
|
lines.append("\t// TODO: доработать поведение")
|
||||||
lines.append("\tВозврат Результат;")
|
lines.append("\t%s %s;" % (kw["Return"], kw["ResultVar"]))
|
||||||
else:
|
else:
|
||||||
lines.append("\tПродолжитьВызов(%s);" % names_joined)
|
lines.append("\t%s(%s);" % (kw["Proceed"], names_joined))
|
||||||
lines.append("\t// TODO: доработать поведение")
|
lines.append("\t// TODO: доработать поведение")
|
||||||
elif interceptor_type == "ModificationAndControl":
|
elif interceptor_type == "ModificationAndControl":
|
||||||
lines.extend(method["body_lines"])
|
lines.extend(method["body_lines"])
|
||||||
@@ -474,16 +574,18 @@ def build_interceptor_core(method, interceptor_type, interceptor_name):
|
|||||||
return lines
|
return lines
|
||||||
|
|
||||||
|
|
||||||
def build_wrapped_block(chain_arr, core):
|
def build_wrapped_block(chain_arr, core, lang):
|
||||||
"""Wrap core with region/preprocessor lines, adding blank lines around each boundary."""
|
"""Wrap core with region/preprocessor lines, adding blank lines around each boundary."""
|
||||||
|
kw = BSL_KW[lang]
|
||||||
b = []
|
b = []
|
||||||
for w in chain_arr:
|
for w in chain_arr:
|
||||||
b.append("#Область %s" % w["name"] if w["kind"] == "region" else "#Если %s Тогда" % w["cond"])
|
b.append(("#%s %s" % (kw["Region"], w["name"])) if w["kind"] == "region"
|
||||||
|
else ("#%s %s %s" % (kw["If"], w["cond"], kw["Then"])))
|
||||||
b.append("")
|
b.append("")
|
||||||
b.extend(core)
|
b.extend(core)
|
||||||
for w in reversed(chain_arr):
|
for w in reversed(chain_arr):
|
||||||
b.append("")
|
b.append("")
|
||||||
b.append("#КонецОбласти" if w["kind"] == "region" else "#КонецЕсли")
|
b.append(("#" + kw["EndRegion"]) if w["kind"] == "region" else ("#" + kw["EndIf"]))
|
||||||
return b
|
return b
|
||||||
|
|
||||||
|
|
||||||
@@ -512,25 +614,34 @@ def parse_marked_body(body_lines):
|
|||||||
i = 0
|
i = 0
|
||||||
n = len(body_lines)
|
n = len(body_lines)
|
||||||
while i < n:
|
while i < n:
|
||||||
t = body_lines[i].strip()
|
kind = bsl_marker_kind(body_lines[i])
|
||||||
if t == "#Вставка":
|
if kind == "Insert":
|
||||||
|
# Marker lines are kept verbatim: they carry the author's spelling, indent and — since
|
||||||
|
# a tail comment is legal — the note explaining the edit. Re-emitting a bare keyword
|
||||||
|
# would silently drop that note on -Actualize.
|
||||||
|
open_line = body_lines[i]
|
||||||
ins = []
|
ins = []
|
||||||
i += 1
|
i += 1
|
||||||
while i < n and body_lines[i].strip() != "#КонецВставки":
|
while i < n and bsl_marker_kind(body_lines[i]) != "EndInsert":
|
||||||
ins.append(body_lines[i])
|
ins.append(body_lines[i])
|
||||||
i += 1
|
i += 1
|
||||||
|
close_line = body_lines[i] if i < n else None
|
||||||
i += 1
|
i += 1
|
||||||
ops.append({"kind": "insert", "after": len(v1) - 1, "lines": ins})
|
ops.append({"kind": "insert", "after": len(v1) - 1, "lines": ins,
|
||||||
elif t == "#Удаление":
|
"open": open_line, "close": close_line})
|
||||||
|
elif kind == "Delete":
|
||||||
|
open_line = body_lines[i]
|
||||||
start_idx = len(v1)
|
start_idx = len(v1)
|
||||||
i += 1
|
i += 1
|
||||||
dels = []
|
dels = []
|
||||||
while i < n and body_lines[i].strip() != "#КонецУдаления":
|
while i < n and bsl_marker_kind(body_lines[i]) != "EndDelete":
|
||||||
dels.append(body_lines[i])
|
dels.append(body_lines[i])
|
||||||
v1.append(body_lines[i])
|
v1.append(body_lines[i])
|
||||||
i += 1
|
i += 1
|
||||||
|
close_line = body_lines[i] if i < n else None
|
||||||
i += 1
|
i += 1
|
||||||
ops.append({"kind": "delete", "start": start_idx, "end": len(v1) - 1, "lines": dels})
|
ops.append({"kind": "delete", "start": start_idx, "end": len(v1) - 1, "lines": dels,
|
||||||
|
"open": open_line, "close": close_line})
|
||||||
else:
|
else:
|
||||||
v1.append(body_lines[i])
|
v1.append(body_lines[i])
|
||||||
i += 1
|
i += 1
|
||||||
@@ -879,6 +990,8 @@ def main():
|
|||||||
"(перехват &Перед/&После к функциям неприменим)." % method_name)
|
"(перехват &Перед/&После к функциям неприменим)." % method_name)
|
||||||
|
|
||||||
decorator_ru = DECORATOR_MAP[interceptor_type]
|
decorator_ru = DECORATOR_MAP[interceptor_type]
|
||||||
|
# What the annotation will actually look like in the module: the source's spelling
|
||||||
|
decorator_out = BSL_KW[method["lang"]][DECORATOR_KEY[interceptor_type]]
|
||||||
|
|
||||||
# --- Read existing extension module (if any) ---
|
# --- Read existing extension module (if any) ---
|
||||||
ext_exists = os.path.isfile(ext_bsl)
|
ext_exists = os.path.isfile(ext_bsl)
|
||||||
@@ -896,7 +1009,7 @@ def main():
|
|||||||
if dup:
|
if dup:
|
||||||
if interceptor_type != "ModificationAndControl":
|
if interceptor_type != "ModificationAndControl":
|
||||||
print('[ПРОПУЩЕН] Перехватчик &%s("%s") уже есть в модуле — дубль не создаётся.'
|
print('[ПРОПУЩЕН] Перехватчик &%s("%s") уже есть в модуле — дубль не создаётся.'
|
||||||
% (decorator_ru, method_name))
|
% (dup["raw"], method_name))
|
||||||
print(" Файл: %s" % ext_bsl)
|
print(" Файл: %s" % ext_bsl)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
rel = rel_parts_under(extension_path, ext_bsl)
|
rel = rel_parts_under(extension_path, ext_bsl)
|
||||||
@@ -940,14 +1053,11 @@ def main():
|
|||||||
taken = [n.lower() for n in existing_proc_names]
|
taken = [n.lower() for n in existing_proc_names]
|
||||||
interceptor_name = candidate
|
interceptor_name = candidate
|
||||||
if candidate.lower() in taken:
|
if candidate.lower() in taken:
|
||||||
if interceptor_type == "ModificationAndControl":
|
interceptor_name = candidate + "_" + decorator_out
|
||||||
interceptor_name = candidate + "_ИзменениеИКонтроль"
|
|
||||||
else:
|
|
||||||
interceptor_name = candidate + "_" + decorator_ru
|
|
||||||
|
|
||||||
core = build_interceptor_core(method, interceptor_type, interceptor_name)
|
core = build_interceptor_core(method, interceptor_type, interceptor_name)
|
||||||
|
|
||||||
place_new(ext_bsl, ext_lines, ext_exists, method["chain"], core)
|
place_new(ext_bsl, ext_lines, ext_exists, method["chain"], core, method["lang"])
|
||||||
|
|
||||||
# Модуль в расширении есть — отражаем это в метаданных объекта (формат ≥ 2.19).
|
# Модуль в расширении есть — отражаем это в метаданных объекта (формат ≥ 2.19).
|
||||||
flag_target = get_module_flag_target(rel_parts, extension_path)
|
flag_target = get_module_flag_target(rel_parts, extension_path)
|
||||||
@@ -957,7 +1067,7 @@ def main():
|
|||||||
|
|
||||||
# emit summary
|
# emit summary
|
||||||
placement = place_new.placement
|
placement = place_new.placement
|
||||||
print('[OK] Перехватчик &%s("%s") — %s' % (decorator_ru, method_name, placement))
|
print('[OK] Перехватчик &%s("%s") — %s' % (decorator_out, method_name, placement))
|
||||||
print(" Файл: %s" % ext_bsl)
|
print(" Файл: %s" % ext_bsl)
|
||||||
print(" Процедура: %s(%s)" % (interceptor_name, normalize(method["params_text"])))
|
print(" Процедура: %s(%s)" % (interceptor_name, normalize(method["params_text"])))
|
||||||
if method["context"]:
|
if method["context"]:
|
||||||
@@ -970,7 +1080,7 @@ def main():
|
|||||||
print(" Обрамление: %s" % desc)
|
print(" Обрамление: %s" % desc)
|
||||||
|
|
||||||
|
|
||||||
def place_new(ext_bsl, ext_lines, ext_exists, chain, core):
|
def place_new(ext_bsl, ext_lines, ext_exists, chain, core, lang):
|
||||||
enc_bom = True # noqa
|
enc_bom = True # noqa
|
||||||
|
|
||||||
# find innermost region in chain that already exists in the extension module
|
# find innermost region in chain that already exists in the extension module
|
||||||
@@ -980,7 +1090,7 @@ def place_new(ext_bsl, ext_lines, ext_exists, chain, core):
|
|||||||
for c in range(len(chain) - 1, -1, -1):
|
for c in range(len(chain) - 1, -1, -1):
|
||||||
if chain[c]["kind"] == "region":
|
if chain[c]["kind"] == "region":
|
||||||
rname = chain[c]["name"]
|
rname = chain[c]["name"]
|
||||||
rre = re.compile(r'^#Область\s+' + re.escape(rname) + r'\s*$')
|
rre = re.compile(r'^#' + kw_alt("Region") + r'\s+' + re.escape(rname) + r'\s*$', re.IGNORECASE)
|
||||||
for li in range(len(ext_lines)):
|
for li in range(len(ext_lines)):
|
||||||
if rre.match(ext_lines[li].strip()):
|
if rre.match(ext_lines[li].strip()):
|
||||||
reuse_region_idx = c
|
reuse_region_idx = c
|
||||||
@@ -991,15 +1101,15 @@ def place_new(ext_bsl, ext_lines, ext_exists, chain, core):
|
|||||||
|
|
||||||
if reuse_region_idx >= 0:
|
if reuse_region_idx >= 0:
|
||||||
inner_chain = chain[reuse_region_idx + 1:]
|
inner_chain = chain[reuse_region_idx + 1:]
|
||||||
block = build_wrapped_block(inner_chain, core)
|
block = build_wrapped_block(inner_chain, core, lang)
|
||||||
|
|
||||||
depth = 0
|
depth = 0
|
||||||
close_idx = -1
|
close_idx = -1
|
||||||
for li in range(reuse_line_idx, len(ext_lines)):
|
for li in range(reuse_line_idx, len(ext_lines)):
|
||||||
t = ext_lines[li].strip()
|
t = ext_lines[li].strip()
|
||||||
if re.match(r'^#Область\s', t):
|
if re.match(r'^#' + kw_alt("Region") + r'\s', t, re.IGNORECASE):
|
||||||
depth += 1
|
depth += 1
|
||||||
elif re.match(r'^#КонецОбласти', t):
|
elif re.match(r'^#' + kw_alt("EndRegion"), t, re.IGNORECASE):
|
||||||
depth -= 1
|
depth -= 1
|
||||||
if depth == 0:
|
if depth == 0:
|
||||||
close_idx = li
|
close_idx = li
|
||||||
@@ -1021,7 +1131,7 @@ def place_new(ext_bsl, ext_lines, ext_exists, chain, core):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# full wrapper chain, append (or create)
|
# full wrapper chain, append (or create)
|
||||||
block = build_wrapped_block(chain, core)
|
block = build_wrapped_block(chain, core, lang)
|
||||||
block_text = "\r\n".join(block) + "\r\n"
|
block_text = "\r\n".join(block) + "\r\n"
|
||||||
|
|
||||||
bsl_dir = os.path.dirname(ext_bsl)
|
bsl_dir = os.path.dirname(ext_bsl)
|
||||||
@@ -1083,7 +1193,8 @@ def conflict_reason(disputed):
|
|||||||
return "; ".join(parts)
|
return "; ".join(parts)
|
||||||
|
|
||||||
|
|
||||||
def write_conflict_folder(folder, method_id, ext_bsl, existing_name, method, v1, marked_body, v2, v1norm, v2norm, disputed):
|
def write_conflict_folder(folder, method_id, ext_bsl, existing_name, method, v1, marked_body, v2, v1norm, v2norm, disputed, lang):
|
||||||
|
kw = BSL_KW[lang]
|
||||||
os.makedirs(folder, exist_ok=True)
|
os.makedirs(folder, exist_ok=True)
|
||||||
write_bsl(os.path.join(folder, "base.bsl"), v1)
|
write_bsl(os.path.join(folder, "base.bsl"), v1)
|
||||||
write_bsl(os.path.join(folder, "local.bsl"), marked_body)
|
write_bsl(os.path.join(folder, "local.bsl"), marked_body)
|
||||||
@@ -1091,7 +1202,7 @@ def write_conflict_folder(folder, method_id, ext_bsl, existing_name, method, v1,
|
|||||||
md = []
|
md = []
|
||||||
md.append("# %s" % method_id)
|
md.append("# %s" % method_id)
|
||||||
md.append("Править: %s" % ext_bsl)
|
md.append("Править: %s" % ext_bsl)
|
||||||
md.append('Метод: %s (&ИзменениеИКонтроль("%s"))' % (existing_name, method["canonical"]))
|
md.append('Метод: %s (&%s("%s"))' % (existing_name, kw["Control"], method["canonical"]))
|
||||||
md.append("Причина: %s" % conflict_reason(disputed))
|
md.append("Причина: %s" % conflict_reason(disputed))
|
||||||
md.append("")
|
md.append("")
|
||||||
md.append("## Не размещено — перенести вручную")
|
md.append("## Не размещено — перенести вручную")
|
||||||
@@ -1105,12 +1216,13 @@ def write_conflict_folder(folder, method_id, ext_bsl, existing_name, method, v1,
|
|||||||
if d.get("before"):
|
if d.get("before"):
|
||||||
for l in d["before"]:
|
for l in d["before"]:
|
||||||
md.append(l)
|
md.append(l)
|
||||||
md.append("#Вставка"); md.extend(d["lines"]); md.append("#КонецВставки")
|
md.append(d.get("open") or ("#" + kw["Insert"])); md.extend(d["lines"])
|
||||||
|
md.append(d.get("close") or ("#" + kw["EndInsert"]))
|
||||||
if d.get("after"):
|
if d.get("after"):
|
||||||
for l in d["after"]:
|
for l in d["after"]:
|
||||||
md.append(l)
|
md.append(l)
|
||||||
md.append("")
|
md.append("")
|
||||||
md.append("Якорь (строки вокруг #Вставка) изменился/исчез в новом оригинале — блок не лёг автоматически (см. дифф base→remote ниже).")
|
md.append("Якорь (строки вокруг #%s) изменился/исчез в новом оригинале — блок не лёг автоматически (см. дифф base→remote ниже)." % kw["Insert"])
|
||||||
md.append("В модуле расширения блок припаркован в конце метода под меткой // [РЕСИНК-КОНФЛИКТ №%d] — найди по ней." % cn)
|
md.append("В модуле расширения блок припаркован в конце метода под меткой // [РЕСИНК-КОНФЛИКТ №%d] — найди по ней." % cn)
|
||||||
md.append("Куда переносить: если якорного кода в новом методе больше нет — он, вероятно, вынесен/отрефакторен (ищите в диффе новый вызов/процедуру). Размести адаптацию по смыслу: например пост-обработкой после нового вызова, либо в заимствованной процедуре, куда переехал код. При правке файла сохрани кодировку (UTF-8 с BOM).")
|
md.append("Куда переносить: если якорного кода в новом методе больше нет — он, вероятно, вынесен/отрефакторен (ищите в диффе новый вызов/процедуру). Размести адаптацию по смыслу: например пост-обработкой после нового вызова, либо в заимствованной процедуре, куда переехал код. При правке файла сохрани кодировку (UTF-8 с BOM).")
|
||||||
else:
|
else:
|
||||||
@@ -1136,17 +1248,23 @@ def resync_one(ext_bsl, ext_lines, dup, method, logical_module, conflict_folder,
|
|||||||
method_id = "%s.%s" % (logical_module, method["canonical"])
|
method_id = "%s.%s" % (logical_module, method["canonical"])
|
||||||
dec_line = dup["line"]
|
dec_line = dup["line"]
|
||||||
sig_line_idx = dec_line + 1
|
sig_line_idx = dec_line + 1
|
||||||
name_re = re.compile(r'^\s*(?:Асинх\s+)?(?:Процедура|Функция)\s+([\w]+)\s*\(', re.IGNORECASE)
|
name_re = re.compile(r'^\s*(?:' + kw_alt("Async") + r'\s+)?(' + kw_alt("Proc") + '|' + kw_alt("Func")
|
||||||
|
+ r')\s+([\w]+)\s*\(', re.IGNORECASE)
|
||||||
m0 = name_re.match(ext_lines[sig_line_idx]) if sig_line_idx < len(ext_lines) else None
|
m0 = name_re.match(ext_lines[sig_line_idx]) if sig_line_idx < len(ext_lines) else None
|
||||||
if not m0:
|
if not m0:
|
||||||
return {"id": method_id, "status": "ОШИБКА", "ext_bsl": ext_bsl, "reason": "не разобрать сигнатуру перехватчика"}
|
return {"id": method_id, "status": "ОШИБКА", "ext_bsl": ext_bsl, "reason": "не разобрать сигнатуру перехватчика"}
|
||||||
existing_name = m0.group(1)
|
# The block we are about to rewrite is the interceptor already in the extension, so it is
|
||||||
|
# its spelling — not the source module's — that the rewritten block must keep.
|
||||||
|
lang = bsl_lang(m0.group(1))
|
||||||
|
kw = BSL_KW[lang]
|
||||||
|
existing_name = m0.group(2)
|
||||||
sig = read_signature(ext_lines, sig_line_idx)
|
sig = read_signature(ext_lines, sig_line_idx)
|
||||||
if not sig:
|
if not sig:
|
||||||
return {"id": method_id, "status": "ОШИБКА", "ext_bsl": ext_bsl, "reason": "не разобрать сигнатуру"}
|
return {"id": method_id, "status": "ОШИБКА", "ext_bsl": ext_bsl, "reason": "не разобрать сигнатуру"}
|
||||||
ext_params_text, sig_end = sig
|
ext_params_text, sig_end = sig
|
||||||
is_func = bool(re.match(r'^\s*(?:Асинх\s+)?Функция\b', ext_lines[sig_line_idx], re.IGNORECASE))
|
is_func = bool(re.match(r'^\s*(?:' + kw_alt("Async") + r'\s+)?' + kw_alt("Func") + r'\b',
|
||||||
end_re = re.compile(r'^\s*КонецФункции\b' if is_func else r'^\s*КонецПроцедуры\b', re.IGNORECASE)
|
ext_lines[sig_line_idx], re.IGNORECASE))
|
||||||
|
end_re = re.compile(r'^\s*' + (kw_alt("EndFunc") if is_func else kw_alt("EndProc")) + r'\b', re.IGNORECASE)
|
||||||
block_end = -1
|
block_end = -1
|
||||||
for j in range(sig_end + 1, len(ext_lines)):
|
for j in range(sig_end + 1, len(ext_lines)):
|
||||||
if end_re.match(ext_lines[j]):
|
if end_re.match(ext_lines[j]):
|
||||||
@@ -1172,7 +1290,7 @@ def resync_one(ext_bsl, ext_lines, dup, method, logical_module, conflict_folder,
|
|||||||
if not params_drift and control_key(v1) == control_key(v2):
|
if not params_drift and control_key(v1) == control_key(v2):
|
||||||
return {"id": method_id, "status": "АКТУАЛЕН", "ext_bsl": ext_bsl}
|
return {"id": method_id, "status": "АКТУАЛЕН", "ext_bsl": ext_bsl}
|
||||||
|
|
||||||
insert_top = []; insert_after = {}; del_start = set(); del_end = set(); disputed = []; transferred = 0; absorbed = 0; absorbed_notes = []
|
insert_top = []; insert_after = {}; del_start = {}; del_end = {}; disputed = []; transferred = 0; absorbed = 0; absorbed_notes = []
|
||||||
v2sig, v2map = significant_projection(v2norm)
|
v2sig, v2map = significant_projection(v2norm)
|
||||||
for op in ops:
|
for op in ops:
|
||||||
if op["kind"] == "insert":
|
if op["kind"] == "insert":
|
||||||
@@ -1198,16 +1316,17 @@ def resync_one(ext_bsl, ext_lines, dup, method, logical_module, conflict_folder,
|
|||||||
elif k is None:
|
elif k is None:
|
||||||
dbefore = v1[max(0, after - 2):after + 1] if after >= 0 else []
|
dbefore = v1[max(0, after - 2):after + 1] if after >= 0 else []
|
||||||
dafter = v1[after + 1:after + 4]
|
dafter = v1[after + 1:after + 4]
|
||||||
disputed.append({"kind": "insert", "lines": op["lines"], "before": dbefore, "after": dafter})
|
disputed.append({"kind": "insert", "lines": op["lines"], "open": op.get("open"),
|
||||||
|
"close": op.get("close"), "before": dbefore, "after": dafter})
|
||||||
elif k < 0:
|
elif k < 0:
|
||||||
insert_top.append(op["lines"]); transferred += 1
|
insert_top.append(op); transferred += 1
|
||||||
else:
|
else:
|
||||||
insert_after.setdefault(k, []).append(op["lines"]); transferred += 1
|
insert_after.setdefault(k, []).append(op); transferred += 1
|
||||||
else:
|
else:
|
||||||
keys = v1norm[op["start"]:op["end"] + 1]
|
keys = v1norm[op["start"]:op["end"] + 1]
|
||||||
p = find_unique_run(v2norm, keys)
|
p = find_unique_run(v2norm, keys)
|
||||||
if p >= 0:
|
if p >= 0:
|
||||||
del_start.add(p); del_end.add(p + len(keys) - 1); transferred += 1
|
del_start[p] = op.get("open"); del_end[p + len(keys) - 1] = op.get("close"); transferred += 1
|
||||||
else:
|
else:
|
||||||
# Nearest significant neighbours; adjacency in the significant projection means the
|
# Nearest significant neighbours; adjacency in the significant projection means the
|
||||||
# block is already cut (blanks/comments left behind don't matter).
|
# block is already cut (blanks/comments left behind don't matter).
|
||||||
@@ -1239,18 +1358,25 @@ def resync_one(ext_bsl, ext_lines, dup, method, logical_module, conflict_folder,
|
|||||||
return {"id": method_id, "status": st, "ext_bsl": ext_bsl, "transferred": transferred,
|
return {"id": method_id, "status": st, "ext_bsl": ext_bsl, "transferred": transferred,
|
||||||
"absorbed": absorbed, "disputed": len(disputed), "reason": rsn, "absorbed_notes": absorbed_notes}
|
"absorbed": absorbed, "disputed": len(disputed), "reason": rsn, "absorbed_notes": absorbed_notes}
|
||||||
|
|
||||||
|
# Marker lines come back as they were read (open/close); the keyword form is only a fallback
|
||||||
|
# for a block whose closing marker the module never had.
|
||||||
|
def mk(orig, key):
|
||||||
|
return ("#" + kw[key]) if not orig else orig
|
||||||
|
|
||||||
new_body = []
|
new_body = []
|
||||||
for blk in insert_top:
|
for blk in insert_top:
|
||||||
new_body.append("#Вставка"); new_body.extend(blk); new_body.append("#КонецВставки")
|
new_body.append(mk(blk.get("open"), "Insert")); new_body.extend(blk["lines"])
|
||||||
|
new_body.append(mk(blk.get("close"), "EndInsert"))
|
||||||
for k in range(len(v2)):
|
for k in range(len(v2)):
|
||||||
if k in del_start:
|
if k in del_start:
|
||||||
new_body.append("#Удаление")
|
new_body.append(mk(del_start[k], "Delete"))
|
||||||
new_body.append(v2[k])
|
new_body.append(v2[k])
|
||||||
if k in del_end:
|
if k in del_end:
|
||||||
new_body.append("#КонецУдаления")
|
new_body.append(mk(del_end[k], "EndDelete"))
|
||||||
if k in insert_after:
|
if k in insert_after:
|
||||||
for blk in insert_after[k]:
|
for blk in insert_after[k]:
|
||||||
new_body.append("#Вставка"); new_body.extend(blk); new_body.append("#КонецВставки")
|
new_body.append(mk(blk.get("open"), "Insert")); new_body.extend(blk["lines"])
|
||||||
|
new_body.append(mk(blk.get("close"), "EndInsert"))
|
||||||
if disputed:
|
if disputed:
|
||||||
new_body.append("\t// [РЕСИНК-КОНФЛИКТ] блоки ниже не легли автоматически — перенесите вручную (по № см. conflict.md / index.md в merge-воркспейсе, путь в выводе).")
|
new_body.append("\t// [РЕСИНК-КОНФЛИКТ] блоки ниже не легли автоматически — перенесите вручную (по № см. conflict.md / index.md в merge-воркспейсе, путь в выводе).")
|
||||||
cn = 0
|
cn = 0
|
||||||
@@ -1258,19 +1384,20 @@ def resync_one(ext_bsl, ext_lines, dup, method, logical_module, conflict_folder,
|
|||||||
cn += 1
|
cn += 1
|
||||||
if d["kind"] == "insert":
|
if d["kind"] == "insert":
|
||||||
new_body.append("\t// [РЕСИНК-КОНФЛИКТ №%d] вставка — исходный якорь изменён в новом оригинале." % cn)
|
new_body.append("\t// [РЕСИНК-КОНФЛИКТ №%d] вставка — исходный якорь изменён в новом оригинале." % cn)
|
||||||
new_body.append("#Вставка"); new_body.extend(d["lines"]); new_body.append("#КонецВставки")
|
new_body.append(mk(d.get("open"), "Insert")); new_body.extend(d["lines"])
|
||||||
|
new_body.append(mk(d.get("close"), "EndInsert"))
|
||||||
else:
|
else:
|
||||||
new_body.append("\t// [РЕСИНК-КОНФЛИКТ №%d] удаление — строки не найдены в новом оригинале:" % cn)
|
new_body.append("\t// [РЕСИНК-КОНФЛИКТ №%d] удаление — строки не найдены в новом оригинале:" % cn)
|
||||||
for l in d["lines"]:
|
for l in d["lines"]:
|
||||||
new_body.append("\t// " + l.strip())
|
new_body.append("\t// " + l.strip())
|
||||||
|
|
||||||
async_prefix = "Асинх " if method["is_async"] else ""
|
async_prefix = (kw["Async"] + " ") if method["is_async"] else ""
|
||||||
keyword = "Функция" if method["is_function"] else "Процедура"
|
keyword = kw["Func"] if method["is_function"] else kw["Proc"]
|
||||||
end_keyword = "КонецФункции" if method["is_function"] else "КонецПроцедуры"
|
end_keyword = kw["EndFunc"] if method["is_function"] else kw["EndProc"]
|
||||||
new_block = []
|
new_block = []
|
||||||
if method["context"]:
|
if method["context"]:
|
||||||
new_block.append(method["context"])
|
new_block.append(method["context"])
|
||||||
new_block.append('&ИзменениеИКонтроль("%s")' % method["canonical"])
|
new_block.append('&%s("%s")' % (kw["Control"], method["canonical"]))
|
||||||
new_block.append("%s%s %s(%s)" % (async_prefix, keyword, existing_name, method["params_text"]))
|
new_block.append("%s%s %s(%s)" % (async_prefix, keyword, existing_name, method["params_text"]))
|
||||||
new_block.extend(new_body)
|
new_block.extend(new_body)
|
||||||
new_block.append(end_keyword)
|
new_block.append(end_keyword)
|
||||||
@@ -1284,7 +1411,7 @@ def resync_one(ext_bsl, ext_lines, dup, method, logical_module, conflict_folder,
|
|||||||
conflict_dir = None
|
conflict_dir = None
|
||||||
if disputed:
|
if disputed:
|
||||||
conflict_dir = conflict_folder
|
conflict_dir = conflict_folder
|
||||||
write_conflict_folder(conflict_folder, method_id, ext_bsl, existing_name, method, v1, marked_body, v2, v1norm, v2norm, disputed)
|
write_conflict_folder(conflict_folder, method_id, ext_bsl, existing_name, method, v1, marked_body, v2, v1norm, v2norm, disputed, lang)
|
||||||
if disputed:
|
if disputed:
|
||||||
status = "ЧАСТИЧНО"
|
status = "ЧАСТИЧНО"
|
||||||
elif transferred == 0 and absorbed > 0:
|
elif transferred == 0 and absorbed > 0:
|
||||||
@@ -36,7 +36,7 @@ allowed-tools:
|
|||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-validate.ps1" -ExtensionPath "src\cfe\extname"
|
python ".roo/skills/cfe-validate/scripts/cfe-validate.py" -ExtensionPath "src\cfe\extname"
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-validate.ps1" -ExtensionPath "src\cfe\extname\Configuration.xml"
|
python ".roo/skills/cfe-validate/scripts/cfe-validate.py" -ExtensionPath "src\cfe\extname\Configuration.xml"
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-validate.ps1" -ExtensionPath "src\cfe\extname" -ConfigPath "src\cf"
|
python ".roo/skills/cfe-validate/scripts/cfe-validate.py" -ExtensionPath "src\cfe\extname" -ConfigPath "src\cf"
|
||||||
```
|
```
|
||||||
+40
-2
@@ -1,4 +1,4 @@
|
|||||||
# cfe-validate v1.16 — Validate 1C configuration extension structure (CFE)
|
# cfe-validate v1.17 — Validate 1C configuration extension structure (CFE)
|
||||||
# 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(
|
||||||
@@ -1299,12 +1299,50 @@ if ($versionRank -ge 219 -and $childObjNode) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Built-in language keywords in both spellings. The platform accepts either one in any module
|
||||||
|
# (pairs taken from the platform string tables), so a module written in English is ordinary
|
||||||
|
# source, not a broken one: we must read both and emit the spelling we read.
|
||||||
|
function Get-BslKeywords {
|
||||||
|
return @{
|
||||||
|
ru = @{
|
||||||
|
Async="Асинх"; Proc="Процедура"; EndProc="КонецПроцедуры"
|
||||||
|
Func="Функция"; EndFunc="КонецФункции"; Val="Знач"
|
||||||
|
Region="Область"; EndRegion="КонецОбласти"
|
||||||
|
If="Если"; Then="Тогда"; ElsIf="ИначеЕсли"; Else="Иначе"; EndIf="КонецЕсли"
|
||||||
|
# Platform spells the negation "Не"; we emit "НЕ" as this skill always has —
|
||||||
|
# the language is case-insensitive, and the lower case would churn every snapshot.
|
||||||
|
And="И"; Or="Или"; Not="НЕ"
|
||||||
|
Insert="Вставка"; EndInsert="КонецВставки"; Delete="Удаление"; EndDelete="КонецУдаления"
|
||||||
|
Before="Перед"; After="После"; Around="Вместо"; Control="ИзменениеИКонтроль"
|
||||||
|
Proceed="ПродолжитьВызов"; Return="Возврат"
|
||||||
|
# Not a keyword: name of the local the generated Instead-stub declares. Lives here so
|
||||||
|
# that the language of emitted text is decided in exactly one place.
|
||||||
|
ResultVar="Результат"
|
||||||
|
Directives=@("НаКлиенте", "НаСервере", "НаСервереБезКонтекста", "НаКлиентеНаСервереБезКонтекста", "НаКлиентеНаСервере")
|
||||||
|
}
|
||||||
|
en = @{
|
||||||
|
Async="Async"; Proc="Procedure"; EndProc="EndProcedure"
|
||||||
|
Func="Function"; EndFunc="EndFunction"; Val="Val"
|
||||||
|
Region="Region"; EndRegion="EndRegion"
|
||||||
|
If="If"; Then="Then"; ElsIf="ElsIf"; Else="Else"; EndIf="EndIf"
|
||||||
|
And="And"; Or="Or"; Not="Not"
|
||||||
|
Insert="Insert"; EndInsert="EndInsert"; Delete="Delete"; EndDelete="EndDelete"
|
||||||
|
Before="Before"; After="After"; Around="Around"; Control="ChangeAndValidate"
|
||||||
|
Proceed="ProceedWithCall"; Return="Return"
|
||||||
|
ResultVar="Result"
|
||||||
|
Directives=@("AtClient", "AtServer", "AtServerNoContext", "AtClientAtServerNoContext", "AtClientAtServer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# --- Breadcrumb: controlled methods (&ИзменениеИКонтроль) drift is not checked here ---
|
# --- Breadcrumb: controlled methods (&ИзменениеИКонтроль) drift is not checked here ---
|
||||||
$extRootDir = Split-Path $resolvedPath -Parent
|
$extRootDir = Split-Path $resolvedPath -Parent
|
||||||
|
$ctrlKw = Get-BslKeywords
|
||||||
|
$ctrlRe = '(?im)^\s*&(?:' + $ctrlKw.ru.Control + '|' + $ctrlKw.en.Control + ')\('
|
||||||
$ctrlCount = 0
|
$ctrlCount = 0
|
||||||
foreach ($bslFile in (Get-ChildItem -Path $extRootDir -Recurse -Filter *.bsl -File -ErrorAction SilentlyContinue)) {
|
foreach ($bslFile in (Get-ChildItem -Path $extRootDir -Recurse -Filter *.bsl -File -ErrorAction SilentlyContinue)) {
|
||||||
$txt = [System.IO.File]::ReadAllText($bslFile.FullName, [System.Text.Encoding]::UTF8)
|
$txt = [System.IO.File]::ReadAllText($bslFile.FullName, [System.Text.Encoding]::UTF8)
|
||||||
$ctrlCount += ([regex]::Matches($txt, '(?m)^\s*&ИзменениеИКонтроль\(')).Count
|
$ctrlCount += ([regex]::Matches($txt, $ctrlRe)).Count
|
||||||
}
|
}
|
||||||
if ($ctrlCount -gt 0) {
|
if ($ctrlCount -gt 0) {
|
||||||
Out-Line "[INFO] Контролируемых методов (&ИзменениеИКонтроль): $ctrlCount — их актуальность здесь не проверяется. Сверьте: /cfe-patch-method -Check -ExtensionPath <ext> -ConfigPath <cf>"
|
Out-Line "[INFO] Контролируемых методов (&ИзменениеИКонтроль): $ctrlCount — их актуальность здесь не проверяется. Сверьте: /cfe-patch-method -Check -ExtensionPath <ext> -ConfigPath <cf>"
|
||||||
+40
-2
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# cfe-validate v1.16 — Validate 1C configuration extension XML structure (CFE)
|
# cfe-validate v1.17 — Validate 1C configuration extension XML structure (CFE)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
"""Validates extension Configuration.xml: root, InternalInfo, extension properties, ChildObjects, borrowed objects."""
|
"""Validates extension Configuration.xml: root, InternalInfo, extension properties, ChildObjects, borrowed objects."""
|
||||||
import sys, os, argparse, re
|
import sys, os, argparse, re
|
||||||
@@ -27,6 +27,42 @@ def ci_parse_args(parser, argv=None):
|
|||||||
return parser.parse_args(argv)
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
|
# Built-in language keywords in both spellings. The platform accepts either one in any module
|
||||||
|
# (pairs taken from the platform string tables), so a module written in English is ordinary
|
||||||
|
# source, not a broken one: we must read both and emit the spelling we read.
|
||||||
|
def bsl_keywords():
|
||||||
|
return {
|
||||||
|
"ru": {
|
||||||
|
"Async": "Асинх", "Proc": "Процедура", "EndProc": "КонецПроцедуры",
|
||||||
|
"Func": "Функция", "EndFunc": "КонецФункции", "Val": "Знач",
|
||||||
|
"Region": "Область", "EndRegion": "КонецОбласти",
|
||||||
|
"If": "Если", "Then": "Тогда", "ElsIf": "ИначеЕсли", "Else": "Иначе", "EndIf": "КонецЕсли",
|
||||||
|
# Platform spells the negation "Не"; we emit "НЕ" as this skill always has —
|
||||||
|
# the language is case-insensitive, and the lower case would churn every snapshot.
|
||||||
|
"And": "И", "Or": "Или", "Not": "НЕ",
|
||||||
|
"Insert": "Вставка", "EndInsert": "КонецВставки", "Delete": "Удаление", "EndDelete": "КонецУдаления",
|
||||||
|
"Before": "Перед", "After": "После", "Around": "Вместо", "Control": "ИзменениеИКонтроль",
|
||||||
|
"Proceed": "ПродолжитьВызов", "Return": "Возврат",
|
||||||
|
# Not a keyword: name of the local the generated Instead-stub declares. Lives here so
|
||||||
|
# that the language of emitted text is decided in exactly one place.
|
||||||
|
"ResultVar": "Результат",
|
||||||
|
"Directives": ["НаКлиенте", "НаСервере", "НаСервереБезКонтекста", "НаКлиентеНаСервереБезКонтекста", "НаКлиентеНаСервере"],
|
||||||
|
},
|
||||||
|
"en": {
|
||||||
|
"Async": "Async", "Proc": "Procedure", "EndProc": "EndProcedure",
|
||||||
|
"Func": "Function", "EndFunc": "EndFunction", "Val": "Val",
|
||||||
|
"Region": "Region", "EndRegion": "EndRegion",
|
||||||
|
"If": "If", "Then": "Then", "ElsIf": "ElsIf", "Else": "Else", "EndIf": "EndIf",
|
||||||
|
"And": "And", "Or": "Or", "Not": "Not",
|
||||||
|
"Insert": "Insert", "EndInsert": "EndInsert", "Delete": "Delete", "EndDelete": "EndDelete",
|
||||||
|
"Before": "Before", "After": "After", "Around": "Around", "Control": "ChangeAndValidate",
|
||||||
|
"Proceed": "ProceedWithCall", "Return": "Return",
|
||||||
|
"ResultVar": "Result",
|
||||||
|
"Directives": ["AtClient", "AtServer", "AtServerNoContext", "AtClientAtServerNoContext", "AtClientAtServer"],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
NS = {
|
NS = {
|
||||||
'md': 'http://v8.1c.ru/8.3/MDClasses',
|
'md': 'http://v8.1c.ru/8.3/MDClasses',
|
||||||
'v8': 'http://v8.1c.ru/8.1/data/core',
|
'v8': 'http://v8.1c.ru/8.1/data/core',
|
||||||
@@ -1267,6 +1303,8 @@ def main():
|
|||||||
r.warn(f'16. {issue}')
|
r.warn(f'16. {issue}')
|
||||||
|
|
||||||
# --- Breadcrumb: controlled methods (&ИзменениеИКонтроль) drift is not checked here ---
|
# --- Breadcrumb: controlled methods (&ИзменениеИКонтроль) drift is not checked here ---
|
||||||
|
ctrl_kw = bsl_keywords()
|
||||||
|
ctrl_re = re.compile(r'^\s*&(?:' + ctrl_kw["ru"]["Control"] + '|' + ctrl_kw["en"]["Control"] + r')\(', re.IGNORECASE)
|
||||||
ctrl_count = 0
|
ctrl_count = 0
|
||||||
for dp, _dn, files in os.walk(config_dir):
|
for dp, _dn, files in os.walk(config_dir):
|
||||||
for fn in files:
|
for fn in files:
|
||||||
@@ -1274,7 +1312,7 @@ def main():
|
|||||||
try:
|
try:
|
||||||
with open(os.path.join(dp, fn), 'r', encoding='utf-8-sig') as f:
|
with open(os.path.join(dp, fn), 'r', encoding='utf-8-sig') as f:
|
||||||
for ln in f:
|
for ln in f:
|
||||||
if re.match(r'^\s*&ИзменениеИКонтроль\(', ln):
|
if ctrl_re.match(ln):
|
||||||
ctrl_count += 1
|
ctrl_count += 1
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
@@ -25,18 +25,18 @@ allowed-tools:
|
|||||||
|
|
||||||
## Параметры подключения
|
## Параметры подключения
|
||||||
|
|
||||||
Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу:
|
Прочитай `.v8-project.json` из корня проекта и разреши базу:
|
||||||
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
||||||
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
||||||
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
||||||
4. Если ветка не совпала — используй `default`
|
4. Если ветка не совпала — используй `default`
|
||||||
|
|
||||||
Если `v8path` не задан — скрипт сам попытается определить платформу.
|
Платформа — `v8path` найденной записи базы, иначе корневой `v8path`. Не задан ни там, ни там — `-V8Path` не передавай.
|
||||||
|
|
||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-cfe-admin.ps1" -Command <команда> <параметры>
|
python ".roo/skills/db-cfe-admin/scripts/db-cfe-admin.py" -Command <команда> <параметры>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Общие параметры
|
### Общие параметры
|
||||||
+9
-4
@@ -1,4 +1,4 @@
|
|||||||
# db-cfe-admin v1.0 — Configuration extensions in a 1C infobase: list, check, properties, delete
|
# db-cfe-admin v1.1 — Configuration extensions in a 1C infobase: list, check, properties, delete
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
||||||
<#
|
<#
|
||||||
@@ -110,9 +110,10 @@ $OutputEncoding = [System.Text.Encoding]::UTF8
|
|||||||
# Копии держит одинаковыми tests/skills/check-inline-drift.mjs — правку вносить в навык-эталон.
|
# Копии держит одинаковыми tests/skills/check-inline-drift.mjs — правку вносить в навык-эталон.
|
||||||
$Extension = $Name
|
$Extension = $Name
|
||||||
|
|
||||||
# --- Реквизиты хранилища из .v8-project.json ---
|
# --- Запись базы в .v8-project.json ---
|
||||||
# Модель их не передаёт: скрипт сопоставляет параметры соединения с записью в databases[]
|
# Модель не передаёт ни путь к платформе конкретной базы, ни реквизиты хранилища: скрипт
|
||||||
# и берёт repository оттуда. Тот же приём, что в cf-edit.ps1 (сопоставление по configSrc).
|
# сопоставляет параметры соединения с записью в databases[] и берёт их оттуда. Тот же приём,
|
||||||
|
# что в cf-edit.ps1 (сопоставление по configSrc).
|
||||||
function Find-V8Project([string]$startDir) {
|
function Find-V8Project([string]$startDir) {
|
||||||
$d = $startDir
|
$d = $startDir
|
||||||
for ($i = 0; $i -lt 20 -and $d; $i++) {
|
for ($i = 0; $i -lt 20 -and $d; $i++) {
|
||||||
@@ -396,6 +397,10 @@ Assert-InfoBaseExists $InfoBasePath
|
|||||||
|
|
||||||
# --- Resolve V8Path ---
|
# --- Resolve V8Path ---
|
||||||
function Find-ProjectV8Path {
|
function Find-ProjectV8Path {
|
||||||
|
# v8path записи базы сильнее корневого: в одном проекте базы живут на разных версиях
|
||||||
|
# платформы, а версию формата выгрузки задаёт та платформа, которая выгружает.
|
||||||
|
$dbRec = Find-ProjectDatabase
|
||||||
|
if ($dbRec -and $dbRec.v8path) { return [string]$dbRec.v8path }
|
||||||
$dir = (Get-Location).Path
|
$dir = (Get-Location).Path
|
||||||
while ($dir) {
|
while ($dir) {
|
||||||
$pf = Join-Path $dir ".v8-project.json"
|
$pf = Join-Path $dir ".v8-project.json"
|
||||||
+17
-9
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# db-cfe-admin v1.0 — Configuration extensions in a 1C infobase: list, check, properties, delete
|
# db-cfe-admin v1.1 — Configuration extensions in a 1C infobase: list, check, properties, delete
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -38,8 +38,15 @@ def ci_parse_args(parser, argv=None):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _find_project_v8path():
|
def _find_project_v8path(args):
|
||||||
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
"""Walk up from CWD to find .v8-project.json and read its v8path.
|
||||||
|
|
||||||
|
v8path записи базы сильнее корневого: в одном проекте базы живут на разных версиях
|
||||||
|
платформы, а версию формата выгрузки задаёт та платформа, которая выгружает.
|
||||||
|
"""
|
||||||
|
db = find_project_database(args)
|
||||||
|
if db and db.get("v8path"):
|
||||||
|
return db["v8path"]
|
||||||
d = os.getcwd()
|
d = os.getcwd()
|
||||||
while True:
|
while True:
|
||||||
pf = os.path.join(d, ".v8-project.json")
|
pf = os.path.join(d, ".v8-project.json")
|
||||||
@@ -88,9 +95,10 @@ V8_SECRET_KEYS = ["/P", "/UC", "/WSP", "/AWSP", "/ConfigurationRepositoryP"]
|
|||||||
IBCMD_SECRET_KEYS = ["--password", "--token", "--db-pwd"]
|
IBCMD_SECRET_KEYS = ["--password", "--token", "--db-pwd"]
|
||||||
|
|
||||||
|
|
||||||
# --- Реквизиты хранилища из .v8-project.json ---
|
# --- Запись базы в .v8-project.json ---
|
||||||
# Модель их не передаёт: скрипт сопоставляет параметры соединения с записью в databases[]
|
# Модель не передаёт ни путь к платформе конкретной базы, ни реквизиты хранилища: скрипт
|
||||||
# и берёт repository оттуда. Тот же приём, что в cf-edit.py (сопоставление по configSrc).
|
# сопоставляет параметры соединения с записью в databases[] и берёт их оттуда. Тот же приём,
|
||||||
|
# что в cf-edit.py (сопоставление по configSrc).
|
||||||
def _sg_find_v8project(start_dir):
|
def _sg_find_v8project(start_dir):
|
||||||
d = start_dir
|
d = start_dir
|
||||||
for _ in range(20):
|
for _ in range(20):
|
||||||
@@ -329,10 +337,10 @@ def _version_key(p):
|
|||||||
return [int(x) for x in re.findall(r"\d+", _version_dir(p))]
|
return [int(x) for x in re.findall(r"\d+", _version_dir(p))]
|
||||||
|
|
||||||
|
|
||||||
def resolve_v8path(v8path):
|
def resolve_v8path(v8path, args):
|
||||||
"""Resolve path to a 1C executable (1cv8; ibcmd only when given explicitly)."""
|
"""Resolve path to a 1C executable (1cv8; ibcmd only when given explicitly)."""
|
||||||
if not v8path:
|
if not v8path:
|
||||||
v8path = _find_project_v8path()
|
v8path = _find_project_v8path(args)
|
||||||
if not v8path:
|
if not v8path:
|
||||||
if os.name == "nt":
|
if os.name == "nt":
|
||||||
candidates = (
|
candidates = (
|
||||||
@@ -629,7 +637,7 @@ def main():
|
|||||||
args.InfoBasePath = clean_path(args.InfoBasePath, "-InfoBasePath")
|
args.InfoBasePath = clean_path(args.InfoBasePath, "-InfoBasePath")
|
||||||
assert_infobase_exists(args.InfoBasePath)
|
assert_infobase_exists(args.InfoBasePath)
|
||||||
|
|
||||||
v8path = resolve_v8path(args.V8Path)
|
v8path = resolve_v8path(args.V8Path, args)
|
||||||
|
|
||||||
# --- Утилиты платформы: нужны обе, выбор по команде ---
|
# --- Утилиты платформы: нужны обе, выбор по команде ---
|
||||||
# -V8Path указывает на каталог bin либо на любой из двух файлов; второй берётся соседом.
|
# -V8Path указывает на каталог bin либо на любой из двух файлов; второй берётся соседом.
|
||||||
@@ -24,14 +24,14 @@ allowed-tools:
|
|||||||
|
|
||||||
## Параметры подключения
|
## Параметры подключения
|
||||||
|
|
||||||
Прочитай `.v8-project.json` из корня проекта для `v8path` (путь к платформе).
|
Прочитай `.v8-project.json` из корня проекта. Платформа — `v8path` записи создаваемой базы,
|
||||||
Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
|
если она уже в реестре, иначе корневой `v8path`. Не задан ни там, ни там — `-V8Path` не передавай.
|
||||||
После создания базы предложи зарегистрировать через `/db-list add`.
|
После создания базы предложи зарегистрировать через `/db-list add`.
|
||||||
|
|
||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-create.ps1" <параметры>
|
python ".roo/skills/db-create/scripts/db-create.py" <параметры>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Параметры скрипта
|
### Параметры скрипта
|
||||||
@@ -59,14 +59,14 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-create.ps1" <п
|
|||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# Создать файловую базу
|
# Создать файловую базу
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-create.ps1" -InfoBasePath "C:\Bases\NewDB"
|
python ".roo/skills/db-create/scripts/db-create.py" -InfoBasePath "C:\Bases\NewDB"
|
||||||
|
|
||||||
# Создать серверную базу
|
# Создать серверную базу
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-create.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Test"
|
python ".roo/skills/db-create/scripts/db-create.py" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Test"
|
||||||
|
|
||||||
# Создать из шаблона CF
|
# Создать из шаблона CF
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-create.ps1" -InfoBasePath "C:\Bases\NewDB" -UseTemplate "C:\Templates\config.cf"
|
python ".roo/skills/db-create/scripts/db-create.py" -InfoBasePath "C:\Bases\NewDB" -UseTemplate "C:\Templates\config.cf"
|
||||||
|
|
||||||
# Создать и добавить в список баз
|
# Создать и добавить в список баз
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-create.ps1" -InfoBasePath "C:\Bases\NewDB" -AddToList -ListName "Новая база"
|
python ".roo/skills/db-create/scripts/db-create.py" -InfoBasePath "C:\Bases\NewDB" -AddToList -ListName "Новая база"
|
||||||
```
|
```
|
||||||
+45
-1
@@ -1,4 +1,4 @@
|
|||||||
# db-create v1.14 — Create 1C information base
|
# db-create v1.15 — Create 1C information base
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
||||||
<#
|
<#
|
||||||
@@ -243,8 +243,52 @@ $V8Path = ConvertTo-CleanPath $V8Path '-V8Path'
|
|||||||
$InfoBasePath = ConvertTo-CleanPath $InfoBasePath '-InfoBasePath'
|
$InfoBasePath = ConvertTo-CleanPath $InfoBasePath '-InfoBasePath'
|
||||||
$UseTemplate = ConvertTo-CleanPath $UseTemplate '-UseTemplate'
|
$UseTemplate = ConvertTo-CleanPath $UseTemplate '-UseTemplate'
|
||||||
|
|
||||||
|
# --- Запись базы в .v8-project.json ---
|
||||||
|
# Модель не передаёт ни путь к платформе конкретной базы, ни реквизиты хранилища: скрипт
|
||||||
|
# сопоставляет параметры соединения с записью в databases[] и берёт их оттуда. Тот же приём,
|
||||||
|
# что в cf-edit.ps1 (сопоставление по configSrc).
|
||||||
|
function Find-V8Project([string]$startDir) {
|
||||||
|
$d = $startDir
|
||||||
|
for ($i = 0; $i -lt 20 -and $d; $i++) {
|
||||||
|
$pj = Join-Path $d ".v8-project.json"
|
||||||
|
if (Test-Path $pj) { return $pj }
|
||||||
|
$parent = [System.IO.Path]::GetDirectoryName($d)
|
||||||
|
if ($parent -eq $d) { break }
|
||||||
|
$d = $parent
|
||||||
|
}
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
function Test-SamePath {
|
||||||
|
param([string]$A, [string]$B)
|
||||||
|
if (-not $A -or -not $B) { return $false }
|
||||||
|
try {
|
||||||
|
$na = [System.IO.Path]::GetFullPath($A).TrimEnd('\', '/')
|
||||||
|
$nb = [System.IO.Path]::GetFullPath($B).TrimEnd('\', '/')
|
||||||
|
return $na.Equals($nb, [System.StringComparison]::OrdinalIgnoreCase)
|
||||||
|
} catch { return $false }
|
||||||
|
}
|
||||||
|
function Find-ProjectDatabase {
|
||||||
|
# Запись базы в реестре, соответствующая переданному соединению. $null, если не найдена.
|
||||||
|
$pf = Find-V8Project (Get-Location).Path
|
||||||
|
if (-not $pf) { return $null }
|
||||||
|
try { $proj = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json } catch { return $null }
|
||||||
|
if (-not $proj.databases) { return $null }
|
||||||
|
foreach ($db in $proj.databases) {
|
||||||
|
if ($InfoBasePath -and $db.path -and (Test-SamePath $db.path $InfoBasePath)) { return $db }
|
||||||
|
if ($InfoBaseServer -and $InfoBaseRef -and $db.server -and $db.ref) {
|
||||||
|
if ($db.server.Equals($InfoBaseServer, [System.StringComparison]::OrdinalIgnoreCase) -and
|
||||||
|
$db.ref.Equals($InfoBaseRef, [System.StringComparison]::OrdinalIgnoreCase)) { return $db }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
# --- Resolve V8Path ---
|
# --- Resolve V8Path ---
|
||||||
function Find-ProjectV8Path {
|
function Find-ProjectV8Path {
|
||||||
|
# v8path записи базы сильнее корневого: в одном проекте базы живут на разных версиях
|
||||||
|
# платформы, а версию формата выгрузки задаёт та платформа, которая выгружает.
|
||||||
|
$dbRec = Find-ProjectDatabase
|
||||||
|
if ($dbRec -and $dbRec.v8path) { return [string]$dbRec.v8path }
|
||||||
$dir = (Get-Location).Path
|
$dir = (Get-Location).Path
|
||||||
while ($dir) {
|
while ($dir) {
|
||||||
$pf = Join-Path $dir ".v8-project.json"
|
$pf = Join-Path $dir ".v8-project.json"
|
||||||
+60
-6
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# db-create v1.14 — Create 1C information base
|
# db-create v1.15 — Create 1C information base
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -37,8 +37,62 @@ def ci_parse_args(parser, argv=None):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _find_project_v8path():
|
# --- Запись базы в .v8-project.json ---
|
||||||
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
# Модель не передаёт ни путь к платформе конкретной базы, ни реквизиты хранилища: скрипт
|
||||||
|
# сопоставляет параметры соединения с записью в databases[] и берёт их оттуда. Тот же приём,
|
||||||
|
# что в cf-edit.py (сопоставление по configSrc).
|
||||||
|
def _sg_find_v8project(start_dir):
|
||||||
|
d = start_dir
|
||||||
|
for _ in range(20):
|
||||||
|
if not d:
|
||||||
|
break
|
||||||
|
pj = os.path.join(d, ".v8-project.json")
|
||||||
|
if os.path.isfile(pj):
|
||||||
|
return pj
|
||||||
|
parent = os.path.dirname(d)
|
||||||
|
if parent == d:
|
||||||
|
break
|
||||||
|
d = parent
|
||||||
|
return None
|
||||||
|
|
||||||
|
def same_path(a, b):
|
||||||
|
if not a or not b:
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
return os.path.abspath(a).rstrip("\\/").lower() == os.path.abspath(b).rstrip("\\/").lower()
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def find_project_database(args):
|
||||||
|
"""Запись базы в реестре, соответствующая переданному соединению. None, если не найдена."""
|
||||||
|
pf = _sg_find_v8project(os.getcwd())
|
||||||
|
if not pf:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
with open(pf, encoding="utf-8-sig") as f:
|
||||||
|
proj = json.load(f)
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
for db in proj.get("databases") or []:
|
||||||
|
if args.InfoBasePath and db.get("path") and same_path(db["path"], args.InfoBasePath):
|
||||||
|
return db
|
||||||
|
if args.InfoBaseServer and args.InfoBaseRef and db.get("server") and db.get("ref"):
|
||||||
|
if (db["server"].lower() == args.InfoBaseServer.lower()
|
||||||
|
and db["ref"].lower() == args.InfoBaseRef.lower()):
|
||||||
|
return db
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _find_project_v8path(args):
|
||||||
|
"""Walk up from CWD to find .v8-project.json and read its v8path.
|
||||||
|
|
||||||
|
v8path записи базы сильнее корневого: в одном проекте базы живут на разных версиях
|
||||||
|
платформы, а версию формата выгрузки задаёт та платформа, которая выгружает.
|
||||||
|
"""
|
||||||
|
db = find_project_database(args)
|
||||||
|
if db and db.get("v8path"):
|
||||||
|
return db["v8path"]
|
||||||
d = os.getcwd()
|
d = os.getcwd()
|
||||||
while True:
|
while True:
|
||||||
pf = os.path.join(d, ".v8-project.json")
|
pf = os.path.join(d, ".v8-project.json")
|
||||||
@@ -243,10 +297,10 @@ def _version_key(p):
|
|||||||
return [int(x) for x in re.findall(r"\d+", _version_dir(p))]
|
return [int(x) for x in re.findall(r"\d+", _version_dir(p))]
|
||||||
|
|
||||||
|
|
||||||
def resolve_v8path(v8path):
|
def resolve_v8path(v8path, args):
|
||||||
"""Resolve path to a 1C executable (1cv8; ibcmd only when given explicitly)."""
|
"""Resolve path to a 1C executable (1cv8; ibcmd only when given explicitly)."""
|
||||||
if not v8path:
|
if not v8path:
|
||||||
v8path = _find_project_v8path()
|
v8path = _find_project_v8path(args)
|
||||||
if not v8path:
|
if not v8path:
|
||||||
if os.name == "nt":
|
if os.name == "nt":
|
||||||
candidates = (
|
candidates = (
|
||||||
@@ -418,7 +472,7 @@ def main():
|
|||||||
args.InfoBasePath = clean_path(args.InfoBasePath, "-InfoBasePath")
|
args.InfoBasePath = clean_path(args.InfoBasePath, "-InfoBasePath")
|
||||||
args.UseTemplate = clean_path(args.UseTemplate, "-UseTemplate")
|
args.UseTemplate = clean_path(args.UseTemplate, "-UseTemplate")
|
||||||
|
|
||||||
v8path = resolve_v8path(args.V8Path)
|
v8path = resolve_v8path(args.V8Path, args)
|
||||||
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
|
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
|
||||||
|
|
||||||
# --- Resolve additional arguments for the selected engine ---
|
# --- Resolve additional arguments for the selected engine ---
|
||||||
@@ -23,19 +23,19 @@ allowed-tools:
|
|||||||
|
|
||||||
## Параметры подключения
|
## Параметры подключения
|
||||||
|
|
||||||
Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу:
|
Прочитай `.v8-project.json` из корня проекта и разреши базу:
|
||||||
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
||||||
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
||||||
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
||||||
4. Если ветка не совпала — используй `default`
|
4. Если ветка не совпала — используй `default`
|
||||||
Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
|
Платформа — `v8path` найденной записи базы, иначе корневой `v8path`. Не задан ни там, ни там — `-V8Path` не передавай.
|
||||||
Если файла нет — предложи `/db-list add`.
|
Если файла нет — предложи `/db-list add`.
|
||||||
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
||||||
|
|
||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-cf.ps1" <параметры>
|
python ".roo/skills/db-dump-cf/scripts/db-dump-cf.py" <параметры>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Параметры скрипта
|
### Параметры скрипта
|
||||||
@@ -60,11 +60,11 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-cf.ps1" <п
|
|||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# Выгрузка конфигурации (файловая база)
|
# Выгрузка конфигурации (файловая база)
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-cf.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -OutputFile "C:\backup\config.cf"
|
python ".roo/skills/db-dump-cf/scripts/db-dump-cf.py" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -OutputFile "C:\backup\config.cf"
|
||||||
|
|
||||||
# Серверная база
|
# Серверная база
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-cf.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Dev" -UserName "Admin" -Password "secret" -OutputFile "config.cf"
|
python ".roo/skills/db-dump-cf/scripts/db-dump-cf.py" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Dev" -UserName "Admin" -Password "secret" -OutputFile "config.cf"
|
||||||
|
|
||||||
# Выгрузка расширения
|
# Выгрузка расширения
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-cf.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -OutputFile "ext.cfe" -Extension "МоёРасширение"
|
python ".roo/skills/db-dump-cf/scripts/db-dump-cf.py" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -OutputFile "ext.cfe" -Extension "МоёРасширение"
|
||||||
```
|
```
|
||||||
+45
-1
@@ -1,4 +1,4 @@
|
|||||||
# db-dump-cf v1.16 — Dump 1C configuration to CF file
|
# db-dump-cf v1.17 — Dump 1C configuration to CF file
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
||||||
<#
|
<#
|
||||||
@@ -272,8 +272,52 @@ function Assert-InfoBaseExists {
|
|||||||
|
|
||||||
Assert-InfoBaseExists $InfoBasePath
|
Assert-InfoBaseExists $InfoBasePath
|
||||||
|
|
||||||
|
# --- Запись базы в .v8-project.json ---
|
||||||
|
# Модель не передаёт ни путь к платформе конкретной базы, ни реквизиты хранилища: скрипт
|
||||||
|
# сопоставляет параметры соединения с записью в databases[] и берёт их оттуда. Тот же приём,
|
||||||
|
# что в cf-edit.ps1 (сопоставление по configSrc).
|
||||||
|
function Find-V8Project([string]$startDir) {
|
||||||
|
$d = $startDir
|
||||||
|
for ($i = 0; $i -lt 20 -and $d; $i++) {
|
||||||
|
$pj = Join-Path $d ".v8-project.json"
|
||||||
|
if (Test-Path $pj) { return $pj }
|
||||||
|
$parent = [System.IO.Path]::GetDirectoryName($d)
|
||||||
|
if ($parent -eq $d) { break }
|
||||||
|
$d = $parent
|
||||||
|
}
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
function Test-SamePath {
|
||||||
|
param([string]$A, [string]$B)
|
||||||
|
if (-not $A -or -not $B) { return $false }
|
||||||
|
try {
|
||||||
|
$na = [System.IO.Path]::GetFullPath($A).TrimEnd('\', '/')
|
||||||
|
$nb = [System.IO.Path]::GetFullPath($B).TrimEnd('\', '/')
|
||||||
|
return $na.Equals($nb, [System.StringComparison]::OrdinalIgnoreCase)
|
||||||
|
} catch { return $false }
|
||||||
|
}
|
||||||
|
function Find-ProjectDatabase {
|
||||||
|
# Запись базы в реестре, соответствующая переданному соединению. $null, если не найдена.
|
||||||
|
$pf = Find-V8Project (Get-Location).Path
|
||||||
|
if (-not $pf) { return $null }
|
||||||
|
try { $proj = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json } catch { return $null }
|
||||||
|
if (-not $proj.databases) { return $null }
|
||||||
|
foreach ($db in $proj.databases) {
|
||||||
|
if ($InfoBasePath -and $db.path -and (Test-SamePath $db.path $InfoBasePath)) { return $db }
|
||||||
|
if ($InfoBaseServer -and $InfoBaseRef -and $db.server -and $db.ref) {
|
||||||
|
if ($db.server.Equals($InfoBaseServer, [System.StringComparison]::OrdinalIgnoreCase) -and
|
||||||
|
$db.ref.Equals($InfoBaseRef, [System.StringComparison]::OrdinalIgnoreCase)) { return $db }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
# --- Resolve V8Path ---
|
# --- Resolve V8Path ---
|
||||||
function Find-ProjectV8Path {
|
function Find-ProjectV8Path {
|
||||||
|
# v8path записи базы сильнее корневого: в одном проекте базы живут на разных версиях
|
||||||
|
# платформы, а версию формата выгрузки задаёт та платформа, которая выгружает.
|
||||||
|
$dbRec = Find-ProjectDatabase
|
||||||
|
if ($dbRec -and $dbRec.v8path) { return [string]$dbRec.v8path }
|
||||||
$dir = (Get-Location).Path
|
$dir = (Get-Location).Path
|
||||||
while ($dir) {
|
while ($dir) {
|
||||||
$pf = Join-Path $dir ".v8-project.json"
|
$pf = Join-Path $dir ".v8-project.json"
|
||||||
+60
-6
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# db-dump-cf v1.16 — Dump 1C configuration to CF file
|
# db-dump-cf v1.17 — Dump 1C configuration to CF file
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -37,8 +37,62 @@ def ci_parse_args(parser, argv=None):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _find_project_v8path():
|
# --- Запись базы в .v8-project.json ---
|
||||||
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
# Модель не передаёт ни путь к платформе конкретной базы, ни реквизиты хранилища: скрипт
|
||||||
|
# сопоставляет параметры соединения с записью в databases[] и берёт их оттуда. Тот же приём,
|
||||||
|
# что в cf-edit.py (сопоставление по configSrc).
|
||||||
|
def _sg_find_v8project(start_dir):
|
||||||
|
d = start_dir
|
||||||
|
for _ in range(20):
|
||||||
|
if not d:
|
||||||
|
break
|
||||||
|
pj = os.path.join(d, ".v8-project.json")
|
||||||
|
if os.path.isfile(pj):
|
||||||
|
return pj
|
||||||
|
parent = os.path.dirname(d)
|
||||||
|
if parent == d:
|
||||||
|
break
|
||||||
|
d = parent
|
||||||
|
return None
|
||||||
|
|
||||||
|
def same_path(a, b):
|
||||||
|
if not a or not b:
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
return os.path.abspath(a).rstrip("\\/").lower() == os.path.abspath(b).rstrip("\\/").lower()
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def find_project_database(args):
|
||||||
|
"""Запись базы в реестре, соответствующая переданному соединению. None, если не найдена."""
|
||||||
|
pf = _sg_find_v8project(os.getcwd())
|
||||||
|
if not pf:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
with open(pf, encoding="utf-8-sig") as f:
|
||||||
|
proj = json.load(f)
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
for db in proj.get("databases") or []:
|
||||||
|
if args.InfoBasePath and db.get("path") and same_path(db["path"], args.InfoBasePath):
|
||||||
|
return db
|
||||||
|
if args.InfoBaseServer and args.InfoBaseRef and db.get("server") and db.get("ref"):
|
||||||
|
if (db["server"].lower() == args.InfoBaseServer.lower()
|
||||||
|
and db["ref"].lower() == args.InfoBaseRef.lower()):
|
||||||
|
return db
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _find_project_v8path(args):
|
||||||
|
"""Walk up from CWD to find .v8-project.json and read its v8path.
|
||||||
|
|
||||||
|
v8path записи базы сильнее корневого: в одном проекте базы живут на разных версиях
|
||||||
|
платформы, а версию формата выгрузки задаёт та платформа, которая выгружает.
|
||||||
|
"""
|
||||||
|
db = find_project_database(args)
|
||||||
|
if db and db.get("v8path"):
|
||||||
|
return db["v8path"]
|
||||||
d = os.getcwd()
|
d = os.getcwd()
|
||||||
while True:
|
while True:
|
||||||
pf = os.path.join(d, ".v8-project.json")
|
pf = os.path.join(d, ".v8-project.json")
|
||||||
@@ -243,10 +297,10 @@ def _version_key(p):
|
|||||||
return [int(x) for x in re.findall(r"\d+", _version_dir(p))]
|
return [int(x) for x in re.findall(r"\d+", _version_dir(p))]
|
||||||
|
|
||||||
|
|
||||||
def resolve_v8path(v8path):
|
def resolve_v8path(v8path, args):
|
||||||
"""Resolve path to a 1C executable (1cv8; ibcmd only when given explicitly)."""
|
"""Resolve path to a 1C executable (1cv8; ibcmd only when given explicitly)."""
|
||||||
if not v8path:
|
if not v8path:
|
||||||
v8path = _find_project_v8path()
|
v8path = _find_project_v8path(args)
|
||||||
if not v8path:
|
if not v8path:
|
||||||
if os.name == "nt":
|
if os.name == "nt":
|
||||||
candidates = (
|
candidates = (
|
||||||
@@ -439,7 +493,7 @@ def main():
|
|||||||
assert_infobase_exists(args.InfoBasePath)
|
assert_infobase_exists(args.InfoBasePath)
|
||||||
args.OutputFile = clean_path(args.OutputFile, "-OutputFile")
|
args.OutputFile = clean_path(args.OutputFile, "-OutputFile")
|
||||||
|
|
||||||
v8path = resolve_v8path(args.V8Path)
|
v8path = resolve_v8path(args.V8Path, args)
|
||||||
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
|
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
|
||||||
|
|
||||||
# --- Resolve additional arguments for the selected engine ---
|
# --- Resolve additional arguments for the selected engine ---
|
||||||
@@ -26,19 +26,19 @@ allowed-tools:
|
|||||||
|
|
||||||
## Параметры подключения
|
## Параметры подключения
|
||||||
|
|
||||||
Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу:
|
Прочитай `.v8-project.json` из корня проекта и разреши базу:
|
||||||
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
||||||
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
||||||
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
||||||
4. Если ветка не совпала — используй `default`
|
4. Если ветка не совпала — используй `default`
|
||||||
Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
|
Платформа — `v8path` найденной записи базы, иначе корневой `v8path`. Не задан ни там, ни там — `-V8Path` не передавай.
|
||||||
Если файла нет — предложи `/db-list add`.
|
Если файла нет — предложи `/db-list add`.
|
||||||
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
||||||
|
|
||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-dt.ps1" <параметры>
|
python ".roo/skills/db-dump-dt/scripts/db-dump-dt.py" <параметры>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Параметры скрипта
|
### Параметры скрипта
|
||||||
@@ -61,10 +61,10 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-dt.ps1" <п
|
|||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# Выгрузка ИБ (файловая база)
|
# Выгрузка ИБ (файловая база)
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-dt.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -OutputFile "C:\backup\base.dt"
|
python ".roo/skills/db-dump-dt/scripts/db-dump-dt.py" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -OutputFile "C:\backup\base.dt"
|
||||||
|
|
||||||
# Серверная база
|
# Серверная база
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-dt.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Dev" -UserName "Admin" -Password "secret" -OutputFile "base.dt"
|
python ".roo/skills/db-dump-dt/scripts/db-dump-dt.py" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Dev" -UserName "Admin" -Password "secret" -OutputFile "base.dt"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Связанные навыки
|
## Связанные навыки
|
||||||
+45
-1
@@ -1,4 +1,4 @@
|
|||||||
# db-dump-dt v1.15 — Dump 1C information base to DT file
|
# db-dump-dt v1.16 — Dump 1C information base to DT file
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
||||||
<#
|
<#
|
||||||
@@ -256,8 +256,52 @@ function Assert-InfoBaseExists {
|
|||||||
|
|
||||||
Assert-InfoBaseExists $InfoBasePath
|
Assert-InfoBaseExists $InfoBasePath
|
||||||
|
|
||||||
|
# --- Запись базы в .v8-project.json ---
|
||||||
|
# Модель не передаёт ни путь к платформе конкретной базы, ни реквизиты хранилища: скрипт
|
||||||
|
# сопоставляет параметры соединения с записью в databases[] и берёт их оттуда. Тот же приём,
|
||||||
|
# что в cf-edit.ps1 (сопоставление по configSrc).
|
||||||
|
function Find-V8Project([string]$startDir) {
|
||||||
|
$d = $startDir
|
||||||
|
for ($i = 0; $i -lt 20 -and $d; $i++) {
|
||||||
|
$pj = Join-Path $d ".v8-project.json"
|
||||||
|
if (Test-Path $pj) { return $pj }
|
||||||
|
$parent = [System.IO.Path]::GetDirectoryName($d)
|
||||||
|
if ($parent -eq $d) { break }
|
||||||
|
$d = $parent
|
||||||
|
}
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
function Test-SamePath {
|
||||||
|
param([string]$A, [string]$B)
|
||||||
|
if (-not $A -or -not $B) { return $false }
|
||||||
|
try {
|
||||||
|
$na = [System.IO.Path]::GetFullPath($A).TrimEnd('\', '/')
|
||||||
|
$nb = [System.IO.Path]::GetFullPath($B).TrimEnd('\', '/')
|
||||||
|
return $na.Equals($nb, [System.StringComparison]::OrdinalIgnoreCase)
|
||||||
|
} catch { return $false }
|
||||||
|
}
|
||||||
|
function Find-ProjectDatabase {
|
||||||
|
# Запись базы в реестре, соответствующая переданному соединению. $null, если не найдена.
|
||||||
|
$pf = Find-V8Project (Get-Location).Path
|
||||||
|
if (-not $pf) { return $null }
|
||||||
|
try { $proj = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json } catch { return $null }
|
||||||
|
if (-not $proj.databases) { return $null }
|
||||||
|
foreach ($db in $proj.databases) {
|
||||||
|
if ($InfoBasePath -and $db.path -and (Test-SamePath $db.path $InfoBasePath)) { return $db }
|
||||||
|
if ($InfoBaseServer -and $InfoBaseRef -and $db.server -and $db.ref) {
|
||||||
|
if ($db.server.Equals($InfoBaseServer, [System.StringComparison]::OrdinalIgnoreCase) -and
|
||||||
|
$db.ref.Equals($InfoBaseRef, [System.StringComparison]::OrdinalIgnoreCase)) { return $db }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
# --- Resolve V8Path ---
|
# --- Resolve V8Path ---
|
||||||
function Find-ProjectV8Path {
|
function Find-ProjectV8Path {
|
||||||
|
# v8path записи базы сильнее корневого: в одном проекте базы живут на разных версиях
|
||||||
|
# платформы, а версию формата выгрузки задаёт та платформа, которая выгружает.
|
||||||
|
$dbRec = Find-ProjectDatabase
|
||||||
|
if ($dbRec -and $dbRec.v8path) { return [string]$dbRec.v8path }
|
||||||
$dir = (Get-Location).Path
|
$dir = (Get-Location).Path
|
||||||
while ($dir) {
|
while ($dir) {
|
||||||
$pf = Join-Path $dir ".v8-project.json"
|
$pf = Join-Path $dir ".v8-project.json"
|
||||||
+60
-6
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# db-dump-dt v1.15 — Dump 1C information base to DT file
|
# db-dump-dt v1.16 — Dump 1C information base to DT file
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -37,8 +37,62 @@ def ci_parse_args(parser, argv=None):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _find_project_v8path():
|
# --- Запись базы в .v8-project.json ---
|
||||||
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
# Модель не передаёт ни путь к платформе конкретной базы, ни реквизиты хранилища: скрипт
|
||||||
|
# сопоставляет параметры соединения с записью в databases[] и берёт их оттуда. Тот же приём,
|
||||||
|
# что в cf-edit.py (сопоставление по configSrc).
|
||||||
|
def _sg_find_v8project(start_dir):
|
||||||
|
d = start_dir
|
||||||
|
for _ in range(20):
|
||||||
|
if not d:
|
||||||
|
break
|
||||||
|
pj = os.path.join(d, ".v8-project.json")
|
||||||
|
if os.path.isfile(pj):
|
||||||
|
return pj
|
||||||
|
parent = os.path.dirname(d)
|
||||||
|
if parent == d:
|
||||||
|
break
|
||||||
|
d = parent
|
||||||
|
return None
|
||||||
|
|
||||||
|
def same_path(a, b):
|
||||||
|
if not a or not b:
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
return os.path.abspath(a).rstrip("\\/").lower() == os.path.abspath(b).rstrip("\\/").lower()
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def find_project_database(args):
|
||||||
|
"""Запись базы в реестре, соответствующая переданному соединению. None, если не найдена."""
|
||||||
|
pf = _sg_find_v8project(os.getcwd())
|
||||||
|
if not pf:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
with open(pf, encoding="utf-8-sig") as f:
|
||||||
|
proj = json.load(f)
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
for db in proj.get("databases") or []:
|
||||||
|
if args.InfoBasePath and db.get("path") and same_path(db["path"], args.InfoBasePath):
|
||||||
|
return db
|
||||||
|
if args.InfoBaseServer and args.InfoBaseRef and db.get("server") and db.get("ref"):
|
||||||
|
if (db["server"].lower() == args.InfoBaseServer.lower()
|
||||||
|
and db["ref"].lower() == args.InfoBaseRef.lower()):
|
||||||
|
return db
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _find_project_v8path(args):
|
||||||
|
"""Walk up from CWD to find .v8-project.json and read its v8path.
|
||||||
|
|
||||||
|
v8path записи базы сильнее корневого: в одном проекте базы живут на разных версиях
|
||||||
|
платформы, а версию формата выгрузки задаёт та платформа, которая выгружает.
|
||||||
|
"""
|
||||||
|
db = find_project_database(args)
|
||||||
|
if db and db.get("v8path"):
|
||||||
|
return db["v8path"]
|
||||||
d = os.getcwd()
|
d = os.getcwd()
|
||||||
while True:
|
while True:
|
||||||
pf = os.path.join(d, ".v8-project.json")
|
pf = os.path.join(d, ".v8-project.json")
|
||||||
@@ -243,10 +297,10 @@ def _version_key(p):
|
|||||||
return [int(x) for x in re.findall(r"\d+", _version_dir(p))]
|
return [int(x) for x in re.findall(r"\d+", _version_dir(p))]
|
||||||
|
|
||||||
|
|
||||||
def resolve_v8path(v8path):
|
def resolve_v8path(v8path, args):
|
||||||
"""Resolve path to a 1C executable (1cv8; ibcmd only when given explicitly)."""
|
"""Resolve path to a 1C executable (1cv8; ibcmd only when given explicitly)."""
|
||||||
if not v8path:
|
if not v8path:
|
||||||
v8path = _find_project_v8path()
|
v8path = _find_project_v8path(args)
|
||||||
if not v8path:
|
if not v8path:
|
||||||
if os.name == "nt":
|
if os.name == "nt":
|
||||||
candidates = (
|
candidates = (
|
||||||
@@ -437,7 +491,7 @@ def main():
|
|||||||
assert_infobase_exists(args.InfoBasePath)
|
assert_infobase_exists(args.InfoBasePath)
|
||||||
args.OutputFile = clean_path(args.OutputFile, "-OutputFile")
|
args.OutputFile = clean_path(args.OutputFile, "-OutputFile")
|
||||||
|
|
||||||
v8path = resolve_v8path(args.V8Path)
|
v8path = resolve_v8path(args.V8Path, args)
|
||||||
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
|
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
|
||||||
|
|
||||||
# --- Resolve additional arguments for the selected engine ---
|
# --- Resolve additional arguments for the selected engine ---
|
||||||
@@ -24,12 +24,12 @@ allowed-tools:
|
|||||||
|
|
||||||
## Параметры подключения
|
## Параметры подключения
|
||||||
|
|
||||||
Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу:
|
Прочитай `.v8-project.json` из корня проекта и разреши базу:
|
||||||
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
||||||
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
||||||
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
||||||
4. Если ветка не совпала — используй `default`
|
4. Если ветка не совпала — используй `default`
|
||||||
Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
|
Платформа — `v8path` найденной записи базы, иначе корневой `v8path`. Не задан ни там, ни там — `-V8Path` не передавай.
|
||||||
Если файла нет — предложи `/db-list add`.
|
Если файла нет — предложи `/db-list add`.
|
||||||
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
||||||
Если в записи базы указан `configSrc` — используй как каталог выгрузки по умолчанию.
|
Если в записи базы указан `configSrc` — используй как каталог выгрузки по умолчанию.
|
||||||
@@ -38,7 +38,7 @@ allowed-tools:
|
|||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-xml.ps1" <параметры>
|
python ".roo/skills/db-dump-xml/scripts/db-dump-xml.py" <параметры>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Параметры скрипта
|
### Параметры скрипта
|
||||||
@@ -77,17 +77,17 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-xml.ps1" <
|
|||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# Полная выгрузка (файловая база)
|
# Полная выгрузка (файловая база)
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-xml.ps1" -V8Path "C:\Program Files\1cv8\8.3.25.1257\bin" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Full
|
python ".roo/skills/db-dump-xml/scripts/db-dump-xml.py" -V8Path "C:\Program Files\1cv8\8.3.25.1257\bin" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Full
|
||||||
|
|
||||||
# Инкрементальная выгрузка
|
# Инкрементальная выгрузка
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-xml.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Changes
|
python ".roo/skills/db-dump-xml/scripts/db-dump-xml.py" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Changes
|
||||||
|
|
||||||
# Частичная выгрузка
|
# Частичная выгрузка
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-xml.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Partial -Objects "Справочник.Номенклатура,Документ.Заказ"
|
python ".roo/skills/db-dump-xml/scripts/db-dump-xml.py" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Partial -Objects "Справочник.Номенклатура,Документ.Заказ"
|
||||||
|
|
||||||
# Серверная база
|
# Серверная база
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-xml.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Dev" -UserName "Admin" -Password "secret" -ConfigDir "C:\WS\cfsrc" -Mode Full
|
python ".roo/skills/db-dump-xml/scripts/db-dump-xml.py" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Dev" -UserName "Admin" -Password "secret" -ConfigDir "C:\WS\cfsrc" -Mode Full
|
||||||
|
|
||||||
# Выгрузка расширения
|
# Выгрузка расширения
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-xml.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\ext_src" -Mode Full -Extension "МоёРасширение"
|
python ".roo/skills/db-dump-xml/scripts/db-dump-xml.py" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\ext_src" -Mode Full -Extension "МоёРасширение"
|
||||||
```
|
```
|
||||||
+9
-4
@@ -1,4 +1,4 @@
|
|||||||
# db-dump-xml v1.21 — Dump 1C configuration to XML files
|
# db-dump-xml v1.22 — Dump 1C configuration to XML files
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
||||||
<#
|
<#
|
||||||
@@ -125,9 +125,10 @@ param(
|
|||||||
$OutputEncoding = [System.Text.Encoding]::UTF8
|
$OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
|
|
||||||
# --- Реквизиты хранилища из .v8-project.json ---
|
# --- Запись базы в .v8-project.json ---
|
||||||
# Модель их не передаёт: скрипт сопоставляет параметры соединения с записью в databases[]
|
# Модель не передаёт ни путь к платформе конкретной базы, ни реквизиты хранилища: скрипт
|
||||||
# и берёт repository оттуда. Тот же приём, что в cf-edit.ps1 (сопоставление по configSrc).
|
# сопоставляет параметры соединения с записью в databases[] и берёт их оттуда. Тот же приём,
|
||||||
|
# что в cf-edit.ps1 (сопоставление по configSrc).
|
||||||
function Find-V8Project([string]$startDir) {
|
function Find-V8Project([string]$startDir) {
|
||||||
$d = $startDir
|
$d = $startDir
|
||||||
for ($i = 0; $i -lt 20 -and $d; $i++) {
|
for ($i = 0; $i -lt 20 -and $d; $i++) {
|
||||||
@@ -395,6 +396,10 @@ Assert-InfoBaseExists $InfoBasePath
|
|||||||
|
|
||||||
# --- Resolve V8Path ---
|
# --- Resolve V8Path ---
|
||||||
function Find-ProjectV8Path {
|
function Find-ProjectV8Path {
|
||||||
|
# v8path записи базы сильнее корневого: в одном проекте базы живут на разных версиях
|
||||||
|
# платформы, а версию формата выгрузки задаёт та платформа, которая выгружает.
|
||||||
|
$dbRec = Find-ProjectDatabase
|
||||||
|
if ($dbRec -and $dbRec.v8path) { return [string]$dbRec.v8path }
|
||||||
$dir = (Get-Location).Path
|
$dir = (Get-Location).Path
|
||||||
while ($dir) {
|
while ($dir) {
|
||||||
$pf = Join-Path $dir ".v8-project.json"
|
$pf = Join-Path $dir ".v8-project.json"
|
||||||
+17
-9
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# db-dump-xml v1.21 — Dump 1C configuration to XML files
|
# db-dump-xml v1.22 — Dump 1C configuration to XML files
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -37,8 +37,15 @@ def ci_parse_args(parser, argv=None):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _find_project_v8path():
|
def _find_project_v8path(args):
|
||||||
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
"""Walk up from CWD to find .v8-project.json and read its v8path.
|
||||||
|
|
||||||
|
v8path записи базы сильнее корневого: в одном проекте базы живут на разных версиях
|
||||||
|
платформы, а версию формата выгрузки задаёт та платформа, которая выгружает.
|
||||||
|
"""
|
||||||
|
db = find_project_database(args)
|
||||||
|
if db and db.get("v8path"):
|
||||||
|
return db["v8path"]
|
||||||
d = os.getcwd()
|
d = os.getcwd()
|
||||||
while True:
|
while True:
|
||||||
pf = os.path.join(d, ".v8-project.json")
|
pf = os.path.join(d, ".v8-project.json")
|
||||||
@@ -87,9 +94,10 @@ V8_SECRET_KEYS = ["/P", "/UC", "/WSP", "/AWSP", "/ConfigurationRepositoryP"]
|
|||||||
IBCMD_SECRET_KEYS = ["--password", "--token", "--db-pwd"]
|
IBCMD_SECRET_KEYS = ["--password", "--token", "--db-pwd"]
|
||||||
|
|
||||||
|
|
||||||
# --- Реквизиты хранилища из .v8-project.json ---
|
# --- Запись базы в .v8-project.json ---
|
||||||
# Модель их не передаёт: скрипт сопоставляет параметры соединения с записью в databases[]
|
# Модель не передаёт ни путь к платформе конкретной базы, ни реквизиты хранилища: скрипт
|
||||||
# и берёт repository оттуда. Тот же приём, что в cf-edit.py (сопоставление по configSrc).
|
# сопоставляет параметры соединения с записью в databases[] и берёт их оттуда. Тот же приём,
|
||||||
|
# что в cf-edit.py (сопоставление по configSrc).
|
||||||
def _sg_find_v8project(start_dir):
|
def _sg_find_v8project(start_dir):
|
||||||
d = start_dir
|
d = start_dir
|
||||||
for _ in range(20):
|
for _ in range(20):
|
||||||
@@ -328,10 +336,10 @@ def _version_key(p):
|
|||||||
return [int(x) for x in re.findall(r"\d+", _version_dir(p))]
|
return [int(x) for x in re.findall(r"\d+", _version_dir(p))]
|
||||||
|
|
||||||
|
|
||||||
def resolve_v8path(v8path):
|
def resolve_v8path(v8path, args):
|
||||||
"""Resolve path to a 1C executable (1cv8; ibcmd only when given explicitly)."""
|
"""Resolve path to a 1C executable (1cv8; ibcmd only when given explicitly)."""
|
||||||
if not v8path:
|
if not v8path:
|
||||||
v8path = _find_project_v8path()
|
v8path = _find_project_v8path(args)
|
||||||
if not v8path:
|
if not v8path:
|
||||||
if os.name == "nt":
|
if os.name == "nt":
|
||||||
candidates = (
|
candidates = (
|
||||||
@@ -542,7 +550,7 @@ def main():
|
|||||||
args.ConfigDir = clean_path(args.ConfigDir, "-ConfigDir")
|
args.ConfigDir = clean_path(args.ConfigDir, "-ConfigDir")
|
||||||
|
|
||||||
# --- Resolve V8Path ---
|
# --- Resolve V8Path ---
|
||||||
v8path = resolve_v8path(args.V8Path)
|
v8path = resolve_v8path(args.V8Path, args)
|
||||||
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
|
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
|
||||||
|
|
||||||
# --- Resolve additional arguments for the selected engine ---
|
# --- Resolve additional arguments for the selected engine ---
|
||||||
@@ -36,6 +36,7 @@ allowed-tools:
|
|||||||
"name": "Разработка",
|
"name": "Разработка",
|
||||||
"type": "file",
|
"type": "file",
|
||||||
"path": "C:\\Bases\\MyApp_Dev",
|
"path": "C:\\Bases\\MyApp_Dev",
|
||||||
|
"v8path": "C:\\Program Files\\1cv8\\8.3.27.1688\\bin",
|
||||||
"user": "Admin",
|
"user": "Admin",
|
||||||
"password": "",
|
"password": "",
|
||||||
"aliases": ["dev", "разработка"],
|
"aliases": ["dev", "разработка"],
|
||||||
@@ -92,6 +93,7 @@ allowed-tools:
|
|||||||
| `path` | string | для file | Путь к каталогу файловой базы |
|
| `path` | string | для file | Путь к каталогу файловой базы |
|
||||||
| `server` | string | для server | Адрес сервера 1С |
|
| `server` | string | для server | Адрес сервера 1С |
|
||||||
| `ref` | string | для server | Имя базы на сервере |
|
| `ref` | string | для server | Имя базы на сервере |
|
||||||
|
| `v8path` | string | нет | Платформа именно этой базы — перекрывает корневой `v8path` (см. ниже) |
|
||||||
| `user` | string | нет | Имя пользователя 1С |
|
| `user` | string | нет | Имя пользователя 1С |
|
||||||
| `password` | string | нет | Пароль |
|
| `password` | string | нет | Пароль |
|
||||||
| `aliases` | string[] | нет | Альтернативные имена для быстрого доступа |
|
| `aliases` | string[] | нет | Альтернативные имена для быстрого доступа |
|
||||||
@@ -145,9 +147,15 @@ allowed-tools:
|
|||||||
|
|
||||||
После выполнения: если использованная база не зарегистрирована — предложи добавить через `/db-list add`.
|
После выполнения: если использованная база не зарегистрирована — предложи добавить через `/db-list add`.
|
||||||
|
|
||||||
### Автоопределение платформы
|
### Выбор платформы
|
||||||
|
|
||||||
Если `v8path` не задан в конфиге:
|
Порядок: явный `-V8Path` в вызове → `v8path` записи базы → корневой `v8path` → автоопределение.
|
||||||
|
Запись базы находит сам скрипт, сопоставляя параметры соединения с `databases[]`, — передавать
|
||||||
|
`-V8Path` руками не нужно. Поле у базы нужно, когда базы проекта живут на разных версиях платформы:
|
||||||
|
формат XML-выгрузки задаёт та платформа, которая выгружает, а автоопределение берёт самую старшую
|
||||||
|
установленную.
|
||||||
|
|
||||||
|
Если `v8path` не задан нигде:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
$v8 = Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort-Object -Descending | Select-Object -First 1
|
$v8 = Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort-Object -Descending | Select-Object -First 1
|
||||||
@@ -24,19 +24,19 @@ allowed-tools:
|
|||||||
|
|
||||||
## Параметры подключения
|
## Параметры подключения
|
||||||
|
|
||||||
Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу:
|
Прочитай `.v8-project.json` из корня проекта и разреши базу:
|
||||||
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
||||||
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
||||||
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
||||||
4. Если ветка не совпала — используй `default`
|
4. Если ветка не совпала — используй `default`
|
||||||
Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
|
Платформа — `v8path` найденной записи базы, иначе корневой `v8path`. Не задан ни там, ни там — `-V8Path` не передавай.
|
||||||
Если файла нет — предложи `/db-list add`.
|
Если файла нет — предложи `/db-list add`.
|
||||||
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
||||||
|
|
||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-cf.ps1" <параметры>
|
python ".roo/skills/db-load-cf/scripts/db-load-cf.py" <параметры>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Параметры скрипта
|
### Параметры скрипта
|
||||||
@@ -66,11 +66,11 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-cf.ps1" <п
|
|||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# Файловая база
|
# Файловая база
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-cf.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -InputFile "C:\backup\config.cf"
|
python ".roo/skills/db-load-cf/scripts/db-load-cf.py" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -InputFile "C:\backup\config.cf"
|
||||||
|
|
||||||
# Серверная база
|
# Серверная база
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-cf.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Test" -UserName "Admin" -Password "secret" -InputFile "config.cf"
|
python ".roo/skills/db-load-cf/scripts/db-load-cf.py" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Test" -UserName "Admin" -Password "secret" -InputFile "config.cf"
|
||||||
|
|
||||||
# Загрузка расширения
|
# Загрузка расширения
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-cf.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -InputFile "ext.cfe" -Extension "МоёРасширение"
|
python ".roo/skills/db-load-cf/scripts/db-load-cf.py" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -InputFile "ext.cfe" -Extension "МоёРасширение"
|
||||||
```
|
```
|
||||||
+45
-13
@@ -1,4 +1,4 @@
|
|||||||
# db-load-cf v1.17 — Load 1C configuration from CF file
|
# db-load-cf v1.19 — Load 1C configuration from CF file
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
||||||
<#
|
<#
|
||||||
@@ -299,8 +299,52 @@ function Assert-InfoBaseExists {
|
|||||||
|
|
||||||
Assert-InfoBaseExists $InfoBasePath
|
Assert-InfoBaseExists $InfoBasePath
|
||||||
|
|
||||||
|
# --- Запись базы в .v8-project.json ---
|
||||||
|
# Модель не передаёт ни путь к платформе конкретной базы, ни реквизиты хранилища: скрипт
|
||||||
|
# сопоставляет параметры соединения с записью в databases[] и берёт их оттуда. Тот же приём,
|
||||||
|
# что в cf-edit.ps1 (сопоставление по configSrc).
|
||||||
|
function Find-V8Project([string]$startDir) {
|
||||||
|
$d = $startDir
|
||||||
|
for ($i = 0; $i -lt 20 -and $d; $i++) {
|
||||||
|
$pj = Join-Path $d ".v8-project.json"
|
||||||
|
if (Test-Path $pj) { return $pj }
|
||||||
|
$parent = [System.IO.Path]::GetDirectoryName($d)
|
||||||
|
if ($parent -eq $d) { break }
|
||||||
|
$d = $parent
|
||||||
|
}
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
function Test-SamePath {
|
||||||
|
param([string]$A, [string]$B)
|
||||||
|
if (-not $A -or -not $B) { return $false }
|
||||||
|
try {
|
||||||
|
$na = [System.IO.Path]::GetFullPath($A).TrimEnd('\', '/')
|
||||||
|
$nb = [System.IO.Path]::GetFullPath($B).TrimEnd('\', '/')
|
||||||
|
return $na.Equals($nb, [System.StringComparison]::OrdinalIgnoreCase)
|
||||||
|
} catch { return $false }
|
||||||
|
}
|
||||||
|
function Find-ProjectDatabase {
|
||||||
|
# Запись базы в реестре, соответствующая переданному соединению. $null, если не найдена.
|
||||||
|
$pf = Find-V8Project (Get-Location).Path
|
||||||
|
if (-not $pf) { return $null }
|
||||||
|
try { $proj = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json } catch { return $null }
|
||||||
|
if (-not $proj.databases) { return $null }
|
||||||
|
foreach ($db in $proj.databases) {
|
||||||
|
if ($InfoBasePath -and $db.path -and (Test-SamePath $db.path $InfoBasePath)) { return $db }
|
||||||
|
if ($InfoBaseServer -and $InfoBaseRef -and $db.server -and $db.ref) {
|
||||||
|
if ($db.server.Equals($InfoBaseServer, [System.StringComparison]::OrdinalIgnoreCase) -and
|
||||||
|
$db.ref.Equals($InfoBaseRef, [System.StringComparison]::OrdinalIgnoreCase)) { return $db }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
# --- Resolve V8Path ---
|
# --- Resolve V8Path ---
|
||||||
function Find-ProjectV8Path {
|
function Find-ProjectV8Path {
|
||||||
|
# v8path записи базы сильнее корневого: в одном проекте базы живут на разных версиях
|
||||||
|
# платформы, а версию формата выгрузки задаёт та платформа, которая выгружает.
|
||||||
|
$dbRec = Find-ProjectDatabase
|
||||||
|
if ($dbRec -and $dbRec.v8path) { return [string]$dbRec.v8path }
|
||||||
$dir = (Get-Location).Path
|
$dir = (Get-Location).Path
|
||||||
while ($dir) {
|
while ($dir) {
|
||||||
$pf = Join-Path $dir ".v8-project.json"
|
$pf = Join-Path $dir ".v8-project.json"
|
||||||
@@ -413,18 +457,6 @@ function Write-PlatformOutput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function Find-V8Project([string]$startDir) {
|
|
||||||
$d = $startDir
|
|
||||||
for ($i = 0; $i -lt 20 -and $d; $i++) {
|
|
||||||
$pj = Join-Path $d ".v8-project.json"
|
|
||||||
if (Test-Path $pj) { return $pj }
|
|
||||||
$parent = [System.IO.Path]::GetDirectoryName($d)
|
|
||||||
if ($parent -eq $d) { break }
|
|
||||||
$d = $parent
|
|
||||||
}
|
|
||||||
return $null
|
|
||||||
}
|
|
||||||
|
|
||||||
# Постусловие применимости расширения: платформа отчитывается успехом и о расширении, которое
|
# Постусловие применимости расширения: платформа отчитывается успехом и о расширении, которое
|
||||||
# не применит — отказ всплывает лениво, при первом вызове метода, записью в журнал регистрации.
|
# не применит — отказ всплывает лениво, при первом вызове метода, записью в журнал регистрации.
|
||||||
#
|
#
|
||||||
+46
-6
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# db-load-cf v1.17 — Load 1C configuration from CF file
|
# db-load-cf v1.19 — Load 1C configuration from CF file
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -37,8 +37,48 @@ def ci_parse_args(parser, argv=None):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _find_project_v8path():
|
# --- Запись базы в .v8-project.json ---
|
||||||
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
# Модель не передаёт ни путь к платформе конкретной базы, ни реквизиты хранилища: скрипт
|
||||||
|
# сопоставляет параметры соединения с записью в databases[] и берёт их оттуда. Тот же приём,
|
||||||
|
# что в cf-edit.py (сопоставление по configSrc).
|
||||||
|
def same_path(a, b):
|
||||||
|
if not a or not b:
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
return os.path.abspath(a).rstrip("\\/").lower() == os.path.abspath(b).rstrip("\\/").lower()
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def find_project_database(args):
|
||||||
|
"""Запись базы в реестре, соответствующая переданному соединению. None, если не найдена."""
|
||||||
|
pf = _sg_find_v8project(os.getcwd())
|
||||||
|
if not pf:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
with open(pf, encoding="utf-8-sig") as f:
|
||||||
|
proj = json.load(f)
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
for db in proj.get("databases") or []:
|
||||||
|
if args.InfoBasePath and db.get("path") and same_path(db["path"], args.InfoBasePath):
|
||||||
|
return db
|
||||||
|
if args.InfoBaseServer and args.InfoBaseRef and db.get("server") and db.get("ref"):
|
||||||
|
if (db["server"].lower() == args.InfoBaseServer.lower()
|
||||||
|
and db["ref"].lower() == args.InfoBaseRef.lower()):
|
||||||
|
return db
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _find_project_v8path(args):
|
||||||
|
"""Walk up from CWD to find .v8-project.json and read its v8path.
|
||||||
|
|
||||||
|
v8path записи базы сильнее корневого: в одном проекте базы живут на разных версиях
|
||||||
|
платформы, а версию формата выгрузки задаёт та платформа, которая выгружает.
|
||||||
|
"""
|
||||||
|
db = find_project_database(args)
|
||||||
|
if db and db.get("v8path"):
|
||||||
|
return db["v8path"]
|
||||||
d = os.getcwd()
|
d = os.getcwd()
|
||||||
while True:
|
while True:
|
||||||
pf = os.path.join(d, ".v8-project.json")
|
pf = os.path.join(d, ".v8-project.json")
|
||||||
@@ -243,10 +283,10 @@ def _version_key(p):
|
|||||||
return [int(x) for x in re.findall(r"\d+", _version_dir(p))]
|
return [int(x) for x in re.findall(r"\d+", _version_dir(p))]
|
||||||
|
|
||||||
|
|
||||||
def resolve_v8path(v8path):
|
def resolve_v8path(v8path, args):
|
||||||
"""Resolve path to a 1C executable (1cv8; ibcmd only when given explicitly)."""
|
"""Resolve path to a 1C executable (1cv8; ibcmd only when given explicitly)."""
|
||||||
if not v8path:
|
if not v8path:
|
||||||
v8path = _find_project_v8path()
|
v8path = _find_project_v8path(args)
|
||||||
if not v8path:
|
if not v8path:
|
||||||
if os.name == "nt":
|
if os.name == "nt":
|
||||||
candidates = (
|
candidates = (
|
||||||
@@ -548,7 +588,7 @@ def main():
|
|||||||
assert_infobase_exists(args.InfoBasePath)
|
assert_infobase_exists(args.InfoBasePath)
|
||||||
args.InputFile = clean_path(args.InputFile, "-InputFile")
|
args.InputFile = clean_path(args.InputFile, "-InputFile")
|
||||||
|
|
||||||
v8path = resolve_v8path(args.V8Path)
|
v8path = resolve_v8path(args.V8Path, args)
|
||||||
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
|
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
|
||||||
|
|
||||||
# --- Resolve additional arguments for the selected engine ---
|
# --- Resolve additional arguments for the selected engine ---
|
||||||
@@ -40,19 +40,19 @@ allowed-tools:
|
|||||||
|
|
||||||
## Параметры подключения
|
## Параметры подключения
|
||||||
|
|
||||||
Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу:
|
Прочитай `.v8-project.json` из корня проекта и разреши базу:
|
||||||
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
||||||
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
||||||
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
||||||
4. Если ветка не совпала — используй `default`
|
4. Если ветка не совпала — используй `default`
|
||||||
Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
|
Платформа — `v8path` найденной записи базы, иначе корневой `v8path`. Не задан ни там, ни там — `-V8Path` не передавай.
|
||||||
Если файла нет — предложи `/db-list add`.
|
Если файла нет — предложи `/db-list add`.
|
||||||
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
||||||
|
|
||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-dt.ps1" <параметры>
|
python ".roo/skills/db-load-dt/scripts/db-load-dt.py" <параметры>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Параметры скрипта
|
### Параметры скрипта
|
||||||
@@ -82,10 +82,10 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-dt.ps1" <п
|
|||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# Файловая база
|
# Файловая база
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-dt.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -InputFile "C:\backup\base.dt"
|
python ".roo/skills/db-load-dt/scripts/db-load-dt.py" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -InputFile "C:\backup\base.dt"
|
||||||
|
|
||||||
# Серверная база с ускорением загрузки
|
# Серверная база с ускорением загрузки
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-dt.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Test" -UserName "Admin" -Password "secret" -InputFile "base.dt" -JobsCount 4
|
python ".roo/skills/db-load-dt/scripts/db-load-dt.py" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Test" -UserName "Admin" -Password "secret" -InputFile "base.dt" -JobsCount 4
|
||||||
```
|
```
|
||||||
|
|
||||||
## Связанные навыки
|
## Связанные навыки
|
||||||
+45
-1
@@ -1,4 +1,4 @@
|
|||||||
# db-load-dt v1.16 — Load 1C information base from DT file
|
# db-load-dt v1.17 — Load 1C information base from DT file
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
||||||
<#
|
<#
|
||||||
@@ -286,8 +286,52 @@ function Assert-InfoBaseExists {
|
|||||||
|
|
||||||
Assert-InfoBaseExists $InfoBasePath
|
Assert-InfoBaseExists $InfoBasePath
|
||||||
|
|
||||||
|
# --- Запись базы в .v8-project.json ---
|
||||||
|
# Модель не передаёт ни путь к платформе конкретной базы, ни реквизиты хранилища: скрипт
|
||||||
|
# сопоставляет параметры соединения с записью в databases[] и берёт их оттуда. Тот же приём,
|
||||||
|
# что в cf-edit.ps1 (сопоставление по configSrc).
|
||||||
|
function Find-V8Project([string]$startDir) {
|
||||||
|
$d = $startDir
|
||||||
|
for ($i = 0; $i -lt 20 -and $d; $i++) {
|
||||||
|
$pj = Join-Path $d ".v8-project.json"
|
||||||
|
if (Test-Path $pj) { return $pj }
|
||||||
|
$parent = [System.IO.Path]::GetDirectoryName($d)
|
||||||
|
if ($parent -eq $d) { break }
|
||||||
|
$d = $parent
|
||||||
|
}
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
function Test-SamePath {
|
||||||
|
param([string]$A, [string]$B)
|
||||||
|
if (-not $A -or -not $B) { return $false }
|
||||||
|
try {
|
||||||
|
$na = [System.IO.Path]::GetFullPath($A).TrimEnd('\', '/')
|
||||||
|
$nb = [System.IO.Path]::GetFullPath($B).TrimEnd('\', '/')
|
||||||
|
return $na.Equals($nb, [System.StringComparison]::OrdinalIgnoreCase)
|
||||||
|
} catch { return $false }
|
||||||
|
}
|
||||||
|
function Find-ProjectDatabase {
|
||||||
|
# Запись базы в реестре, соответствующая переданному соединению. $null, если не найдена.
|
||||||
|
$pf = Find-V8Project (Get-Location).Path
|
||||||
|
if (-not $pf) { return $null }
|
||||||
|
try { $proj = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json } catch { return $null }
|
||||||
|
if (-not $proj.databases) { return $null }
|
||||||
|
foreach ($db in $proj.databases) {
|
||||||
|
if ($InfoBasePath -and $db.path -and (Test-SamePath $db.path $InfoBasePath)) { return $db }
|
||||||
|
if ($InfoBaseServer -and $InfoBaseRef -and $db.server -and $db.ref) {
|
||||||
|
if ($db.server.Equals($InfoBaseServer, [System.StringComparison]::OrdinalIgnoreCase) -and
|
||||||
|
$db.ref.Equals($InfoBaseRef, [System.StringComparison]::OrdinalIgnoreCase)) { return $db }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
# --- Resolve V8Path ---
|
# --- Resolve V8Path ---
|
||||||
function Find-ProjectV8Path {
|
function Find-ProjectV8Path {
|
||||||
|
# v8path записи базы сильнее корневого: в одном проекте базы живут на разных версиях
|
||||||
|
# платформы, а версию формата выгрузки задаёт та платформа, которая выгружает.
|
||||||
|
$dbRec = Find-ProjectDatabase
|
||||||
|
if ($dbRec -and $dbRec.v8path) { return [string]$dbRec.v8path }
|
||||||
$dir = (Get-Location).Path
|
$dir = (Get-Location).Path
|
||||||
while ($dir) {
|
while ($dir) {
|
||||||
$pf = Join-Path $dir ".v8-project.json"
|
$pf = Join-Path $dir ".v8-project.json"
|
||||||
+60
-6
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# db-load-dt v1.16 — Load 1C information base from DT file
|
# db-load-dt v1.17 — Load 1C information base from DT file
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -37,8 +37,62 @@ def ci_parse_args(parser, argv=None):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _find_project_v8path():
|
# --- Запись базы в .v8-project.json ---
|
||||||
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
# Модель не передаёт ни путь к платформе конкретной базы, ни реквизиты хранилища: скрипт
|
||||||
|
# сопоставляет параметры соединения с записью в databases[] и берёт их оттуда. Тот же приём,
|
||||||
|
# что в cf-edit.py (сопоставление по configSrc).
|
||||||
|
def _sg_find_v8project(start_dir):
|
||||||
|
d = start_dir
|
||||||
|
for _ in range(20):
|
||||||
|
if not d:
|
||||||
|
break
|
||||||
|
pj = os.path.join(d, ".v8-project.json")
|
||||||
|
if os.path.isfile(pj):
|
||||||
|
return pj
|
||||||
|
parent = os.path.dirname(d)
|
||||||
|
if parent == d:
|
||||||
|
break
|
||||||
|
d = parent
|
||||||
|
return None
|
||||||
|
|
||||||
|
def same_path(a, b):
|
||||||
|
if not a or not b:
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
return os.path.abspath(a).rstrip("\\/").lower() == os.path.abspath(b).rstrip("\\/").lower()
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def find_project_database(args):
|
||||||
|
"""Запись базы в реестре, соответствующая переданному соединению. None, если не найдена."""
|
||||||
|
pf = _sg_find_v8project(os.getcwd())
|
||||||
|
if not pf:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
with open(pf, encoding="utf-8-sig") as f:
|
||||||
|
proj = json.load(f)
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
for db in proj.get("databases") or []:
|
||||||
|
if args.InfoBasePath and db.get("path") and same_path(db["path"], args.InfoBasePath):
|
||||||
|
return db
|
||||||
|
if args.InfoBaseServer and args.InfoBaseRef and db.get("server") and db.get("ref"):
|
||||||
|
if (db["server"].lower() == args.InfoBaseServer.lower()
|
||||||
|
and db["ref"].lower() == args.InfoBaseRef.lower()):
|
||||||
|
return db
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _find_project_v8path(args):
|
||||||
|
"""Walk up from CWD to find .v8-project.json and read its v8path.
|
||||||
|
|
||||||
|
v8path записи базы сильнее корневого: в одном проекте базы живут на разных версиях
|
||||||
|
платформы, а версию формата выгрузки задаёт та платформа, которая выгружает.
|
||||||
|
"""
|
||||||
|
db = find_project_database(args)
|
||||||
|
if db and db.get("v8path"):
|
||||||
|
return db["v8path"]
|
||||||
d = os.getcwd()
|
d = os.getcwd()
|
||||||
while True:
|
while True:
|
||||||
pf = os.path.join(d, ".v8-project.json")
|
pf = os.path.join(d, ".v8-project.json")
|
||||||
@@ -243,10 +297,10 @@ def _version_key(p):
|
|||||||
return [int(x) for x in re.findall(r"\d+", _version_dir(p))]
|
return [int(x) for x in re.findall(r"\d+", _version_dir(p))]
|
||||||
|
|
||||||
|
|
||||||
def resolve_v8path(v8path):
|
def resolve_v8path(v8path, args):
|
||||||
"""Resolve path to a 1C executable (1cv8; ibcmd only when given explicitly)."""
|
"""Resolve path to a 1C executable (1cv8; ibcmd only when given explicitly)."""
|
||||||
if not v8path:
|
if not v8path:
|
||||||
v8path = _find_project_v8path()
|
v8path = _find_project_v8path(args)
|
||||||
if not v8path:
|
if not v8path:
|
||||||
if os.name == "nt":
|
if os.name == "nt":
|
||||||
candidates = (
|
candidates = (
|
||||||
@@ -457,7 +511,7 @@ def main():
|
|||||||
assert_infobase_exists(args.InfoBasePath)
|
assert_infobase_exists(args.InfoBasePath)
|
||||||
args.InputFile = clean_path(args.InputFile, "-InputFile")
|
args.InputFile = clean_path(args.InputFile, "-InputFile")
|
||||||
|
|
||||||
v8path = resolve_v8path(args.V8Path)
|
v8path = resolve_v8path(args.V8Path, args)
|
||||||
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
|
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
|
||||||
|
|
||||||
# --- Resolve additional arguments for the selected engine ---
|
# --- Resolve additional arguments for the selected engine ---
|
||||||
@@ -25,12 +25,12 @@ allowed-tools:
|
|||||||
|
|
||||||
## Параметры подключения
|
## Параметры подключения
|
||||||
|
|
||||||
Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу:
|
Прочитай `.v8-project.json` из корня проекта и разреши базу:
|
||||||
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
||||||
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
||||||
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
||||||
4. Если ветка не совпала — используй `default`
|
4. Если ветка не совпала — используй `default`
|
||||||
Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
|
Платформа — `v8path` найденной записи базы, иначе корневой `v8path`. Не задан ни там, ни там — `-V8Path` не передавай.
|
||||||
Если файла нет — предложи `/db-list add`.
|
Если файла нет — предложи `/db-list add`.
|
||||||
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
||||||
Если в записи базы указан `configSrc` — используй как каталог конфигурации.
|
Если в записи базы указан `configSrc` — используй как каталог конфигурации.
|
||||||
@@ -38,7 +38,7 @@ allowed-tools:
|
|||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-git.ps1" <параметры>
|
python ".roo/skills/db-load-git/scripts/db-load-git.py" <параметры>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Параметры скрипта
|
### Параметры скрипта
|
||||||
@@ -73,8 +73,8 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-git.ps1" <
|
|||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# Все незафиксированные изменения
|
# Все незафиксированные изменения
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-git.ps1" -V8Path "C:\Program Files\1cv8\8.3.25.1257\bin" -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\WS\cfsrc" -Source All -UpdateDB
|
python ".roo/skills/db-load-git/scripts/db-load-git.py" -V8Path "C:\Program Files\1cv8\8.3.25.1257\bin" -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\WS\cfsrc" -Source All -UpdateDB
|
||||||
|
|
||||||
# Из диапазона коммитов
|
# Из диапазона коммитов
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-git.ps1" -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\WS\cfsrc" -Source Commit -CommitRange "HEAD~3..HEAD"
|
python ".roo/skills/db-load-git/scripts/db-load-git.py" -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\WS\cfsrc" -Source Commit -CommitRange "HEAD~3..HEAD"
|
||||||
```
|
```
|
||||||
+9
-4
@@ -1,4 +1,4 @@
|
|||||||
# db-load-git v1.26 — Load Git changes into 1C database
|
# db-load-git v1.27 — Load Git changes into 1C database
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
||||||
<#
|
<#
|
||||||
@@ -139,9 +139,10 @@ param(
|
|||||||
$OutputEncoding = [System.Text.Encoding]::UTF8
|
$OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
|
|
||||||
# --- Реквизиты хранилища из .v8-project.json ---
|
# --- Запись базы в .v8-project.json ---
|
||||||
# Модель их не передаёт: скрипт сопоставляет параметры соединения с записью в databases[]
|
# Модель не передаёт ни путь к платформе конкретной базы, ни реквизиты хранилища: скрипт
|
||||||
# и берёт repository оттуда. Тот же приём, что в cf-edit.ps1 (сопоставление по configSrc).
|
# сопоставляет параметры соединения с записью в databases[] и берёт их оттуда. Тот же приём,
|
||||||
|
# что в cf-edit.ps1 (сопоставление по configSrc).
|
||||||
function Find-V8Project([string]$startDir) {
|
function Find-V8Project([string]$startDir) {
|
||||||
$d = $startDir
|
$d = $startDir
|
||||||
for ($i = 0; $i -lt 20 -and $d; $i++) {
|
for ($i = 0; $i -lt 20 -and $d; $i++) {
|
||||||
@@ -427,6 +428,10 @@ function Get-ObjectXmlFromSubFile {
|
|||||||
# --- Resolve V8Path (skip if DryRun) ---
|
# --- Resolve V8Path (skip if DryRun) ---
|
||||||
if (-not $DryRun) {
|
if (-not $DryRun) {
|
||||||
function Find-ProjectV8Path {
|
function Find-ProjectV8Path {
|
||||||
|
# v8path записи базы сильнее корневого: в одном проекте базы живут на разных версиях
|
||||||
|
# платформы, а версию формата выгрузки задаёт та платформа, которая выгружает.
|
||||||
|
$dbRec = Find-ProjectDatabase
|
||||||
|
if ($dbRec -and $dbRec.v8path) { return [string]$dbRec.v8path }
|
||||||
$dir = (Get-Location).Path
|
$dir = (Get-Location).Path
|
||||||
while ($dir) {
|
while ($dir) {
|
||||||
$pf = Join-Path $dir ".v8-project.json"
|
$pf = Join-Path $dir ".v8-project.json"
|
||||||
+17
-9
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# db-load-git v1.26 — Load Git changes into 1C database
|
# db-load-git v1.27 — Load Git changes into 1C database
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -37,8 +37,15 @@ def ci_parse_args(parser, argv=None):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _find_project_v8path():
|
def _find_project_v8path(args):
|
||||||
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
"""Walk up from CWD to find .v8-project.json and read its v8path.
|
||||||
|
|
||||||
|
v8path записи базы сильнее корневого: в одном проекте базы живут на разных версиях
|
||||||
|
платформы, а версию формата выгрузки задаёт та платформа, которая выгружает.
|
||||||
|
"""
|
||||||
|
db = find_project_database(args)
|
||||||
|
if db and db.get("v8path"):
|
||||||
|
return db["v8path"]
|
||||||
d = os.getcwd()
|
d = os.getcwd()
|
||||||
while True:
|
while True:
|
||||||
pf = os.path.join(d, ".v8-project.json")
|
pf = os.path.join(d, ".v8-project.json")
|
||||||
@@ -87,9 +94,10 @@ V8_SECRET_KEYS = ["/P", "/UC", "/WSP", "/AWSP", "/ConfigurationRepositoryP"]
|
|||||||
IBCMD_SECRET_KEYS = ["--password", "--token", "--db-pwd"]
|
IBCMD_SECRET_KEYS = ["--password", "--token", "--db-pwd"]
|
||||||
|
|
||||||
|
|
||||||
# --- Реквизиты хранилища из .v8-project.json ---
|
# --- Запись базы в .v8-project.json ---
|
||||||
# Модель их не передаёт: скрипт сопоставляет параметры соединения с записью в databases[]
|
# Модель не передаёт ни путь к платформе конкретной базы, ни реквизиты хранилища: скрипт
|
||||||
# и берёт repository оттуда. Тот же приём, что в cf-edit.py (сопоставление по configSrc).
|
# сопоставляет параметры соединения с записью в databases[] и берёт их оттуда. Тот же приём,
|
||||||
|
# что в cf-edit.py (сопоставление по configSrc).
|
||||||
def _sg_find_v8project(start_dir):
|
def _sg_find_v8project(start_dir):
|
||||||
d = start_dir
|
d = start_dir
|
||||||
for _ in range(20):
|
for _ in range(20):
|
||||||
@@ -349,10 +357,10 @@ def _version_key(p):
|
|||||||
return [int(x) for x in re.findall(r"\d+", _version_dir(p))]
|
return [int(x) for x in re.findall(r"\d+", _version_dir(p))]
|
||||||
|
|
||||||
|
|
||||||
def resolve_v8path(v8path):
|
def resolve_v8path(v8path, args):
|
||||||
"""Resolve path to a 1C executable (1cv8; ibcmd only when given explicitly)."""
|
"""Resolve path to a 1C executable (1cv8; ibcmd only when given explicitly)."""
|
||||||
if not v8path:
|
if not v8path:
|
||||||
v8path = _find_project_v8path()
|
v8path = _find_project_v8path(args)
|
||||||
if not v8path:
|
if not v8path:
|
||||||
if os.name == "nt":
|
if os.name == "nt":
|
||||||
candidates = (
|
candidates = (
|
||||||
@@ -714,7 +722,7 @@ def main():
|
|||||||
# --- Resolve V8Path (skip if DryRun) ---
|
# --- Resolve V8Path (skip if DryRun) ---
|
||||||
v8path = None
|
v8path = None
|
||||||
if not args.DryRun:
|
if not args.DryRun:
|
||||||
v8path = resolve_v8path(args.V8Path)
|
v8path = resolve_v8path(args.V8Path, args)
|
||||||
|
|
||||||
# --- Detect engine + validate connection (skip if DryRun) ---
|
# --- Detect engine + validate connection (skip if DryRun) ---
|
||||||
engine = "1cv8"
|
engine = "1cv8"
|
||||||
@@ -25,12 +25,12 @@ allowed-tools:
|
|||||||
|
|
||||||
## Параметры подключения
|
## Параметры подключения
|
||||||
|
|
||||||
Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу:
|
Прочитай `.v8-project.json` из корня проекта и разреши базу:
|
||||||
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
||||||
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
||||||
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
||||||
4. Если ветка не совпала — используй `default`
|
4. Если ветка не совпала — используй `default`
|
||||||
Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
|
Платформа — `v8path` найденной записи базы, иначе корневой `v8path`. Не задан ни там, ни там — `-V8Path` не передавай.
|
||||||
Если файла нет — предложи `/db-list add`.
|
Если файла нет — предложи `/db-list add`.
|
||||||
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
||||||
Если в записи базы указан `configSrc` — используй как каталог загрузки по умолчанию.
|
Если в записи базы указан `configSrc` — используй как каталог загрузки по умолчанию.
|
||||||
@@ -39,7 +39,7 @@ allowed-tools:
|
|||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-xml.ps1" <параметры>
|
python ".roo/skills/db-load-xml/scripts/db-load-xml.py" <параметры>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Параметры скрипта
|
### Параметры скрипта
|
||||||
@@ -92,14 +92,14 @@ Documents/Заказ/Forms/ФормаДокумента.xml
|
|||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# Полная загрузка
|
# Полная загрузка
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-xml.ps1" -V8Path "C:\Program Files\1cv8\8.3.25.1257\bin" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Full
|
python ".roo/skills/db-load-xml/scripts/db-load-xml.py" -V8Path "C:\Program Files\1cv8\8.3.25.1257\bin" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Full
|
||||||
|
|
||||||
# Частичная загрузка конкретных файлов
|
# Частичная загрузка конкретных файлов
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-xml.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Partial -Files "Catalogs/Номенклатура.xml,Catalogs/Номенклатура/Ext/ObjectModule.bsl"
|
python ".roo/skills/db-load-xml/scripts/db-load-xml.py" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Partial -Files "Catalogs/Номенклатура.xml,Catalogs/Номенклатура/Ext/ObjectModule.bsl"
|
||||||
|
|
||||||
# Загрузка расширения
|
# Загрузка расширения
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-xml.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\ext_src" -Mode Full -Extension "МоёРасширение"
|
python ".roo/skills/db-load-xml/scripts/db-load-xml.py" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\ext_src" -Mode Full -Extension "МоёРасширение"
|
||||||
|
|
||||||
# Загрузка + обновление БД в одном запуске
|
# Загрузка + обновление БД в одном запуске
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-xml.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Full -UpdateDB
|
python ".roo/skills/db-load-xml/scripts/db-load-xml.py" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Full -UpdateDB
|
||||||
```
|
```
|
||||||
+9
-4
@@ -1,4 +1,4 @@
|
|||||||
# db-load-xml v1.28 — Load 1C configuration from XML files
|
# db-load-xml v1.29 — Load 1C configuration from XML files
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
||||||
<#
|
<#
|
||||||
@@ -135,9 +135,10 @@ param(
|
|||||||
$OutputEncoding = [System.Text.Encoding]::UTF8
|
$OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
|
|
||||||
# --- Реквизиты хранилища из .v8-project.json ---
|
# --- Запись базы в .v8-project.json ---
|
||||||
# Модель их не передаёт: скрипт сопоставляет параметры соединения с записью в databases[]
|
# Модель не передаёт ни путь к платформе конкретной базы, ни реквизиты хранилища: скрипт
|
||||||
# и берёт repository оттуда. Тот же приём, что в cf-edit.ps1 (сопоставление по configSrc).
|
# сопоставляет параметры соединения с записью в databases[] и берёт их оттуда. Тот же приём,
|
||||||
|
# что в cf-edit.ps1 (сопоставление по configSrc).
|
||||||
function Find-V8Project([string]$startDir) {
|
function Find-V8Project([string]$startDir) {
|
||||||
$d = $startDir
|
$d = $startDir
|
||||||
for ($i = 0; $i -lt 20 -and $d; $i++) {
|
for ($i = 0; $i -lt 20 -and $d; $i++) {
|
||||||
@@ -448,6 +449,10 @@ Assert-InfoBaseExists $InfoBasePath
|
|||||||
|
|
||||||
# --- Resolve V8Path ---
|
# --- Resolve V8Path ---
|
||||||
function Find-ProjectV8Path {
|
function Find-ProjectV8Path {
|
||||||
|
# v8path записи базы сильнее корневого: в одном проекте базы живут на разных версиях
|
||||||
|
# платформы, а версию формата выгрузки задаёт та платформа, которая выгружает.
|
||||||
|
$dbRec = Find-ProjectDatabase
|
||||||
|
if ($dbRec -and $dbRec.v8path) { return [string]$dbRec.v8path }
|
||||||
$dir = (Get-Location).Path
|
$dir = (Get-Location).Path
|
||||||
while ($dir) {
|
while ($dir) {
|
||||||
$pf = Join-Path $dir ".v8-project.json"
|
$pf = Join-Path $dir ".v8-project.json"
|
||||||
+17
-9
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# db-load-xml v1.28 — Load 1C configuration from XML files
|
# db-load-xml v1.29 — Load 1C configuration from XML files
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -37,8 +37,15 @@ def ci_parse_args(parser, argv=None):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _find_project_v8path():
|
def _find_project_v8path(args):
|
||||||
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
"""Walk up from CWD to find .v8-project.json and read its v8path.
|
||||||
|
|
||||||
|
v8path записи базы сильнее корневого: в одном проекте базы живут на разных версиях
|
||||||
|
платформы, а версию формата выгрузки задаёт та платформа, которая выгружает.
|
||||||
|
"""
|
||||||
|
db = find_project_database(args)
|
||||||
|
if db and db.get("v8path"):
|
||||||
|
return db["v8path"]
|
||||||
d = os.getcwd()
|
d = os.getcwd()
|
||||||
while True:
|
while True:
|
||||||
pf = os.path.join(d, ".v8-project.json")
|
pf = os.path.join(d, ".v8-project.json")
|
||||||
@@ -87,9 +94,10 @@ V8_SECRET_KEYS = ["/P", "/UC", "/WSP", "/AWSP", "/ConfigurationRepositoryP"]
|
|||||||
IBCMD_SECRET_KEYS = ["--password", "--token", "--db-pwd"]
|
IBCMD_SECRET_KEYS = ["--password", "--token", "--db-pwd"]
|
||||||
|
|
||||||
|
|
||||||
# --- Реквизиты хранилища из .v8-project.json ---
|
# --- Запись базы в .v8-project.json ---
|
||||||
# Модель их не передаёт: скрипт сопоставляет параметры соединения с записью в databases[]
|
# Модель не передаёт ни путь к платформе конкретной базы, ни реквизиты хранилища: скрипт
|
||||||
# и берёт repository оттуда. Тот же приём, что в cf-edit.py (сопоставление по configSrc).
|
# сопоставляет параметры соединения с записью в databases[] и берёт их оттуда. Тот же приём,
|
||||||
|
# что в cf-edit.py (сопоставление по configSrc).
|
||||||
def _sg_find_v8project(start_dir):
|
def _sg_find_v8project(start_dir):
|
||||||
d = start_dir
|
d = start_dir
|
||||||
for _ in range(20):
|
for _ in range(20):
|
||||||
@@ -349,10 +357,10 @@ def _version_key(p):
|
|||||||
return [int(x) for x in re.findall(r"\d+", _version_dir(p))]
|
return [int(x) for x in re.findall(r"\d+", _version_dir(p))]
|
||||||
|
|
||||||
|
|
||||||
def resolve_v8path(v8path):
|
def resolve_v8path(v8path, args):
|
||||||
"""Resolve path to a 1C executable (1cv8; ibcmd only when given explicitly)."""
|
"""Resolve path to a 1C executable (1cv8; ibcmd only when given explicitly)."""
|
||||||
if not v8path:
|
if not v8path:
|
||||||
v8path = _find_project_v8path()
|
v8path = _find_project_v8path(args)
|
||||||
if not v8path:
|
if not v8path:
|
||||||
if os.name == "nt":
|
if os.name == "nt":
|
||||||
candidates = (
|
candidates = (
|
||||||
@@ -692,7 +700,7 @@ def main():
|
|||||||
args.ListFile = clean_path(args.ListFile, "-ListFile")
|
args.ListFile = clean_path(args.ListFile, "-ListFile")
|
||||||
|
|
||||||
# --- Resolve V8Path ---
|
# --- Resolve V8Path ---
|
||||||
v8path = resolve_v8path(args.V8Path)
|
v8path = resolve_v8path(args.V8Path, args)
|
||||||
|
|
||||||
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
|
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
|
||||||
|
|
||||||
@@ -63,12 +63,14 @@ allowed-tools:
|
|||||||
|
|
||||||
## Параметры подключения
|
## Параметры подключения
|
||||||
|
|
||||||
Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` и разреши базу:
|
Прочитай `.v8-project.json` из корня проекта и разреши базу:
|
||||||
1. Если пользователь указал параметры подключения — используй напрямую
|
1. Если пользователь указал параметры подключения — используй напрямую
|
||||||
2. Если указал базу по имени — ищи по id / alias / name
|
2. Если указал базу по имени — ищи по id / alias / name
|
||||||
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
||||||
4. Если ветка не совпала — используй `default`
|
4. Если ветка не совпала — используй `default`
|
||||||
|
|
||||||
|
Платформа — `v8path` найденной записи базы, иначе корневой `v8path`. Не задан ни там, ни там — `-V8Path` не передавай.
|
||||||
|
|
||||||
Реквизиты хранилища передавать не нужно: запись базы находится по переданным параметрам
|
Реквизиты хранилища передавать не нужно: запись базы находится по переданным параметрам
|
||||||
соединения (`-InfoBasePath` либо `-InfoBaseServer` + `-InfoBaseRef`), реквизиты берутся из её
|
соединения (`-InfoBasePath` либо `-InfoBaseServer` + `-InfoBaseRef`), реквизиты берутся из её
|
||||||
`repository`. Задать их явно можно параметрами `-Repository*`.
|
`repository`. Задать их явно можно параметрами `-Repository*`.
|
||||||
@@ -76,7 +78,7 @@ allowed-tools:
|
|||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-repo.ps1" -Command <подкоманда> <параметры>
|
python ".roo/skills/db-repo/scripts/db-repo.py" -Command <подкоманда> <параметры>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Рабочий цикл
|
### Рабочий цикл
|
||||||
@@ -174,22 +176,22 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-repo.ps1" -Comma
|
|||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# Захватить справочник вместе с подчинёнными объектами
|
# Захватить справочник вместе с подчинёнными объектами
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-repo.ps1" -Command lock -InfoBasePath "C:\Bases\MyDB" -Objects "Справочник.Номенклатура" -WithChildren
|
python ".roo/skills/db-repo/scripts/db-repo.py" -Command lock -InfoBasePath "C:\Bases\MyDB" -Objects "Справочник.Номенклатура" -WithChildren
|
||||||
|
|
||||||
# Захватить корень — он нужен, чтобы добавить или удалить объект конфигурации
|
# Захватить корень — он нужен, чтобы добавить или удалить объект конфигурации
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-repo.ps1" -Command lock -InfoBasePath "C:\Bases\MyDB" -Objects "Конфигурация"
|
python ".roo/skills/db-repo/scripts/db-repo.py" -Command lock -InfoBasePath "C:\Bases\MyDB" -Objects "Конфигурация"
|
||||||
|
|
||||||
# Поместить новый объект: он уже существует, поэтому называется вместе с корнем
|
# Поместить новый объект: он уже существует, поэтому называется вместе с корнем
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-repo.ps1" -Command commit -InfoBasePath "C:\Bases\MyDB" -Objects "Конфигурация,Справочник.Склады" -Comment "Добавлен справочник Склады"
|
python ".roo/skills/db-repo/scripts/db-repo.py" -Command commit -InfoBasePath "C:\Bases\MyDB" -Objects "Конфигурация,Справочник.Склады" -Comment "Добавлен справочник Склады"
|
||||||
|
|
||||||
# Поместить с комментарием, оставив захват
|
# Поместить с комментарием, оставив захват
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-repo.ps1" -Command commit -InfoBasePath "C:\Bases\MyDB" -Objects "Справочник.Номенклатура" -Comment "Добавлен реквизит Артикул" -KeepLocked
|
python ".roo/skills/db-repo/scripts/db-repo.py" -Command commit -InfoBasePath "C:\Bases\MyDB" -Objects "Справочник.Номенклатура" -Comment "Добавлен реквизит Артикул" -KeepLocked
|
||||||
|
|
||||||
# Получить изменения из хранилища
|
# Получить изменения из хранилища
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-repo.ps1" -Command update -InfoBasePath "C:\Bases\MyDB"
|
python ".roo/skills/db-repo/scripts/db-repo.py" -Command update -InfoBasePath "C:\Bases\MyDB"
|
||||||
|
|
||||||
# Серверная база, расширение
|
# Серверная база, расширение
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-repo.ps1" -Command lock -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Dev" -UserName "Admin" -Extension "МоёРасширение" -Objects "Справочник.Номенклатура"
|
python ".roo/skills/db-repo/scripts/db-repo.py" -Command lock -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Dev" -UserName "Admin" -Extension "МоёРасширение" -Objects "Справочник.Номенклатура"
|
||||||
```
|
```
|
||||||
|
|
||||||
## После выполнения
|
## После выполнения
|
||||||
+3
-3
@@ -3,7 +3,7 @@
|
|||||||
## create — создать хранилище
|
## create — создать хранилище
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-repo.ps1" -Command create -InfoBasePath "C:\Bases\MyDB" -RepositoryPath "C:\Repo\MyApp" -RepositoryUser "Admin" -RepositoryPassword "…"
|
python ".roo/skills/db-repo/scripts/db-repo.py" -Command create -InfoBasePath "C:\Bases\MyDB" -RepositoryPath "C:\Repo\MyApp" -RepositoryUser "Admin" -RepositoryPassword "…"
|
||||||
```
|
```
|
||||||
|
|
||||||
| Параметр | Описание |
|
| Параметр | Описание |
|
||||||
@@ -22,7 +22,7 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-repo.ps1" -Comma
|
|||||||
## add-user — создать пользователя
|
## add-user — создать пользователя
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-repo.ps1" -Command add-user -InfoBasePath "C:\Bases\MyDB" -NewUser "Ivanov" -NewUserPassword "…" -Rights LockObjects
|
python ".roo/skills/db-repo/scripts/db-repo.py" -Command add-user -InfoBasePath "C:\Bases\MyDB" -NewUser "Ivanov" -NewUserPassword "…" -Rights LockObjects
|
||||||
```
|
```
|
||||||
|
|
||||||
| Право | Что даёт |
|
| Право | Что даёт |
|
||||||
@@ -38,7 +38,7 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-repo.ps1" -Comma
|
|||||||
## copy-users — скопировать пользователей из другого хранилища
|
## copy-users — скопировать пользователей из другого хранилища
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-repo.ps1" -Command copy-users -InfoBasePath "C:\Bases\MyDB" -SourcePath "\\srv01\repo\Other" -SourceUser "Admin" -SourcePassword "…"
|
python ".roo/skills/db-repo/scripts/db-repo.py" -Command copy-users -InfoBasePath "C:\Bases\MyDB" -SourcePath "\\srv01\repo\Other" -SourceUser "Admin" -SourcePassword "…"
|
||||||
```
|
```
|
||||||
|
|
||||||
`-SourcePath`, `-SourceUser`, `-SourcePassword` описывают хранилище-**источник**. Удалённые пользователи
|
`-SourcePath`, `-SourceUser`, `-SourcePassword` описывают хранилище-**источник**. Удалённые пользователи
|
||||||
+2
-2
@@ -3,7 +3,7 @@
|
|||||||
## connect — подключить
|
## connect — подключить
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-repo.ps1" -Command connect -InfoBasePath "C:\Bases\MyDB" -RepositoryPath "\\srv01\repo\MyApp" -RepositoryUser "Ivanov" -RepositoryPassword "…"
|
python ".roo/skills/db-repo/scripts/db-repo.py" -Command connect -InfoBasePath "C:\Bases\MyDB" -RepositoryPath "\\srv01\repo\MyApp" -RepositoryUser "Ivanov" -RepositoryPassword "…"
|
||||||
```
|
```
|
||||||
|
|
||||||
| Параметр | Описание |
|
| Параметр | Описание |
|
||||||
@@ -23,7 +23,7 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-repo.ps1" -Comma
|
|||||||
## disconnect — отключить
|
## disconnect — отключить
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-repo.ps1" -Command disconnect -InfoBasePath "C:\Bases\MyDB"
|
python ".roo/skills/db-repo/scripts/db-repo.py" -Command disconnect -InfoBasePath "C:\Bases\MyDB"
|
||||||
```
|
```
|
||||||
|
|
||||||
**Спроси подтверждение у пользователя.** Отключение снимает связь и на стороне самого хранилища:
|
**Спроси подтверждение у пользователя.** Отключение снимает связь и на стороне самого хранилища:
|
||||||
+2
-2
@@ -3,7 +3,7 @@
|
|||||||
## report — отчёт по версиям
|
## report — отчёт по версиям
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-repo.ps1" -Command report -InfoBasePath "C:\Bases\MyDB" -OutputFile "C:\tmp\repo.txt"
|
python ".roo/skills/db-repo/scripts/db-repo.py" -Command report -InfoBasePath "C:\Bases\MyDB" -OutputFile "C:\tmp\repo.txt"
|
||||||
```
|
```
|
||||||
|
|
||||||
| Параметр | Описание |
|
| Параметр | Описание |
|
||||||
@@ -25,7 +25,7 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-repo.ps1" -Comma
|
|||||||
## dump-cfg — выгрузить версию в CF
|
## dump-cfg — выгрузить версию в CF
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-repo.ps1" -Command dump-cfg -InfoBasePath "C:\Bases\MyDB" -OutputFile "C:\tmp\v120.cf" -Version 120
|
python ".roo/skills/db-repo/scripts/db-repo.py" -Command dump-cfg -InfoBasePath "C:\Bases\MyDB" -OutputFile "C:\tmp\v120.cf" -Version 120
|
||||||
```
|
```
|
||||||
|
|
||||||
Без `-Version` (или при `-1`) выгружается последняя версия.
|
Без `-Version` (или при `-1`) выгружается последняя версия.
|
||||||
+3
-3
@@ -3,7 +3,7 @@
|
|||||||
## set-label — метка на версию
|
## set-label — метка на версию
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-repo.ps1" -Command set-label -InfoBasePath "C:\Bases\MyDB" -Label "Релиз 1.2" -Version 120 -Comment "Передано в тест"
|
python ".roo/skills/db-repo/scripts/db-repo.py" -Command set-label -InfoBasePath "C:\Bases\MyDB" -Label "Релиз 1.2" -Version 120 -Comment "Передано в тест"
|
||||||
```
|
```
|
||||||
|
|
||||||
Без `-Version` метка ставится на последнюю версию. Несуществующая версия — ошибка.
|
Без `-Version` метка ставится на последнюю версию. Несуществующая версия — ошибка.
|
||||||
@@ -11,7 +11,7 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-repo.ps1" -Comma
|
|||||||
## optimize — оптимизация хранения
|
## optimize — оптимизация хранения
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-repo.ps1" -Command optimize -InfoBasePath "C:\Bases\MyDB"
|
python ".roo/skills/db-repo/scripts/db-repo.py" -Command optimize -InfoBasePath "C:\Bases\MyDB"
|
||||||
```
|
```
|
||||||
|
|
||||||
Оптимизирует хранение данных в хранилище. Операция долгая.
|
Оптимизирует хранение данных в хранилище. Операция долгая.
|
||||||
@@ -19,7 +19,7 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-repo.ps1" -Comma
|
|||||||
## clear-cache — очистка кеша
|
## clear-cache — очистка кеша
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-repo.ps1" -Command clear-cache -InfoBasePath "C:\Bases\MyDB" -CacheScope local
|
python ".roo/skills/db-repo/scripts/db-repo.py" -Command clear-cache -InfoBasePath "C:\Bases\MyDB" -CacheScope local
|
||||||
```
|
```
|
||||||
|
|
||||||
| `-CacheScope` | Что чистит |
|
| `-CacheScope` | Что чистит |
|
||||||
+45
-41
@@ -1,4 +1,4 @@
|
|||||||
# db-repo v1.14 — 1C configuration repository operations
|
# db-repo v1.16 — 1C configuration repository operations
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
# NB: движок только 1cv8 — ibcmd работу с хранилищем не поддерживает (нет такого режима).
|
# NB: движок только 1cv8 — ibcmd работу с хранилищем не поддерживает (нет такого режима).
|
||||||
<#
|
<#
|
||||||
@@ -320,8 +320,52 @@ function Assert-InfoBaseExists {
|
|||||||
|
|
||||||
Assert-InfoBaseExists $InfoBasePath
|
Assert-InfoBaseExists $InfoBasePath
|
||||||
|
|
||||||
|
# --- Запись базы в .v8-project.json ---
|
||||||
|
# Модель не передаёт ни путь к платформе конкретной базы, ни реквизиты хранилища: скрипт
|
||||||
|
# сопоставляет параметры соединения с записью в databases[] и берёт их оттуда. Тот же приём,
|
||||||
|
# что в cf-edit.ps1 (сопоставление по configSrc).
|
||||||
|
function Find-V8Project([string]$startDir) {
|
||||||
|
$d = $startDir
|
||||||
|
for ($i = 0; $i -lt 20 -and $d; $i++) {
|
||||||
|
$pj = Join-Path $d ".v8-project.json"
|
||||||
|
if (Test-Path $pj) { return $pj }
|
||||||
|
$parent = [System.IO.Path]::GetDirectoryName($d)
|
||||||
|
if ($parent -eq $d) { break }
|
||||||
|
$d = $parent
|
||||||
|
}
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
function Test-SamePath {
|
||||||
|
param([string]$A, [string]$B)
|
||||||
|
if (-not $A -or -not $B) { return $false }
|
||||||
|
try {
|
||||||
|
$na = [System.IO.Path]::GetFullPath($A).TrimEnd('\', '/')
|
||||||
|
$nb = [System.IO.Path]::GetFullPath($B).TrimEnd('\', '/')
|
||||||
|
return $na.Equals($nb, [System.StringComparison]::OrdinalIgnoreCase)
|
||||||
|
} catch { return $false }
|
||||||
|
}
|
||||||
|
function Find-ProjectDatabase {
|
||||||
|
# Запись базы в реестре, соответствующая переданному соединению. $null, если не найдена.
|
||||||
|
$pf = Find-V8Project (Get-Location).Path
|
||||||
|
if (-not $pf) { return $null }
|
||||||
|
try { $proj = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json } catch { return $null }
|
||||||
|
if (-not $proj.databases) { return $null }
|
||||||
|
foreach ($db in $proj.databases) {
|
||||||
|
if ($InfoBasePath -and $db.path -and (Test-SamePath $db.path $InfoBasePath)) { return $db }
|
||||||
|
if ($InfoBaseServer -and $InfoBaseRef -and $db.server -and $db.ref) {
|
||||||
|
if ($db.server.Equals($InfoBaseServer, [System.StringComparison]::OrdinalIgnoreCase) -and
|
||||||
|
$db.ref.Equals($InfoBaseRef, [System.StringComparison]::OrdinalIgnoreCase)) { return $db }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
# --- Resolve V8Path ---
|
# --- Resolve V8Path ---
|
||||||
function Find-ProjectV8Path {
|
function Find-ProjectV8Path {
|
||||||
|
# v8path записи базы сильнее корневого: в одном проекте базы живут на разных версиях
|
||||||
|
# платформы, а версию формата выгрузки задаёт та платформа, которая выгружает.
|
||||||
|
$dbRec = Find-ProjectDatabase
|
||||||
|
if ($dbRec -and $dbRec.v8path) { return [string]$dbRec.v8path }
|
||||||
$dir = (Get-Location).Path
|
$dir = (Get-Location).Path
|
||||||
while ($dir) {
|
while ($dir) {
|
||||||
$pf = Join-Path $dir ".v8-project.json"
|
$pf = Join-Path $dir ".v8-project.json"
|
||||||
@@ -499,46 +543,6 @@ function Invoke-PlatformProcess {
|
|||||||
return [pscustomobject]@{ Output = $out; ExitCode = $p.ExitCode }
|
return [pscustomobject]@{ Output = $out; ExitCode = $p.ExitCode }
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Реквизиты хранилища из .v8-project.json ---
|
|
||||||
# Модель их не передаёт: скрипт сопоставляет параметры соединения с записью в databases[]
|
|
||||||
# и берёт repository оттуда. Тот же приём, что в cf-edit.ps1 (сопоставление по configSrc).
|
|
||||||
function Find-V8Project([string]$startDir) {
|
|
||||||
$d = $startDir
|
|
||||||
for ($i = 0; $i -lt 20 -and $d; $i++) {
|
|
||||||
$pj = Join-Path $d ".v8-project.json"
|
|
||||||
if (Test-Path $pj) { return $pj }
|
|
||||||
$parent = [System.IO.Path]::GetDirectoryName($d)
|
|
||||||
if ($parent -eq $d) { break }
|
|
||||||
$d = $parent
|
|
||||||
}
|
|
||||||
return $null
|
|
||||||
}
|
|
||||||
function Test-SamePath {
|
|
||||||
param([string]$A, [string]$B)
|
|
||||||
if (-not $A -or -not $B) { return $false }
|
|
||||||
try {
|
|
||||||
$na = [System.IO.Path]::GetFullPath($A).TrimEnd('\', '/')
|
|
||||||
$nb = [System.IO.Path]::GetFullPath($B).TrimEnd('\', '/')
|
|
||||||
return $na.Equals($nb, [System.StringComparison]::OrdinalIgnoreCase)
|
|
||||||
} catch { return $false }
|
|
||||||
}
|
|
||||||
|
|
||||||
function Find-ProjectDatabase {
|
|
||||||
# Запись базы в реестре, соответствующая переданному соединению. $null, если не найдена.
|
|
||||||
$pf = Find-V8Project (Get-Location).Path
|
|
||||||
if (-not $pf) { return $null }
|
|
||||||
try { $proj = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json } catch { return $null }
|
|
||||||
if (-not $proj.databases) { return $null }
|
|
||||||
foreach ($db in $proj.databases) {
|
|
||||||
if ($InfoBasePath -and $db.path -and (Test-SamePath $db.path $InfoBasePath)) { return $db }
|
|
||||||
if ($InfoBaseServer -and $InfoBaseRef -and $db.server -and $db.ref) {
|
|
||||||
if ($db.server.Equals($InfoBaseServer, [System.StringComparison]::OrdinalIgnoreCase) -and
|
|
||||||
$db.ref.Equals($InfoBaseRef, [System.StringComparison]::OrdinalIgnoreCase)) { return $db }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $null
|
|
||||||
}
|
|
||||||
|
|
||||||
function Resolve-RepositorySettings {
|
function Resolve-RepositorySettings {
|
||||||
# Возвращает @{ Path; User; Password; FromRegistry }. Явные -Repository* всегда сильнее реестра.
|
# Возвращает @{ Path; User; Password; FromRegistry }. Явные -Repository* всегда сильнее реестра.
|
||||||
$dbRec = Find-ProjectDatabase
|
$dbRec = Find-ProjectDatabase
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# db-repo v1.14 — 1C configuration repository operations
|
# db-repo v1.16 — 1C configuration repository operations
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
# NB: движок только 1cv8 — ibcmd работу с хранилищем не поддерживает (нет такого режима).
|
# NB: движок только 1cv8 — ibcmd работу с хранилищем не поддерживает (нет такого режима).
|
||||||
"""Работа с хранилищем конфигурации 1С.
|
"""Работа с хранилищем конфигурации 1С.
|
||||||
@@ -78,8 +78,15 @@ def ci_parse_args(parser, argv=None):
|
|||||||
return parser.parse_args(argv)
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
def _find_project_v8path():
|
def _find_project_v8path(args):
|
||||||
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
"""Walk up from CWD to find .v8-project.json and read its v8path.
|
||||||
|
|
||||||
|
v8path записи базы сильнее корневого: в одном проекте базы живут на разных версиях
|
||||||
|
платформы, а версию формата выгрузки задаёт та платформа, которая выгружает.
|
||||||
|
"""
|
||||||
|
db = find_project_database(args)
|
||||||
|
if db and db.get("v8path"):
|
||||||
|
return db["v8path"]
|
||||||
d = os.getcwd()
|
d = os.getcwd()
|
||||||
while True:
|
while True:
|
||||||
pf = os.path.join(d, ".v8-project.json")
|
pf = os.path.join(d, ".v8-project.json")
|
||||||
@@ -224,10 +231,10 @@ def _version_key(p):
|
|||||||
return [int(x) for x in re.findall(r"\d+", _version_dir(p))]
|
return [int(x) for x in re.findall(r"\d+", _version_dir(p))]
|
||||||
|
|
||||||
|
|
||||||
def resolve_v8path(v8path):
|
def resolve_v8path(v8path, args):
|
||||||
"""Resolve path to a 1C executable (1cv8; ibcmd only when given explicitly)."""
|
"""Resolve path to a 1C executable (1cv8; ibcmd only when given explicitly)."""
|
||||||
if not v8path:
|
if not v8path:
|
||||||
v8path = _find_project_v8path()
|
v8path = _find_project_v8path(args)
|
||||||
if not v8path:
|
if not v8path:
|
||||||
if os.name == "nt":
|
if os.name == "nt":
|
||||||
candidates = (
|
candidates = (
|
||||||
@@ -405,9 +412,10 @@ def resolve_extra_args(v8_extra, hints):
|
|||||||
return extra
|
return extra
|
||||||
|
|
||||||
|
|
||||||
# --- Реквизиты хранилища из .v8-project.json ---
|
# --- Запись базы в .v8-project.json ---
|
||||||
# Модель их не передаёт: скрипт сопоставляет параметры соединения с записью в databases[]
|
# Модель не передаёт ни путь к платформе конкретной базы, ни реквизиты хранилища: скрипт
|
||||||
# и берёт repository оттуда. Тот же приём, что в cf-edit.py (сопоставление по configSrc).
|
# сопоставляет параметры соединения с записью в databases[] и берёт их оттуда. Тот же приём,
|
||||||
|
# что в cf-edit.py (сопоставление по configSrc).
|
||||||
def _sg_find_v8project(start_dir):
|
def _sg_find_v8project(start_dir):
|
||||||
d = start_dir
|
d = start_dir
|
||||||
for _ in range(20):
|
for _ in range(20):
|
||||||
@@ -983,7 +991,7 @@ def main():
|
|||||||
args.V8Path = clean_path(args.V8Path, "-V8Path")
|
args.V8Path = clean_path(args.V8Path, "-V8Path")
|
||||||
args.InfoBasePath = clean_path(args.InfoBasePath, "-InfoBasePath")
|
args.InfoBasePath = clean_path(args.InfoBasePath, "-InfoBasePath")
|
||||||
assert_infobase_exists(args.InfoBasePath)
|
assert_infobase_exists(args.InfoBasePath)
|
||||||
v8path = resolve_v8path(args.V8Path)
|
v8path = resolve_v8path(args.V8Path, args)
|
||||||
|
|
||||||
if not args.Command:
|
if not args.Command:
|
||||||
print("Error: -Command is required. Known: %s" % ", ".join(sorted(COMMAND_KEYS)))
|
print("Error: -Command is required. Known: %s" % ", ".join(sorted(COMMAND_KEYS)))
|
||||||
@@ -24,19 +24,19 @@ allowed-tools:
|
|||||||
|
|
||||||
## Параметры подключения
|
## Параметры подключения
|
||||||
|
|
||||||
Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу:
|
Прочитай `.v8-project.json` из корня проекта и разреши базу:
|
||||||
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
||||||
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
||||||
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
||||||
4. Если ветка не совпала — используй `default`
|
4. Если ветка не совпала — используй `default`
|
||||||
Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
|
Платформа — `v8path` найденной записи базы, иначе корневой `v8path`. Не задан ни там, ни там — `-V8Path` не передавай.
|
||||||
Если файла нет — предложи `/db-list add`.
|
Если файла нет — предложи `/db-list add`.
|
||||||
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
||||||
|
|
||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-run.ps1" <параметры>
|
python ".roo/skills/db-run/scripts/db-run.py" <параметры>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Параметры скрипта
|
### Параметры скрипта
|
||||||
@@ -64,14 +64,14 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-run.ps1" <пар
|
|||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# Простой запуск
|
# Простой запуск
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-run.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin"
|
python ".roo/skills/db-run/scripts/db-run.py" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin"
|
||||||
|
|
||||||
# Запуск с обработкой
|
# Запуск с обработкой
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-run.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -Execute "C:\epf\МояОбработка.epf"
|
python ".roo/skills/db-run/scripts/db-run.py" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -Execute "C:\epf\МояОбработка.epf"
|
||||||
|
|
||||||
# Открыть по навигационной ссылке
|
# Открыть по навигационной ссылке
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-run.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -URL "e1cib/data/Справочник.Номенклатура"
|
python ".roo/skills/db-run/scripts/db-run.py" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -URL "e1cib/data/Справочник.Номенклатура"
|
||||||
|
|
||||||
# Серверная база с параметром запуска
|
# Серверная база с параметром запуска
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-run.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -CParam "ЗапуститьОбновление"
|
python ".roo/skills/db-run/scripts/db-run.py" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -CParam "ЗапуститьОбновление"
|
||||||
```
|
```
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# db-run v1.10 — Launch 1C:Enterprise
|
# db-run v1.11 — Launch 1C:Enterprise
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
||||||
<#
|
<#
|
||||||
@@ -262,8 +262,52 @@ $V8Path = ConvertTo-CleanPath $V8Path '-V8Path'
|
|||||||
$InfoBasePath = ConvertTo-CleanPath $InfoBasePath '-InfoBasePath'
|
$InfoBasePath = ConvertTo-CleanPath $InfoBasePath '-InfoBasePath'
|
||||||
$Execute = ConvertTo-CleanPath $Execute '-Execute'
|
$Execute = ConvertTo-CleanPath $Execute '-Execute'
|
||||||
|
|
||||||
|
# --- Запись базы в .v8-project.json ---
|
||||||
|
# Модель не передаёт ни путь к платформе конкретной базы, ни реквизиты хранилища: скрипт
|
||||||
|
# сопоставляет параметры соединения с записью в databases[] и берёт их оттуда. Тот же приём,
|
||||||
|
# что в cf-edit.ps1 (сопоставление по configSrc).
|
||||||
|
function Find-V8Project([string]$startDir) {
|
||||||
|
$d = $startDir
|
||||||
|
for ($i = 0; $i -lt 20 -and $d; $i++) {
|
||||||
|
$pj = Join-Path $d ".v8-project.json"
|
||||||
|
if (Test-Path $pj) { return $pj }
|
||||||
|
$parent = [System.IO.Path]::GetDirectoryName($d)
|
||||||
|
if ($parent -eq $d) { break }
|
||||||
|
$d = $parent
|
||||||
|
}
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
function Test-SamePath {
|
||||||
|
param([string]$A, [string]$B)
|
||||||
|
if (-not $A -or -not $B) { return $false }
|
||||||
|
try {
|
||||||
|
$na = [System.IO.Path]::GetFullPath($A).TrimEnd('\', '/')
|
||||||
|
$nb = [System.IO.Path]::GetFullPath($B).TrimEnd('\', '/')
|
||||||
|
return $na.Equals($nb, [System.StringComparison]::OrdinalIgnoreCase)
|
||||||
|
} catch { return $false }
|
||||||
|
}
|
||||||
|
function Find-ProjectDatabase {
|
||||||
|
# Запись базы в реестре, соответствующая переданному соединению. $null, если не найдена.
|
||||||
|
$pf = Find-V8Project (Get-Location).Path
|
||||||
|
if (-not $pf) { return $null }
|
||||||
|
try { $proj = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json } catch { return $null }
|
||||||
|
if (-not $proj.databases) { return $null }
|
||||||
|
foreach ($db in $proj.databases) {
|
||||||
|
if ($InfoBasePath -and $db.path -and (Test-SamePath $db.path $InfoBasePath)) { return $db }
|
||||||
|
if ($InfoBaseServer -and $InfoBaseRef -and $db.server -and $db.ref) {
|
||||||
|
if ($db.server.Equals($InfoBaseServer, [System.StringComparison]::OrdinalIgnoreCase) -and
|
||||||
|
$db.ref.Equals($InfoBaseRef, [System.StringComparison]::OrdinalIgnoreCase)) { return $db }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
# --- Resolve V8Path ---
|
# --- Resolve V8Path ---
|
||||||
function Find-ProjectV8Path {
|
function Find-ProjectV8Path {
|
||||||
|
# v8path записи базы сильнее корневого: в одном проекте базы живут на разных версиях
|
||||||
|
# платформы, а версию формата выгрузки задаёт та платформа, которая выгружает.
|
||||||
|
$dbRec = Find-ProjectDatabase
|
||||||
|
if ($dbRec -and $dbRec.v8path) { return [string]$dbRec.v8path }
|
||||||
$dir = (Get-Location).Path
|
$dir = (Get-Location).Path
|
||||||
while ($dir) {
|
while ($dir) {
|
||||||
$pf = Join-Path $dir ".v8-project.json"
|
$pf = Join-Path $dir ".v8-project.json"
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# db-run v1.10 — Launch 1C:Enterprise
|
# db-run v1.11 — Launch 1C:Enterprise
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -34,8 +34,62 @@ def ci_parse_args(parser, argv=None):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _find_project_v8path():
|
# --- Запись базы в .v8-project.json ---
|
||||||
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
# Модель не передаёт ни путь к платформе конкретной базы, ни реквизиты хранилища: скрипт
|
||||||
|
# сопоставляет параметры соединения с записью в databases[] и берёт их оттуда. Тот же приём,
|
||||||
|
# что в cf-edit.py (сопоставление по configSrc).
|
||||||
|
def _sg_find_v8project(start_dir):
|
||||||
|
d = start_dir
|
||||||
|
for _ in range(20):
|
||||||
|
if not d:
|
||||||
|
break
|
||||||
|
pj = os.path.join(d, ".v8-project.json")
|
||||||
|
if os.path.isfile(pj):
|
||||||
|
return pj
|
||||||
|
parent = os.path.dirname(d)
|
||||||
|
if parent == d:
|
||||||
|
break
|
||||||
|
d = parent
|
||||||
|
return None
|
||||||
|
|
||||||
|
def same_path(a, b):
|
||||||
|
if not a or not b:
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
return os.path.abspath(a).rstrip("\\/").lower() == os.path.abspath(b).rstrip("\\/").lower()
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def find_project_database(args):
|
||||||
|
"""Запись базы в реестре, соответствующая переданному соединению. None, если не найдена."""
|
||||||
|
pf = _sg_find_v8project(os.getcwd())
|
||||||
|
if not pf:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
with open(pf, encoding="utf-8-sig") as f:
|
||||||
|
proj = json.load(f)
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
for db in proj.get("databases") or []:
|
||||||
|
if args.InfoBasePath and db.get("path") and same_path(db["path"], args.InfoBasePath):
|
||||||
|
return db
|
||||||
|
if args.InfoBaseServer and args.InfoBaseRef and db.get("server") and db.get("ref"):
|
||||||
|
if (db["server"].lower() == args.InfoBaseServer.lower()
|
||||||
|
and db["ref"].lower() == args.InfoBaseRef.lower()):
|
||||||
|
return db
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _find_project_v8path(args):
|
||||||
|
"""Walk up from CWD to find .v8-project.json and read its v8path.
|
||||||
|
|
||||||
|
v8path записи базы сильнее корневого: в одном проекте базы живут на разных версиях
|
||||||
|
платформы, а версию формата выгрузки задаёт та платформа, которая выгружает.
|
||||||
|
"""
|
||||||
|
db = find_project_database(args)
|
||||||
|
if db and db.get("v8path"):
|
||||||
|
return db["v8path"]
|
||||||
d = os.getcwd()
|
d = os.getcwd()
|
||||||
while True:
|
while True:
|
||||||
pf = os.path.join(d, ".v8-project.json")
|
pf = os.path.join(d, ".v8-project.json")
|
||||||
@@ -258,10 +312,10 @@ def _version_key(p):
|
|||||||
return [int(x) for x in re.findall(r"\d+", _version_dir(p))]
|
return [int(x) for x in re.findall(r"\d+", _version_dir(p))]
|
||||||
|
|
||||||
|
|
||||||
def resolve_v8path(v8path):
|
def resolve_v8path(v8path, args):
|
||||||
"""Resolve path to a 1C executable (1cv8; ibcmd only when given explicitly)."""
|
"""Resolve path to a 1C executable (1cv8; ibcmd only when given explicitly)."""
|
||||||
if not v8path:
|
if not v8path:
|
||||||
v8path = _find_project_v8path()
|
v8path = _find_project_v8path(args)
|
||||||
if not v8path:
|
if not v8path:
|
||||||
if os.name == "nt":
|
if os.name == "nt":
|
||||||
candidates = (
|
candidates = (
|
||||||
@@ -324,7 +378,7 @@ def main():
|
|||||||
args.InfoBasePath = clean_path(args.InfoBasePath, "-InfoBasePath")
|
args.InfoBasePath = clean_path(args.InfoBasePath, "-InfoBasePath")
|
||||||
args.Execute = clean_path(args.Execute, "-Execute")
|
args.Execute = clean_path(args.Execute, "-Execute")
|
||||||
|
|
||||||
v8path = resolve_v8path(args.V8Path)
|
v8path = resolve_v8path(args.V8Path, args)
|
||||||
|
|
||||||
# --- Resolve additional arguments ---
|
# --- Resolve additional arguments ---
|
||||||
# 1C:Enterprise is always launched by 1cv8 — ibcmd has no interactive mode.
|
# 1C:Enterprise is always launched by 1cv8 — ibcmd has no interactive mode.
|
||||||
@@ -25,19 +25,19 @@ allowed-tools:
|
|||||||
|
|
||||||
## Параметры подключения
|
## Параметры подключения
|
||||||
|
|
||||||
Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу:
|
Прочитай `.v8-project.json` из корня проекта и разреши базу:
|
||||||
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
||||||
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
||||||
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
||||||
4. Если ветка не совпала — используй `default`
|
4. Если ветка не совпала — используй `default`
|
||||||
Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
|
Платформа — `v8path` найденной записи базы, иначе корневой `v8path`. Не задан ни там, ни там — `-V8Path` не передавай.
|
||||||
Если файла нет — предложи `/db-list add`.
|
Если файла нет — предложи `/db-list add`.
|
||||||
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
||||||
|
|
||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-update.ps1" <параметры>
|
python ".roo/skills/db-update/scripts/db-update.py" <параметры>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Параметры скрипта
|
### Параметры скрипта
|
||||||
@@ -75,11 +75,11 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-update.ps1" <п
|
|||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# Обычное обновление (файловая база)
|
# Обычное обновление (файловая база)
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-update.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin"
|
python ".roo/skills/db-update/scripts/db-update.py" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin"
|
||||||
|
|
||||||
# Динамическое обновление
|
# Динамическое обновление
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-update.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -Dynamic on
|
python ".roo/skills/db-update/scripts/db-update.py" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -Dynamic on
|
||||||
|
|
||||||
# Обновление расширения
|
# Обновление расширения
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-update.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -Extension "МоёРасширение"
|
python ".roo/skills/db-update/scripts/db-update.py" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -Extension "МоёРасширение"
|
||||||
```
|
```
|
||||||
+9
-4
@@ -1,4 +1,4 @@
|
|||||||
# db-update v1.20 — Update 1C database configuration
|
# db-update v1.21 — Update 1C database configuration
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
||||||
<#
|
<#
|
||||||
@@ -124,9 +124,10 @@ if ($Dynamic) { $Dynamic = if (@('on', 'yes', '+') -contains $Dynamic.ToLower())
|
|||||||
$OutputEncoding = [System.Text.Encoding]::UTF8
|
$OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
|
|
||||||
# --- Реквизиты хранилища из .v8-project.json ---
|
# --- Запись базы в .v8-project.json ---
|
||||||
# Модель их не передаёт: скрипт сопоставляет параметры соединения с записью в databases[]
|
# Модель не передаёт ни путь к платформе конкретной базы, ни реквизиты хранилища: скрипт
|
||||||
# и берёт repository оттуда. Тот же приём, что в cf-edit.ps1 (сопоставление по configSrc).
|
# сопоставляет параметры соединения с записью в databases[] и берёт их оттуда. Тот же приём,
|
||||||
|
# что в cf-edit.ps1 (сопоставление по configSrc).
|
||||||
function Find-V8Project([string]$startDir) {
|
function Find-V8Project([string]$startDir) {
|
||||||
$d = $startDir
|
$d = $startDir
|
||||||
for ($i = 0; $i -lt 20 -and $d; $i++) {
|
for ($i = 0; $i -lt 20 -and $d; $i++) {
|
||||||
@@ -410,6 +411,10 @@ Assert-InfoBaseExists $InfoBasePath
|
|||||||
|
|
||||||
# --- Resolve V8Path ---
|
# --- Resolve V8Path ---
|
||||||
function Find-ProjectV8Path {
|
function Find-ProjectV8Path {
|
||||||
|
# v8path записи базы сильнее корневого: в одном проекте базы живут на разных версиях
|
||||||
|
# платформы, а версию формата выгрузки задаёт та платформа, которая выгружает.
|
||||||
|
$dbRec = Find-ProjectDatabase
|
||||||
|
if ($dbRec -and $dbRec.v8path) { return [string]$dbRec.v8path }
|
||||||
$dir = (Get-Location).Path
|
$dir = (Get-Location).Path
|
||||||
while ($dir) {
|
while ($dir) {
|
||||||
$pf = Join-Path $dir ".v8-project.json"
|
$pf = Join-Path $dir ".v8-project.json"
|
||||||
+17
-9
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# db-update v1.20 — Update 1C database configuration
|
# db-update v1.21 — Update 1C database configuration
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -37,8 +37,15 @@ def ci_parse_args(parser, argv=None):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _find_project_v8path():
|
def _find_project_v8path(args):
|
||||||
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
"""Walk up from CWD to find .v8-project.json and read its v8path.
|
||||||
|
|
||||||
|
v8path записи базы сильнее корневого: в одном проекте базы живут на разных версиях
|
||||||
|
платформы, а версию формата выгрузки задаёт та платформа, которая выгружает.
|
||||||
|
"""
|
||||||
|
db = find_project_database(args)
|
||||||
|
if db and db.get("v8path"):
|
||||||
|
return db["v8path"]
|
||||||
d = os.getcwd()
|
d = os.getcwd()
|
||||||
while True:
|
while True:
|
||||||
pf = os.path.join(d, ".v8-project.json")
|
pf = os.path.join(d, ".v8-project.json")
|
||||||
@@ -87,9 +94,10 @@ V8_SECRET_KEYS = ["/P", "/UC", "/WSP", "/AWSP", "/ConfigurationRepositoryP"]
|
|||||||
IBCMD_SECRET_KEYS = ["--password", "--token", "--db-pwd"]
|
IBCMD_SECRET_KEYS = ["--password", "--token", "--db-pwd"]
|
||||||
|
|
||||||
|
|
||||||
# --- Реквизиты хранилища из .v8-project.json ---
|
# --- Запись базы в .v8-project.json ---
|
||||||
# Модель их не передаёт: скрипт сопоставляет параметры соединения с записью в databases[]
|
# Модель не передаёт ни путь к платформе конкретной базы, ни реквизиты хранилища: скрипт
|
||||||
# и берёт repository оттуда. Тот же приём, что в cf-edit.py (сопоставление по configSrc).
|
# сопоставляет параметры соединения с записью в databases[] и берёт их оттуда. Тот же приём,
|
||||||
|
# что в cf-edit.py (сопоставление по configSrc).
|
||||||
def _sg_find_v8project(start_dir):
|
def _sg_find_v8project(start_dir):
|
||||||
d = start_dir
|
d = start_dir
|
||||||
for _ in range(20):
|
for _ in range(20):
|
||||||
@@ -328,10 +336,10 @@ def _version_key(p):
|
|||||||
return [int(x) for x in re.findall(r"\d+", _version_dir(p))]
|
return [int(x) for x in re.findall(r"\d+", _version_dir(p))]
|
||||||
|
|
||||||
|
|
||||||
def resolve_v8path(v8path):
|
def resolve_v8path(v8path, args):
|
||||||
"""Resolve path to a 1C executable (1cv8; ibcmd only when given explicitly)."""
|
"""Resolve path to a 1C executable (1cv8; ibcmd only when given explicitly)."""
|
||||||
if not v8path:
|
if not v8path:
|
||||||
v8path = _find_project_v8path()
|
v8path = _find_project_v8path(args)
|
||||||
if not v8path:
|
if not v8path:
|
||||||
if os.name == "nt":
|
if os.name == "nt":
|
||||||
candidates = (
|
candidates = (
|
||||||
@@ -659,7 +667,7 @@ def main():
|
|||||||
args.InfoBasePath = clean_path(args.InfoBasePath, "-InfoBasePath")
|
args.InfoBasePath = clean_path(args.InfoBasePath, "-InfoBasePath")
|
||||||
assert_infobase_exists(args.InfoBasePath)
|
assert_infobase_exists(args.InfoBasePath)
|
||||||
|
|
||||||
v8path = resolve_v8path(args.V8Path)
|
v8path = resolve_v8path(args.V8Path, args)
|
||||||
|
|
||||||
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
|
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
|
||||||
|
|
||||||
@@ -28,20 +28,20 @@ allowed-tools:
|
|||||||
Предпочтительно использовать конкретную базу — это надёжнее. Временная база всё равно поднимается
|
Предпочтительно использовать конкретную базу — это надёжнее. Временная база всё равно поднимается
|
||||||
под проверку исходников, если она не отключена.
|
под проверку исходников, если она не отключена.
|
||||||
|
|
||||||
1. Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` и разреши базу:
|
1. Прочитай `.v8-project.json` из корня проекта и разреши базу:
|
||||||
2. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
2. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
||||||
3. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
3. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
||||||
4. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
4. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
||||||
5. Если ветка не совпала — используй `default`
|
5. Если ветка не совпала — используй `default`
|
||||||
6. Если `.v8-project.json` нет или база не найдена — не указывай параметры подключения: скрипт автоматически создаст временную базу. Для EPF со ссылочными типами (CatalogRef, DocumentRef и т.д.) генерируются заглушки метаданных. Временная база удаляется после сборки.
|
6. Если `.v8-project.json` нет или база не найдена — не указывай параметры подключения: скрипт автоматически создаст временную базу. Для EPF со ссылочными типами (CatalogRef, DocumentRef и т.д.) генерируются заглушки метаданных. Временная база удаляется после сборки.
|
||||||
|
|
||||||
Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
|
Платформа — `v8path` найденной записи базы, иначе корневой `v8path`. Не задан ни там, ни там — `-V8Path` не передавай.
|
||||||
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
||||||
|
|
||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-build.ps1" <параметры>
|
python ".roo/skills/epf-build/scripts/epf-build.py" <параметры>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Параметры скрипта
|
### Параметры скрипта
|
||||||
@@ -76,8 +76,8 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-build.ps1" <п
|
|||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# Сборка обработки (файловая база)
|
# Сборка обработки (файловая база)
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-build.ps1" -InfoBasePath "C:\Bases\MyDB" -SourceFile "src/МояОбработка.xml" -OutputFile "build/МояОбработка.epf"
|
python ".roo/skills/epf-build/scripts/epf-build.py" -InfoBasePath "C:\Bases\MyDB" -SourceFile "src/МояОбработка.xml" -OutputFile "build/МояОбработка.epf"
|
||||||
|
|
||||||
# Серверная база
|
# Серверная база
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-build.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -SourceFile "src/МояОбработка.xml" -OutputFile "build/МояОбработка.epf"
|
python ".roo/skills/epf-build/scripts/epf-build.py" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -SourceFile "src/МояОбработка.xml" -OutputFile "build/МояОбработка.epf"
|
||||||
```
|
```
|
||||||
+45
-13
@@ -1,4 +1,4 @@
|
|||||||
# epf-build v1.17 — Build external data processor or report (EPF/ERF) from XML sources
|
# epf-build v1.19 — Build external data processor or report (EPF/ERF) from XML sources
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
||||||
<#
|
<#
|
||||||
@@ -276,8 +276,52 @@ function Assert-InfoBaseExists {
|
|||||||
|
|
||||||
Assert-InfoBaseExists $InfoBasePath
|
Assert-InfoBaseExists $InfoBasePath
|
||||||
|
|
||||||
|
# --- Запись базы в .v8-project.json ---
|
||||||
|
# Модель не передаёт ни путь к платформе конкретной базы, ни реквизиты хранилища: скрипт
|
||||||
|
# сопоставляет параметры соединения с записью в databases[] и берёт их оттуда. Тот же приём,
|
||||||
|
# что в cf-edit.ps1 (сопоставление по configSrc).
|
||||||
|
function Find-V8Project([string]$startDir) {
|
||||||
|
$d = $startDir
|
||||||
|
for ($i = 0; $i -lt 20 -and $d; $i++) {
|
||||||
|
$pj = Join-Path $d ".v8-project.json"
|
||||||
|
if (Test-Path $pj) { return $pj }
|
||||||
|
$parent = [System.IO.Path]::GetDirectoryName($d)
|
||||||
|
if ($parent -eq $d) { break }
|
||||||
|
$d = $parent
|
||||||
|
}
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
function Test-SamePath {
|
||||||
|
param([string]$A, [string]$B)
|
||||||
|
if (-not $A -or -not $B) { return $false }
|
||||||
|
try {
|
||||||
|
$na = [System.IO.Path]::GetFullPath($A).TrimEnd('\', '/')
|
||||||
|
$nb = [System.IO.Path]::GetFullPath($B).TrimEnd('\', '/')
|
||||||
|
return $na.Equals($nb, [System.StringComparison]::OrdinalIgnoreCase)
|
||||||
|
} catch { return $false }
|
||||||
|
}
|
||||||
|
function Find-ProjectDatabase {
|
||||||
|
# Запись базы в реестре, соответствующая переданному соединению. $null, если не найдена.
|
||||||
|
$pf = Find-V8Project (Get-Location).Path
|
||||||
|
if (-not $pf) { return $null }
|
||||||
|
try { $proj = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json } catch { return $null }
|
||||||
|
if (-not $proj.databases) { return $null }
|
||||||
|
foreach ($db in $proj.databases) {
|
||||||
|
if ($InfoBasePath -and $db.path -and (Test-SamePath $db.path $InfoBasePath)) { return $db }
|
||||||
|
if ($InfoBaseServer -and $InfoBaseRef -and $db.server -and $db.ref) {
|
||||||
|
if ($db.server.Equals($InfoBaseServer, [System.StringComparison]::OrdinalIgnoreCase) -and
|
||||||
|
$db.ref.Equals($InfoBaseRef, [System.StringComparison]::OrdinalIgnoreCase)) { return $db }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
# --- Resolve V8Path ---
|
# --- Resolve V8Path ---
|
||||||
function Find-ProjectV8Path {
|
function Find-ProjectV8Path {
|
||||||
|
# v8path записи базы сильнее корневого: в одном проекте базы живут на разных версиях
|
||||||
|
# платформы, а версию формата выгрузки задаёт та платформа, которая выгружает.
|
||||||
|
$dbRec = Find-ProjectDatabase
|
||||||
|
if ($dbRec -and $dbRec.v8path) { return [string]$dbRec.v8path }
|
||||||
$dir = (Get-Location).Path
|
$dir = (Get-Location).Path
|
||||||
while ($dir) {
|
while ($dir) {
|
||||||
$pf = Join-Path $dir ".v8-project.json"
|
$pf = Join-Path $dir ".v8-project.json"
|
||||||
@@ -397,18 +441,6 @@ function Test-OutputNonEmpty {
|
|||||||
return (Test-Path $Path -PathType Leaf) -and ((Get-Item $Path -ErrorAction SilentlyContinue).Length -gt 0)
|
return (Test-Path $Path -PathType Leaf) -and ((Get-Item $Path -ErrorAction SilentlyContinue).Length -gt 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
function Find-V8Project([string]$startDir) {
|
|
||||||
$d = $startDir
|
|
||||||
for ($i = 0; $i -lt 20 -and $d; $i++) {
|
|
||||||
$pj = Join-Path $d ".v8-project.json"
|
|
||||||
if (Test-Path $pj) { return $pj }
|
|
||||||
$parent = [System.IO.Path]::GetDirectoryName($d)
|
|
||||||
if ($parent -eq $d) { break }
|
|
||||||
$d = $parent
|
|
||||||
}
|
|
||||||
return $null
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Проверка исходников платформой ---
|
# --- Проверка исходников платформой ---
|
||||||
# Сборка .epf/.erf ничего не проверяет: /LoadExternalDataProcessorOrReportFromFiles упаковывает XML
|
# Сборка .epf/.erf ничего не проверяет: /LoadExternalDataProcessorOrReportFromFiles упаковывает XML
|
||||||
# и модули не компилирует, поэтому сломанный модуль доезжает до пользователя и падает при открытии
|
# и модули не компилирует, поэтому сломанный модуль доезжает до пользователя и падает при открытии
|
||||||
+46
-6
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# epf-build v1.17 — Build external data processor or report (EPF/ERF) from XML sources
|
# epf-build v1.19 — Build external data processor or report (EPF/ERF) from XML sources
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -38,8 +38,48 @@ def ci_parse_args(parser, argv=None):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _find_project_v8path():
|
# --- Запись базы в .v8-project.json ---
|
||||||
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
# Модель не передаёт ни путь к платформе конкретной базы, ни реквизиты хранилища: скрипт
|
||||||
|
# сопоставляет параметры соединения с записью в databases[] и берёт их оттуда. Тот же приём,
|
||||||
|
# что в cf-edit.py (сопоставление по configSrc).
|
||||||
|
def same_path(a, b):
|
||||||
|
if not a or not b:
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
return os.path.abspath(a).rstrip("\\/").lower() == os.path.abspath(b).rstrip("\\/").lower()
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def find_project_database(args):
|
||||||
|
"""Запись базы в реестре, соответствующая переданному соединению. None, если не найдена."""
|
||||||
|
pf = _sg_find_v8project(os.getcwd())
|
||||||
|
if not pf:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
with open(pf, encoding="utf-8-sig") as f:
|
||||||
|
proj = json.load(f)
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
for db in proj.get("databases") or []:
|
||||||
|
if args.InfoBasePath and db.get("path") and same_path(db["path"], args.InfoBasePath):
|
||||||
|
return db
|
||||||
|
if args.InfoBaseServer and args.InfoBaseRef and db.get("server") and db.get("ref"):
|
||||||
|
if (db["server"].lower() == args.InfoBaseServer.lower()
|
||||||
|
and db["ref"].lower() == args.InfoBaseRef.lower()):
|
||||||
|
return db
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _find_project_v8path(args):
|
||||||
|
"""Walk up from CWD to find .v8-project.json and read its v8path.
|
||||||
|
|
||||||
|
v8path записи базы сильнее корневого: в одном проекте базы живут на разных версиях
|
||||||
|
платформы, а версию формата выгрузки задаёт та платформа, которая выгружает.
|
||||||
|
"""
|
||||||
|
db = find_project_database(args)
|
||||||
|
if db and db.get("v8path"):
|
||||||
|
return db["v8path"]
|
||||||
d = os.getcwd()
|
d = os.getcwd()
|
||||||
while True:
|
while True:
|
||||||
pf = os.path.join(d, ".v8-project.json")
|
pf = os.path.join(d, ".v8-project.json")
|
||||||
@@ -244,10 +284,10 @@ def _version_key(p):
|
|||||||
return [int(x) for x in re.findall(r"\d+", _version_dir(p))]
|
return [int(x) for x in re.findall(r"\d+", _version_dir(p))]
|
||||||
|
|
||||||
|
|
||||||
def resolve_v8path(v8path):
|
def resolve_v8path(v8path, args):
|
||||||
"""Resolve path to a 1C executable (1cv8; ibcmd only when given explicitly)."""
|
"""Resolve path to a 1C executable (1cv8; ibcmd only when given explicitly)."""
|
||||||
if not v8path:
|
if not v8path:
|
||||||
v8path = _find_project_v8path()
|
v8path = _find_project_v8path(args)
|
||||||
if not v8path:
|
if not v8path:
|
||||||
if os.name == "nt":
|
if os.name == "nt":
|
||||||
candidates = (
|
candidates = (
|
||||||
@@ -576,7 +616,7 @@ def main():
|
|||||||
args.OutputFile = clean_path(args.OutputFile, "-OutputFile")
|
args.OutputFile = clean_path(args.OutputFile, "-OutputFile")
|
||||||
|
|
||||||
# --- Resolve V8Path ---
|
# --- Resolve V8Path ---
|
||||||
v8path = resolve_v8path(args.V8Path)
|
v8path = resolve_v8path(args.V8Path, args)
|
||||||
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
|
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
|
||||||
|
|
||||||
# --- Resolve additional arguments for the selected engine ---
|
# --- Resolve additional arguments for the selected engine ---
|
||||||
+13
-3
@@ -1,4 +1,4 @@
|
|||||||
# stub-db-create v1.10 — Create temp 1C infobase with metadata stubs for EPF/ERF build
|
# stub-db-create v1.11 — Create temp 1C infobase with metadata stubs for EPF/ERF build
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
@@ -387,6 +387,16 @@ if ($needsRegistrator) {
|
|||||||
#
|
#
|
||||||
# Подстановка типа делается ТОЛЬКО в .xml (это DefaultForm и основной реквизит формы); в .bsl
|
# Подстановка типа делается ТОЛЬКО в .xml (это DefaultForm и основной реквизит формы); в .bsl
|
||||||
# такой же текст был бы кодом, и трогать его нельзя.
|
# такой же текст был бы кодом, и трогать его нельзя.
|
||||||
|
function Convert-RefTags {
|
||||||
|
param([string]$Text, [string]$ExtTag, [string]$CfgTag)
|
||||||
|
|
||||||
|
# Только префикс вида объекта в начале квалифицированного имени. Глобальная замена подстроки
|
||||||
|
# резала бы и имя самого объекта, если оно оканчивается так же (ПриёмкаExternalReport), —
|
||||||
|
# ссылка расходилась с именем, и заглушка не грузилась. [regex]::Replace, а не -replace:
|
||||||
|
# оператор регистронезависим, а теги 1С регистрозависимы.
|
||||||
|
return [regex]::Replace($Text, "(?<![\w.])$ExtTag(Object)?\.", ($CfgTag + '$1.'))
|
||||||
|
}
|
||||||
|
|
||||||
function Add-SourceObjectToConfig {
|
function Add-SourceObjectToConfig {
|
||||||
param([string]$SourceXml, [string]$CfgDir)
|
param([string]$SourceXml, [string]$CfgDir)
|
||||||
|
|
||||||
@@ -416,7 +426,7 @@ function Add-SourceObjectToConfig {
|
|||||||
|
|
||||||
$conv = $reGuid.Replace($text, $reissue)
|
$conv = $reGuid.Replace($text, $reissue)
|
||||||
$conv = $conv.Replace("<$extTag ", "<$cfgTag ").Replace("<$extTag>", "<$cfgTag>").Replace("</$extTag>", "</$cfgTag>")
|
$conv = $conv.Replace("<$extTag ", "<$cfgTag ").Replace("<$extTag>", "<$cfgTag>").Replace("</$extTag>", "</$cfgTag>")
|
||||||
$conv = $conv.Replace("${extTag}Object.", "${cfgTag}Object.").Replace("$extTag.", "$cfgTag.")
|
$conv = Convert-RefTags $conv $extTag $cfgTag
|
||||||
|
|
||||||
# Тип менеджера у внешней обработки не объявлен, а объекту конфигурации он обязателен:
|
# Тип менеджера у внешней обработки не объявлен, а объекту конфигурации он обязателен:
|
||||||
# без него платформа отвечает «отсутствует один или более типов объекта».
|
# без него платформа отвечает «отсутствует один или более типов объекта».
|
||||||
@@ -454,7 +464,7 @@ function Add-SourceObjectToConfig {
|
|||||||
if ($f.Extension -ieq '.xml') {
|
if ($f.Extension -ieq '.xml') {
|
||||||
$t = [IO.File]::ReadAllText($f.FullName, [Text.Encoding]::UTF8)
|
$t = [IO.File]::ReadAllText($f.FullName, [Text.Encoding]::UTF8)
|
||||||
$t = $reGuid.Replace($t, $reissue)
|
$t = $reGuid.Replace($t, $reissue)
|
||||||
$t = $t.Replace("${extTag}Object.", "${cfgTag}Object.").Replace("$extTag.", "$cfgTag.")
|
$t = Convert-RefTags $t $extTag $cfgTag
|
||||||
[IO.File]::WriteAllText($dst, $t, $encBom)
|
[IO.File]::WriteAllText($dst, $t, $encBom)
|
||||||
} else {
|
} else {
|
||||||
Copy-Item -Path $f.FullName -Destination $dst -Force
|
Copy-Item -Path $f.FullName -Destination $dst -Force
|
||||||
+11
-3
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# stub-db-create v1.10 — Create temp 1C infobase with metadata stubs for EPF/ERF build
|
# stub-db-create v1.11 — Create temp 1C infobase with metadata stubs for EPF/ERF build
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -1133,6 +1133,14 @@ def write_bom(path, content):
|
|||||||
GUID_RE = re.compile(r'[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}')
|
GUID_RE = re.compile(r'[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}')
|
||||||
|
|
||||||
|
|
||||||
|
def retag_refs(text, ext_tag, cfg_tag):
|
||||||
|
# Только префикс вида объекта в начале квалифицированного имени. Глобальная замена подстроки
|
||||||
|
# резала бы и имя самого объекта, если оно оканчивается так же (ПриёмкаExternalReport), —
|
||||||
|
# ссылка расходилась с именем, и заглушка не грузилась.
|
||||||
|
return re.sub(r'(?<![\w.])%s(Object)?\.' % ext_tag,
|
||||||
|
lambda m: cfg_tag + (m.group(1) or '') + '.', text)
|
||||||
|
|
||||||
|
|
||||||
def add_source_object_to_config(source_xml, cfg_dir):
|
def add_source_object_to_config(source_xml, cfg_dir):
|
||||||
# Копия объекта живёт в конфигурации базы, а следом в ту же базу грузится исходник как ВНЕШНЯЯ
|
# Копия объекта живёт в конфигурации базы, а следом в ту же базу грузится исходник как ВНЕШНЯЯ
|
||||||
# обработка. С одинаковыми идентификаторами платформа путает их и через раз отвечает «Исключение
|
# обработка. С одинаковыми идентификаторами платформа путает их и через раз отвечает «Исключение
|
||||||
@@ -1163,7 +1171,7 @@ def add_source_object_to_config(source_xml, cfg_dir):
|
|||||||
conv = reissue(text)
|
conv = reissue(text)
|
||||||
conv = conv.replace('<%s ' % ext_tag, '<%s ' % cfg_tag).replace('<%s>' % ext_tag, '<%s>' % cfg_tag)
|
conv = conv.replace('<%s ' % ext_tag, '<%s ' % cfg_tag).replace('<%s>' % ext_tag, '<%s>' % cfg_tag)
|
||||||
conv = conv.replace('</%s>' % ext_tag, '</%s>' % cfg_tag)
|
conv = conv.replace('</%s>' % ext_tag, '</%s>' % cfg_tag)
|
||||||
conv = conv.replace('%sObject.' % ext_tag, '%sObject.' % cfg_tag).replace('%s.' % ext_tag, '%s.' % cfg_tag)
|
conv = retag_refs(conv, ext_tag, cfg_tag)
|
||||||
|
|
||||||
# Тип менеджера у внешней обработки не объявлен, а объекту конфигурации он обязателен:
|
# Тип менеджера у внешней обработки не объявлен, а объекту конфигурации он обязателен:
|
||||||
# без него платформа отвечает «отсутствует один или более типов объекта».
|
# без него платформа отвечает «отсутствует один или более типов объекта».
|
||||||
@@ -1200,7 +1208,7 @@ def add_source_object_to_config(source_xml, cfg_dir):
|
|||||||
with io.open(full, encoding='utf-8-sig') as fh:
|
with io.open(full, encoding='utf-8-sig') as fh:
|
||||||
t = fh.read()
|
t = fh.read()
|
||||||
t = reissue(t)
|
t = reissue(t)
|
||||||
t = t.replace('%sObject.' % ext_tag, '%sObject.' % cfg_tag).replace('%s.' % ext_tag, '%s.' % cfg_tag)
|
t = retag_refs(t, ext_tag, cfg_tag)
|
||||||
write_bom(dst, t)
|
write_bom(dst, t)
|
||||||
else:
|
else:
|
||||||
shutil.copyfile(full, dst)
|
shutil.copyfile(full, dst)
|
||||||
@@ -26,20 +26,20 @@ allowed-tools:
|
|||||||
|
|
||||||
Для разборки EPF/ERF требуется информационная база с конфигурацией. Без базы ссылочные типы безвозвратно теряются.
|
Для разборки EPF/ERF требуется информационная база с конфигурацией. Без базы ссылочные типы безвозвратно теряются.
|
||||||
|
|
||||||
1. Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` и разреши базу:
|
1. Прочитай `.v8-project.json` из корня проекта и разреши базу:
|
||||||
2. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
2. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
||||||
3. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
3. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
||||||
4. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
4. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
||||||
5. Если ветка не совпала — используй `default`
|
5. Если ветка не совпала — используй `default`
|
||||||
6. Если `.v8-project.json` нет или база не найдена — **сообщи пользователю об ошибке**. Для dump база обязательна: в пустой базе ссылочные типы (CatalogRef, DocumentRef и т.д.) безвозвратно сбрасываются в строки. Предложи указать базу или зарегистрировать через `/db-list add`.
|
6. Если `.v8-project.json` нет или база не найдена — **сообщи пользователю об ошибке**. Для dump база обязательна: в пустой базе ссылочные типы (CatalogRef, DocumentRef и т.д.) безвозвратно сбрасываются в строки. Предложи указать базу или зарегистрировать через `/db-list add`.
|
||||||
|
|
||||||
Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
|
Платформа — `v8path` найденной записи базы, иначе корневой `v8path`. Не задан ни там, ни там — `-V8Path` не передавай.
|
||||||
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
||||||
|
|
||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-dump.ps1" <параметры>
|
python ".roo/skills/epf-dump/scripts/epf-dump.py" <параметры>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Параметры скрипта
|
### Параметры скрипта
|
||||||
@@ -64,8 +64,8 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-dump.ps1" <па
|
|||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# Разборка обработки (файловая база)
|
# Разборка обработки (файловая база)
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-dump.ps1" -InfoBasePath "C:\Bases\MyDB" -InputFile "build/МояОбработка.epf" -OutputDir "src"
|
python ".roo/skills/epf-dump/scripts/epf-dump.py" -InfoBasePath "C:\Bases\MyDB" -InputFile "build/МояОбработка.epf" -OutputDir "src"
|
||||||
|
|
||||||
# Серверная база
|
# Серверная база
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-dump.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -InputFile "build/МояОбработка.epf" -OutputDir "src"
|
python ".roo/skills/epf-dump/scripts/epf-dump.py" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -InputFile "build/МояОбработка.epf" -OutputDir "src"
|
||||||
```
|
```
|
||||||
+45
-1
@@ -1,4 +1,4 @@
|
|||||||
# epf-dump v1.15 — Dump external data processor or report (EPF/ERF) to XML sources
|
# epf-dump v1.16 — Dump external data processor or report (EPF/ERF) to XML sources
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
||||||
<#
|
<#
|
||||||
@@ -267,8 +267,52 @@ function Assert-InfoBaseExists {
|
|||||||
|
|
||||||
Assert-InfoBaseExists $InfoBasePath
|
Assert-InfoBaseExists $InfoBasePath
|
||||||
|
|
||||||
|
# --- Запись базы в .v8-project.json ---
|
||||||
|
# Модель не передаёт ни путь к платформе конкретной базы, ни реквизиты хранилища: скрипт
|
||||||
|
# сопоставляет параметры соединения с записью в databases[] и берёт их оттуда. Тот же приём,
|
||||||
|
# что в cf-edit.ps1 (сопоставление по configSrc).
|
||||||
|
function Find-V8Project([string]$startDir) {
|
||||||
|
$d = $startDir
|
||||||
|
for ($i = 0; $i -lt 20 -and $d; $i++) {
|
||||||
|
$pj = Join-Path $d ".v8-project.json"
|
||||||
|
if (Test-Path $pj) { return $pj }
|
||||||
|
$parent = [System.IO.Path]::GetDirectoryName($d)
|
||||||
|
if ($parent -eq $d) { break }
|
||||||
|
$d = $parent
|
||||||
|
}
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
function Test-SamePath {
|
||||||
|
param([string]$A, [string]$B)
|
||||||
|
if (-not $A -or -not $B) { return $false }
|
||||||
|
try {
|
||||||
|
$na = [System.IO.Path]::GetFullPath($A).TrimEnd('\', '/')
|
||||||
|
$nb = [System.IO.Path]::GetFullPath($B).TrimEnd('\', '/')
|
||||||
|
return $na.Equals($nb, [System.StringComparison]::OrdinalIgnoreCase)
|
||||||
|
} catch { return $false }
|
||||||
|
}
|
||||||
|
function Find-ProjectDatabase {
|
||||||
|
# Запись базы в реестре, соответствующая переданному соединению. $null, если не найдена.
|
||||||
|
$pf = Find-V8Project (Get-Location).Path
|
||||||
|
if (-not $pf) { return $null }
|
||||||
|
try { $proj = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json } catch { return $null }
|
||||||
|
if (-not $proj.databases) { return $null }
|
||||||
|
foreach ($db in $proj.databases) {
|
||||||
|
if ($InfoBasePath -and $db.path -and (Test-SamePath $db.path $InfoBasePath)) { return $db }
|
||||||
|
if ($InfoBaseServer -and $InfoBaseRef -and $db.server -and $db.ref) {
|
||||||
|
if ($db.server.Equals($InfoBaseServer, [System.StringComparison]::OrdinalIgnoreCase) -and
|
||||||
|
$db.ref.Equals($InfoBaseRef, [System.StringComparison]::OrdinalIgnoreCase)) { return $db }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
# --- Resolve V8Path ---
|
# --- Resolve V8Path ---
|
||||||
function Find-ProjectV8Path {
|
function Find-ProjectV8Path {
|
||||||
|
# v8path записи базы сильнее корневого: в одном проекте базы живут на разных версиях
|
||||||
|
# платформы, а версию формата выгрузки задаёт та платформа, которая выгружает.
|
||||||
|
$dbRec = Find-ProjectDatabase
|
||||||
|
if ($dbRec -and $dbRec.v8path) { return [string]$dbRec.v8path }
|
||||||
$dir = (Get-Location).Path
|
$dir = (Get-Location).Path
|
||||||
while ($dir) {
|
while ($dir) {
|
||||||
$pf = Join-Path $dir ".v8-project.json"
|
$pf = Join-Path $dir ".v8-project.json"
|
||||||
+60
-6
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# epf-dump v1.15 — Dump external data processor or report (EPF/ERF) to XML sources
|
# epf-dump v1.16 — Dump external data processor or report (EPF/ERF) to XML sources
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -37,8 +37,62 @@ def ci_parse_args(parser, argv=None):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _find_project_v8path():
|
# --- Запись базы в .v8-project.json ---
|
||||||
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
# Модель не передаёт ни путь к платформе конкретной базы, ни реквизиты хранилища: скрипт
|
||||||
|
# сопоставляет параметры соединения с записью в databases[] и берёт их оттуда. Тот же приём,
|
||||||
|
# что в cf-edit.py (сопоставление по configSrc).
|
||||||
|
def _sg_find_v8project(start_dir):
|
||||||
|
d = start_dir
|
||||||
|
for _ in range(20):
|
||||||
|
if not d:
|
||||||
|
break
|
||||||
|
pj = os.path.join(d, ".v8-project.json")
|
||||||
|
if os.path.isfile(pj):
|
||||||
|
return pj
|
||||||
|
parent = os.path.dirname(d)
|
||||||
|
if parent == d:
|
||||||
|
break
|
||||||
|
d = parent
|
||||||
|
return None
|
||||||
|
|
||||||
|
def same_path(a, b):
|
||||||
|
if not a or not b:
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
return os.path.abspath(a).rstrip("\\/").lower() == os.path.abspath(b).rstrip("\\/").lower()
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def find_project_database(args):
|
||||||
|
"""Запись базы в реестре, соответствующая переданному соединению. None, если не найдена."""
|
||||||
|
pf = _sg_find_v8project(os.getcwd())
|
||||||
|
if not pf:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
with open(pf, encoding="utf-8-sig") as f:
|
||||||
|
proj = json.load(f)
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
for db in proj.get("databases") or []:
|
||||||
|
if args.InfoBasePath and db.get("path") and same_path(db["path"], args.InfoBasePath):
|
||||||
|
return db
|
||||||
|
if args.InfoBaseServer and args.InfoBaseRef and db.get("server") and db.get("ref"):
|
||||||
|
if (db["server"].lower() == args.InfoBaseServer.lower()
|
||||||
|
and db["ref"].lower() == args.InfoBaseRef.lower()):
|
||||||
|
return db
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _find_project_v8path(args):
|
||||||
|
"""Walk up from CWD to find .v8-project.json and read its v8path.
|
||||||
|
|
||||||
|
v8path записи базы сильнее корневого: в одном проекте базы живут на разных версиях
|
||||||
|
платформы, а версию формата выгрузки задаёт та платформа, которая выгружает.
|
||||||
|
"""
|
||||||
|
db = find_project_database(args)
|
||||||
|
if db and db.get("v8path"):
|
||||||
|
return db["v8path"]
|
||||||
d = os.getcwd()
|
d = os.getcwd()
|
||||||
while True:
|
while True:
|
||||||
pf = os.path.join(d, ".v8-project.json")
|
pf = os.path.join(d, ".v8-project.json")
|
||||||
@@ -243,10 +297,10 @@ def _version_key(p):
|
|||||||
return [int(x) for x in re.findall(r"\d+", _version_dir(p))]
|
return [int(x) for x in re.findall(r"\d+", _version_dir(p))]
|
||||||
|
|
||||||
|
|
||||||
def resolve_v8path(v8path):
|
def resolve_v8path(v8path, args):
|
||||||
"""Resolve path to a 1C executable (1cv8; ibcmd only when given explicitly)."""
|
"""Resolve path to a 1C executable (1cv8; ibcmd only when given explicitly)."""
|
||||||
if not v8path:
|
if not v8path:
|
||||||
v8path = _find_project_v8path()
|
v8path = _find_project_v8path(args)
|
||||||
if not v8path:
|
if not v8path:
|
||||||
if os.name == "nt":
|
if os.name == "nt":
|
||||||
candidates = (
|
candidates = (
|
||||||
@@ -446,7 +500,7 @@ def main():
|
|||||||
args.OutputDir = clean_path(args.OutputDir, "-OutputDir")
|
args.OutputDir = clean_path(args.OutputDir, "-OutputDir")
|
||||||
|
|
||||||
# --- Resolve V8Path ---
|
# --- Resolve V8Path ---
|
||||||
v8path = resolve_v8path(args.V8Path)
|
v8path = resolve_v8path(args.V8Path, args)
|
||||||
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
|
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
|
||||||
|
|
||||||
# --- Resolve additional arguments for the selected engine ---
|
# --- Resolve additional arguments for the selected engine ---
|
||||||
@@ -37,7 +37,7 @@ allowed-tools:
|
|||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/init.ps1" -Name "<Name>" [-Synonym "<Synonym>"] [-SrcDir "<SrcDir>"] [-FormatVersion "<версия>"]
|
python ".roo/skills/epf-init/scripts/init.py" -Name "<Name>" [-Synonym "<Synonym>"] [-SrcDir "<SrcDir>"] [-FormatVersion "<версия>"]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Дальнейшие шаги
|
## Дальнейшие шаги
|
||||||
@@ -26,7 +26,7 @@ allowed-tools:
|
|||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-validate.ps1" -ObjectPath "src/МояОбработка"
|
python ".roo/skills/epf-validate/scripts/epf-validate.py" -ObjectPath "src/МояОбработка"
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-validate.ps1" -ObjectPath "src/МояОбработка/МояОбработка.xml"
|
python ".roo/skills/epf-validate/scripts/epf-validate.py" -ObjectPath "src/МояОбработка/МояОбработка.xml"
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -28,14 +28,14 @@ allowed-tools:
|
|||||||
Предпочтительно использовать конкретную базу — это надёжнее. Временная база всё равно поднимается
|
Предпочтительно использовать конкретную базу — это надёжнее. Временная база всё равно поднимается
|
||||||
под проверку исходников, если она не отключена.
|
под проверку исходников, если она не отключена.
|
||||||
|
|
||||||
1. Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` и разреши базу:
|
1. Прочитай `.v8-project.json` из корня проекта и разреши базу:
|
||||||
2. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
2. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
||||||
3. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
3. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
||||||
4. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
4. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
||||||
5. Если ветка не совпала — используй `default`
|
5. Если ветка не совпала — используй `default`
|
||||||
6. Если `.v8-project.json` нет или база не найдена — не указывай параметры подключения: скрипт автоматически создаст временную базу. Для ERF со ссылочными типами (CatalogRef, DocumentRef и т.д.) генерируются заглушки метаданных. Временная база удаляется после сборки.
|
6. Если `.v8-project.json` нет или база не найдена — не указывай параметры подключения: скрипт автоматически создаст временную базу. Для ERF со ссылочными типами (CatalogRef, DocumentRef и т.д.) генерируются заглушки метаданных. Временная база удаляется после сборки.
|
||||||
|
|
||||||
Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
|
Платформа — `v8path` найденной записи базы, иначе корневой `v8path`. Не задан ни там, ни там — `-V8Path` не передавай.
|
||||||
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
||||||
|
|
||||||
## Команда
|
## Команда
|
||||||
@@ -43,7 +43,7 @@ allowed-tools:
|
|||||||
Используй общий скрипт из epf-build:
|
Используй общий скрипт из epf-build:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-build/scripts/epf-build.ps1" <параметры>
|
python ".roo/skills/epf-build/scripts/epf-build.py" <параметры>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Параметры скрипта
|
### Параметры скрипта
|
||||||
@@ -78,8 +78,8 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-build/scripts/epf-bu
|
|||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# Сборка отчёта (файловая база)
|
# Сборка отчёта (файловая база)
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-build/scripts/epf-build.ps1" -InfoBasePath "C:\Bases\MyDB" -SourceFile "src/МойОтчёт.xml" -OutputFile "build/МойОтчёт.erf"
|
python ".roo/skills/epf-build/scripts/epf-build.py" -InfoBasePath "C:\Bases\MyDB" -SourceFile "src/МойОтчёт.xml" -OutputFile "build/МойОтчёт.erf"
|
||||||
|
|
||||||
# Серверная база
|
# Серверная база
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-build/scripts/epf-build.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -SourceFile "src/МойОтчёт.xml" -OutputFile "build/МойОтчёт.erf"
|
python ".roo/skills/epf-build/scripts/epf-build.py" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -SourceFile "src/МойОтчёт.xml" -OutputFile "build/МойОтчёт.erf"
|
||||||
```
|
```
|
||||||
@@ -26,14 +26,14 @@ allowed-tools:
|
|||||||
|
|
||||||
Для разборки EPF/ERF требуется информационная база с конфигурацией. Без базы ссылочные типы безвозвратно теряются.
|
Для разборки EPF/ERF требуется информационная база с конфигурацией. Без базы ссылочные типы безвозвратно теряются.
|
||||||
|
|
||||||
1. Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` и разреши базу:
|
1. Прочитай `.v8-project.json` из корня проекта и разреши базу:
|
||||||
2. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
2. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
||||||
3. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
3. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
||||||
4. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
4. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
||||||
5. Если ветка не совпала — используй `default`
|
5. Если ветка не совпала — используй `default`
|
||||||
6. Если `.v8-project.json` нет или база не найдена — **сообщи пользователю об ошибке**. Для dump база обязательна: в пустой базе ссылочные типы (CatalogRef, DocumentRef и т.д.) безвозвратно сбрасываются в строки. Предложи указать базу или зарегистрировать через `/db-list add`.
|
6. Если `.v8-project.json` нет или база не найдена — **сообщи пользователю об ошибке**. Для dump база обязательна: в пустой базе ссылочные типы (CatalogRef, DocumentRef и т.д.) безвозвратно сбрасываются в строки. Предложи указать базу или зарегистрировать через `/db-list add`.
|
||||||
|
|
||||||
Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
|
Платформа — `v8path` найденной записи базы, иначе корневой `v8path`. Не задан ни там, ни там — `-V8Path` не передавай.
|
||||||
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
||||||
|
|
||||||
## Команда
|
## Команда
|
||||||
@@ -41,7 +41,7 @@ allowed-tools:
|
|||||||
Используй общий скрипт из epf-dump:
|
Используй общий скрипт из epf-dump:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-dump/scripts/epf-dump.ps1" <параметры>
|
python ".roo/skills/epf-dump/scripts/epf-dump.py" <параметры>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Параметры скрипта
|
### Параметры скрипта
|
||||||
@@ -66,8 +66,8 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-dump/scripts/epf-dum
|
|||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# Разборка отчёта (файловая база)
|
# Разборка отчёта (файловая база)
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-dump/scripts/epf-dump.ps1" -InfoBasePath "C:\Bases\MyDB" -InputFile "build/МойОтчёт.erf" -OutputDir "src"
|
python ".roo/skills/epf-dump/scripts/epf-dump.py" -InfoBasePath "C:\Bases\MyDB" -InputFile "build/МойОтчёт.erf" -OutputDir "src"
|
||||||
|
|
||||||
# Серверная база
|
# Серверная база
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-dump/scripts/epf-dump.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -InputFile "build/МойОтчёт.erf" -OutputDir "src"
|
python ".roo/skills/epf-dump/scripts/epf-dump.py" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -InputFile "build/МойОтчёт.erf" -OutputDir "src"
|
||||||
```
|
```
|
||||||
@@ -38,7 +38,7 @@ allowed-tools:
|
|||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/init.ps1" -Name "<Name>" [-Synonym "<Synonym>"] [-SrcDir "<SrcDir>"] [-FormatVersion "<версия>"] [-WithSKD]
|
python ".roo/skills/erf-init/scripts/init.py" -Name "<Name>" [-Synonym "<Synonym>"] [-SrcDir "<SrcDir>"] [-FormatVersion "<версия>"] [-WithSKD]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Дальнейшие шаги
|
## Дальнейшие шаги
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user