chore: finish BME JSON validator
This commit is contained in:
		@ -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()
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user