Initial SQL-only 1C adapter baseline
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Return module/file context for a 1C object with base/effective/extension views."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import base64
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
from resolve_1c_object import load_json, resolve_object # noqa: E402
|
||||
|
||||
|
||||
MODULE_PATTERNS = {
|
||||
"Ext/ObjectModule.bsl": ("object_module", "МодульОбъекта"),
|
||||
"Ext/ManagerModule.bsl": ("manager_module", "МодульМенеджера"),
|
||||
}
|
||||
|
||||
|
||||
def decode_arg(value: str | None, encoded: str | None) -> str | None:
|
||||
if encoded:
|
||||
return base64.b64decode(encoded).decode("utf-8")
|
||||
return value
|
||||
|
||||
|
||||
def extension_name_from_path(path: str | None) -> str | None:
|
||||
parts = str(path or "").replace("/", "\\").split("\\")
|
||||
lowered = [part.casefold() for part in parts]
|
||||
for marker in ("расширения", "extensions"):
|
||||
if marker in lowered:
|
||||
index = lowered.index(marker)
|
||||
if index + 1 < len(parts):
|
||||
return parts[index + 1]
|
||||
return None
|
||||
|
||||
|
||||
def module_record(path: Path, *, owner_dir: Path, source: str, extension_name: str | None = None) -> dict[str, Any]:
|
||||
relative = str(path.relative_to(owner_dir)).replace("\\", "/")
|
||||
kind, name = MODULE_PATTERNS.get(relative, ("module", path.stem))
|
||||
if relative.startswith("Forms/") and relative.endswith("/Ext/Form/Module.bsl"):
|
||||
form_name = relative.split("/", 2)[1]
|
||||
kind = "form_module"
|
||||
name = f"МодульФормы.{form_name}"
|
||||
elif relative.startswith("Commands/") and relative.endswith("/Ext/CommandModule.bsl"):
|
||||
command_name = relative.split("/", 2)[1]
|
||||
kind = "command_module"
|
||||
name = f"МодульКоманды.{command_name}"
|
||||
return {
|
||||
"name": name,
|
||||
"kind": kind,
|
||||
"relative_path": relative,
|
||||
"path": str(path),
|
||||
"size": path.stat().st_size,
|
||||
"origin": {"layer": source, **({"extension": extension_name} if extension_name else {})},
|
||||
}
|
||||
|
||||
|
||||
def collect_modules(owner_xml_path: str | None, *, source: str, extension_name: str | None = None) -> list[dict[str, Any]]:
|
||||
if not owner_xml_path:
|
||||
return []
|
||||
owner_xml = Path(owner_xml_path)
|
||||
owner_dir = owner_xml.with_suffix("")
|
||||
if not owner_dir.is_dir():
|
||||
return []
|
||||
result = []
|
||||
for path in sorted(owner_dir.rglob("*.bsl")):
|
||||
result.append(module_record(path, owner_dir=owner_dir, source=source, extension_name=extension_name))
|
||||
return result
|
||||
|
||||
|
||||
def merge_effective(base_modules: list[dict[str, Any]], extension_modules: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
result = []
|
||||
by_key: dict[tuple[str, str], dict[str, Any]] = {}
|
||||
for module in base_modules:
|
||||
copy = dict(module)
|
||||
copy["effective_action"] = "base"
|
||||
by_key[(copy["kind"], copy["name"])] = copy
|
||||
result.append(copy)
|
||||
for module in extension_modules:
|
||||
key = (module["kind"], module["name"])
|
||||
copy = dict(module)
|
||||
if key in by_key:
|
||||
copy["effective_action"] = "extended"
|
||||
by_key[key].setdefault("extension_overlays", []).append(copy)
|
||||
else:
|
||||
copy["effective_action"] = "added"
|
||||
result.append(copy)
|
||||
return result
|
||||
|
||||
|
||||
def filter_extension(modules: list[dict[str, Any]], extension: str) -> list[dict[str, Any]]:
|
||||
result = []
|
||||
for module in modules:
|
||||
if (module.get("origin") or {}).get("extension") == extension:
|
||||
copy = dict(module)
|
||||
copy["effective_action"] = "extended_or_added"
|
||||
result.append(copy)
|
||||
return result
|
||||
|
||||
|
||||
def build_context(index: dict[str, Any], *, kind: str, name: str, view: str, extension: str | None) -> dict[str, Any]:
|
||||
resolution = resolve_object(index, kind=kind, name=name, limit=100)
|
||||
canonical = resolution.get("canonical")
|
||||
if not canonical:
|
||||
raise SystemExit(f"Object not found: {kind}.{name}")
|
||||
base_modules = collect_modules(canonical.get("path"), source="base")
|
||||
|
||||
extension_modules = []
|
||||
for overlay in resolution.get("extension_overlays") or []:
|
||||
overlay_extension = extension_name_from_path(overlay.get("path")) or overlay.get("extension_name")
|
||||
extension_modules.extend(collect_modules(overlay.get("path"), source="extension", extension_name=overlay_extension))
|
||||
|
||||
if view == "base":
|
||||
modules = [{**module, "effective_action": "base"} for module in base_modules]
|
||||
elif view == "effective":
|
||||
modules = merge_effective(base_modules, extension_modules)
|
||||
elif view == "extension":
|
||||
if not extension:
|
||||
raise SystemExit("Use --extension with --view extension.")
|
||||
modules = filter_extension(extension_modules, extension)
|
||||
else:
|
||||
raise SystemExit(f"Unsupported view: {view}")
|
||||
|
||||
return {
|
||||
"schema": "onec_object_code_context.v1",
|
||||
"view": view,
|
||||
"extension": extension if view == "extension" else None,
|
||||
"object": {
|
||||
"kind": canonical.get("kind"),
|
||||
"name": canonical.get("name"),
|
||||
"synonym": canonical.get("synonym"),
|
||||
"uuid": canonical.get("guid"),
|
||||
},
|
||||
"modules": modules,
|
||||
"resolution": {
|
||||
"schema": resolution.get("schema"),
|
||||
"canonical": canonical,
|
||||
"summary": resolution.get("summary"),
|
||||
},
|
||||
"counts": {
|
||||
"modules": len(modules),
|
||||
"base_modules": len(base_modules),
|
||||
"extension_modules": len(extension_modules),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Get 1C object code context.")
|
||||
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("--view", choices=["effective", "base", "extension"], default="effective")
|
||||
parser.add_argument("--extension")
|
||||
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_context(load_json(args.index), kind=kind, name=name, view=args.view, extension=args.extension)
|
||||
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