Initial SQL-only 1C adapter baseline

This commit is contained in:
2026-07-22 03:03:47 +03:00
commit e2503b77e7
545 changed files with 184711 additions and 0 deletions
+228
View File
@@ -0,0 +1,228 @@
#!/usr/bin/env python3
"""Atomically add a BSL handler, form command, and visible form button."""
from __future__ import annotations
import argparse
import base64
import json
from pathlib import Path
from typing import Any
from diff_1c_patch_workspace import build_diff
from edit_1c_bsl_routine import edit_workspace as edit_bsl_routine
from edit_1c_form_button import edit_workspace as edit_form_button
from edit_1c_form_command import edit_workspace as edit_form_command
from validate_1c_patch_workspace_semantics import validate_workspace
def load_json(path: Path) -> dict[str, Any]:
return json.loads(path.read_text(encoding="utf-8-sig"))
def write_json(path: Path, data: dict[str, Any]) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
def read_bytes(path: Path) -> bytes:
return path.read_bytes()
def workspace_working_files(workspace: Path) -> list[Path]:
manifest = load_json(workspace / "manifest.json")
paths = []
for record in manifest.get("files") or []:
relative = Path(str(record.get("relative_path") or ""))
if relative.is_absolute() or ".." in relative.parts:
raise SystemExit(f"Unsafe manifest relative path: {relative}")
path = workspace / "working" / relative
if path.exists():
paths.append(path)
return paths
def snapshot_working_files(workspace: Path) -> dict[Path, bytes]:
return {path: read_bytes(path) for path in workspace_working_files(workspace)}
def restore_snapshot(snapshot: dict[Path, bytes]) -> None:
for path, content in snapshot.items():
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(content)
def decode_routine_text(args: argparse.Namespace) -> str:
sources = [bool(args.routine_text), bool(args.routine_text_b64), bool(args.routine_file)]
if sum(sources) != 1:
raise SystemExit("Use exactly one of --routine-text, --routine-text-b64, or --routine-file.")
if args.routine_text is not None:
return args.routine_text
if args.routine_text_b64:
return base64.b64decode(args.routine_text_b64).decode("utf-8")
return Path(args.routine_file).read_text(encoding="utf-8-sig")
def build_result(
*,
workspace: Path,
form_relative_path: str,
bsl_relative_path: str,
operation: str,
routine_text: str,
command_name: str,
command_title: str,
command_action: str,
button_parent_name: str,
button_name: str,
button_title: str,
keep_on_failure: bool,
max_patch_chars: int,
) -> dict[str, Any]:
snapshot = snapshot_working_files(workspace)
steps: list[dict[str, Any]] = []
rolled_back = False
error: dict[str, Any] | None = None
try:
bsl = edit_bsl_routine(
workspace,
bsl_relative_path,
routine_text,
operation=operation,
keep_on_failure=True,
)
steps.append({"name": "bsl_routine", "result": bsl})
if not bsl.get("semantic_validation", {}).get("passed"):
raise RuntimeError("BSL routine edit failed semantic validation.")
command = edit_form_command(
workspace,
form_relative_path,
name=command_name,
title=command_title,
action=command_action,
tooltip=None,
command_id=None,
call_type="Override",
operation=operation,
keep_on_failure=True,
)
steps.append({"name": "form_command", "result": command})
if not command.get("semantic_validation", {}).get("passed"):
raise RuntimeError("Form command edit failed semantic validation.")
button = edit_form_button(
workspace,
form_relative_path,
parent_name=button_parent_name,
name=button_name,
title=button_title,
command_name=command_name,
button_id=None,
button_type="CommandBarButton",
operation=operation,
keep_on_failure=True,
)
steps.append({"name": "form_button", "result": button})
if not button.get("semantic_validation", {}).get("passed"):
raise RuntimeError("Form button edit failed semantic validation.")
semantic = validate_workspace(workspace)
if not semantic.get("passed"):
raise RuntimeError("Final semantic validation failed.")
diff = build_diff(workspace, max_patch_chars=max_patch_chars)
except (Exception, SystemExit) as exc:
error = {"type": type(exc).__name__, "message": str(exc)}
semantic = validate_workspace(workspace)
diff = build_diff(workspace, max_patch_chars=max_patch_chars)
if not keep_on_failure:
restore_snapshot(snapshot)
rolled_back = True
semantic = validate_workspace(workspace)
diff = build_diff(workspace, max_patch_chars=max_patch_chars)
passed = error is None and bool(semantic.get("passed")) and bool(diff.get("passed"))
return {
"schema": "onec_form_button_workflow.v1",
"workspace": str(workspace),
"operation": operation,
"inputs": {
"form_relative_path": form_relative_path,
"bsl_relative_path": bsl_relative_path,
"command_name": command_name,
"command_title": command_title,
"command_action": command_action,
"button_parent_name": button_parent_name,
"button_name": button_name,
"button_title": button_title,
},
"passed": passed,
"rolled_back": rolled_back,
"error": error,
"steps": steps,
"semantic_validation": {
"schema": semantic.get("schema"),
"passed": semantic.get("passed"),
"counts": semantic.get("counts"),
"findings": semantic.get("findings"),
},
"diff_summary": diff.get("counts"),
}
def main() -> int:
parser = argparse.ArgumentParser(description="Atomically add BSL handler, form command, and visible button in a 1C patch workspace.")
parser.add_argument("--workspace", type=Path, required=True)
parser.add_argument("--form-relative-path", required=True)
parser.add_argument("--bsl-relative-path", required=True)
parser.add_argument("--operation", choices=["append", "replace", "upsert"], default="upsert")
parser.add_argument("--routine-text")
parser.add_argument("--routine-text-b64")
parser.add_argument("--routine-file", type=Path)
parser.add_argument("--command-name", required=True)
parser.add_argument("--command-title", required=True)
parser.add_argument("--command-action", required=True)
parser.add_argument("--button-parent-name", required=True)
parser.add_argument("--button-name", required=True)
parser.add_argument("--button-title", required=True)
parser.add_argument("--keep-on-failure", action="store_true", help="Keep partial edits when any step fails.")
parser.add_argument("--max-patch-chars", type=int, default=200000)
parser.add_argument("--output", type=Path)
args = parser.parse_args()
result = build_result(
workspace=args.workspace,
form_relative_path=args.form_relative_path,
bsl_relative_path=args.bsl_relative_path,
operation=args.operation,
routine_text=decode_routine_text(args),
command_name=args.command_name,
command_title=args.command_title,
command_action=args.command_action,
button_parent_name=args.button_parent_name,
button_name=args.button_name,
button_title=args.button_title,
keep_on_failure=args.keep_on_failure,
max_patch_chars=args.max_patch_chars,
)
if args.output:
write_json(args.output, result)
print(
json.dumps(
{
"output": str(args.output) if args.output else None,
"passed": result["passed"],
"rolled_back": result["rolled_back"],
"error": result["error"],
"semantic": result["semantic_validation"]["counts"],
"diff": result["diff_summary"],
},
ensure_ascii=False,
)
)
return 0 if result["passed"] else 2
if __name__ == "__main__":
raise SystemExit(main())