Initial SQL-only 1C adapter baseline
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Diff original/working files in a 1C patch workspace."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import difflib
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from check_1c_patch_workspace_integrity import check_workspace
|
||||
|
||||
|
||||
def load_json(path: Path) -> dict[str, Any]:
|
||||
return json.loads(path.read_text(encoding="utf-8-sig"))
|
||||
|
||||
|
||||
def sha256_file(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as stream:
|
||||
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def read_text_lines(path: Path) -> list[str]:
|
||||
try:
|
||||
return path.read_text(encoding="utf-8-sig").splitlines(keepends=True)
|
||||
except UnicodeDecodeError:
|
||||
return path.read_text(encoding="cp1251", errors="replace").splitlines(keepends=True)
|
||||
|
||||
|
||||
def count_diff_lines(diff_lines: list[str]) -> dict[str, int]:
|
||||
added = 0
|
||||
removed = 0
|
||||
hunks = 0
|
||||
for line in diff_lines:
|
||||
if line.startswith("@@"):
|
||||
hunks += 1
|
||||
elif line.startswith("+") and not line.startswith("+++"):
|
||||
added += 1
|
||||
elif line.startswith("-") and not line.startswith("---"):
|
||||
removed += 1
|
||||
return {"added_lines": added, "removed_lines": removed, "hunks": hunks}
|
||||
|
||||
|
||||
def diff_file(record: dict[str, Any], *, workspace: Path, max_patch_chars: int) -> dict[str, Any]:
|
||||
rel = Path(str(record.get("relative_path") or ""))
|
||||
original = workspace / "original" / rel
|
||||
working = workspace / "working" / rel
|
||||
result = {
|
||||
"relative_path": str(rel).replace("\\", "/"),
|
||||
"kind": record.get("kind"),
|
||||
"name": record.get("name"),
|
||||
"origin": record.get("origin"),
|
||||
"original_path": str(original),
|
||||
"working_path": str(working),
|
||||
"exists": {"original": original.exists(), "working": working.exists()},
|
||||
}
|
||||
if not original.exists() or not working.exists():
|
||||
result["status"] = "missing_file"
|
||||
return result
|
||||
original_hash = sha256_file(original)
|
||||
working_hash = sha256_file(working)
|
||||
result["sha256"] = {"original": original_hash, "working": working_hash}
|
||||
if original_hash == working_hash:
|
||||
result["status"] = "unchanged"
|
||||
result["diff"] = {"added_lines": 0, "removed_lines": 0, "hunks": 0, "patch": ""}
|
||||
return result
|
||||
old_lines = read_text_lines(original)
|
||||
new_lines = read_text_lines(working)
|
||||
diff_lines = list(
|
||||
difflib.unified_diff(
|
||||
old_lines,
|
||||
new_lines,
|
||||
fromfile=f"original/{result['relative_path']}",
|
||||
tofile=f"working/{result['relative_path']}",
|
||||
lineterm="",
|
||||
)
|
||||
)
|
||||
patch = "\n".join(line.rstrip("\n") for line in diff_lines)
|
||||
counts = count_diff_lines(diff_lines)
|
||||
result["status"] = "modified"
|
||||
result["diff"] = {
|
||||
**counts,
|
||||
"patch": patch[:max_patch_chars],
|
||||
"patch_truncated": len(patch) > max_patch_chars,
|
||||
"patch_chars": len(patch),
|
||||
}
|
||||
return result
|
||||
|
||||
|
||||
def build_diff(workspace: Path, *, max_patch_chars: int) -> dict[str, Any]:
|
||||
integrity = check_workspace(workspace)
|
||||
if not integrity.get("passed"):
|
||||
return {
|
||||
"schema": "onec_patch_workspace_diff.v1",
|
||||
"workspace": str(workspace),
|
||||
"passed": False,
|
||||
"integrity": integrity,
|
||||
"files": [],
|
||||
"counts": {
|
||||
"files": 0,
|
||||
"modified": 0,
|
||||
"unchanged": 0,
|
||||
"missing": 0,
|
||||
"added_lines": 0,
|
||||
"removed_lines": 0,
|
||||
"hunks": 0,
|
||||
},
|
||||
}
|
||||
manifest_path = workspace / "manifest.json"
|
||||
manifest = load_json(manifest_path)
|
||||
files = [diff_file(record, workspace=workspace, max_patch_chars=max_patch_chars) for record in manifest.get("files") or []]
|
||||
modified = [item for item in files if item.get("status") == "modified"]
|
||||
missing = [item for item in files if item.get("status") == "missing_file"]
|
||||
return {
|
||||
"schema": "onec_patch_workspace_diff.v1",
|
||||
"workspace": str(workspace),
|
||||
"manifest": str(manifest_path),
|
||||
"task": manifest.get("task"),
|
||||
"preferred_extension": manifest.get("preferred_extension"),
|
||||
"integrity": {
|
||||
"schema": integrity.get("schema"),
|
||||
"passed": integrity.get("passed"),
|
||||
"counts": integrity.get("counts"),
|
||||
},
|
||||
"files": files,
|
||||
"counts": {
|
||||
"files": len(files),
|
||||
"modified": len(modified),
|
||||
"unchanged": len([item for item in files if item.get("status") == "unchanged"]),
|
||||
"missing": len(missing),
|
||||
"added_lines": sum((item.get("diff") or {}).get("added_lines", 0) for item in modified),
|
||||
"removed_lines": sum((item.get("diff") or {}).get("removed_lines", 0) for item in modified),
|
||||
"hunks": sum((item.get("diff") or {}).get("hunks", 0) for item in modified),
|
||||
},
|
||||
"passed": not missing,
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Diff 1C patch workspace original/working files.")
|
||||
parser.add_argument("--workspace", type=Path, required=True)
|
||||
parser.add_argument("--max-patch-chars", type=int, default=200000)
|
||||
parser.add_argument("--output", type=Path)
|
||||
args = parser.parse_args()
|
||||
|
||||
result = build_diff(args.workspace, max_patch_chars=args.max_patch_chars)
|
||||
output = 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(output, encoding="utf-8")
|
||||
print(json.dumps({"output": str(args.output) if args.output else None, "passed": result["passed"], "counts": result["counts"]}, ensure_ascii=False))
|
||||
return 0 if result["passed"] else 2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user