diff --git a/scripts/validate_jsons.py b/scripts/validate_jsons.py index 8ef7aa3..dbce1ae 100644 --- a/scripts/validate_jsons.py +++ b/scripts/validate_jsons.py @@ -3,34 +3,175 @@ import common, bme from common import AssetKind import pydantic -#region Assistant Validator +#region Assistant Checker -def _validate_programmable_field(probe: str) -> None: +# TODO: +# If possible, following check should be done. +# They are not done now because they are so complex to implement. +# - The reference to variables and functions in programmable fields. +# - The return type of prorgammable fields. +# - Texture name referred in the programmable field in Face. +# - In instance, passed params to instance is fulfilled. + + +def _try_add(entries: set[str], entry: str) -> bool: + if entry in entries: + return False + else: + entries.add(entry) + return True + + +def _check_programmable_field(probe: str) -> None: + # TODO: + # If possible, allow checking the reference to variables and function, + # to make sure the statement must can be executed. try: ast.parse(probe) except SyntaxError: logging.error(f'String {probe} may not be a valid Python statement which is not suit for programmable field.') -def _validate_showcase_icon(icon_name: str) -> None: +def _check_showcase_icon(icon_name: str) -> None: icon_path = common.get_raw_assets_folder(AssetKind.Icons) / 'bme' / f'{icon_name}.png' if not icon_path.is_file(): - logging.error(f'Icon value {icon_name} may not be valid because it do not existing.') + logging.error(f'Showcase icon value {icon_name} may be invalid.') + #endregion #region Core Validator -def _validate_prototype(prototype: bme.Prototype) -> None: - pass + +def _pre_validate_prototype(prototype: bme.Prototype, identifiers: set[str]) -> None: + identifier = prototype.identifier + + # Show status + logging.info(f'Pre-checking prototype {identifier}') + + # Check identifier and add it. + if not _try_add(identifiers, identifier): + logging.error(f'Identifier {identifier} is already registered.') + + +def _validate_showcase(showcase: bme.Showcase, variables: set[str]) -> None: + # I18N Module Req: + # The title of showcase should not be empty + if len(showcase.title) == 0: + logging.error('The title of showcase should not be empty.') + + # Check icon name + _check_showcase_icon(showcase.icon) + + # Check configuration list. + for cfg in showcase.cfgs: + # Check name + field_name = cfg.field + if not _try_add(variables, field_name): + logging.error(f'Field {field_name} is already registered.') + + # I18N Module Req: + # The title and desc of cfg should not be empty. + # And they are should not be the same string. + if len(cfg.title) == 0: + logging.error('The title of showcase configuration entry should not be empty.') + if len(cfg.desc) == 0: + logging.error('The description of showcase configuration entry should not be empty.') + if cfg.title == cfg.desc: + logging.error('The title of showcase configuration entry and its description should not be same string.') + + # Check programmable field + _check_programmable_field(cfg.default) + + +def _validate_params(params: list[bme.Param], variables: set[str]) -> None: + for param in params: + # Check name + field_name = param.field + if not _try_add(variables, field_name): + logging.error(f'Field {field_name} is already registered.') + + # Check programmable fields + _check_programmable_field(param.data) + + +def _validate_vars(vars: list[bme.Var], variables: set[str]) -> None: + for var in vars: + # Check name + field_name = var.field + if not _try_add(variables, field_name): + logging.error(f'Field {field_name} is already registered.') + + # Check programmable fields + _check_programmable_field(var.data) + + +def _validate_vertices(vertices: list[bme.Vertex]) -> None: + for vertex in vertices: + # Check programmable fields + _check_programmable_field(vertex.skip) + _check_programmable_field(vertex.data) + + +def _validate_faces(faces: list[bme.Face], vertices_count: int) -> None: + for face in faces: + # The index referred in indices should not be exceed the max value of vertices count. + for index in face.indices: + if index >= vertices_count: + logging.error(f'Index {index} is out of vertices range.') + + # The size of uvs list and normals list (if existing) + # should be equal to the size of indices list. + edges = len(face.indices) + if len(face.uvs) != edges: + logging.error(f'The size of UVs list is not matched with indices.') + if face.normals is not None and len(face.normals) != edges: + logging.error(f'The size of Normals list is not matched with indices.') + + # Check programmable fields + _check_programmable_field(face.skip) + _check_programmable_field(face.texture) + for uv in face.uvs: + _check_programmable_field(uv) + if face.normals is not None: + for normal in face.normals: + _check_programmable_field(normal) + + +def _validate_instances(instances: list[bme.Instance], identifiers: set[str]) -> None: + for instance in instances: + # The reference of identifier should be existing. + referred_identifier = instance.identifier + if referred_identifier not in identifiers: + logging.error(f'The identifier {referred_identifier} referred in instance is not existing.') + + # Check programmable fields + _check_programmable_field(instance.skip) + for v in instance.params.values(): + _check_programmable_field(v) + _check_programmable_field(instance.transform) + + +def _validate_prototype(prototype: bme.Prototype, identifiers: set[str]) -> None: + # Show status + logging.info(f'Checking prototype {prototype.identifier}') + + # A set of all variable names registered in this prototypes + variables: set[str] = set() + + # Check fields + if prototype.showcase is not None: + _validate_showcase(prototype.showcase, variables) + _validate_params(prototype.params, variables) + _check_programmable_field(prototype.skip) + _validate_vars(prototype.vars, identifiers) + _validate_vertices(prototype.vertices) + _validate_faces(prototype.faces, len(prototype.vertices)) + _validate_instances(prototype.instances, identifiers) #endregion -# 把提取JSON翻译的要求写入到验证中: -# - Showcase::Cfgs::Title或Desc不能为空。 -# - Showcase::Cfgs::Title和Showcase::Cfgs::Desc不能重复 - def validate_jsons() -> None: raw_jsons_dir = common.get_raw_assets_folder(AssetKind.Jsons) @@ -58,18 +199,16 @@ def validate_jsons() -> None: # Append all prototypes into list prototypes += file_prototypes.root - # Collect identifier and check identifier first. + # Pre-validate first to collect identifier and check identifier first. + # We need collect it first because "instances" field need it to check the validation of identifier. identifiers: set[str] = set() for prototype in prototypes: - identifier = prototype.identifier - if prototype.identifier in identifiers: - logging.error(f'Identifier {identifier} is registered more than once.') - else: - identifiers.add(identifier) + _pre_validate_prototype(prototype, identifiers) # Start custom validation - for protype in prototypes: - _validate_prototype(prototype) + for prototype in prototypes: + _validate_prototype(prototype, identifiers) + if __name__ == '__main__': common.setup_logging()