Files
life_generators/scripts/check-theme.py
tobiichi3227 e1e17bd2bc Feat(Fortune): Preview result url (#62)
* Feat(Fortune): Support preview result depends on url params

* Feat(Fortune): Specify commit hash in preview result to ensure event consistency

* Feat(Fortune): Add copy preview url button

* Feat(Fortune): Support theme color for copy preview button

* Impr(Fortune): Make UI looks better

* Docs(CONTRIBUTING): Add missing theme field
2025-03-09 15:52:26 +08:00

166 lines
4.8 KiB
Python

#!/bin/python3
import json
import logging
import argparse
import collections
args_parser = argparse.ArgumentParser(description="theme checker")
args_parser.add_argument("path", type=str, help="event json file path")
args = args_parser.parse_args()
errors: dict[int, list[str]] = collections.defaultdict(list)
themes: list[dict[str]] = None
j = None
try:
with open(args.path) as f:
j = json.loads(f.read())
except json.JSONDecodeError:
print(f"`{args.path}` json syntax error.")
exit(-1)
except FileNotFoundError:
print(f"`{args.path}` not found.")
print("Please contact developer to solve this problem.")
exit(-1)
if not isinstance(j, dict):
print(f"`{args.path}` should contain a dict")
exit(-1)
try:
themes = j["themes"]
except KeyError:
print(f"`{args.path}` should contain `themes`")
exit(-1)
if not isinstance(themes, list):
print("`themes` should be a list.")
exit(-1)
def require_field_check(
obj: dict,
theme_idx: int,
fields: list[tuple[str, type]],
required_field: str = "",
) -> bool:
"""
Validates the presence and types of required fields in a given object.
Args:
obj (dict): The object (dictionary) to validate.
theme_idx (int): The index of the fortune for associating validation errors.
fields (list[tuple[str, type]]): A list of tuples where each tuple contains a field name and its expected type.
required_field (str, optional): An optional prefix for error messages to indicate a higher-level required field. Defaults to "".
Returns:
bool: True if all required fields are present and have the correct types, otherwise False.
Validation Rules:
- If a required field is missing, an error message is recorded.
- If a field is present but its type does not match the expected type, an error message is recorded.
- The `required_field` parameter, if provided, is prepended to error messages for context.
"""
error_found = False
for field_name, field_type in fields:
if field_name not in obj:
error_found = True
msg = ""
if required_field != "":
msg = f"`{required_field}` "
msg += f"missing `{field_name}`."
errors[theme_idx].append(msg)
elif not isinstance(obj[field_name], field_type):
error_found = True
errors[theme_idx].append(
f"`{field_name}` should be a `{field_type}` type."
)
if error_found:
return False
return True
theme_names = set()
def check_theme(theme, idx: int):
if not isinstance(theme, dict):
errors[idx].append("theme should be a dict.")
return False
if not require_field_check(theme, idx, [
("name", str),
("properties", dict)
]):
return False
theme_name = theme["name"]
if theme_name in theme_names:
errors[idx].append(f"theme `{theme_name}` already exists.")
if not theme_name:
errors[idx].append("theme name should not be empty.")
properties = theme["properties"]
properties_field_required = [
("bg-color", str),
("good-fortune-color", str),
("bad-fortune-color", str),
("middle-fortune-color", str),
("title-color", str),
("desc-color", str),
("button-color", str),
("button-hover-color", str),
("toggle-theme-button-color", str),
("copy-result-button-color", str),
("copy-preview-result-url-button-color", str),
("date-color", str),
("special-event-color", str),
]
if not require_field_check(properties, idx, properties_field_required):
return False
for field_name in (v[0] for v in properties_field_required):
color: str = properties[field_name]
if color[0] != "#":
errors[idx].append(f"color {color} should starts with `#`.")
continue
color = color[1:]
if any(not ch.isdigit() and not ch.islower() for ch in color):
errors[idx].append(f"color {color} should be all lowercase.")
continue
hex = set("0123456789abcdef")
if any(ch not in hex for ch in color):
errors[idx].append(f"color {color} should be a hex value.")
continue
if len(color) != len("rrggbb") and len(color) != len("rrggbbaa"):
errors[idx].append(f"color {color} should be in `rrggbb` or `rrggbbaa` format.")
continue
theme_names.add(theme_name)
return True
for idx, theme in enumerate(themes):
check_theme(theme, idx)
if errors:
logging.error(args.path)
for idx, error_msgs in errors.items():
logging.error(
json.dumps(
themes[idx], indent=4, ensure_ascii=False
)
)
for msg in error_msgs:
logging.error(msg)
exit(-1)