Initial SQL-only 1C adapter baseline
This commit is contained in:
@@ -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())
|
||||
Reference in New Issue
Block a user