Skip to content

Commit a94444f

Browse files
authored
Create validate-yml.py
1 parent 964e263 commit a94444f

File tree

1 file changed

+107
-0
lines changed

1 file changed

+107
-0
lines changed

scripts/validate-yml.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import yaml
2+
import re
3+
import sys
4+
5+
REQUIRED_FIELDS = {
6+
'name': str,
7+
'repo': str,
8+
'description': str,
9+
'creator': dict,
10+
'latest_release_tag': str,
11+
'screenshots': list,
12+
}
13+
14+
REQUIRED_CREATOR_FIELDS = {
15+
'name': str,
16+
'url': str,
17+
'avatar': str,
18+
}
19+
20+
YOUTUBE_REGEX = re.compile(r'https://img\.youtube\.com/vi/([^/]+)/[^/]+\.jpg')
21+
REPO_REGEX = re.compile(r'^[\w\-]+/[\w\-\.]+$')
22+
23+
def validate_entry(entry, is_plugin=True, index=0):
24+
name = entry.get('name', f'<Unnamed {index}>')
25+
errors = []
26+
27+
for field, field_type in REQUIRED_FIELDS.items():
28+
if field not in entry:
29+
errors.append(f"❌ Missing field `{field}` in entry '{name}'")
30+
elif not isinstance(entry[field], field_type):
31+
errors.append(f"❌ Field `{field}` in entry '{name}' must be {field_type.__name__}")
32+
33+
repo = entry.get('repo')
34+
if repo and not REPO_REGEX.match(repo):
35+
errors.append(f"❌ Invalid `repo` format in '{name}', must be 'owner/repo'")
36+
37+
creator = entry.get('creator', {})
38+
if not isinstance(creator, dict):
39+
errors.append(f"❌ `creator` must be a dictionary in entry '{name}'")
40+
else:
41+
for cfield, ctype in REQUIRED_CREATOR_FIELDS.items():
42+
if cfield not in creator:
43+
errors.append(f"❌ Missing `creator.{cfield}` in entry '{name}'")
44+
elif not isinstance(creator[cfield], ctype):
45+
errors.append(f"❌ `creator.{cfield}` in entry '{name}' must be {ctype.__name__}")
46+
elif isinstance(creator[cfield], str) and not creator[cfield].startswith("https://"):
47+
errors.append(f"❌ `creator.{cfield}` URL must start with https:// in '{name}'")
48+
49+
for ss in entry.get('screenshots', []):
50+
if not isinstance(ss, dict):
51+
errors.append(f"❌ Screenshot must be a dictionary in '{name}'")
52+
continue
53+
if 'url' not in ss or not isinstance(ss['url'], str):
54+
errors.append(f"❌ Missing or invalid `url` in screenshot of '{name}'")
55+
elif not ss['url'].startswith("https://"):
56+
errors.append(f"❌ Screenshot URL must start with https:// in '{name}'")
57+
elif ss['url'].startswith("https://img.youtube.com/") and not YOUTUBE_REGEX.match(ss['url']):
58+
errors.append(f"❌ Invalid YouTube thumbnail format in screenshot of '{name}'")
59+
60+
if 'alt' in ss and not isinstance(ss['alt'], str):
61+
errors.append(f"❌ `alt` must be a string in screenshot of '{name}'")
62+
63+
if 'width' in ss and not isinstance(ss['width'], int):
64+
errors.append(f"❌ `width` must be an integer in screenshot of '{name}'")
65+
66+
if is_plugin:
67+
if 'mc_versions' not in entry:
68+
errors.append(f"❌ Missing `mc_versions` in plugin '{name}'")
69+
elif not isinstance(entry['mc_versions'], str):
70+
errors.append(f"❌ `mc_versions` must be a string in plugin '{name}'")
71+
72+
if 'is_core' in entry and not isinstance(entry['is_core'], bool):
73+
errors.append(f"❌ `is_core` must be a boolean in plugin '{name}'")
74+
75+
return errors
76+
77+
def main():
78+
try:
79+
with open('data/plugins-and-themes.yml', 'r') as f:
80+
data = yaml.safe_load(f)
81+
except Exception as e:
82+
print(f"❌ Failed to load YAML: {e}")
83+
sys.exit(1)
84+
85+
if not isinstance(data, dict):
86+
print("❌ Top-level YAML must be a dictionary with 'plugins' and 'themes'")
87+
sys.exit(1)
88+
89+
all_errors = []
90+
91+
for i, plugin in enumerate(data.get("plugins", [])):
92+
all_errors.extend(validate_entry(plugin, is_plugin=True, index=i))
93+
94+
for i, theme in enumerate(data.get("themes", [])):
95+
all_errors.extend(validate_entry(theme, is_plugin=False, index=i))
96+
97+
if all_errors:
98+
print("\n--- VALIDATION ERRORS ---\n")
99+
for err in all_errors:
100+
print(err)
101+
print(f"\n{len(all_errors)} issue(s) found.")
102+
sys.exit(1)
103+
else:
104+
print("✅ plugins-and-themes.yml validation passed with no issues.")
105+
106+
if __name__ == "__main__":
107+
main()

0 commit comments

Comments
 (0)