mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-09-20 17:25:52 +03:00
feat(cfe-patch-method,cfe-diff,cfe-validate): распознавание английских ключевых слов BSL
Платформа принимает встроенный язык в двух написаниях, а навыки читали только русское: в расширении на английском перехватчики и блоки правок не находились, и отчёт выглядел чистым. -Check молчал, cfe-diff не показывал вставки, счётчик контролируемых методов в cfe-validate давал ноль. Пары ключевых слов сверены по таблицам строк платформы (backbas.dll, bsl.dll), а не по памяти. Разбор стал двуязычным везде, где навык читает модуль: аннотации, объявления и Конец*, Знач, директивы контекста, препроцессор в цепочке обрамления, поиск региона при переиспользовании. Маркеры правок распознаются по началу строки, а не сравнением целой строки, — заодно перестал теряться хвостовой комментарий (#Вставка // старая логика). Эмиссия подчиняется одному правилу: отдаём тем языком, который прочитали. Генерация берёт язык метода-источника, -Actualize — язык переписываемого перехватчика, чтобы актуализация не превращала чужие Procedure и #Insert в русские. Комментарии и вывод в консоль остаются русскими. Таблица ключевых слов и разбор аннотаций скопированы в три навыка и заведены семьями в реестре анти-дрейфа: разъехавшаяся пара слов дала бы ровно тот же тихий ложно-чистый отчёт. Проверено платформой: расширение с &ChangeAndValidate принимается (/CheckCanApplyConfigurationExtensions), после дрейфа оригинала отвергается, после -Actualize принимается снова. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
e2c7f38518
commit
1b2c9388e8
@@ -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
|
||||
[CmdletBinding(PositionalBinding=$false)]
|
||||
param(
|
||||
@@ -162,24 +162,90 @@ function Get-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="КонецЕсли"
|
||||
And="И"; 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"; 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 {
|
||||
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)
|
||||
|
||||
if (-not (Test-Path $bslPath)) { return @() }
|
||||
$lines = [System.IO.File]::ReadAllLines($bslPath, [System.Text.Encoding]::UTF8)
|
||||
$interceptors = @()
|
||||
$i = 0
|
||||
while ($i -lt $lines.Count) {
|
||||
$line = $lines[$i].Trim()
|
||||
if ($line -match '^&(Перед|После|ИзменениеИКонтроль|Вместо)\("([^"]+)"\)') {
|
||||
$type = $Matches[1]
|
||||
$method = $Matches[2]
|
||||
$interceptors += @{ Type = $type; Method = $method; Line = $i + 1; File = $bslPath }
|
||||
}
|
||||
$i++
|
||||
$result = @()
|
||||
foreach ($ic in (Get-Interceptors $lines)) {
|
||||
$result += @{ Type = $ic.Type; Raw = $ic.Raw; Method = $ic.Method; Line = $ic.Line + 1; File = $bslPath }
|
||||
}
|
||||
return $interceptors
|
||||
return $result
|
||||
}
|
||||
|
||||
# --- Helper: extract #Вставка blocks from .bsl ---
|
||||
@@ -194,12 +260,13 @@ function Get-InsertionBlocks {
|
||||
$startLine = 0
|
||||
|
||||
for ($i = 0; $i -lt $lines.Count; $i++) {
|
||||
$line = $lines[$i].Trim()
|
||||
if ($line -eq "#Вставка") {
|
||||
$line = $lines[$i]
|
||||
$kind = Get-BslMarkerKind $line
|
||||
if ($kind -eq "Insert") {
|
||||
$inBlock = $true
|
||||
$blockLines = @()
|
||||
$startLine = $i + 1
|
||||
} elseif ($line -eq "#КонецВставки" -and $inBlock) {
|
||||
} elseif ($kind -eq "EndInsert" -and $inBlock) {
|
||||
$inBlock = $false
|
||||
$blocks += @{
|
||||
StartLine = $startLine
|
||||
@@ -298,10 +365,10 @@ if ($Mode -eq "A") {
|
||||
$bslFiles = Get-BslFiles $obj.Type $obj.Name
|
||||
foreach ($bsl in $bslFiles) {
|
||||
$relPath = $bsl.Replace($ExtensionPath, "").TrimStart("\", "/")
|
||||
$interceptors = Get-Interceptors $bsl
|
||||
$interceptors = Get-FileInterceptors $bsl
|
||||
if ($interceptors.Count -gt 0) {
|
||||
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 {
|
||||
Write-Host " $relPath (no interceptors)"
|
||||
@@ -412,7 +479,7 @@ if ($Mode -eq "B") {
|
||||
# Find .bsl files with &ИзменениеИКонтроль
|
||||
$bslFiles = Get-BslFiles $obj.Type $obj.Name
|
||||
foreach ($bsl in $bslFiles) {
|
||||
$interceptors = Get-Interceptors $bsl
|
||||
$interceptors = Get-FileInterceptors $bsl
|
||||
$macInterceptors = @($interceptors | Where-Object { $_.Type -eq "ИзменениеИКонтроль" })
|
||||
|
||||
if ($macInterceptors.Count -eq 0) { continue }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/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
|
||||
|
||||
import argparse
|
||||
@@ -165,34 +165,95 @@ def get_bsl_files(obj_type, obj_name, extension_path):
|
||||
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": "КонецЕсли",
|
||||
"And": "И", "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", "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):
|
||||
return []
|
||||
|
||||
with open(bsl_path, "r", encoding="utf-8-sig") as fh:
|
||||
lines = fh.readlines()
|
||||
|
||||
interceptors = []
|
||||
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)\("([^"]+)"\)')
|
||||
# The above is: ^&(Перед|После|ИзменениеИКонтроль|Вместо)\("([^"]+)"\)
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
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,
|
||||
})
|
||||
|
||||
return interceptors
|
||||
return [{
|
||||
"Type": ic["type"],
|
||||
"Raw": ic["raw"],
|
||||
"Method": ic["method"],
|
||||
"Line": ic["line"] + 1,
|
||||
"File": bsl_path,
|
||||
} for ic in get_interceptors(lines)]
|
||||
|
||||
|
||||
# --- Helper: extract #Вставка blocks from .bsl ---
|
||||
# --- Helper: extract insert-marker blocks from .bsl ---
|
||||
|
||||
def get_insertion_blocks(bsl_path):
|
||||
if not os.path.isfile(bsl_path):
|
||||
@@ -207,14 +268,12 @@ def get_insertion_blocks(bsl_path):
|
||||
start_line = 0
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
stripped = line.strip()
|
||||
if stripped == "\u0023\u0412\u0441\u0442\u0430\u0432\u043a\u0430":
|
||||
# #Вставка
|
||||
kind = bsl_marker_kind(line)
|
||||
if kind == "Insert":
|
||||
in_block = True
|
||||
block_lines = []
|
||||
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
|
||||
blocks.append({
|
||||
"StartLine": start_line,
|
||||
@@ -319,10 +378,10 @@ def mode_a(objects, extension_path):
|
||||
bsl_files = get_bsl_files(obj["Type"], obj["Name"], extension_path)
|
||||
for bsl in bsl_files:
|
||||
rel_path = bsl.replace(extension_path, "").lstrip("\\/")
|
||||
interceptor_list = get_interceptors(bsl)
|
||||
interceptor_list = get_file_interceptors(bsl)
|
||||
if len(interceptor_list) > 0:
|
||||
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:
|
||||
print(f" {rel_path} (no interceptors)")
|
||||
|
||||
@@ -434,7 +493,7 @@ def mode_b(objects, extension_path, config_path):
|
||||
# Find .bsl files with &ИзменениеИКонтроль
|
||||
bsl_files = get_bsl_files(obj["Type"], obj["Name"], extension_path)
|
||||
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"]
|
||||
|
||||
if len(mac_interceptors) == 0:
|
||||
|
||||
@@ -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
|
||||
[CmdletBinding(PositionalBinding=$false)]
|
||||
param(
|
||||
@@ -52,6 +52,103 @@ $script:decoratorMap = @{
|
||||
"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="КонецЕсли"
|
||||
And="И"; 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"; 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)
|
||||
function Get-ModuleRelPath {
|
||||
param([string]$modulePath)
|
||||
@@ -125,7 +222,8 @@ function Split-TopLevel {
|
||||
# Is a trimmed line a context-directive annotation?
|
||||
function Test-ContextDirective {
|
||||
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 }
|
||||
@@ -163,46 +261,54 @@ function Read-Signature {
|
||||
|
||||
# Compute effective condition of the current branch of an #Если frame
|
||||
function Get-EffectiveCondition {
|
||||
param($frame)
|
||||
param($frame, [string]$lang)
|
||||
$kwNot = $script:bslKw[$lang].Not
|
||||
$kwAnd = $script:bslKw[$lang].And
|
||||
$conds = $frame.Conds
|
||||
$n = $conds.Count
|
||||
if ($frame.InElse) {
|
||||
$parts = @()
|
||||
foreach ($c in $conds) { $parts += "НЕ ($c)" }
|
||||
return ($parts -join " И ")
|
||||
foreach ($c in $conds) { $parts += "$kwNot ($c)" }
|
||||
return ($parts -join " $kwAnd ")
|
||||
}
|
||||
if ($n -eq 1) { return $conds[0] }
|
||||
$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]
|
||||
return ($parts -join " И ")
|
||||
return ($parts -join " $kwAnd ")
|
||||
}
|
||||
|
||||
# Extract the enclosing wrapper chain (regions + preprocessor) at a target line.
|
||||
# Returns array outer->inner of @{ Kind='region'|'if'; Name=..; Cond=.. }
|
||||
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
|
||||
for ($i = 0; $i -lt $targetIdx; $i++) {
|
||||
$t = $lines[$i].Trim()
|
||||
if ($t -match '^#Область\s+(\S+)') {
|
||||
if ($t -match $reRegion) {
|
||||
[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 } }
|
||||
} elseif ($t -match '^#Если\s+(.+?)\s+Тогда') {
|
||||
} elseif ($t -match $reIf) {
|
||||
[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 } }
|
||||
} 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 } }
|
||||
} 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 } }
|
||||
}
|
||||
}
|
||||
$chain = @()
|
||||
foreach ($f in $stack) {
|
||||
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
|
||||
}
|
||||
@@ -210,13 +316,14 @@ function Get-EnclosingChain {
|
||||
# Extract a method from source .bsl lines. Returns $null if not found.
|
||||
function Extract-Method {
|
||||
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++) {
|
||||
if ($lines[$i] -imatch $declRe) {
|
||||
$isAsync = [bool]$Matches[1]
|
||||
$keyword = $Matches[2]
|
||||
$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
|
||||
if (-not $sig) { throw "Не удалось разобрать сигнатуру метода '$methodName'" }
|
||||
@@ -227,13 +334,13 @@ function Extract-Method {
|
||||
$paramNames = @()
|
||||
if ($paramsText.Trim().Length -gt 0) {
|
||||
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] }
|
||||
}
|
||||
}
|
||||
|
||||
# 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
|
||||
$bodyEnd = -1
|
||||
for ($j = $bodyStart; $j -lt $lines.Count; $j++) {
|
||||
@@ -251,10 +358,11 @@ function Extract-Method {
|
||||
}
|
||||
|
||||
# Enclosing chain (regions + preprocessor)
|
||||
$chain = Get-EnclosingChain $lines $i
|
||||
$chain = Get-EnclosingChain $lines $i $lang
|
||||
|
||||
return @{
|
||||
Canonical = $canonical
|
||||
Lang = $lang
|
||||
IsFunction = $isFunction
|
||||
IsAsync = $isAsync
|
||||
ParamsText = $paramsText
|
||||
@@ -271,25 +379,13 @@ function Extract-Method {
|
||||
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
|
||||
function Get-ProcNames {
|
||||
param($lines)
|
||||
$re = '^\s*(?:' + (Get-KwAlt 'Async') + '\s+)?(?:' + (Get-KwAlt 'Proc') + '|' + (Get-KwAlt 'Func') + ')\s+([\w]+)\s*\('
|
||||
$names = @()
|
||||
foreach ($line in $lines) {
|
||||
if ($line -imatch '^\s*(?:Асинх\s+)?(?:Процедура|Функция)\s+([\w]+)\s*\(') { $names += $Matches[1] }
|
||||
if ($line -imatch $re) { $names += $Matches[1] }
|
||||
}
|
||||
return $names
|
||||
}
|
||||
@@ -300,14 +396,15 @@ function Get-ProcNames {
|
||||
function Build-InterceptorCore {
|
||||
param($method, [string]$interceptorType, [string]$interceptorName)
|
||||
|
||||
$decoratorRu = $script:decoratorMap[$interceptorType]
|
||||
$asyncPrefix = if ($method.IsAsync) { "Асинх " } else { "" }
|
||||
$keyword = if ($method.IsFunction) { "Функция" } else { "Процедура" }
|
||||
$endKeyword = if ($method.IsFunction) { "КонецФункции" } else { "КонецПроцедуры" }
|
||||
$kw = $script:bslKw[$method.Lang]
|
||||
$decorator = $kw[$script:decoratorKey[$interceptorType]]
|
||||
$asyncPrefix = if ($method.IsAsync) { "$($kw.Async) " } else { "" }
|
||||
$keyword = if ($method.IsFunction) { $kw.Func } else { $kw.Proc }
|
||||
$endKeyword = if ($method.IsFunction) { $kw.EndFunc } else { $kw.EndProc }
|
||||
|
||||
$lines = @()
|
||||
if ($method.Context) { $lines += $method.Context }
|
||||
$lines += "&$decoratorRu(`"$($method.Canonical)`")"
|
||||
$lines += "&$decorator(`"$($method.Canonical)`")"
|
||||
$lines += "$asyncPrefix$keyword $interceptorName($($method.ParamsText))"
|
||||
|
||||
switch ($interceptorType) {
|
||||
@@ -320,11 +417,11 @@ function Build-InterceptorCore {
|
||||
"Instead" {
|
||||
$namesJoined = ($method.ParamNames -join ", ")
|
||||
if ($method.IsFunction) {
|
||||
$lines += "`tРезультат = ПродолжитьВызов($namesJoined);"
|
||||
$lines += "`t$($kw.ResultVar) = $($kw.Proceed)($namesJoined);"
|
||||
$lines += "`t// TODO: доработать поведение"
|
||||
$lines += "`tВозврат Результат;"
|
||||
$lines += "`t$($kw.Return) $($kw.ResultVar);"
|
||||
} else {
|
||||
$lines += "`tПродолжитьВызов($namesJoined);"
|
||||
$lines += "`t$($kw.Proceed)($namesJoined);"
|
||||
$lines += "`t// TODO: доработать поведение"
|
||||
}
|
||||
}
|
||||
@@ -339,16 +436,17 @@ function Build-InterceptorCore {
|
||||
# Wrap core with region/preprocessor lines, adding blank lines ("air") around
|
||||
# each structural boundary. Empty chain -> core as-is.
|
||||
function Build-WrappedBlock {
|
||||
param($chainArr, $core)
|
||||
param($chainArr, $core, [string]$lang)
|
||||
$kw = $script:bslKw[$lang]
|
||||
$b = @()
|
||||
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 += $core
|
||||
for ($c = $chainArr.Count - 1; $c -ge 0; $c--) {
|
||||
$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
|
||||
}
|
||||
@@ -385,18 +483,18 @@ function Parse-MarkedBody {
|
||||
$ops = @() # edit operations
|
||||
$i = 0
|
||||
while ($i -lt $bodyLines.Count) {
|
||||
$t = $bodyLines[$i].Trim()
|
||||
if ($t -eq '#Вставка') {
|
||||
$kind = Get-BslMarkerKind $bodyLines[$i]
|
||||
if ($kind -eq 'Insert') {
|
||||
$ins = @()
|
||||
$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++ }
|
||||
$i++ # skip #КонецВставки
|
||||
$ops += @{ Kind = 'insert'; After = ($v1.Count - 1); Lines = $ins }
|
||||
} elseif ($t -eq '#Удаление') {
|
||||
} elseif ($kind -eq 'Delete') {
|
||||
$startIdx = $v1.Count
|
||||
$i++
|
||||
$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++ }
|
||||
$i++ # skip #КонецУдаления
|
||||
$ops += @{ Kind = 'delete'; Start = $startIdx; End = ($v1.Count - 1); Lines = $del }
|
||||
} else {
|
||||
@@ -594,7 +692,8 @@ function Get-ResyncConflictReason {
|
||||
|
||||
# Write per-method conflict folder: conflict.md + base/local/remote
|
||||
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 }
|
||||
[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)
|
||||
@@ -602,7 +701,7 @@ function Write-ConflictFolder {
|
||||
$md = @()
|
||||
$md += "# $methodId"
|
||||
$md += "Править: $extBsl"
|
||||
$md += "Метод: $existingName (&ИзменениеИКонтроль(`"$($method.Canonical)`"))"
|
||||
$md += "Метод: $existingName (&$($kw.Control)(`"$($method.Canonical)`"))"
|
||||
$md += "Причина: $(Get-ResyncConflictReason $disputed)"
|
||||
$md += ""
|
||||
$md += "## Не размещено — перенести вручную"
|
||||
@@ -614,10 +713,10 @@ function Write-ConflictFolder {
|
||||
$md += "### Конфликт №$cn — вставка"
|
||||
$md += "Как блок стоял в вашей версии (local):"
|
||||
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 += "#КонецВставки"
|
||||
$md += "#$($kw.Insert)"; foreach ($l in $d.Lines) { $md += $l }; $md += "#$($kw.EndInsert)"
|
||||
if ($d.After -and $d.After.Count -gt 0) { foreach ($l in $d.After) { $md += $l } }
|
||||
$md += ""
|
||||
$md += "Якорь (строки вокруг #Вставка) изменился/исчез в новом оригинале — блок не лёг автоматически (см. дифф base→remote ниже)."
|
||||
$md += "Якорь (строки вокруг #$($kw.Insert)) изменился/исчез в новом оригинале — блок не лёг автоматически (см. дифф base→remote ниже)."
|
||||
$md += "В модуле расширения блок припаркован в конце метода под меткой // [РЕСИНК-КОНФЛИКТ №$cn] — найди по ней."
|
||||
$md += "Куда переносить: если якорного кода в новом методе больше нет — он, вероятно, вынесен/отрефакторен (ищите в диффе новый вызов/процедуру). Размести адаптацию по смыслу: например пост-обработкой после нового вызова, либо в заимствованной процедуре, куда переехал код. При правке файла сохрани кодировку (UTF-8 с BOM)."
|
||||
} else {
|
||||
@@ -644,15 +743,20 @@ function Invoke-Resync {
|
||||
$methodId = "$logicalModule.$($method.Canonical)"
|
||||
$decLine = $dup.Line
|
||||
$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 = 'не разобрать сигнатуру перехватчика' }
|
||||
}
|
||||
$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
|
||||
if (-not $sig) { return @{ Id = $methodId; Status = 'ОШИБКА'; ExtBsl = $extBsl; Reason = 'не разобрать сигнатуру' } }
|
||||
$sigEnd = $sig.EndLineIdx
|
||||
$isFunc = ($extLines[$sigLineIdx] -imatch '^\s*(?:Асинх\s+)?Функция\b')
|
||||
$endRe = if ($isFunc) { '^\s*КонецФункции\b' } else { '^\s*КонецПроцедуры\b' }
|
||||
$isFunc = ($extLines[$sigLineIdx] -imatch ('^\s*(?:' + (Get-KwAlt 'Async') + '\s+)?' + (Get-KwAlt 'Func') + '\b'))
|
||||
$endRe = if ($isFunc) { '^\s*' + (Get-KwAlt 'EndFunc') + '\b' } else { '^\s*' + (Get-KwAlt 'EndProc') + '\b' }
|
||||
$blockEnd = -1
|
||||
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 = 'не найден конец перехватчика' } }
|
||||
@@ -732,12 +836,12 @@ function Invoke-Resync {
|
||||
|
||||
# assemble new marked body
|
||||
$newBody = @()
|
||||
foreach ($blk in $insertTop) { $newBody += "#Вставка"; foreach ($l in $blk) { $newBody += $l }; $newBody += "#КонецВставки" }
|
||||
foreach ($blk in $insertTop) { $newBody += "#$($kw.Insert)"; foreach ($l in $blk) { $newBody += $l }; $newBody += "#$($kw.EndInsert)" }
|
||||
for ($k = 0; $k -lt $v2.Count; $k++) {
|
||||
if ($delStart.ContainsKey($k)) { $newBody += "#Удаление" }
|
||||
if ($delStart.ContainsKey($k)) { $newBody += "#$($kw.Delete)" }
|
||||
$newBody += $v2[$k]
|
||||
if ($delEnd.ContainsKey($k)) { $newBody += "#КонецУдаления" }
|
||||
if ($insertAfter.ContainsKey($k)) { foreach ($blk in $insertAfter[$k]) { $newBody += "#Вставка"; foreach ($l in $blk) { $newBody += $l }; $newBody += "#КонецВставки" } }
|
||||
if ($delEnd.ContainsKey($k)) { $newBody += "#$($kw.EndDelete)" }
|
||||
if ($insertAfter.ContainsKey($k)) { foreach ($blk in $insertAfter[$k]) { $newBody += "#$($kw.Insert)"; foreach ($l in $blk) { $newBody += $l }; $newBody += "#$($kw.EndInsert)" } }
|
||||
}
|
||||
if ($disputed.Count -gt 0) {
|
||||
$newBody += "`t// [РЕСИНК-КОНФЛИКТ] блоки ниже не легли автоматически — перенесите вручную (по № см. conflict.md / index.md в merge-воркспейсе, путь в выводе)."
|
||||
@@ -746,7 +850,7 @@ function Invoke-Resync {
|
||||
$cn++
|
||||
if ($d.Kind -eq 'insert') {
|
||||
$newBody += "`t// [РЕСИНК-КОНФЛИКТ №$cn] вставка — исходный якорь изменён в новом оригинале."
|
||||
$newBody += "#Вставка"; foreach ($l in $d.Lines) { $newBody += $l }; $newBody += "#КонецВставки"
|
||||
$newBody += "#$($kw.Insert)"; foreach ($l in $d.Lines) { $newBody += $l }; $newBody += "#$($kw.EndInsert)"
|
||||
}
|
||||
else {
|
||||
$newBody += "`t// [РЕСИНК-КОНФЛИКТ №$cn] удаление — строки не найдены в новом оригинале:"
|
||||
@@ -754,12 +858,12 @@ function Invoke-Resync {
|
||||
}
|
||||
}
|
||||
}
|
||||
$asyncPrefix = if ($method.IsAsync) { "Асинх " } else { "" }
|
||||
$keyword = if ($method.IsFunction) { "Функция" } else { "Процедура" }
|
||||
$endKeyword = if ($method.IsFunction) { "КонецФункции" } else { "КонецПроцедуры" }
|
||||
$asyncPrefix = if ($method.IsAsync) { "$($kw.Async) " } else { "" }
|
||||
$keyword = if ($method.IsFunction) { $kw.Func } else { $kw.Proc }
|
||||
$endKeyword = if ($method.IsFunction) { $kw.EndFunc } else { $kw.EndProc }
|
||||
$newBlock = @()
|
||||
if ($method.Context) { $newBlock += $method.Context }
|
||||
$newBlock += "&ИзменениеИКонтроль(`"$($method.Canonical)`")"
|
||||
$newBlock += "&$($kw.Control)(`"$($method.Canonical)`")"
|
||||
$newBlock += "$asyncPrefix$keyword $existingName($($method.ParamsText))"
|
||||
$newBlock += $newBody
|
||||
$newBlock += $endKeyword
|
||||
@@ -775,7 +879,7 @@ function Invoke-Resync {
|
||||
$conflictDir = $null
|
||||
if ($disputed.Count -gt 0) {
|
||||
$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 { 'АКТУАЛИЗИРОВАН' }
|
||||
$rsn = if ($disputed.Count -gt 0) { Get-ResyncConflictReason $disputed } elseif ($status -eq 'ПЕРЕНЕСЕНО В ОСНОВНУЮ') { 'все правки уже в основной конфигурации — перехватчик можно удалить' } else { '' }
|
||||
@@ -1062,6 +1166,8 @@ if ($method.IsFunction -and ($InterceptorType -eq "Before" -or $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) ---
|
||||
$extLines = @()
|
||||
@@ -1078,7 +1184,7 @@ $enc = New-Object System.Text.UTF8Encoding($true)
|
||||
|
||||
if ($dup) {
|
||||
if ($InterceptorType -ne "ModificationAndControl") {
|
||||
Write-Host "[ПРОПУЩЕН] Перехватчик &$decoratorRu(`"$MethodName`") уже есть в модуле — дубль не создаётся."
|
||||
Write-Host "[ПРОПУЩЕН] Перехватчик &$($dup.Raw)(`"$MethodName`") уже есть в модуле — дубль не создаётся."
|
||||
Write-Host " Файл: $extBsl"
|
||||
exit 0
|
||||
}
|
||||
@@ -1123,11 +1229,7 @@ $candidate = "${namePrefix}$($method.Canonical)"
|
||||
$taken = @($existingProcNames | ForEach-Object { $_.ToLower() })
|
||||
$interceptorName = $candidate
|
||||
if ($taken -contains $candidate.ToLower()) {
|
||||
if ($InterceptorType -eq "ModificationAndControl") {
|
||||
$interceptorName = "${candidate}_ИзменениеИКонтроль"
|
||||
} else {
|
||||
$interceptorName = "${candidate}_$decoratorRu"
|
||||
}
|
||||
$interceptorName = "${candidate}_$($script:bslKw[$method.Lang][$script:decoratorKey[$InterceptorType]])"
|
||||
}
|
||||
|
||||
$core = Build-InterceptorCore $method $InterceptorType $interceptorName
|
||||
@@ -1142,7 +1244,7 @@ if ($extExists) {
|
||||
if ($chain[$c].Kind -eq 'region') {
|
||||
$rname = $chain[$c].Name
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -1157,14 +1259,14 @@ if ($reuseRegionIdx -ge 0) {
|
||||
$innerChain = @()
|
||||
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
|
||||
$depth = 0; $closeIdx = -1
|
||||
for ($li = $reuseLineIdx; $li -lt $extLines.Count; $li++) {
|
||||
$t = $extLines[$li].Trim()
|
||||
if ($t -match '^#Область\s') { $depth++ }
|
||||
elseif ($t -match '^#КонецОбласти') { $depth--; if ($depth -eq 0) { $closeIdx = $li; break } }
|
||||
if ($t -match ('^#' + (Get-KwAlt 'Region') + '\s')) { $depth++ }
|
||||
elseif ($t -match ('^#' + (Get-KwAlt 'EndRegion'))) { $depth--; if ($depth -eq 0) { $closeIdx = $li; break } }
|
||||
}
|
||||
if ($closeIdx -lt 0) { Write-Error "Не найден #КонецОбласти для региона (переиспользование)"; exit 1 }
|
||||
|
||||
@@ -1181,7 +1283,7 @@ if ($reuseRegionIdx -ge 0) {
|
||||
$placement = "в существующий регион '$($chain[$reuseRegionIdx].Name)'"
|
||||
} else {
|
||||
# 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"
|
||||
|
||||
$bslDir = Split-Path $extBsl -Parent
|
||||
@@ -1210,7 +1312,7 @@ if ($flagTarget) {
|
||||
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 " Процедура: $interceptorName($($method.ParamsText -replace '\s+', ' '))"
|
||||
if ($method.Context) { Write-Host " Контекст: $($method.Context)" }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/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
|
||||
|
||||
import argparse
|
||||
@@ -70,8 +70,81 @@ DECORATOR_MAP = {
|
||||
"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": "КонецЕсли",
|
||||
"And": "И", "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", "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(
|
||||
r'^&(НаКлиенте|НаСервере|НаСервереБезКонтекста|НаКлиентеНаСервереБезКонтекста|НаКлиентеНаСервере)\s*$'
|
||||
r'^&(' + '|'.join(BSL_KW["ru"]["Directives"] + BSL_KW["en"]["Directives"]) + r')\s*$',
|
||||
re.IGNORECASE
|
||||
)
|
||||
|
||||
|
||||
@@ -300,37 +373,45 @@ def read_signature(lines, start_idx):
|
||||
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"]
|
||||
n = len(conds)
|
||||
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:
|
||||
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])
|
||||
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 = []
|
||||
for i in range(target_idx):
|
||||
t = lines[i].strip()
|
||||
m = re.match(r'^#Область\s+(\S+)', t)
|
||||
m = re_region.match(t)
|
||||
if m:
|
||||
stack.append({"kind": "region", "name": m.group(1)})
|
||||
continue
|
||||
if re.match(r'^#КонецОбласти', t):
|
||||
if re_end_region.match(t):
|
||||
for k in range(len(stack) - 1, -1, -1):
|
||||
if stack[k]["kind"] == "region":
|
||||
del stack[k]
|
||||
break
|
||||
continue
|
||||
m = re.match(r'^#Если\s+(.+?)\s+Тогда', t)
|
||||
m = re_if.match(t)
|
||||
if m:
|
||||
stack.append({"kind": "if", "conds": [m.group(1).strip()], "in_else": False})
|
||||
continue
|
||||
m = re.match(r'^#ИначеЕсли\s+(.+?)\s+Тогда', t)
|
||||
m = re_elsif.match(t)
|
||||
if m:
|
||||
for k in range(len(stack) - 1, -1, -1):
|
||||
if stack[k]["kind"] == "if":
|
||||
@@ -338,13 +419,13 @@ def get_enclosing_chain(lines, target_idx):
|
||||
stack[k]["in_else"] = False
|
||||
break
|
||||
continue
|
||||
if re.match(r'^#Иначе(\s|$)', t):
|
||||
if re_else.match(t):
|
||||
for k in range(len(stack) - 1, -1, -1):
|
||||
if stack[k]["kind"] == "if":
|
||||
stack[k]["in_else"] = True
|
||||
break
|
||||
continue
|
||||
if re.match(r'^#КонецЕсли', t):
|
||||
if re_endif.match(t):
|
||||
for k in range(len(stack) - 1, -1, -1):
|
||||
if stack[k]["kind"] == "if":
|
||||
del stack[k]
|
||||
@@ -355,13 +436,14 @@ def get_enclosing_chain(lines, target_idx):
|
||||
if f["kind"] == "region":
|
||||
chain.append({"kind": "region", "name": f["name"]})
|
||||
else:
|
||||
chain.append({"kind": "if", "cond": effective_condition(f)})
|
||||
chain.append({"kind": "if", "cond": effective_condition(f, lang)})
|
||||
return chain
|
||||
|
||||
|
||||
def extract_method(lines, method_name):
|
||||
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,
|
||||
)
|
||||
for i in range(len(lines)):
|
||||
@@ -371,7 +453,8 @@ def extract_method(lines, method_name):
|
||||
is_async = bool(m.group(1))
|
||||
keyword = m.group(2)
|
||||
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)
|
||||
if not sig:
|
||||
@@ -381,12 +464,12 @@ def extract_method(lines, method_name):
|
||||
param_names = []
|
||||
if params_text.strip():
|
||||
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)
|
||||
if mm:
|
||||
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)
|
||||
body_start = sig_end + 1
|
||||
body_end = -1
|
||||
@@ -404,10 +487,11 @@ def extract_method(lines, method_name):
|
||||
if is_context_directive(prev):
|
||||
context = prev
|
||||
|
||||
chain = get_enclosing_chain(lines, i)
|
||||
chain = get_enclosing_chain(lines, i, lang)
|
||||
|
||||
return {
|
||||
"canonical": canonical,
|
||||
"lang": lang,
|
||||
"is_function": is_function,
|
||||
"is_async": is_async,
|
||||
"params_text": params_text,
|
||||
@@ -422,19 +506,32 @@ def extract_method(lines, method_name):
|
||||
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 = []
|
||||
pat = re.compile(r'^&(Перед|После|ИзменениеИКонтроль|Вместо)\("([^"]+)"\)')
|
||||
for i in range(len(lines)):
|
||||
m = pat.match(lines[i].strip())
|
||||
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
|
||||
|
||||
|
||||
def get_proc_names(lines):
|
||||
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:
|
||||
m = pat.match(line)
|
||||
if m:
|
||||
@@ -443,15 +540,16 @@ def get_proc_names(lines):
|
||||
|
||||
|
||||
def build_interceptor_core(method, interceptor_type, interceptor_name):
|
||||
decorator_ru = DECORATOR_MAP[interceptor_type]
|
||||
async_prefix = "Асинх " if method["is_async"] else ""
|
||||
keyword = "Функция" if method["is_function"] else "Процедура"
|
||||
end_keyword = "КонецФункции" if method["is_function"] else "КонецПроцедуры"
|
||||
kw = BSL_KW[method["lang"]]
|
||||
decorator = kw[DECORATOR_KEY[interceptor_type]]
|
||||
async_prefix = (kw["Async"] + " ") if method["is_async"] else ""
|
||||
keyword = kw["Func"] if method["is_function"] else kw["Proc"]
|
||||
end_keyword = kw["EndFunc"] if method["is_function"] else kw["EndProc"]
|
||||
|
||||
lines = []
|
||||
if 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"]))
|
||||
|
||||
if interceptor_type == "Before":
|
||||
@@ -461,11 +559,11 @@ def build_interceptor_core(method, interceptor_type, interceptor_name):
|
||||
elif interceptor_type == "Instead":
|
||||
names_joined = ", ".join(method["param_names"])
|
||||
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Возврат Результат;")
|
||||
lines.append("\t%s %s;" % (kw["Return"], kw["ResultVar"]))
|
||||
else:
|
||||
lines.append("\tПродолжитьВызов(%s);" % names_joined)
|
||||
lines.append("\t%s(%s);" % (kw["Proceed"], names_joined))
|
||||
lines.append("\t// TODO: доработать поведение")
|
||||
elif interceptor_type == "ModificationAndControl":
|
||||
lines.extend(method["body_lines"])
|
||||
@@ -474,16 +572,18 @@ def build_interceptor_core(method, interceptor_type, interceptor_name):
|
||||
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."""
|
||||
kw = BSL_KW[lang]
|
||||
b = []
|
||||
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.extend(core)
|
||||
for w in reversed(chain_arr):
|
||||
b.append("")
|
||||
b.append("#КонецОбласти" if w["kind"] == "region" else "#КонецЕсли")
|
||||
b.append(("#" + kw["EndRegion"]) if w["kind"] == "region" else ("#" + kw["EndIf"]))
|
||||
return b
|
||||
|
||||
|
||||
@@ -512,20 +612,20 @@ def parse_marked_body(body_lines):
|
||||
i = 0
|
||||
n = len(body_lines)
|
||||
while i < n:
|
||||
t = body_lines[i].strip()
|
||||
if t == "#Вставка":
|
||||
kind = bsl_marker_kind(body_lines[i])
|
||||
if kind == "Insert":
|
||||
ins = []
|
||||
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])
|
||||
i += 1
|
||||
i += 1
|
||||
ops.append({"kind": "insert", "after": len(v1) - 1, "lines": ins})
|
||||
elif t == "#Удаление":
|
||||
elif kind == "Delete":
|
||||
start_idx = len(v1)
|
||||
i += 1
|
||||
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])
|
||||
v1.append(body_lines[i])
|
||||
i += 1
|
||||
@@ -879,6 +979,8 @@ def main():
|
||||
"(перехват &Перед/&После к функциям неприменим)." % method_name)
|
||||
|
||||
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) ---
|
||||
ext_exists = os.path.isfile(ext_bsl)
|
||||
@@ -896,7 +998,7 @@ def main():
|
||||
if dup:
|
||||
if interceptor_type != "ModificationAndControl":
|
||||
print('[ПРОПУЩЕН] Перехватчик &%s("%s") уже есть в модуле — дубль не создаётся.'
|
||||
% (decorator_ru, method_name))
|
||||
% (dup["raw"], method_name))
|
||||
print(" Файл: %s" % ext_bsl)
|
||||
sys.exit(0)
|
||||
rel = rel_parts_under(extension_path, ext_bsl)
|
||||
@@ -940,14 +1042,11 @@ def main():
|
||||
taken = [n.lower() for n in existing_proc_names]
|
||||
interceptor_name = candidate
|
||||
if candidate.lower() in taken:
|
||||
if interceptor_type == "ModificationAndControl":
|
||||
interceptor_name = candidate + "_ИзменениеИКонтроль"
|
||||
else:
|
||||
interceptor_name = candidate + "_" + decorator_ru
|
||||
interceptor_name = candidate + "_" + decorator_out
|
||||
|
||||
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).
|
||||
flag_target = get_module_flag_target(rel_parts, extension_path)
|
||||
@@ -957,7 +1056,7 @@ def main():
|
||||
|
||||
# emit summary
|
||||
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(%s)" % (interceptor_name, normalize(method["params_text"])))
|
||||
if method["context"]:
|
||||
@@ -970,7 +1069,7 @@ def main():
|
||||
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
|
||||
|
||||
# find innermost region in chain that already exists in the extension module
|
||||
@@ -980,7 +1079,7 @@ def place_new(ext_bsl, ext_lines, ext_exists, chain, core):
|
||||
for c in range(len(chain) - 1, -1, -1):
|
||||
if chain[c]["kind"] == "region":
|
||||
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)):
|
||||
if rre.match(ext_lines[li].strip()):
|
||||
reuse_region_idx = c
|
||||
@@ -991,15 +1090,15 @@ def place_new(ext_bsl, ext_lines, ext_exists, chain, core):
|
||||
|
||||
if reuse_region_idx >= 0:
|
||||
inner_chain = chain[reuse_region_idx + 1:]
|
||||
block = build_wrapped_block(inner_chain, core)
|
||||
block = build_wrapped_block(inner_chain, core, lang)
|
||||
|
||||
depth = 0
|
||||
close_idx = -1
|
||||
for li in range(reuse_line_idx, len(ext_lines)):
|
||||
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
|
||||
elif re.match(r'^#КонецОбласти', t):
|
||||
elif re.match(r'^#' + kw_alt("EndRegion"), t, re.IGNORECASE):
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
close_idx = li
|
||||
@@ -1021,7 +1120,7 @@ def place_new(ext_bsl, ext_lines, ext_exists, chain, core):
|
||||
return
|
||||
|
||||
# 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"
|
||||
|
||||
bsl_dir = os.path.dirname(ext_bsl)
|
||||
@@ -1083,7 +1182,8 @@ def conflict_reason(disputed):
|
||||
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)
|
||||
write_bsl(os.path.join(folder, "base.bsl"), v1)
|
||||
write_bsl(os.path.join(folder, "local.bsl"), marked_body)
|
||||
@@ -1091,7 +1191,7 @@ def write_conflict_folder(folder, method_id, ext_bsl, existing_name, method, v1,
|
||||
md = []
|
||||
md.append("# %s" % method_id)
|
||||
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("")
|
||||
md.append("## Не размещено — перенести вручную")
|
||||
@@ -1105,12 +1205,12 @@ def write_conflict_folder(folder, method_id, ext_bsl, existing_name, method, v1,
|
||||
if d.get("before"):
|
||||
for l in d["before"]:
|
||||
md.append(l)
|
||||
md.append("#Вставка"); md.extend(d["lines"]); md.append("#КонецВставки")
|
||||
md.append("#" + kw["Insert"]); md.extend(d["lines"]); md.append("#" + kw["EndInsert"])
|
||||
if d.get("after"):
|
||||
for l in d["after"]:
|
||||
md.append(l)
|
||||
md.append("")
|
||||
md.append("Якорь (строки вокруг #Вставка) изменился/исчез в новом оригинале — блок не лёг автоматически (см. дифф base→remote ниже).")
|
||||
md.append("Якорь (строки вокруг #%s) изменился/исчез в новом оригинале — блок не лёг автоматически (см. дифф base→remote ниже)." % kw["Insert"])
|
||||
md.append("В модуле расширения блок припаркован в конце метода под меткой // [РЕСИНК-КОНФЛИКТ №%d] — найди по ней." % cn)
|
||||
md.append("Куда переносить: если якорного кода в новом методе больше нет — он, вероятно, вынесен/отрефакторен (ищите в диффе новый вызов/процедуру). Размести адаптацию по смыслу: например пост-обработкой после нового вызова, либо в заимствованной процедуре, куда переехал код. При правке файла сохрани кодировку (UTF-8 с BOM).")
|
||||
else:
|
||||
@@ -1136,17 +1236,23 @@ def resync_one(ext_bsl, ext_lines, dup, method, logical_module, conflict_folder,
|
||||
method_id = "%s.%s" % (logical_module, method["canonical"])
|
||||
dec_line = dup["line"]
|
||||
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
|
||||
if not m0:
|
||||
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)
|
||||
if not sig:
|
||||
return {"id": method_id, "status": "ОШИБКА", "ext_bsl": ext_bsl, "reason": "не разобрать сигнатуру"}
|
||||
ext_params_text, sig_end = sig
|
||||
is_func = bool(re.match(r'^\s*(?:Асинх\s+)?Функция\b', ext_lines[sig_line_idx], re.IGNORECASE))
|
||||
end_re = re.compile(r'^\s*КонецФункции\b' if is_func else r'^\s*КонецПроцедуры\b', re.IGNORECASE)
|
||||
is_func = bool(re.match(r'^\s*(?:' + kw_alt("Async") + r'\s+)?' + kw_alt("Func") + r'\b',
|
||||
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
|
||||
for j in range(sig_end + 1, len(ext_lines)):
|
||||
if end_re.match(ext_lines[j]):
|
||||
@@ -1241,16 +1347,16 @@ def resync_one(ext_bsl, ext_lines, dup, method, logical_module, conflict_folder,
|
||||
|
||||
new_body = []
|
||||
for blk in insert_top:
|
||||
new_body.append("#Вставка"); new_body.extend(blk); new_body.append("#КонецВставки")
|
||||
new_body.append("#" + kw["Insert"]); new_body.extend(blk); new_body.append("#" + kw["EndInsert"])
|
||||
for k in range(len(v2)):
|
||||
if k in del_start:
|
||||
new_body.append("#Удаление")
|
||||
new_body.append("#" + kw["Delete"])
|
||||
new_body.append(v2[k])
|
||||
if k in del_end:
|
||||
new_body.append("#КонецУдаления")
|
||||
new_body.append("#" + kw["EndDelete"])
|
||||
if k in insert_after:
|
||||
for blk in insert_after[k]:
|
||||
new_body.append("#Вставка"); new_body.extend(blk); new_body.append("#КонецВставки")
|
||||
new_body.append("#" + kw["Insert"]); new_body.extend(blk); new_body.append("#" + kw["EndInsert"])
|
||||
if disputed:
|
||||
new_body.append("\t// [РЕСИНК-КОНФЛИКТ] блоки ниже не легли автоматически — перенесите вручную (по № см. conflict.md / index.md в merge-воркспейсе, путь в выводе).")
|
||||
cn = 0
|
||||
@@ -1258,19 +1364,19 @@ def resync_one(ext_bsl, ext_lines, dup, method, logical_module, conflict_folder,
|
||||
cn += 1
|
||||
if d["kind"] == "insert":
|
||||
new_body.append("\t// [РЕСИНК-КОНФЛИКТ №%d] вставка — исходный якорь изменён в новом оригинале." % cn)
|
||||
new_body.append("#Вставка"); new_body.extend(d["lines"]); new_body.append("#КонецВставки")
|
||||
new_body.append("#" + kw["Insert"]); new_body.extend(d["lines"]); new_body.append("#" + kw["EndInsert"])
|
||||
else:
|
||||
new_body.append("\t// [РЕСИНК-КОНФЛИКТ №%d] удаление — строки не найдены в новом оригинале:" % cn)
|
||||
for l in d["lines"]:
|
||||
new_body.append("\t// " + l.strip())
|
||||
|
||||
async_prefix = "Асинх " if method["is_async"] else ""
|
||||
keyword = "Функция" if method["is_function"] else "Процедура"
|
||||
end_keyword = "КонецФункции" if method["is_function"] else "КонецПроцедуры"
|
||||
async_prefix = (kw["Async"] + " ") if method["is_async"] else ""
|
||||
keyword = kw["Func"] if method["is_function"] else kw["Proc"]
|
||||
end_keyword = kw["EndFunc"] if method["is_function"] else kw["EndProc"]
|
||||
new_block = []
|
||||
if 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.extend(new_body)
|
||||
new_block.append(end_keyword)
|
||||
@@ -1284,7 +1390,7 @@ def resync_one(ext_bsl, ext_lines, dup, method, logical_module, conflict_folder,
|
||||
conflict_dir = None
|
||||
if disputed:
|
||||
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:
|
||||
status = "ЧАСТИЧНО"
|
||||
elif transferred == 0 and absorbed > 0:
|
||||
|
||||
@@ -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
|
||||
[CmdletBinding(PositionalBinding=$false)]
|
||||
param(
|
||||
@@ -1299,12 +1299,48 @@ 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="КонецЕсли"
|
||||
And="И"; 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"; 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 ---
|
||||
$extRootDir = Split-Path $resolvedPath -Parent
|
||||
$ctrlKw = Get-BslKeywords
|
||||
$ctrlRe = '(?m)^\s*&(?:' + $ctrlKw.ru.Control + '|' + $ctrlKw.en.Control + ')\('
|
||||
$ctrlCount = 0
|
||||
foreach ($bslFile in (Get-ChildItem -Path $extRootDir -Recurse -Filter *.bsl -File -ErrorAction SilentlyContinue)) {
|
||||
$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) {
|
||||
Out-Line "[INFO] Контролируемых методов (&ИзменениеИКонтроль): $ctrlCount — их актуальность здесь не проверяется. Сверьте: /cfe-patch-method -Check -ExtensionPath <ext> -ConfigPath <cf>"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/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
|
||||
"""Validates extension Configuration.xml: root, InternalInfo, extension properties, ChildObjects, borrowed objects."""
|
||||
import sys, os, argparse, re
|
||||
@@ -27,6 +27,40 @@ def ci_parse_args(parser, argv=None):
|
||||
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": "КонецЕсли",
|
||||
"And": "И", "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", "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 = {
|
||||
'md': 'http://v8.1c.ru/8.3/MDClasses',
|
||||
'v8': 'http://v8.1c.ru/8.1/data/core',
|
||||
@@ -1267,6 +1301,8 @@ def main():
|
||||
r.warn(f'16. {issue}')
|
||||
|
||||
# --- 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')\(')
|
||||
ctrl_count = 0
|
||||
for dp, _dn, files in os.walk(config_dir):
|
||||
for fn in files:
|
||||
@@ -1274,7 +1310,7 @@ def main():
|
||||
try:
|
||||
with open(os.path.join(dp, fn), 'r', encoding='utf-8-sig') as f:
|
||||
for ln in f:
|
||||
if re.match(r'^\s*&ИзменениеИКонтроль\(', ln):
|
||||
if ctrl_re.match(ln):
|
||||
ctrl_count += 1
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
@@ -149,6 +149,30 @@ const FAMILIES = [
|
||||
],
|
||||
},
|
||||
|
||||
// ─── Встроенный язык 1С в двух написаниях ────────────────────────────────
|
||||
// Платформа принимает и русские, и английские ключевые слова, поэтому разбор модуля обязан
|
||||
// читать оба написания. Таблица пар — данные, но живёт функцией: гард видит только функции,
|
||||
// а разъехавшаяся пара слов даёт ровно тот тихий ложно-чистый отчёт, из-за которого всё это
|
||||
// и делалось (ишью #97).
|
||||
{
|
||||
name: 'BSL: таблица ключевых слов', py: 'bsl_keywords', ps1: 'Get-BslKeywords',
|
||||
variants: [
|
||||
{ id: 'base', authority: 'cfe-patch-method', consumers: ['cfe-diff', 'cfe-validate'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'BSL: вид маркера правки', py: 'bsl_marker_kind', ps1: 'Get-BslMarkerKind',
|
||||
variants: [
|
||||
{ id: 'base', authority: 'cfe-patch-method', consumers: ['cfe-diff'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'BSL: разбор аннотаций перехвата', py: 'get_interceptors', ps1: 'Get-Interceptors',
|
||||
variants: [
|
||||
{ id: 'base', authority: 'cfe-patch-method', consumers: ['cfe-diff'] },
|
||||
],
|
||||
},
|
||||
|
||||
// ─── Запись файла в каноне выгрузки ──────────────────────────────────────
|
||||
{
|
||||
name: 'write_xml_file', py: 'write_xml_file', ps1: 'Write-XmlFile',
|
||||
|
||||
Reference in New Issue
Block a user