mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-06-11 00:14:56 +03:00
248 lines
13 KiB
Python
248 lines
13 KiB
Python
#!/usr/bin/env python3
|
|
# cfe-patch-method v1.1 — Generate method interceptor for 1C extension (CFE)
|
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
|
|
|
import argparse
|
|
import os
|
|
import sys
|
|
import xml.etree.ElementTree as ET
|
|
|
|
|
|
def main():
|
|
sys.stdout.reconfigure(encoding="utf-8")
|
|
sys.stderr.reconfigure(encoding="utf-8")
|
|
parser = argparse.ArgumentParser(
|
|
description="Generate method interceptor for 1C extension (CFE)",
|
|
allow_abbrev=False,
|
|
)
|
|
parser.add_argument("-ExtensionPath", required=True)
|
|
parser.add_argument("-ModulePath", required=True)
|
|
parser.add_argument("-MethodName", required=True)
|
|
parser.add_argument(
|
|
"-InterceptorType",
|
|
required=True,
|
|
choices=["Before", "After", "ModificationAndControl"],
|
|
)
|
|
parser.add_argument("-Context", default="\u041d\u0430\u0421\u0435\u0440\u0432\u0435\u0440\u0435") # НаСервере
|
|
parser.add_argument("-IsFunction", action="store_true")
|
|
args = parser.parse_args()
|
|
|
|
extension_path = args.ExtensionPath
|
|
module_path = args.ModulePath
|
|
method_name = args.MethodName
|
|
interceptor_type = args.InterceptorType
|
|
context = args.Context
|
|
is_function = args.IsFunction
|
|
|
|
# --- Resolve extension path ---
|
|
if not os.path.isabs(extension_path):
|
|
extension_path = os.path.join(os.getcwd(), extension_path)
|
|
if os.path.isfile(extension_path):
|
|
extension_path = os.path.dirname(extension_path)
|
|
|
|
cfg_file = os.path.join(extension_path, "Configuration.xml")
|
|
if not os.path.isfile(cfg_file):
|
|
print(f"Configuration.xml not found in: {extension_path}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
# --- Read NamePrefix from Configuration.xml ---
|
|
tree = ET.parse(cfg_file)
|
|
root = tree.getroot()
|
|
|
|
ns = {"md": "http://v8.1c.ru/8.3/MDClasses"}
|
|
props_node = root.find(".//md:Configuration/md:Properties", ns)
|
|
name_prefix = "\u0420\u0430\u0441\u0448_" # Расш_
|
|
if props_node is not None:
|
|
prefix_node = props_node.find("md:NamePrefix", ns)
|
|
if prefix_node is not None and prefix_node.text:
|
|
name_prefix = prefix_node.text
|
|
|
|
# --- Map ModulePath to file path ---
|
|
# ModulePath formats:
|
|
# Catalog.X.ObjectModule -> Catalogs/X/Ext/ObjectModule.bsl
|
|
# Catalog.X.ManagerModule -> Catalogs/X/Ext/ManagerModule.bsl
|
|
# Catalog.X.Form.Y -> Catalogs/X/Forms/Y/Ext/Form/Module.bsl
|
|
# CommonModule.X -> CommonModules/X/Ext/Module.bsl
|
|
# Document.X.ObjectModule -> Documents/X/Ext/ObjectModule.bsl
|
|
# Document.X.ManagerModule -> Documents/X/Ext/ManagerModule.bsl
|
|
# Document.X.Form.Y -> Documents/X/Forms/Y/Ext/Form/Module.bsl
|
|
|
|
type_dir_map = {
|
|
"Catalog": "Catalogs",
|
|
"Document": "Documents",
|
|
"Enum": "Enums",
|
|
"CommonModule": "CommonModules",
|
|
"Report": "Reports",
|
|
"DataProcessor": "DataProcessors",
|
|
"ExchangePlan": "ExchangePlans",
|
|
"ChartOfAccounts": "ChartsOfAccounts",
|
|
"ChartOfCharacteristicTypes": "ChartsOfCharacteristicTypes",
|
|
"ChartOfCalculationTypes": "ChartsOfCalculationTypes",
|
|
"BusinessProcess": "BusinessProcesses",
|
|
"Task": "Tasks",
|
|
"InformationRegister": "InformationRegisters",
|
|
"AccumulationRegister": "AccumulationRegisters",
|
|
"AccountingRegister": "AccountingRegisters",
|
|
"CalculationRegister": "CalculationRegisters",
|
|
"Catalogs": "Catalogs",
|
|
"Documents": "Documents",
|
|
"Enums": "Enums",
|
|
"CommonModules": "CommonModules",
|
|
"Reports": "Reports",
|
|
"DataProcessors": "DataProcessors",
|
|
"ExchangePlans": "ExchangePlans",
|
|
"ChartsOfAccounts": "ChartsOfAccounts",
|
|
"ChartsOfCharacteristicTypes": "ChartsOfCharacteristicTypes",
|
|
"ChartsOfCalculationTypes": "ChartsOfCalculationTypes",
|
|
"BusinessProcesses": "BusinessProcesses",
|
|
"Tasks": "Tasks",
|
|
"InformationRegisters": "InformationRegisters",
|
|
"AccumulationRegisters": "AccumulationRegisters",
|
|
"AccountingRegisters": "AccountingRegisters",
|
|
"CalculationRegisters": "CalculationRegisters",
|
|
}
|
|
|
|
parts = module_path.split(".")
|
|
if len(parts) < 2:
|
|
print(
|
|
f"Invalid ModulePath format: {module_path}. "
|
|
"Expected: Type.Name.Module or CommonModule.Name",
|
|
file=sys.stderr,
|
|
)
|
|
sys.exit(1)
|
|
|
|
obj_type = parts[0]
|
|
obj_name = parts[1]
|
|
|
|
if obj_type not in type_dir_map:
|
|
print(f"Unknown object type: {obj_type}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
dir_name = type_dir_map[obj_type]
|
|
|
|
bsl_file = None
|
|
if obj_type == "CommonModule":
|
|
# CommonModule.X -> CommonModules/X/Ext/Module.bsl
|
|
bsl_file = os.path.join(extension_path, dir_name, obj_name, "Ext", "Module.bsl")
|
|
elif len(parts) >= 4 and parts[2] == "Form":
|
|
# Type.X.Form.Y -> Types/X/Forms/Y/Ext/Form/Module.bsl
|
|
form_name = parts[3]
|
|
bsl_file = os.path.join(
|
|
extension_path, dir_name, obj_name, "Forms", form_name, "Ext", "Form", "Module.bsl"
|
|
)
|
|
elif len(parts) >= 3:
|
|
# Type.X.ObjectModule -> Types/X/Ext/ObjectModule.bsl
|
|
module_name = parts[2]
|
|
module_file_map = {
|
|
"ObjectModule": "ObjectModule.bsl",
|
|
"ManagerModule": "ManagerModule.bsl",
|
|
"RecordSetModule": "RecordSetModule.bsl",
|
|
"CommandModule": "CommandModule.bsl",
|
|
}
|
|
module_file_name = module_file_map.get(module_name, f"{module_name}.bsl")
|
|
bsl_file = os.path.join(extension_path, dir_name, obj_name, "Ext", module_file_name)
|
|
else:
|
|
print(
|
|
f"Invalid ModulePath format: {module_path}. "
|
|
"Expected: Type.Name.Module, Type.Name.Form.FormName, or CommonModule.Name",
|
|
file=sys.stderr,
|
|
)
|
|
sys.exit(1)
|
|
|
|
# --- Map InterceptorType to decorator ---
|
|
decorator_map = {
|
|
"Before": "&\u041f\u0435\u0440\u0435\u0434", # &Перед
|
|
"After": "&\u041f\u043e\u0441\u043b\u0435", # &После
|
|
"ModificationAndControl": "&\u0418\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435\u0418\u041a\u043e\u043d\u0442\u0440\u043e\u043b\u044c", # &ИзменениеИКонтроль
|
|
}
|
|
decorator = decorator_map[interceptor_type]
|
|
|
|
# --- Map Context to annotation ---
|
|
context_map = {
|
|
"\u041d\u0430\u0421\u0435\u0440\u0432\u0435\u0440\u0435": "&\u041d\u0430\u0421\u0435\u0440\u0432\u0435\u0440\u0435", # НаСервере -> &НаСервере
|
|
"\u041d\u0430\u041a\u043b\u0438\u0435\u043d\u0442\u0435": "&\u041d\u0430\u041a\u043b\u0438\u0435\u043d\u0442\u0435", # НаКлиенте -> &НаКлиенте
|
|
"\u041d\u0430\u0421\u0435\u0440\u0432\u0435\u0440\u0435\u0411\u0435\u0437\u041a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430": "&\u041d\u0430\u0421\u0435\u0440\u0432\u0435\u0440\u0435\u0411\u0435\u0437\u041a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430", # НаСервереБезКонтекста -> &НаСервереБезКонтекста
|
|
}
|
|
context_annotation = context_map.get(context, f"&{context}")
|
|
|
|
# --- Procedure name ---
|
|
proc_name = f"{name_prefix}{method_name}"
|
|
|
|
# --- Generate BSL code ---
|
|
keyword = "\u0424\u0443\u043d\u043a\u0446\u0438\u044f" if is_function else "\u041f\u0440\u043e\u0446\u0435\u0434\u0443\u0440\u0430" # Функция / Процедура
|
|
end_keyword = "\u041a\u043e\u043d\u0435\u0446\u0424\u0443\u043d\u043a\u0446\u0438\u0438" if is_function else "\u041a\u043e\u043d\u0435\u0446\u041f\u0440\u043e\u0446\u0435\u0434\u0443\u0440\u044b" # КонецФункции / КонецПроцедуры
|
|
|
|
body_lines = []
|
|
if interceptor_type == "Before":
|
|
body_lines.append("\t// TODO: \u043a\u043e\u0434 \u043f\u0435\u0440\u0435\u0434 \u0432\u044b\u0437\u043e\u0432\u043e\u043c \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043c\u0435\u0442\u043e\u0434\u0430") # код перед вызовом оригинального метода
|
|
elif interceptor_type == "After":
|
|
body_lines.append("\t// TODO: \u043a\u043e\u0434 \u043f\u043e\u0441\u043b\u0435 \u0432\u044b\u0437\u043e\u0432\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043c\u0435\u0442\u043e\u0434\u0430") # код после вызова оригинального метода
|
|
elif interceptor_type == "ModificationAndControl":
|
|
body_lines.append("\t// \u0421\u043a\u043e\u043f\u0438\u0440\u0443\u0439\u0442\u0435 \u0442\u0435\u043b\u043e \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043c\u0435\u0442\u043e\u0434\u0430 \u0438 \u0432\u043d\u0435\u0441\u0438\u0442\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f,") # Скопируйте тело оригинального метода и внесите изменения,
|
|
body_lines.append("\t// \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u043c\u0430\u0440\u043a\u0435\u0440\u044b #\u0423\u0434\u0430\u043b\u0435\u043d\u0438\u0435 / #\u041a\u043e\u043d\u0435\u0446\u0423\u0434\u0430\u043b\u0435\u043d\u0438\u044f \u0438 #\u0412\u0441\u0442\u0430\u0432\u043a\u0430 / #\u041a\u043e\u043d\u0435\u0446\u0412\u0441\u0442\u0430\u0432\u043a\u0438") # используя маркеры #Удаление / #КонецУдаления и #Вставка / #КонецВставки
|
|
|
|
if is_function:
|
|
body_lines.append("\t")
|
|
body_lines.append("\t\u0412\u043e\u0437\u0432\u0440\u0430\u0442 \u041d\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043e; // TODO: \u0437\u0430\u043c\u0435\u043d\u0438\u0442\u044c \u043d\u0430 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u0435 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435") # Возврат Неопределено; // TODO: заменить на реальное возвращаемое значение
|
|
|
|
bsl_code = [
|
|
context_annotation,
|
|
f'{decorator}("{method_name}")',
|
|
f"{keyword} {proc_name}()",
|
|
]
|
|
bsl_code.extend(body_lines)
|
|
bsl_code.append(end_keyword)
|
|
|
|
bsl_text = "\r\n".join(bsl_code) + "\r\n"
|
|
|
|
# --- Check form borrowing for .Form. paths ---
|
|
if len(parts) >= 4 and parts[2] == "Form":
|
|
form_name = parts[3]
|
|
form_meta_file = os.path.join(
|
|
extension_path, dir_name, obj_name, "Forms", f"{form_name}.xml"
|
|
)
|
|
form_xml_file = os.path.join(
|
|
extension_path, dir_name, obj_name, "Forms", form_name, "Ext", "Form.xml"
|
|
)
|
|
|
|
if not os.path.isfile(form_meta_file) or not os.path.isfile(form_xml_file):
|
|
print(f"[WARN] Form '{form_name}' metadata or Form.xml not found in extension.")
|
|
print(" Run /cfe-borrow first:")
|
|
print(
|
|
f" /cfe-borrow -ExtensionPath {extension_path} "
|
|
f'-ConfigPath <ConfigPath> -Object "{obj_type}.{obj_name}.Form.{form_name}"'
|
|
)
|
|
print()
|
|
|
|
# --- Check if file exists and append ---
|
|
bsl_dir = os.path.dirname(bsl_file)
|
|
if not os.path.isdir(bsl_dir):
|
|
os.makedirs(bsl_dir, exist_ok=True)
|
|
|
|
if os.path.isfile(bsl_file):
|
|
# Append to existing file
|
|
with open(bsl_file, "r", encoding="utf-8-sig", newline="") as f:
|
|
existing = f.read()
|
|
|
|
separator = "\r\n"
|
|
if existing and not existing.endswith("\n"):
|
|
separator = "\r\n\r\n"
|
|
new_content = existing + separator + bsl_text
|
|
|
|
with open(bsl_file, "w", encoding="utf-8-sig", newline="") as f:
|
|
f.write(new_content)
|
|
print("[OK] \u0414\u043e\u0431\u0430\u0432\u043b\u0435\u043d \u043f\u0435\u0440\u0435\u0445\u0432\u0430\u0442\u0447\u0438\u043a \u0432 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0439 \u0444\u0430\u0439\u043b") # Добавлен перехватчик в существующий файл
|
|
else:
|
|
with open(bsl_file, "w", encoding="utf-8-sig", newline="") as f:
|
|
f.write(bsl_text)
|
|
print("[OK] \u0421\u043e\u0437\u0434\u0430\u043d \u0444\u0430\u0439\u043b \u043c\u043e\u0434\u0443\u043b\u044f") # Создан файл модуля
|
|
|
|
print(f" \u0424\u0430\u0439\u043b: {bsl_file}") # Файл:
|
|
print(f' \u0414\u0435\u043a\u043e\u0440\u0430\u0442\u043e\u0440: {decorator}("{method_name}")') # Декоратор:
|
|
print(f" \u041f\u0440\u043e\u0446\u0435\u0434\u0443\u0440\u0430: {proc_name}()") # Процедура:
|
|
print(f" \u041a\u043e\u043d\u0442\u0435\u043a\u0441\u0442: {context_annotation}") # Контекст:
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|