chore: finish icons builder
- finish icons builder in scripts.
This commit is contained in:
		@ -1,3 +1,77 @@
 | 
			
		||||
import enum
 | 
			
		||||
from typing import Optional, Self
 | 
			
		||||
from typing import Optional
 | 
			
		||||
from pydantic import BaseModel, RootModel, Field, model_validator, ValidationError
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ShowcaseType(enum.StrEnum):
 | 
			
		||||
    Nothing = 'none'
 | 
			
		||||
    Floor = 'floor'
 | 
			
		||||
    Rail = 'Rail'
 | 
			
		||||
    Wood = 'wood'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ShowcaseCfgType(enum.StrEnum):
 | 
			
		||||
    Float = 'float'
 | 
			
		||||
    Int = 'int'
 | 
			
		||||
    Bool = 'bool'
 | 
			
		||||
    Face = 'face'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ShowcaseCfg(BaseModel):
 | 
			
		||||
    field: str = Field(frozen=True, strict=True)
 | 
			
		||||
    type: ShowcaseCfgType = Field(frozen=True)
 | 
			
		||||
    title: str = Field(frozen=True, strict=True)
 | 
			
		||||
    desc: str = Field(frozen=True, strict=True)
 | 
			
		||||
    default: str = Field(frozen=True, strict=True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Showcase(BaseModel):
 | 
			
		||||
    title: str = Field(frozen=True, strict=True)
 | 
			
		||||
    icon: str = Field(frozen=True, strict=True)
 | 
			
		||||
    type: ShowcaseType = Field(frozen=True)
 | 
			
		||||
    cfgs: list[ShowcaseCfg] = Field(frozen=True, strict=True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Param(BaseModel):
 | 
			
		||||
    field: str = Field(frozen=True, strict=True)
 | 
			
		||||
    data: str = Field(frozen=True, strict=True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Var(BaseModel):
 | 
			
		||||
    field: str = Field(frozen=True, strict=True)
 | 
			
		||||
    data: str = Field(frozen=True, strict=True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Vertex(BaseModel):
 | 
			
		||||
    skip: str = Field(frozen=True, strict=True)
 | 
			
		||||
    data: str = Field(frozen=True, strict=True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Face(BaseModel):
 | 
			
		||||
    skip: str = Field(frozen=True, strict=True)
 | 
			
		||||
    texture: str = Field(frozen=True, strict=True)
 | 
			
		||||
    indices: list[int] = Field(frozen=True, strict=True)
 | 
			
		||||
    uvs: list[str] = Field(frozen=True, strict=True)
 | 
			
		||||
    normals: Optional[list[str]] = Field(frozen=True, strict=True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Instance(BaseModel):
 | 
			
		||||
    identifier: str = Field(frozen=True, strict=True)
 | 
			
		||||
    skip: str = Field(frozen=True, strict=True)
 | 
			
		||||
    params: dict[str, str] = Field(frozen=True, strict=True)
 | 
			
		||||
    transform: str = Field(frozen=True, strict=True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Prototype(BaseModel):
 | 
			
		||||
    identifier: str = Field(frozen=True, strict=True)
 | 
			
		||||
    showcase: Optional[Showcase] = Field(frozen=True, strict=True)
 | 
			
		||||
    params: list[Param] = Field(frozen=True, strict=True)
 | 
			
		||||
    skip: str = Field(frozen=True, strict=True)
 | 
			
		||||
    vars: list[Var] = Field(frozen=True, strict=True)
 | 
			
		||||
    vertices: list[Vertex] = Field(frozen=True, strict=True)
 | 
			
		||||
    faces: list[Face] = Field(frozen=True, strict=True)
 | 
			
		||||
    instances: list[Instance] = Field(frozen=True, strict=True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Prototypes(RootModel):
 | 
			
		||||
    root: list[Prototype] = Field(frozen=True, strict=True)
 | 
			
		||||
 | 
			
		||||
@ -1,51 +1,53 @@
 | 
			
		||||
import logging
 | 
			
		||||
import logging, os
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
import common
 | 
			
		||||
from common import AssetKind
 | 
			
		||||
import PIL, PIL.Image
 | 
			
		||||
 | 
			
		||||
# the config for thumbnail
 | 
			
		||||
# The HW size of thumbnail
 | 
			
		||||
THUMBNAIL_SIZE: int = 16
 | 
			
		||||
 | 
			
		||||
class ThumbnailBuilder():
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        pass
 | 
			
		||||
def _create_thumbnail(src_file: Path, dst_file: Path) -> None:
 | 
			
		||||
    # open image
 | 
			
		||||
    src_image: PIL.Image.Image = PIL.Image.open(src_file)
 | 
			
		||||
    # create thumbnail
 | 
			
		||||
    src_image.thumbnail((THUMBNAIL_SIZE, THUMBNAIL_SIZE))
 | 
			
		||||
    # save to new file
 | 
			
		||||
    src_image.save(dst_file)
 | 
			
		||||
 | 
			
		||||
    def build_thumbnails(self) -> None:
 | 
			
		||||
        # get folder path
 | 
			
		||||
        root_folder = common.get_root_folder()
 | 
			
		||||
 | 
			
		||||
        # prepare handler
 | 
			
		||||
        def folder_handler(rel_name: str, src_folder: Path, dst_folder: Path) -> None:
 | 
			
		||||
            # just create folder
 | 
			
		||||
            logging.info(f'Creating Folder: {src_folder} -> {dst_folder}')
 | 
			
		||||
            dst_folder.mkdir(parents=False, exist_ok=True)
 | 
			
		||||
        def file_handler(rel_name: str, src_file: Path, dst_file: Path) -> None:
 | 
			
		||||
            # skip non-image
 | 
			
		||||
            if src_file.suffix != '.png': return
 | 
			
		||||
            # call thumbnail func
 | 
			
		||||
            logging.info(f'Building Thumbnail: {src_file} -> {dst_file}')
 | 
			
		||||
            self.__resize_image(src_file, dst_file)
 | 
			
		||||
def build_icons() -> None:
 | 
			
		||||
    raw_icons_dir = common.get_raw_assets_folder(AssetKind.Icons)
 | 
			
		||||
    plg_icons_dir = common.get_plugin_assets_folder(AssetKind.Icons)
 | 
			
		||||
 | 
			
		||||
        # call common processor
 | 
			
		||||
        common.common_file_migrator(
 | 
			
		||||
            root_folder / 'raw_icons',
 | 
			
		||||
            root_folder / 'icons',
 | 
			
		||||
            folder_handler,
 | 
			
		||||
            file_handler
 | 
			
		||||
        )
 | 
			
		||||
    # TODO: If we have Python 3.12, use Path.walk instead of current polyfill.
 | 
			
		||||
 | 
			
		||||
        logging.info('Building thumbnail done.')
 | 
			
		||||
    # Icon assets has subdirectory, so we need use another way to process.
 | 
			
		||||
    for root, dirs, files in os.walk(raw_icons_dir):
 | 
			
		||||
        root = Path(root)
 | 
			
		||||
 | 
			
		||||
        # Iterate folders
 | 
			
		||||
        for name in dirs:
 | 
			
		||||
            # Fetch directory path
 | 
			
		||||
            raw_icon_subdir = root / name
 | 
			
		||||
            plg_icon_subdir = plg_icons_dir / raw_icon_subdir.relative_to(raw_icons_dir)
 | 
			
		||||
            # Show message
 | 
			
		||||
            logging.info(f'Creating Folder: {raw_icon_subdir} -> {plg_icon_subdir}')
 | 
			
		||||
            # Create directory
 | 
			
		||||
            plg_icon_subdir.mkdir(parents=True, exist_ok=True)
 | 
			
		||||
 | 
			
		||||
        # Iterate files
 | 
			
		||||
        for name in files:
 | 
			
		||||
            # Fetch file path
 | 
			
		||||
            raw_icon_file = root / name
 | 
			
		||||
            plg_icon_file = plg_icons_dir / raw_icon_file.relative_to(raw_icons_dir)
 | 
			
		||||
            # Show message
 | 
			
		||||
            logging.info(f'Building Thumbnail: {raw_icon_file} -> {plg_icon_file}')
 | 
			
		||||
            # Create thumbnail
 | 
			
		||||
            _create_thumbnail(raw_icon_file, plg_icon_file)
 | 
			
		||||
 | 
			
		||||
    def __resize_image(self, src_file: Path, dst_file: Path) -> None:
 | 
			
		||||
        # open image
 | 
			
		||||
        src_image: PIL.Image.Image = PIL.Image.open(src_file)
 | 
			
		||||
        # create thumbnail
 | 
			
		||||
        src_image.thumbnail((THUMBNAIL_SIZE, THUMBNAIL_SIZE))
 | 
			
		||||
        # save to new file
 | 
			
		||||
        src_image.save(dst_file)
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    common.setup_logging()
 | 
			
		||||
    thumbnail_builder = ThumbnailBuilder()
 | 
			
		||||
    thumbnail_builder.build_thumbnails()
 | 
			
		||||
    build_icons()
 | 
			
		||||
 | 
			
		||||
@ -35,69 +35,6 @@ def get_plugin_assets_folder(kind: AssetKind) -> Path:
 | 
			
		||||
    return get_root_folder() / 'bbp_ng' / str(kind)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# def relative_to_folder(abs_path: Path, src_parent: Path, dst_parent: Path) -> Path:
 | 
			
		||||
#     """
 | 
			
		||||
#     Rebase one path to another path.
 | 
			
		||||
 | 
			
		||||
#     Give a absolute file path and folder path, and compute the relative path of given file to given folder.
 | 
			
		||||
#     Then applied the computed relative path to another given folder path.
 | 
			
		||||
#     Thus it seems like the file was rebased to from a folder to another folder with keeping the folder hierarchy.
 | 
			
		||||
 | 
			
		||||
#     For example, given `/path/to/file` and `/path`, it will compute relative path `to/file`.
 | 
			
		||||
#     Then it was applied to another folder path `/new` and got `/new/to/file`.
 | 
			
		||||
 | 
			
		||||
#     :param abs_path: The absolute path to a folder or file.
 | 
			
		||||
#     :param src_parent: The absolute path to folder which the `abs_path` will have relative path to.
 | 
			
		||||
#     :param dst_parent: The absolute path to folder which the relative path will be applied to.
 | 
			
		||||
#     """
 | 
			
		||||
#     return dst_parent / (abs_path.relative_to(src_parent))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# def common_file_migrator(from_folder: Path, to_folder: Path, fct_proc_folder: typing.Callable[[str, Path, Path], None],
 | 
			
		||||
#                          fct_proc_file: typing.Callable[[str, Path, Path], None]) -> None:
 | 
			
		||||
#     """
 | 
			
		||||
#     Common file migrator used by some build script.
 | 
			
		||||
 | 
			
		||||
#     This function receive 2 absolute folder path. `from_folder` indicate the file migrated out,
 | 
			
		||||
#     and `to_folder` indicate the file migrated in.
 | 
			
		||||
#     `fct_proc_folder` is a function pointer from caller which handle folder migration in detail.
 | 
			
		||||
#     `fct_proc_file` is same but handle file migration.
 | 
			
		||||
 | 
			
		||||
#     `fct_proc_folder` will receive 3 args.
 | 
			
		||||
#     First is the name of this folder which can be shown for end user.
 | 
			
		||||
#     Second is the source folder and third is expected dest folder.
 | 
			
		||||
#     `fct_proc_file` is same, but receive the file path instead.
 | 
			
		||||
#     Both of these function pointer should do the migration in detail. This function will only just iterate
 | 
			
		||||
#     folder and give essential args and will not do any migration operations such as copying or moving.
 | 
			
		||||
 | 
			
		||||
#     :param from_folder: The folder need to be migrated.
 | 
			
		||||
#     :param to_folder: The folder will be migrated to.
 | 
			
		||||
#     :param fct_proc_folder: Folder migration detail handler.
 | 
			
		||||
#     :param fct_proc_file: File migration detail handler.
 | 
			
		||||
#     """
 | 
			
		||||
#     # TODO: If we have Python 3.12, use Path.walk instead of current polyfill.
 | 
			
		||||
 | 
			
		||||
#     # iterate from_folder folder
 | 
			
		||||
#     for root, dirs, files in os.walk(from_folder, topdown=True):
 | 
			
		||||
#         root = Path(root)
 | 
			
		||||
 | 
			
		||||
#         # iterate folders
 | 
			
		||||
#         for name in dirs:
 | 
			
		||||
#             # prepare handler args
 | 
			
		||||
#             src_folder = root / name
 | 
			
		||||
#             dst_folder = relative_to_folder(src_folder, from_folder, to_folder)
 | 
			
		||||
#             # call handler
 | 
			
		||||
#             fct_proc_folder(name, src_folder, dst_folder)
 | 
			
		||||
 | 
			
		||||
#         # iterate files
 | 
			
		||||
#         for name in files:
 | 
			
		||||
#             # prepare handler args
 | 
			
		||||
#             src_file = root / name
 | 
			
		||||
#             dst_file = relative_to_folder(src_file, from_folder, to_folder)
 | 
			
		||||
#             # call handler
 | 
			
		||||
#             fct_proc_file(name, src_file, dst_file)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def setup_logging() -> None:
 | 
			
		||||
    """
 | 
			
		||||
    Setup uniform style for logging module.
 | 
			
		||||
 | 
			
		||||
@ -1,174 +1,70 @@
 | 
			
		||||
import enum
 | 
			
		||||
import json
 | 
			
		||||
import logging
 | 
			
		||||
import ast
 | 
			
		||||
from typing import Optional, Self
 | 
			
		||||
from pydantic import BaseModel, RootModel, Field, model_validator, ValidationError
 | 
			
		||||
import common
 | 
			
		||||
import json, logging, ast, typing
 | 
			
		||||
import pydantic
 | 
			
		||||
import common, bme
 | 
			
		||||
from common import AssetKind
 | 
			
		||||
 | 
			
		||||
#region Assistant Validator
 | 
			
		||||
 | 
			
		||||
def validate_programmable_str(probe: str) -> None:
 | 
			
		||||
def _validate_programmable_field(probe: str) -> None:
 | 
			
		||||
    try:
 | 
			
		||||
        ast.parse(probe)
 | 
			
		||||
    except SyntaxError:
 | 
			
		||||
        raise ValueError(
 | 
			
		||||
            f'String {probe} may not be a valid Python statement which is not suit for programmable field.')
 | 
			
		||||
        logging.error(f'String {probe} may not be a valid Python statement which is not suit for programmable field.')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ShowcaseType(enum.StrEnum):
 | 
			
		||||
    Nothing = 'none'
 | 
			
		||||
    Floor = 'floor'
 | 
			
		||||
    Rail = 'Rail'
 | 
			
		||||
    Wood = 'wood'
 | 
			
		||||
def _validate_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.')
 | 
			
		||||
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
#region Core Validator
 | 
			
		||||
 | 
			
		||||
def _validate_prototype(prototype: bme.Prototype) -> None:
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ShowcaseCfgType(enum.StrEnum):
 | 
			
		||||
    Float = 'float'
 | 
			
		||||
    Int = 'int'
 | 
			
		||||
    Bool = 'bool'
 | 
			
		||||
    Face = 'face'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ShowcaseCfg(BaseModel):
 | 
			
		||||
    field: str = Field(frozen=True, strict=True)
 | 
			
		||||
    type: ShowcaseCfgType = Field(frozen=True)
 | 
			
		||||
    title: str = Field(frozen=True, strict=True)
 | 
			
		||||
    desc: str = Field(frozen=True, strict=True)
 | 
			
		||||
    default: str = Field(frozen=True, strict=True)
 | 
			
		||||
 | 
			
		||||
    @model_validator(mode='after')
 | 
			
		||||
    def verify_prog_field(self) -> Self:
 | 
			
		||||
        validate_programmable_str(self.default)
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Showcase(BaseModel):
 | 
			
		||||
    title: str = Field(frozen=True, strict=True)
 | 
			
		||||
    icon: str = Field(frozen=True, strict=True)
 | 
			
		||||
    type: ShowcaseType = Field(frozen=True)
 | 
			
		||||
    cfgs: list[ShowcaseCfg] = Field(frozen=True, strict=True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Param(BaseModel):
 | 
			
		||||
    field: str = Field(frozen=True, strict=True)
 | 
			
		||||
    data: str = Field(frozen=True, strict=True)
 | 
			
		||||
 | 
			
		||||
    @model_validator(mode='after')
 | 
			
		||||
    def verify_prog_field(self) -> Self:
 | 
			
		||||
        validate_programmable_str(self.data)
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Var(BaseModel):
 | 
			
		||||
    field: str = Field(frozen=True, strict=True)
 | 
			
		||||
    data: str = Field(frozen=True, strict=True)
 | 
			
		||||
 | 
			
		||||
    @model_validator(mode='after')
 | 
			
		||||
    def verify_prog_field(self) -> Self:
 | 
			
		||||
        validate_programmable_str(self.data)
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Vertex(BaseModel):
 | 
			
		||||
    skip: str = Field(frozen=True, strict=True)
 | 
			
		||||
    data: str = Field(frozen=True, strict=True)
 | 
			
		||||
 | 
			
		||||
    @model_validator(mode='after')
 | 
			
		||||
    def verify_prog_field(self) -> Self:
 | 
			
		||||
        validate_programmable_str(self.skip)
 | 
			
		||||
        validate_programmable_str(self.data)
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Face(BaseModel):
 | 
			
		||||
    skip: str = Field(frozen=True, strict=True)
 | 
			
		||||
    texture: str = Field(frozen=True, strict=True)
 | 
			
		||||
    indices: list[int] = Field(frozen=True, strict=True)
 | 
			
		||||
    uvs: list[str] = Field(frozen=True, strict=True)
 | 
			
		||||
    normals: Optional[list[str]] = Field(frozen=True, strict=True)
 | 
			
		||||
 | 
			
		||||
    @model_validator(mode='after')
 | 
			
		||||
    def verify_count(self) -> Self:
 | 
			
		||||
        expected_count = len(self.indices)
 | 
			
		||||
        if len(self.uvs) != expected_count:
 | 
			
		||||
            raise ValueError('The length of uv array is not matched with indices.')
 | 
			
		||||
        if (self.normals is not None) and (len(self.normals) != expected_count):
 | 
			
		||||
            raise ValueError('The length of normal array is not matched with indices.')
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    @model_validator(mode='after')
 | 
			
		||||
    def verify_prog_field(self) -> Self:
 | 
			
		||||
        validate_programmable_str(self.skip)
 | 
			
		||||
        validate_programmable_str(self.texture)
 | 
			
		||||
        for i in self.uvs:
 | 
			
		||||
            validate_programmable_str(i)
 | 
			
		||||
        if self.normals is not None:
 | 
			
		||||
            for i in self.normals:
 | 
			
		||||
                validate_programmable_str(i)
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Instance(BaseModel):
 | 
			
		||||
    identifier: str = Field(frozen=True, strict=True)
 | 
			
		||||
    skip: str = Field(frozen=True, strict=True)
 | 
			
		||||
    params: dict[str, str] = Field(frozen=True, strict=True)
 | 
			
		||||
    transform: str = Field(frozen=True, strict=True)
 | 
			
		||||
 | 
			
		||||
    @model_validator(mode='after')
 | 
			
		||||
    def verify_prog_field(self) -> Self:
 | 
			
		||||
        validate_programmable_str(self.skip)
 | 
			
		||||
        for v in self.params.values():
 | 
			
		||||
            validate_programmable_str(v)
 | 
			
		||||
        validate_programmable_str(self.transform)
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
IDENTIFIERS: set[str] = set()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Prototype(BaseModel):
 | 
			
		||||
    identifier: str = Field(frozen=True, strict=True)
 | 
			
		||||
    showcase: Optional[Showcase] = Field(frozen=True, strict=True)
 | 
			
		||||
    params: list[Param] = Field(frozen=True, strict=True)
 | 
			
		||||
    skip: str = Field(frozen=True, strict=True)
 | 
			
		||||
    vars: list[Var] = Field(frozen=True, strict=True)
 | 
			
		||||
    vertices: list[Vertex] = Field(frozen=True, strict=True)
 | 
			
		||||
    faces: list[Face] = Field(frozen=True, strict=True)
 | 
			
		||||
    instances: list[Instance] = Field(frozen=True, strict=True)
 | 
			
		||||
 | 
			
		||||
    @model_validator(mode='after')
 | 
			
		||||
    def verify_identifier(self) -> Self:
 | 
			
		||||
        global IDENTIFIERS
 | 
			
		||||
        if self.identifier in IDENTIFIERS:
 | 
			
		||||
            raise ValueError(f'Identifier {self.identifier} is already registered.')
 | 
			
		||||
        else:
 | 
			
		||||
            IDENTIFIERS.add(self.identifier)
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    @model_validator(mode='after')
 | 
			
		||||
    def verify_prog_field(self) -> Self:
 | 
			
		||||
        validate_programmable_str(self.skip)
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Prototypes(RootModel):
 | 
			
		||||
    root: list[Prototype] = Field(frozen=True, strict=True)
 | 
			
		||||
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
def validate_json() -> None:
 | 
			
		||||
    raw_json_folder = common.get_root_folder() / 'raw_jsons'
 | 
			
		||||
    raw_jsons_dir = common.get_raw_assets_folder(AssetKind.Jsons)
 | 
			
		||||
 | 
			
		||||
    for json_file in raw_json_folder.rglob('*.json'):
 | 
			
		||||
        logging.info(f'Validating {json_file} ...')
 | 
			
		||||
    # Load all prototypes and check their basic format
 | 
			
		||||
    prototypes: list[bme.Prototype] = []
 | 
			
		||||
    for raw_json_file in raw_jsons_dir.glob('*.json'):
 | 
			
		||||
        # Skip non-file
 | 
			
		||||
        if not raw_json_file.is_file():
 | 
			
		||||
            continue
 | 
			
		||||
 | 
			
		||||
        # Show info
 | 
			
		||||
        logging.info(f'Loading {raw_json_file}')
 | 
			
		||||
 | 
			
		||||
        # Load prototypes
 | 
			
		||||
        try:
 | 
			
		||||
            with open(json_file, 'r', encoding='utf-8') as f:
 | 
			
		||||
            with open(raw_json_file, 'r', encoding='utf-8') as f:
 | 
			
		||||
                docuement = json.load(f)
 | 
			
		||||
                Prototypes.model_validate(docuement)
 | 
			
		||||
                file_prototypes = bme.Prototypes.model_validate(docuement)
 | 
			
		||||
        except json.JSONDecodeError as e:
 | 
			
		||||
            logging.error(f'Can not load file {json_file}. It may not a valid JSON file. Reason: {e}')
 | 
			
		||||
        except ValidationError as e:
 | 
			
		||||
            logging.error(f'File {json_file} is not correct. Reason: {e}')
 | 
			
		||||
            logging.error(f'File {raw_json_file} is not a valid JSON file. Reason: {e}')
 | 
			
		||||
        except pydantic.ValidationError as e:
 | 
			
		||||
            logging.error(f'JSON file {raw_json_file} lose essential fields. Detail: {e}')
 | 
			
		||||
 | 
			
		||||
        # Append all prototypes into list
 | 
			
		||||
        prototypes += file_prototypes.root
 | 
			
		||||
 | 
			
		||||
    # Collect identifier and check identifier first.
 | 
			
		||||
    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)
 | 
			
		||||
 | 
			
		||||
    # Start custom validation
 | 
			
		||||
    for protype in prototypes:
 | 
			
		||||
        _validate_prototype(prototype)
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    common.setup_logging()
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user