191 lines
8.1 KiB
Python
191 lines
8.1 KiB
Python
from __future__ import annotations
|
||
|
||
import re
|
||
|
||
from api_server.normalized_object_models import (
|
||
BslCompletionItemResponse,
|
||
ModuleRoutineResponse,
|
||
ModuleSourceResponse,
|
||
NormalizedObjectDetail,
|
||
)
|
||
from api_server.normalized_project_service import normalized_all_groups
|
||
from one_c_normalizer import NormalizedProject
|
||
|
||
|
||
def normalized_object_detail(normalized: NormalizedProject, qualified_name: str) -> NormalizedObjectDetail | None:
|
||
for group in normalized.configuration.groups:
|
||
for item in group.objects:
|
||
if item.qualified_name == qualified_name:
|
||
return NormalizedObjectDetail(
|
||
project_id=normalized.project_id,
|
||
group_name=group.name,
|
||
object=item,
|
||
)
|
||
return None
|
||
|
||
|
||
def normalized_module_sources_for_object(normalized: NormalizedProject, qualified_name: str) -> list[ModuleSourceResponse]:
|
||
normalized_query = qualified_name.strip().casefold()
|
||
if not normalized_query:
|
||
return []
|
||
selected_module = None
|
||
selected_owner = None
|
||
selected_object = None
|
||
for group in normalized_all_groups(normalized):
|
||
for item in group.objects:
|
||
if item.qualified_name.casefold() == normalized_query or item.name.casefold() == normalized_query:
|
||
selected_object = item
|
||
break
|
||
for module in item.modules:
|
||
module_keys = {
|
||
str(module.qualified_name or "").casefold(),
|
||
str(module.name or "").casefold(),
|
||
str(module.source_path or "").casefold(),
|
||
}
|
||
if normalized_query in module_keys:
|
||
selected_module = module
|
||
selected_owner = item
|
||
break
|
||
if selected_object is not None or selected_module is not None:
|
||
break
|
||
if selected_object is not None or selected_module is not None:
|
||
break
|
||
if selected_module is not None:
|
||
return [_normalized_module_source_response(selected_module, selected_owner)]
|
||
if selected_object is None:
|
||
return []
|
||
return sorted(
|
||
[_normalized_module_source_response(module, selected_object) for module in selected_object.modules],
|
||
key=lambda item: (item.module_role, item.name),
|
||
)
|
||
|
||
|
||
def normalized_bsl_completion_items(
|
||
normalized: NormalizedProject,
|
||
receiver: str | None,
|
||
qualified_name: str | None,
|
||
) -> list[BslCompletionItemResponse]:
|
||
receiver_key = (receiver or "").strip().casefold()
|
||
qualified_key = (qualified_name or "").strip().casefold()
|
||
items: list[BslCompletionItemResponse] = []
|
||
for group in normalized_all_groups(normalized):
|
||
for metadata_object in group.objects:
|
||
object_names = {
|
||
metadata_object.name.casefold(),
|
||
metadata_object.qualified_name.casefold(),
|
||
}
|
||
if receiver_key and receiver_key in object_names:
|
||
items.extend(
|
||
BslCompletionItemResponse(
|
||
label=part.name,
|
||
kind=_completion_kind_for_part(part.kind),
|
||
detail=f"{metadata_object.qualified_name}: {part.kind}",
|
||
insert_text=part.name,
|
||
)
|
||
for part in [
|
||
*metadata_object.attributes,
|
||
*metadata_object.resources,
|
||
*metadata_object.dimensions,
|
||
*metadata_object.tabular_sections,
|
||
*metadata_object.commands,
|
||
]
|
||
)
|
||
for module in metadata_object.modules:
|
||
module_names = {
|
||
str(module.name or "").casefold(),
|
||
str(module.qualified_name or "").casefold(),
|
||
str(module.source_path or "").casefold(),
|
||
f"{metadata_object.name}.{module.name}".casefold(),
|
||
f"{metadata_object.qualified_name}.{module.name}".casefold(),
|
||
}
|
||
if receiver_key and receiver_key not in module_names and receiver_key not in object_names:
|
||
continue
|
||
if not receiver_key and qualified_key and qualified_key not in module_names and qualified_key not in object_names:
|
||
continue
|
||
source_text = str((module.attributes or {}).get("source_text", ""))
|
||
for routine in normalized_module_routines(source_text):
|
||
if receiver_key and not routine.export:
|
||
continue
|
||
items.append(
|
||
BslCompletionItemResponse(
|
||
label=routine.name,
|
||
kind="FUNCTION" if routine.kind == "FUNCTION" else "PROCEDURE",
|
||
detail=f"{module.qualified_name or module.name}{' · Export' if routine.export else ''}",
|
||
insert_text=f"{routine.name}()",
|
||
)
|
||
)
|
||
return items
|
||
|
||
|
||
def _completion_kind_for_part(kind: str) -> str:
|
||
normalized = kind.upper()
|
||
if normalized in {"ATTRIBUTE", "RESOURCE", "DIMENSION", "FIELD"}:
|
||
return "PROPERTY"
|
||
if normalized in {"COMMAND", "METHOD", "OPERATION"}:
|
||
return "METHOD"
|
||
if normalized in {"TABULAR_SECTION", "TABLE"}:
|
||
return "COLLECTION"
|
||
return "VALUE"
|
||
|
||
|
||
def _normalized_module_source_response(module, owner) -> ModuleSourceResponse:
|
||
attributes = module.attributes or {}
|
||
source_text = str(attributes.get("source_text", ""))
|
||
routines = normalized_module_routines(source_text)
|
||
module_role = str(module.module_kind or attributes.get("module_role") or "MODULE")
|
||
return ModuleSourceResponse(
|
||
name=module.name,
|
||
qualified_name=module.qualified_name or module.name,
|
||
module_role=module_role,
|
||
owner_qualified_name=str(attributes.get("owner_qualified_name") or getattr(owner, "qualified_name", "") or "") or None,
|
||
owner_kind=str(attributes.get("owner_kind") or getattr(owner, "object_kind", "") or "") or None,
|
||
object_part=str(
|
||
attributes.get("object_part")
|
||
or module_object_part_for_response(module_role, str(attributes.get("form_name") or ""))
|
||
),
|
||
form_name=str(attributes.get("form_name") or "") or None,
|
||
form_qualified_name=str(attributes.get("form_qualified_name") or "") or None,
|
||
source_path=module.source_path or "",
|
||
source_text=source_text,
|
||
routines_count=len(routines),
|
||
routines=routines,
|
||
)
|
||
|
||
|
||
def module_object_part_for_response(module_role: str, form_name: str = "") -> str:
|
||
return {
|
||
"OBJECT_MODULE": "object.module",
|
||
"MANAGER_MODULE": "object.manager",
|
||
"RECORD_SET_MODULE": "object.record_set",
|
||
"FORM_MODULE": f"form.{form_name}.module" if form_name else "form.module",
|
||
"MODULE": "module",
|
||
}.get(module_role, "module")
|
||
|
||
|
||
def normalized_module_routines(source_text: str) -> list[ModuleRoutineResponse]:
|
||
if not source_text:
|
||
return []
|
||
declarations: list[tuple[int, re.Match[str]]] = []
|
||
pattern = re.compile(
|
||
r"^\s*(Процедура|Функция|Procedure|Function)\s+([A-Za-zА-Яа-яЁё_][\wА-Яа-яЁё]*)\s*\(([^)]*)\)\s*(.*)$",
|
||
re.IGNORECASE | re.MULTILINE,
|
||
)
|
||
for match in pattern.finditer(source_text):
|
||
line_start = source_text.count("\n", 0, match.start()) + 1
|
||
declarations.append((line_start, match))
|
||
routines: list[ModuleRoutineResponse] = []
|
||
for index, (line_start, match) in enumerate(declarations):
|
||
line_end = declarations[index + 1][0] - 1 if index + 1 < len(declarations) else len(source_text.splitlines())
|
||
kind_label = match.group(1).casefold()
|
||
tail = match.group(4) or ""
|
||
routines.append(
|
||
ModuleRoutineResponse(
|
||
name=match.group(2),
|
||
kind="FUNCTION" if kind_label in {"функция", "function"} else "PROCEDURE",
|
||
line_start=line_start,
|
||
line_end=line_end,
|
||
export=bool(re.search(r"\b(Экспорт|Export)\b", tail, re.IGNORECASE)),
|
||
)
|
||
)
|
||
return routines
|