2020import re
2121import sys
2222
23- # ANSI color codes
23+ # ANSI color codes (used sparingly)
2424RED = "\033 [91m"
2525GREEN = "\033 [92m"
2626YELLOW = "\033 [93m"
2727RESET = "\033 [0m"
2828
29- # -----------------------
30- # Required field definitions
31- # -----------------------
32-
3329REQUIRED_FIELDS = {
3430 'name' : str ,
3531 'repo' : str ,
4541 'avatar' : str ,
4642}
4743
48- # -----------------------
49- # Regex patterns for validation
50- # -----------------------
51-
5244REPO_REGEX = re .compile (r'^[\w\-]+/[\w\-\.]+$' )
5345YOUTUBE_REGEX = re .compile (r'https://img\.youtube\.com/vi/([^/]+)/[^/]+\.jpg' )
5446
55- # -----------------------
56- # Screenshot helper
57- # -----------------------
58-
5947def is_valid_screenshot_url (url : str ) -> bool :
6048 return url .startswith ("https://" ) or url .startswith ("./Assets/" )
6149
62- # -----------------------
63- # Entry validation logic
64- # -----------------------
65-
6650def validate_entry (entry , is_plugin = True , index = 0 ):
6751 name = entry .get ('name' , f'<Unnamed { index } >' )
6852 errors = []
6953
7054 for field , field_type in REQUIRED_FIELDS .items ():
7155 if field not in entry :
72- errors .append (f"❌ Missing field `{ field } ` in '{ name } '\n ➤ Example: { field } : { field_type .__name__ } " )
56+ errors .append (
57+ f"{ RED } ❌ Missing field `{ field } ` in plugin '{ name } '\n ➤ Example: { field } : { field_type .__name__ } { RESET } "
58+ )
7359 elif not isinstance (entry [field ], field_type ):
74- example_value = "\" 1.20.1-1.21.4\" " if field == "mc_versions" else field_type .__name__
75- errors .append (f"❌ `{ field } ` must be { field_type .__name__ } in '{ name } '\n ➤ Example: { field } : { example_value } " )
60+ example = "\" 1.20.4-1.21.1\" " if field == "mc_versions" else f"{ field_type .__name__ } "
61+ errors .append (
62+ f"{ RED } ❌ `{ field } ` must be { field_type .__name__ } in plugin '{ name } '\n ➤ Example: { field } : { example } { RESET } "
63+ )
7664
77- # Repo format check
7865 repo = entry .get ('repo' )
7966 if repo and not REPO_REGEX .match (repo ):
80- errors .append (f"❌ `repo` in '{ name } ' must follow format 'owner/repo '\n ➤ Example: repo: \" GarlicRot/AutoBucket\" " )
67+ errors .append (f"{ RED } ❌ `repo` must be in 'owner/repo' format in plugin ' { name } '\n ➤ Example: repo: \" GarlicRot/AutoBucket\" { RESET } " )
8168
82- # Creator block checks
8369 creator = entry .get ('creator' , {})
8470 if not isinstance (creator , dict ):
85- errors .append (f"❌ `creator` must be a dictionary in '{ name } '" )
71+ errors .append (f"{ RED } ❌ `creator` must be a dictionary in plugin '{ name } '{ RESET } " )
8672 else :
87- for cfield , ctype in REQUIRED_CREATOR_FIELDS .items ():
88- if cfield not in creator :
89- errors .append (f"❌ Missing `creator.{ cfield } ` in '{ name } '\n ➤ Example: creator.{ cfield } : \" https://github.com/user.png\" " )
90- elif not isinstance (creator [cfield ], ctype ):
91- errors .append (f"❌ `creator.{ cfield } ` must be { ctype .__name__ } in '{ name } '" )
92- elif cfield in ['url' , 'avatar' ] and not creator [cfield ].startswith ("https://" ):
93- errors .append (f"❌ `creator.{ cfield } ` must start with https:// in '{ name } '" )
94-
95- # Screenshot block checks
96- for ss in entry .get ('screenshots' , []):
97- if not isinstance (ss , dict ):
98- errors .append (f"❌ Each screenshot must be a dictionary in '{ name } '" )
73+ for field , field_type in REQUIRED_CREATOR_FIELDS .items ():
74+ if field not in creator :
75+ errors .append (
76+ f"{ RED } ❌ Missing `creator.{ field } ` in plugin '{ name } '\n ➤ Example: creator.{ field } : \" https://github.com/user.png\" { RESET } "
77+ )
78+ elif not isinstance (creator [field ], field_type ):
79+ errors .append (f"{ RED } ❌ `creator.{ field } ` must be { field_type .__name__ } in plugin '{ name } '{ RESET } " )
80+ elif field in ['url' , 'avatar' ] and not creator [field ].startswith ("https://" ):
81+ errors .append (f"{ RED } ❌ `creator.{ field } ` must start with https:// in plugin '{ name } '{ RESET } " )
82+
83+ for screenshot in entry .get ("screenshots" , []):
84+ if not isinstance (screenshot , dict ):
85+ errors .append (f"{ RED } ❌ Screenshot must be a dictionary in plugin '{ name } '{ RESET } " )
9986 continue
87+ if "url" not in screenshot or not isinstance (screenshot ["url" ], str ):
88+ errors .append (f"{ RED } ❌ Screenshot missing valid `url` in plugin '{ name } '{ RESET } " )
89+ elif not is_valid_screenshot_url (screenshot ["url" ]):
90+ errors .append (f"{ RED } ❌ Screenshot `url` must start with https:// or ./Assets/ in plugin '{ name } '{ RESET } " )
91+ elif screenshot ["url" ].startswith ("https://img.youtube.com/" ) and not YOUTUBE_REGEX .match (screenshot ["url" ]):
92+ errors .append (f"{ RED } ❌ YouTube screenshot URL must match https://img.youtube.com/vi/<id>/hqdefault.jpg in plugin '{ name } '{ RESET } " )
93+ if "alt" in screenshot and not isinstance (screenshot ["alt" ], str ):
94+ errors .append (f"{ RED } ❌ Screenshot `alt` must be a string in plugin '{ name } '{ RESET } " )
95+ if "width" in screenshot and not isinstance (screenshot ["width" ], int ):
96+ errors .append (f"{ RED } ❌ Screenshot `width` must be an integer in plugin '{ name } '{ RESET } " )
10097
101- if 'url' not in ss or not isinstance (ss ['url' ], str ):
102- errors .append (f"❌ Missing or invalid `url` in a screenshot of '{ name } '" )
103- elif not is_valid_screenshot_url (ss ['url' ]):
104- errors .append (f"❌ Screenshot `url` in '{ name } ' must start with https:// or ./Assets/" )
105- elif ss ['url' ].startswith ("https://img.youtube.com/" ) and not YOUTUBE_REGEX .match (ss ['url' ]):
106- errors .append (f"❌ YouTube thumbnail `url` in '{ name } ' must match: https://img.youtube.com/vi/<video-id>/0.jpg" )
107-
108- if 'alt' in ss and not isinstance (ss ['alt' ], str ):
109- errors .append (f"❌ `alt` must be a string in screenshot of '{ name } '" )
110- if 'width' in ss and not isinstance (ss ['width' ], int ):
111- errors .append (f"❌ `width` must be an integer in screenshot of '{ name } '" )
112-
113- # Plugin-only checks
11498 if is_plugin :
115- if ' mc_versions' not in entry :
116- errors .append (f"❌ Missing `mc_versions` in plugin '{ name } '\n ➤ Example: mc_versions: \" 1.20.4-1.21.1\" " )
117- elif not isinstance (entry [' mc_versions' ], str ):
118- errors .append (f"❌ `mc_versions` must be a string in plugin '{ name } '" )
99+ if " mc_versions" not in entry :
100+ errors .append (f"{ RED } ❌ Missing `mc_versions` in plugin '{ name } '\n ➤ Example: mc_versions: \" 1.20.4-1.21.1\" { RESET } " )
101+ elif not isinstance (entry [" mc_versions" ], str ):
102+ errors .append (f"{ RED } ❌ `mc_versions` must be a string in plugin '{ name } '\n ➤ Example: mc_versions: \" 1.20.4-1.21.1 \" { RESET } " )
119103
120- if ' is_core' in entry and not isinstance (entry [' is_core' ], bool ):
121- errors .append (f"❌ `is_core` must be a boolean in plugin '{ name } '" )
104+ if " is_core" in entry and not isinstance (entry [" is_core" ], bool ):
105+ errors .append (f"{ RED } ❌ `is_core` must be a boolean in plugin '{ name } '{ RESET } " )
122106
123107 return errors
124108
125- # -----------------------
126- # Entrypoint
127- # -----------------------
128-
129109def main ():
130110 try :
131- with open (' data/plugins-and-themes.yml' , 'r' ) as f :
111+ with open (" data/plugins-and-themes.yml" , "r" ) as f :
132112 data = yaml .safe_load (f )
133113 except Exception as e :
134114 print (f"{ RED } ❌ Failed to load YAML: { e } { RESET } " )
@@ -147,9 +127,9 @@ def main():
147127 all_errors .extend (validate_entry (theme , is_plugin = False , index = i ))
148128
149129 if all_errors :
150- print (f"{ YELLOW } \n --- VALIDATION ERRORS ---{ RESET } \n " )
151- for err in all_errors :
152- print (err )
130+ print (f"\n { YELLOW } --- VALIDATION ERRORS ---{ RESET } \n " )
131+ for error in all_errors :
132+ print (error )
153133 print (f"\n { RED } ❌ { len (all_errors )} issue(s) found. Fix the above problems to continue.{ RESET } " )
154134 sys .exit (1 )
155135 else :
0 commit comments