Initial SQL-only 1C adapter baseline
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Read a selected 1C object module from base/effective/extension context."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import base64
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
from get_1c_object_code_context import build_context # noqa: E402
|
||||
from resolve_1c_object import load_json # noqa: E402
|
||||
|
||||
|
||||
def decode_arg(value: str | None, encoded: str | None) -> str | None:
|
||||
if encoded:
|
||||
return base64.b64decode(encoded).decode("utf-8")
|
||||
return value
|
||||
|
||||
|
||||
def normalize(value: str | None) -> str:
|
||||
return re.sub(r"[\s._-]+", "", str(value or "")).casefold()
|
||||
|
||||
|
||||
def module_aliases(name: str) -> set[str]:
|
||||
n = name.strip()
|
||||
result = {normalize(n)}
|
||||
aliases = {
|
||||
"object_module": "МодульОбъекта",
|
||||
"manager_module": "МодульМенеджера",
|
||||
"form_module": "МодульФормы",
|
||||
"command_module": "МодульКоманды",
|
||||
}
|
||||
if n in aliases:
|
||||
result.add(normalize(aliases[n]))
|
||||
return result
|
||||
|
||||
|
||||
def match_module(module: dict[str, Any], wanted: str) -> bool:
|
||||
aliases = module_aliases(wanted)
|
||||
values = {
|
||||
module.get("name"),
|
||||
module.get("kind"),
|
||||
module.get("relative_path"),
|
||||
Path(str(module.get("relative_path") or "")).stem,
|
||||
}
|
||||
return any(normalize(value) in aliases or normalize(wanted) in normalize(value) for value in values)
|
||||
|
||||
|
||||
def read_text(path: str) -> str:
|
||||
return Path(path).read_text(encoding="utf-8-sig", errors="replace")
|
||||
|
||||
|
||||
def routine_span(text: str, routine: str) -> tuple[int, int] | None:
|
||||
wanted = normalize(routine)
|
||||
lines = text.splitlines()
|
||||
start = None
|
||||
end_pattern = None
|
||||
for index, line in enumerate(lines):
|
||||
match = re.match(r"^\s*(Процедура|Функция)\s+([A-Za-zА-Яа-я0-9_]+)", line, re.IGNORECASE)
|
||||
if not match:
|
||||
continue
|
||||
if normalize(match.group(2)) != wanted:
|
||||
continue
|
||||
start = index
|
||||
end_pattern = "КонецПроцедуры" if match.group(1).casefold() == "процедура" else "КонецФункции"
|
||||
break
|
||||
if start is None or end_pattern is None:
|
||||
return None
|
||||
for index in range(start + 1, len(lines)):
|
||||
if re.match(rf"^\s*{end_pattern}\b", lines[index], re.IGNORECASE):
|
||||
return start, index + 1
|
||||
return start, len(lines)
|
||||
|
||||
|
||||
def snippet(text: str, *, max_chars: int, routine: str | None) -> dict[str, Any]:
|
||||
source = text
|
||||
line_start = 1
|
||||
found_routine = None
|
||||
if routine:
|
||||
span = routine_span(text, routine)
|
||||
if span:
|
||||
lines = text.splitlines()
|
||||
source = "\n".join(lines[span[0] : span[1]])
|
||||
line_start = span[0] + 1
|
||||
found_routine = routine
|
||||
else:
|
||||
source = ""
|
||||
truncated = len(source) > max_chars
|
||||
return {
|
||||
"routine": found_routine,
|
||||
"line_start": line_start if source else None,
|
||||
"text": source[:max_chars],
|
||||
"truncated": truncated,
|
||||
"char_count": len(source),
|
||||
}
|
||||
|
||||
|
||||
def materialize(module: dict[str, Any], *, max_chars: int, routine: str | None) -> dict[str, Any]:
|
||||
text = read_text(str(module["path"]))
|
||||
return {
|
||||
"name": module.get("name"),
|
||||
"kind": module.get("kind"),
|
||||
"origin": module.get("origin"),
|
||||
"effective_action": module.get("effective_action"),
|
||||
"relative_path": module.get("relative_path"),
|
||||
"path": module.get("path"),
|
||||
"size": module.get("size"),
|
||||
"content": snippet(text, max_chars=max_chars, routine=routine),
|
||||
}
|
||||
|
||||
|
||||
def build_module_result(
|
||||
index: dict[str, Any],
|
||||
*,
|
||||
kind: str,
|
||||
name: str,
|
||||
module_name: str,
|
||||
view: str,
|
||||
extension: str | None,
|
||||
max_chars: int,
|
||||
routine: str | None,
|
||||
) -> dict[str, Any]:
|
||||
context = build_context(index, kind=kind, name=name, view=view, extension=extension)
|
||||
matches = [module for module in context.get("modules") or [] if match_module(module, module_name)]
|
||||
if not matches:
|
||||
raise SystemExit(f"Module not found: {module_name}")
|
||||
modules = []
|
||||
for module in matches:
|
||||
modules.append(materialize(module, max_chars=max_chars, routine=routine))
|
||||
for overlay in module.get("extension_overlays") or []:
|
||||
modules.append(materialize(overlay, max_chars=max_chars, routine=routine))
|
||||
return {
|
||||
"schema": "onec_module_content.v1",
|
||||
"view": view,
|
||||
"extension": extension if view == "extension" else None,
|
||||
"object": context.get("object"),
|
||||
"query": {"module": module_name, "routine": routine, "max_chars": max_chars},
|
||||
"modules": modules,
|
||||
"counts": {"modules": len(modules)},
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Get 1C module content.")
|
||||
parser.add_argument("--index", type=Path, required=True)
|
||||
parser.add_argument("--kind")
|
||||
parser.add_argument("--name")
|
||||
parser.add_argument("--kind-b64")
|
||||
parser.add_argument("--name-b64")
|
||||
parser.add_argument("--module", required=True)
|
||||
parser.add_argument("--view", choices=["effective", "base", "extension"], default="effective")
|
||||
parser.add_argument("--extension")
|
||||
parser.add_argument("--routine")
|
||||
parser.add_argument("--max-chars", type=int, default=20000)
|
||||
parser.add_argument("--output", type=Path)
|
||||
args = parser.parse_args()
|
||||
|
||||
kind = decode_arg(args.kind, args.kind_b64)
|
||||
name = decode_arg(args.name, args.name_b64)
|
||||
if not kind or not name:
|
||||
raise SystemExit("Use --kind/--name or --kind-b64/--name-b64.")
|
||||
result = build_module_result(
|
||||
load_json(args.index),
|
||||
kind=kind,
|
||||
name=name,
|
||||
module_name=args.module,
|
||||
view=args.view,
|
||||
extension=args.extension,
|
||||
max_chars=args.max_chars,
|
||||
routine=args.routine,
|
||||
)
|
||||
text = json.dumps(result, ensure_ascii=False, indent=2) + "\n"
|
||||
if args.output:
|
||||
args.output.parent.mkdir(parents=True, exist_ok=True)
|
||||
args.output.write_text(text, encoding="utf-8")
|
||||
print(json.dumps({"output": str(args.output), "counts": result["counts"], "view": result["view"]}, ensure_ascii=False))
|
||||
else:
|
||||
print(text)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user