Skip to content

Commit 3281fce

Browse files
authored
Create validate-yml.py
1 parent 334c3c4 commit 3281fce

File tree

1 file changed

+115
-0
lines changed

1 file changed

+115
-0
lines changed

scripts/validate-yml.py

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

0 commit comments

Comments
 (0)