-
-
Notifications
You must be signed in to change notification settings - Fork 33
Expand file tree
/
Copy pathvalidate_scripts.py
More file actions
116 lines (92 loc) · 3.65 KB
/
Copy pathvalidate_scripts.py
File metadata and controls
116 lines (92 loc) · 3.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#!/usr/bin/env python3
"""
Validate scripts.json against the expected schema.
Exit 0 = valid or warnings only, Exit 1 = errors found.
"""
import argparse
import json
import sys
from pathlib import Path
REQUIRED_FIELDS = ["name", "category", "description", "infourl", "tags"]
STRING_FIELDS = ["name", "category", "description", "infourl"]
def load_and_parse(path: Path) -> list:
if not path.exists():
print(f"Error: {path} not found", file=sys.stderr)
sys.exit(1)
try:
with open(path, encoding="utf-8") as f:
data = json.load(f)
except OSError as e:
print(f"Error: unable to read {path}: {e}", file=sys.stderr)
sys.exit(1)
except json.JSONDecodeError as e:
print(f"Error: invalid JSON in {path}: {e}", file=sys.stderr)
sys.exit(1)
if not isinstance(data, list):
print(f"Error: {path} must be a JSON array", file=sys.stderr)
sys.exit(1)
return data
def validate(data: list) -> tuple[list[str], list[str]]:
errors = []
warnings = []
seen_names: dict[str, int] = {}
for i, entry in enumerate(data):
prefix = f"Entry {i}"
if not isinstance(entry, dict):
errors.append(f"{prefix}: not an object")
continue
for field in REQUIRED_FIELDS:
if field not in entry:
errors.append(f"{prefix}: missing required field '{field}'")
for field in STRING_FIELDS:
if field in entry:
if not isinstance(entry[field], str) or not entry[field].strip():
errors.append(f"{prefix}: '{field}' must be a non-empty string")
infourl = entry.get("infourl")
if isinstance(infourl, str) and infourl.strip():
if not (infourl.startswith("http://") or infourl.startswith("https://")):
errors.append(f"{prefix}: 'infourl' must start with http:// or https://")
if "images" not in entry:
warnings.append(f"{prefix}: 'images' should be a non-empty list")
elif not isinstance(entry["images"], list):
errors.append(f"{prefix}: 'images' must be a list")
elif len(entry["images"]) == 0:
warnings.append(f"{prefix}: 'images' should be a non-empty list")
if "tags" in entry and (
not isinstance(entry["tags"], list) or len(entry["tags"]) == 0
):
errors.append(f"{prefix}: 'tags' must be a non-empty list")
name = entry.get("name")
if isinstance(name, str) and name.strip():
key = name.strip().lower()
if key in seen_names:
errors.append(
f"{prefix}: duplicate name '{name}'"
f" (also at entry {seen_names[key]})"
)
else:
seen_names[key] = i
return errors, warnings
def main() -> None:
parser = argparse.ArgumentParser(description="Validate scripts.json schema")
parser.add_argument(
"--scripts-json",
default="scripts.json",
type=Path,
help="Path to scripts.json (default: scripts.json)",
)
args = parser.parse_args()
data = load_and_parse(args.scripts_json)
errors, warnings = validate(data)
if warnings:
print(f"Found {len(warnings)} warning(s) in {args.scripts_json}:", file=sys.stderr)
for warning in warnings:
print(f" - {warning}", file=sys.stderr)
if errors:
print(f"Found {len(errors)} error(s) in {args.scripts_json}:")
for error in errors:
print(f" - {error}")
sys.exit(1)
print(f"{args.scripts_json} is valid ({len(data)} entries).")
if __name__ == "__main__":
main()