mirror of
https://github.com/Shu-Ying/main_plugin_farm.git
synced 2026-10-27 20:50:06 +08:00
118 lines
4.1 KiB
Python
118 lines
4.1 KiB
Python
"""Validate the preserved legacy source without starting a bot."""
|
|
|
|
import argparse
|
|
import ast
|
|
from contextlib import closing
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path, PurePosixPath
|
|
import sqlite3
|
|
import sys
|
|
|
|
|
|
def check(root):
|
|
manifest = json.loads(
|
|
(root / "docs/legacy-manifest.json").read_text(encoding="utf-8")
|
|
)
|
|
if manifest.get("schema") != "farm.legacy-baseline.v1":
|
|
raise ValueError("Unsupported baseline manifest schema")
|
|
baseline = (root / manifest["destination"]).resolve()
|
|
if not baseline.is_relative_to(root.resolve()):
|
|
raise ValueError("Baseline destination escapes repository")
|
|
errors = []
|
|
expected = set()
|
|
python_count = 0
|
|
json_count = 0
|
|
for entry in manifest["files"]:
|
|
name = entry["path"]
|
|
relative = PurePosixPath(name)
|
|
if relative.is_absolute() or ".." in relative.parts:
|
|
raise ValueError("Invalid manifest path")
|
|
if name in expected:
|
|
raise ValueError("Duplicate manifest path")
|
|
expected.add(name)
|
|
path = (baseline / name).resolve()
|
|
if not path.is_relative_to(baseline):
|
|
raise ValueError("Manifest path escapes baseline")
|
|
if not path.is_file():
|
|
errors.append({"path": name, "error": "missing file"})
|
|
continue
|
|
content = path.read_bytes()
|
|
digest = hashlib.sha256(content).hexdigest()
|
|
if digest != entry["copied_sha256"]:
|
|
errors.append({"path": name, "error": "SHA-256 mismatch"})
|
|
if len(content) != entry["bytes"]:
|
|
errors.append({"path": name, "error": "size mismatch"})
|
|
if path.suffix == ".py":
|
|
python_count += 1
|
|
try:
|
|
ast.parse(content, filename=name, feature_version=(3, 10))
|
|
except SyntaxError as exc:
|
|
errors.append(
|
|
{"path": name, "error": "syntax", "line": exc.lineno}
|
|
)
|
|
if path.suffix == ".json":
|
|
json_count += 1
|
|
try:
|
|
json.loads(content)
|
|
except (ValueError, UnicodeError):
|
|
errors.append({"path": name, "error": "invalid JSON"})
|
|
actual = {
|
|
path.relative_to(baseline).as_posix()
|
|
for path in baseline.rglob("*")
|
|
if path.is_file()
|
|
and "__pycache__" not in path.parts
|
|
and path.suffix != ".pyc"
|
|
}
|
|
for name in sorted(actual - expected):
|
|
errors.append({"path": name, "error": "unmanifested file"})
|
|
catalogues = []
|
|
for name in ("plant.db", "plant-test.db"):
|
|
path = baseline / "resource/db" / name
|
|
with closing(
|
|
sqlite3.connect(
|
|
path.resolve().as_uri() + "?mode=ro", uri=True
|
|
)
|
|
) as connection:
|
|
integrity = connection.execute("PRAGMA quick_check").fetchone()[0]
|
|
tables = sorted(
|
|
row[0]
|
|
for row in connection.execute(
|
|
"SELECT name FROM sqlite_master WHERE type='table'"
|
|
)
|
|
)
|
|
if integrity != "ok" or tables != ["plant", "sqlite_sequence"]:
|
|
errors.append({"path": name, "error": "unexpected catalogue"})
|
|
catalogues.append(
|
|
{"name": name, "quick_check": integrity, "tables": tables}
|
|
)
|
|
return {
|
|
"status": "passed" if not errors else "failed",
|
|
"files": len(expected),
|
|
"python_syntax_files": python_count,
|
|
"json_files": json_count,
|
|
"catalogues": catalogues,
|
|
"errors": errors,
|
|
"scope": "integrity and syntax only; no host or gameplay verified",
|
|
}
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument(
|
|
"--root",
|
|
type=Path,
|
|
default=Path(__file__).resolve().parents[1],
|
|
)
|
|
args = parser.parse_args()
|
|
try:
|
|
result = check(args.root.resolve())
|
|
except (OSError, ValueError, KeyError, sqlite3.Error) as exc:
|
|
result = {"status": "failed", "error_type": type(exc).__name__}
|
|
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
return 0 if result["status"] == "passed" else 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|