chore: remove useless scripts
This commit is contained in:
		@ -1,433 +0,0 @@
 | 
			
		||||
import typing
 | 
			
		||||
import simple_po, bme_utils
 | 
			
		||||
 | 
			
		||||
#region Translation Constant
 | 
			
		||||
 | 
			
		||||
## TODO:
 | 
			
		||||
#  This translation context string prefix is cpoied from UTIL_translation.py.
 | 
			
		||||
#  If the context string of translation changed, please synchronize it.
 | 
			
		||||
 | 
			
		||||
CTX_TRANSLATION: str = 'BBP/BME'
 | 
			
		||||
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
#region BME Tokens
 | 
			
		||||
 | 
			
		||||
## TODO:
 | 
			
		||||
#  These token are copied from UTIL_bme.py.
 | 
			
		||||
#  If anything changed, such as BME standard, these tokens should be synchronized between these 2 modules.
 | 
			
		||||
 | 
			
		||||
TOKEN_IDENTIFIER: str = 'identifier'
 | 
			
		||||
 | 
			
		||||
TOKEN_SHOWCASE: str = 'showcase'
 | 
			
		||||
TOKEN_SHOWCASE_TITLE: str = 'title'
 | 
			
		||||
TOKEN_SHOWCASE_ICON: str = 'icon'
 | 
			
		||||
TOKEN_SHOWCASE_TYPE: str = 'type'
 | 
			
		||||
TOKEN_SHOWCASE_CFGS: str = 'cfgs'
 | 
			
		||||
TOKEN_SHOWCASE_CFGS_FIELD: str = 'field'
 | 
			
		||||
TOKEN_SHOWCASE_CFGS_TYPE: str = 'type'
 | 
			
		||||
TOKEN_SHOWCASE_CFGS_TITLE: str = 'title'
 | 
			
		||||
TOKEN_SHOWCASE_CFGS_DESC: str = 'desc'
 | 
			
		||||
TOKEN_SHOWCASE_CFGS_DEFAULT: str = 'default'
 | 
			
		||||
 | 
			
		||||
TOKEN_SKIP: str = 'skip'
 | 
			
		||||
 | 
			
		||||
TOKEN_PARAMS: str = 'params'
 | 
			
		||||
TOKEN_PARAMS_FIELD: str = 'field'
 | 
			
		||||
TOKEN_PARAMS_DATA: str = 'data'
 | 
			
		||||
 | 
			
		||||
TOKEN_VARS: str = 'vars'
 | 
			
		||||
TOKEN_VARS_FIELD: str = 'field'
 | 
			
		||||
TOKEN_VARS_DATA: str = 'data'
 | 
			
		||||
 | 
			
		||||
TOKEN_VERTICES: str = 'vertices'
 | 
			
		||||
TOKEN_VERTICES_SKIP: str = 'skip'
 | 
			
		||||
TOKEN_VERTICES_DATA: str = 'data'
 | 
			
		||||
 | 
			
		||||
TOKEN_FACES: str = 'faces'
 | 
			
		||||
TOKEN_FACES_SKIP: str = 'skip'
 | 
			
		||||
TOKEN_FACES_TEXTURE: str = 'texture'
 | 
			
		||||
TOKEN_FACES_INDICES: str = 'indices'
 | 
			
		||||
TOKEN_FACES_UVS: str = 'uvs'
 | 
			
		||||
TOKEN_FACES_NORMALS: str = 'normals'
 | 
			
		||||
 | 
			
		||||
TOKEN_INSTANCES: str = 'instances'
 | 
			
		||||
TOKEN_INSTANCES_IDENTIFIER: str = 'identifier'
 | 
			
		||||
TOKEN_INSTANCES_SKIP: str = 'skip'
 | 
			
		||||
TOKEN_INSTANCES_PARAMS: str = 'params'
 | 
			
		||||
TOKEN_INSTANCES_TRANSFORM: str = 'transform'
 | 
			
		||||
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
# TODO: finish BME validator
 | 
			
		||||
 | 
			
		||||
# class ReporterWithHierarchy():
 | 
			
		||||
#     """
 | 
			
		||||
#     BME validator and extractor specifically used reporter
 | 
			
		||||
#     which auotmatically use hierarchy as its context when outputing.
 | 
			
		||||
#     """
 | 
			
		||||
 | 
			
		||||
#     __mReporter: bme_utils.Reporter
 | 
			
		||||
#     __mHierarchy: bme_utils.Hierarchy
 | 
			
		||||
 | 
			
		||||
#     def __init__(self, reporter: bme_utils.Reporter, hierarchy: bme_utils.Hierarchy):
 | 
			
		||||
#         self.__mReporter = reporter
 | 
			
		||||
#         self.__mHierarchy = hierarchy
 | 
			
		||||
 | 
			
		||||
#     def error(self, msg: str) -> None:
 | 
			
		||||
#         self.__mReporter.error(msg, self.__mHierarchy.build_hierarchy_string())
 | 
			
		||||
#     def warning(self, msg: str) -> None:
 | 
			
		||||
#         self.__mReporter.warning(msg, self.__mHierarchy.build_hierarchy_string())
 | 
			
		||||
#     def info(self, msg: str) -> None:
 | 
			
		||||
#         self.__mReporter.info(msg, self.__mHierarchy.build_hierarchy_string())
 | 
			
		||||
 | 
			
		||||
# class UniqueField():
 | 
			
		||||
#     """
 | 
			
		||||
#     Some BME prototype fields should be unique in globl scope.
 | 
			
		||||
#     So BME validator should check this. That's the feature this class provided.
 | 
			
		||||
 | 
			
		||||
#     This class is an abstract class and should not be used directly.
 | 
			
		||||
#     Use child class please.
 | 
			
		||||
#     """
 | 
			
		||||
 | 
			
		||||
#     __mUniques: set[str]
 | 
			
		||||
#     __mReporter: ReporterWithHierarchy
 | 
			
		||||
 | 
			
		||||
#     def __init__(self, reporter: ReporterWithHierarchy):
 | 
			
		||||
#         self.__mUniques = set()
 | 
			
		||||
#         self.__mReporter = reporter
 | 
			
		||||
 | 
			
		||||
#     def register(self, entry: str) -> bool:
 | 
			
		||||
#         """
 | 
			
		||||
#         @brief Try to register given entry in unique.
 | 
			
		||||
#         @details
 | 
			
		||||
#         If given entry is not presented in unique set, given entry will be inserted and return True.
 | 
			
		||||
#         If given entry is already available in unique set, this function will use reporter to output an error message and return False.
 | 
			
		||||
#         @param[in] entry The entry to be checked and inserted.
 | 
			
		||||
#         @return True if entry is unique, otherwise false.
 | 
			
		||||
#         """
 | 
			
		||||
#         if entry in self.__mUniques:
 | 
			
		||||
#             self.__mReporter.error(self._get_error_msg(entry))
 | 
			
		||||
#             return False
 | 
			
		||||
#         else:
 | 
			
		||||
#             self.__mUniques.add(entry)
 | 
			
		||||
#             return True
 | 
			
		||||
 | 
			
		||||
#     def clear(self) -> None:
 | 
			
		||||
#         """
 | 
			
		||||
#         @brief Clear this unique set for further using.
 | 
			
		||||
#         """
 | 
			
		||||
#         self.__mUniques.clear()
 | 
			
		||||
 | 
			
		||||
#     def _get_error_msg(self, err_entry: str) -> str:
 | 
			
		||||
#         """
 | 
			
		||||
#         @brief Get the error message when error occurs.
 | 
			
		||||
#         @details
 | 
			
		||||
#         This is internal used function to get the error message which will be passed to reporter.
 | 
			
		||||
#         This message is generated by given entry which cause the non-unique issue.
 | 
			
		||||
#         Outer caller should not call this function and every child class should override this function.
 | 
			
		||||
#         @param[in] err_entry The entry cause the error.
 | 
			
		||||
#         @return The error message generated from given error entry.
 | 
			
		||||
#         """
 | 
			
		||||
#         raise NotImplementedError()
 | 
			
		||||
 | 
			
		||||
# class UniqueIdentifier(UniqueField):
 | 
			
		||||
#     """Specific UniqueField for unique prototype identifier."""
 | 
			
		||||
#     def _get_error_msg(self, err_entry: str) -> str:
 | 
			
		||||
#         return f'Trying to register multiple prototype with same name: "{err_entry}".'
 | 
			
		||||
# class UniqueVariable(UniqueField):
 | 
			
		||||
#     """Specific UniqueField for unique variable names within prototype."""
 | 
			
		||||
#     def _get_error_msg(self, err_entry: str) -> str:
 | 
			
		||||
#         return f'Trying to define multiple variable with same name: "{err_entry}" in the same prototype.'
 | 
			
		||||
 | 
			
		||||
# class BMEValidator():
 | 
			
		||||
#     """
 | 
			
		||||
#     The validator for BME prototype declarartions.
 | 
			
		||||
#     This validator will validate given prototype declaration JSON structure,
 | 
			
		||||
#     to check then whether have all essential fields BME standard required and whether have any unknown fields.
 | 
			
		||||
#     """
 | 
			
		||||
 | 
			
		||||
#     __mHierarchy: bme_utils.Hierarchy
 | 
			
		||||
#     __mReporter: ReporterWithHierarchy
 | 
			
		||||
 | 
			
		||||
#     __mUniqueIdentifier: UniqueIdentifier
 | 
			
		||||
#     __mUniqueVariable: UniqueVariable
 | 
			
		||||
 | 
			
		||||
#     def __init__(self, reporter: bme_utils.Reporter):
 | 
			
		||||
#         self.__mHierarchy = bme_utils.Hierarchy()
 | 
			
		||||
#         self.__mReporter = ReporterWithHierarchy(reporter, self.__mHierarchy)
 | 
			
		||||
 | 
			
		||||
#         self.__mUniqueIdentifier = UniqueIdentifier(self.__mReporter)
 | 
			
		||||
#         self.__mUniqueVariable = UniqueVariable(self.__mReporter)
 | 
			
		||||
 | 
			
		||||
#     _TCheckKey = typing.TypeVar('_TCheckKey')
 | 
			
		||||
#     def __check_key(self, data: dict[str, typing.Any], key: str, expected_type: type[_TCheckKey]) -> _TCheckKey | None:
 | 
			
		||||
#         """
 | 
			
		||||
#         @brief Check the existance and tyoe of value stored in given dict and key.
 | 
			
		||||
#         @param[in] data The dict need to be checked
 | 
			
		||||
#         @param[in] key The key for fetching value.
 | 
			
		||||
#         @param[in] expected_type The expected type of fetched value.
 | 
			
		||||
#         @return None if error occurs, otherwise the value stored in given dict and key.
 | 
			
		||||
#         """
 | 
			
		||||
#         gotten_value = data[key]
 | 
			
		||||
#         if gotten_value is None:
 | 
			
		||||
#             # report no key error
 | 
			
		||||
#             self.__mReporter.error(f'Can not find key "{key}". Did you forget it?')
 | 
			
		||||
#         elif not isinstance(gotten_value, expected_type):
 | 
			
		||||
#             # get the type of value
 | 
			
		||||
#             value_type = type(gotten_value)
 | 
			
		||||
#             # format normal error message
 | 
			
		||||
#             err_msg: str = f'The type of value stored inside key "{key}" is incorrect. '
 | 
			
		||||
#             err_msg += f'Expect "{expected_type.__name__}" got "{value_type.__name__}". '
 | 
			
		||||
#             # add special note for easily confusing types
 | 
			
		||||
#             # e.g. forget quote number (number literal are recognise as number accidently)
 | 
			
		||||
#             if issubclass(expected_type, str) and issubclass(type(data), (int, float)):
 | 
			
		||||
#                 err_msg +=  'Did you forgot quote the number?'
 | 
			
		||||
#             # report type error
 | 
			
		||||
#             self.__mReporter.error(err_msg)
 | 
			
		||||
#         else:
 | 
			
		||||
#             # no error, return value
 | 
			
		||||
#             return gotten_value
 | 
			
		||||
#         # error occurs, return null
 | 
			
		||||
#         return None
 | 
			
		||||
 | 
			
		||||
#     def __check_self(self, data: typing.Any, expected_type: type) -> bool:
 | 
			
		||||
#         """
 | 
			
		||||
#         @brief Check the type of given data.
 | 
			
		||||
#         @return True if type matched, otherwise false.
 | 
			
		||||
#         """
 | 
			
		||||
#         if data is None:
 | 
			
		||||
#             self.__mReporter.error('Data is unexpected null.')
 | 
			
		||||
#         elif not isinstance(data, expected_type):
 | 
			
		||||
#             # usually this function is checking list or dict, so no scenario that user forget quote literal number.
 | 
			
		||||
#             self.__mReporter.error(f'The type of given data is not expected. Expect "{expected_type.__name__}" got "{type(data).__name__}".')
 | 
			
		||||
#         else:
 | 
			
		||||
#             # no error, return okey
 | 
			
		||||
#             return True
 | 
			
		||||
#         # error occurs, return failed
 | 
			
		||||
#         return False
 | 
			
		||||
 | 
			
		||||
#     # 按层次递归调用检查。
 | 
			
		||||
#     # 每个层次只负责当前层次的检查。
 | 
			
		||||
#     # 如果值为列表,字典,则在当前层次检查完其类型(容器本身,对每一项不检查),然后对每一项调用对应层次检查。
 | 
			
		||||
#     # 如果值不是上述类型(例如整数,浮点数,字符串等),在当前层次检查。
 | 
			
		||||
 | 
			
		||||
#     def validate(self, assoc_file: str, prototypes: typing.Any) -> None:
 | 
			
		||||
#         # reset hierarchy
 | 
			
		||||
#         self.__mHierarchy.clear()
 | 
			
		||||
#         # start to validate
 | 
			
		||||
#         with self.__mHierarchy.safe_push(assoc_file):
 | 
			
		||||
#             self.__validate_prototypes(prototypes)
 | 
			
		||||
 | 
			
		||||
#     def __validate_prototypes(self, prototypes: typing.Any) -> None:
 | 
			
		||||
#         # the most outer structure must be a list
 | 
			
		||||
#         if not self.__check_self(prototypes, list): return
 | 
			
		||||
#         cast_prototypes = typing.cast(list[typing.Any], prototypes)
 | 
			
		||||
#         # iterate prototype
 | 
			
		||||
#         for prototype_index, prototype in enumerate(cast_prototypes):
 | 
			
		||||
#             with self.__mHierarchy.safe_push(prototype_index) as layer:
 | 
			
		||||
#                 self.__validate_prototype(layer, prototype)
 | 
			
		||||
 | 
			
		||||
#     def __validate_prototype(self, layer: bme_utils.HierarchyLayer, prototype: typing.Any) -> None:
 | 
			
		||||
#         # check whether self is a dict
 | 
			
		||||
#         if not self.__check_self(prototype, dict): return
 | 
			
		||||
#         cast_prototype = typing.cast(dict[str, typing.Any], prototype)
 | 
			
		||||
 | 
			
		||||
#         # clear unique field for each prototype
 | 
			
		||||
#         self.__mUniqueVariable.clear()
 | 
			
		||||
 | 
			
		||||
#         # check identifier
 | 
			
		||||
#         identifier = self.__check_key(cast_prototype, TOKEN_IDENTIFIER, str)
 | 
			
		||||
#         if identifier is not None:
 | 
			
		||||
#             # replace hierarchy
 | 
			
		||||
#             layer.emplace(identifier)
 | 
			
		||||
#             # check unique
 | 
			
		||||
#             self.__mUniqueIdentifier.register(identifier)
 | 
			
		||||
 | 
			
		||||
#         # check showcase but don't use check function
 | 
			
		||||
#         # because it is optional.
 | 
			
		||||
#         showcase = cast_prototype[TOKEN_SHOWCASE]
 | 
			
		||||
#         if showcase is not None:
 | 
			
		||||
#             # we only check non-template prototype
 | 
			
		||||
#             with self.__mHierarchy.safe_push(TOKEN_SHOWCASE):
 | 
			
		||||
#                 self.__validate_showcase(typing.cast(dict[str, typing.Any], showcase))
 | 
			
		||||
 | 
			
		||||
#         # check params, vars, vertices, faces, instances
 | 
			
		||||
#         # they are all list
 | 
			
		||||
#         params = self.__check_key(cast_prototype, TOKEN_PARAMS, list)
 | 
			
		||||
#         if params is not None:
 | 
			
		||||
#             cast_params = typing.cast(list[typing.Any], params)
 | 
			
		||||
#             with self.__mHierarchy.safe_push(TOKEN_PARAMS):
 | 
			
		||||
#                 for param_index, param in enumerate(cast_params):
 | 
			
		||||
#                     with self.__mHierarchy.safe_push(param_index):
 | 
			
		||||
#                         self.__validate_param(param)
 | 
			
		||||
        
 | 
			
		||||
#         vars = self.__check_key(cast_prototype, TOKEN_VARS, list)
 | 
			
		||||
#         if vars is not None:
 | 
			
		||||
#             cast_vars = typing.cast(list[typing.Any], vars)
 | 
			
		||||
#             with self.__mHierarchy.safe_push(TOKEN_VARS):
 | 
			
		||||
#                 for var_index, var in enumerate(cast_vars):
 | 
			
		||||
#                     with self.__mHierarchy.safe_push(var_index):
 | 
			
		||||
#                         self.__validate_var(var)
 | 
			
		||||
 | 
			
		||||
#         vertices = self.__check_key(cast_prototype, TOKEN_VERTICES, list)
 | 
			
		||||
#         if vertices is not None:
 | 
			
		||||
#             cast_vertices = typing.cast(list[typing.Any], vertices)
 | 
			
		||||
#             with self.__mHierarchy.safe_push(TOKEN_VERTICES):
 | 
			
		||||
#                 for vertex_index, vertex in enumerate(cast_vertices):
 | 
			
		||||
#                     with self.__mHierarchy.safe_push(vertex_index):
 | 
			
		||||
#                         self.__validate_vertex(vertex)
 | 
			
		||||
 | 
			
		||||
#         faces = self.__check_key(cast_prototype, TOKEN_FACES, list)
 | 
			
		||||
#         if faces is not None:
 | 
			
		||||
#             cast_faces = typing.cast(list[typing.Any], faces)
 | 
			
		||||
#             with self.__mHierarchy.safe_push(TOKEN_FACES):
 | 
			
		||||
#                 for face_index, face in enumerate(cast_faces):
 | 
			
		||||
#                     with self.__mHierarchy.safe_push(face_index):
 | 
			
		||||
#                         self.__validate_face(face)
 | 
			
		||||
 | 
			
		||||
#         instances = self.__check_key(cast_prototype, TOKEN_INSTANCES, list)
 | 
			
		||||
#         if instances is not None:
 | 
			
		||||
#             cast_instances = typing.cast(list[typing.Any], instances)
 | 
			
		||||
#             with self.__mHierarchy.safe_push(TOKEN_INSTANCES):
 | 
			
		||||
#                 for instance_index, instance in enumerate(cast_instances):
 | 
			
		||||
#                     with self.__mHierarchy.safe_push(instance_index):
 | 
			
		||||
#                         self.__validate_instance(instance)
 | 
			
		||||
 | 
			
		||||
#     def __validate_showcase(self, showcase: dict[str, typing.Any]) -> None:
 | 
			
		||||
#         pass
 | 
			
		||||
 | 
			
		||||
#     def __validate_param(self, param: typing.Any) -> None:
 | 
			
		||||
#         # check whether self is a dict
 | 
			
		||||
#         if not self.__check_self(param, dict): return
 | 
			
		||||
#         cast_param = typing.cast(dict[str, typing.Any], param)
 | 
			
		||||
 | 
			
		||||
#         # check field
 | 
			
		||||
#         field = self.__check_key(cast_param, TOKEN_PARAMS_FIELD, str)
 | 
			
		||||
#         if field is not None:
 | 
			
		||||
#             self.__mUniqueVariable.register(field)
 | 
			
		||||
 | 
			
		||||
#         # check data
 | 
			
		||||
#         self.__check_key(cast_param, TOKEN_PARAMS_DATA, str)
 | 
			
		||||
 | 
			
		||||
#     def __validate_var(self, var: typing.Any) -> None:
 | 
			
		||||
#         # check whether self is a dict
 | 
			
		||||
#         if not self.__check_self(var, dict): return
 | 
			
		||||
#         cast_var = typing.cast(dict[str, typing.Any], var)
 | 
			
		||||
 | 
			
		||||
#         # check field
 | 
			
		||||
#         field = self.__check_key(cast_var, TOKEN_VARS_FIELD, str)
 | 
			
		||||
#         if field is not None:
 | 
			
		||||
#             self.__mUniqueVariable.register(field)
 | 
			
		||||
 | 
			
		||||
#         # check data
 | 
			
		||||
#         self.__check_key(cast_var, TOKEN_VARS_DATA, str)
 | 
			
		||||
 | 
			
		||||
#     def __validate_vertex(self, vertex: typing.Any) -> None:
 | 
			
		||||
#         # check whether self is a dict
 | 
			
		||||
#         if not self.__check_self(vertex, dict): return
 | 
			
		||||
#         cast_vertex = typing.cast(dict[str, typing.Any], vertex)
 | 
			
		||||
 | 
			
		||||
#         # check fields
 | 
			
		||||
#         self.__check_key(cast_vertex, TOKEN_VERTICES_SKIP, str)
 | 
			
		||||
#         self.__check_key(cast_vertex, TOKEN_VERTICES_DATA, str)
 | 
			
		||||
 | 
			
		||||
#     def __validate_face(self, face: typing.Any) -> None:
 | 
			
		||||
#         pass
 | 
			
		||||
 | 
			
		||||
#     def __validate_instance(self, instance: typing.Any) -> None:
 | 
			
		||||
#         pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BMEExtractor():
 | 
			
		||||
    """
 | 
			
		||||
    A GetText extractor for BME prototype declarations.
 | 
			
		||||
    This extractor can extract all UI infomations which will be shown on Blender first.
 | 
			
		||||
    Then write them into caller given PO file. So that translator can translate them.
 | 
			
		||||
 | 
			
		||||
    Blender default I18N plugin can not recognise these dynamic loaded content,
 | 
			
		||||
    so that's the reason why this class invented.
 | 
			
		||||
 | 
			
		||||
    Please note all data should be validate first, then pass to this class.
 | 
			
		||||
    Otherwise it is undefined behavior.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    __mAssocFile: str
 | 
			
		||||
    __mHierarchy: bme_utils.Hierarchy
 | 
			
		||||
    __mReporter: bme_utils.Reporter
 | 
			
		||||
    __mPoWriter: simple_po.PoWriter
 | 
			
		||||
 | 
			
		||||
    def __init__(self, reporter: bme_utils.Reporter, po_writer: simple_po.PoWriter):
 | 
			
		||||
        self.__mAssocFile = ''
 | 
			
		||||
        self.__mHierarchy = bme_utils.Hierarchy()
 | 
			
		||||
        self.__mReporter = reporter
 | 
			
		||||
        self.__mPoWriter = po_writer
 | 
			
		||||
 | 
			
		||||
    def __add_translation(self, msg: str) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        @brief Convenient internal translation adder.
 | 
			
		||||
        @details Add given message into PO file with auto generated hierarchy for translation context.
 | 
			
		||||
        @param[in] msg The message for translating.
 | 
			
		||||
        """
 | 
			
		||||
        self.__mPoWriter.add_entry(
 | 
			
		||||
            msg,
 | 
			
		||||
            CTX_TRANSLATION + '/' + self.__mHierarchy.build_hierarchy_string(),
 | 
			
		||||
            # use associated file as extracted message to tell user where we extract it.
 | 
			
		||||
            # put file name in hierarchy is not proper (file path may be changed when moving prototype between them).
 | 
			
		||||
            self.__mAssocFile
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def __report_duplication_error(self) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        @brief Convenient internal function to report duplicated translation message issue.
 | 
			
		||||
        @details
 | 
			
		||||
        A convenient internal used function to report issue that
 | 
			
		||||
        the "title" field and "desc" field of the same showcase configuration entry have same content
 | 
			
		||||
        which may cause that generated PO file is illegal.
 | 
			
		||||
        """
 | 
			
		||||
        self.__mReporter.error(
 | 
			
		||||
            'The content of "title" and "desc" can not be the same in one entry. Please modify one of them.',
 | 
			
		||||
            self.__mAssocFile + '/' + self.__mHierarchy.build_hierarchy_string()
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def extract(self, assoc_file: str, prototypes: list[dict[str, typing.Any]]) -> None:
 | 
			
		||||
        self.__mAssocFile = assoc_file
 | 
			
		||||
        for prototype in prototypes:
 | 
			
		||||
            self.__extract_prototype(prototype)
 | 
			
		||||
 | 
			
		||||
    def __extract_prototype(self, prototype: dict[str, typing.Any]) -> None:
 | 
			
		||||
        # get identifier first
 | 
			
		||||
        identifier: str = prototype[TOKEN_IDENTIFIER]
 | 
			
		||||
        with self.__mHierarchy.safe_push(identifier):
 | 
			
		||||
            # get showcase node and only write PO file if it is not template prototype
 | 
			
		||||
            showcase: dict[str, typing.Any] | None = prototype[TOKEN_SHOWCASE]
 | 
			
		||||
            if showcase is not None:
 | 
			
		||||
                self.__extract_showcase(showcase)
 | 
			
		||||
 | 
			
		||||
    def __extract_showcase(self, showcase: dict[str, typing.Any]) -> None:
 | 
			
		||||
        # export self name first
 | 
			
		||||
        self.__add_translation(showcase[TOKEN_SHOWCASE_TITLE])
 | 
			
		||||
 | 
			
		||||
        # iterate cfgs
 | 
			
		||||
        cfgs: list[dict[str, typing.Any]] = showcase[TOKEN_SHOWCASE_CFGS]
 | 
			
		||||
        for cfg_index, cfg in enumerate(cfgs):
 | 
			
		||||
            self.__extract_showcase_cfg(cfg_index, cfg)
 | 
			
		||||
 | 
			
		||||
    def __extract_showcase_cfg(self, index: int, cfg: dict[str, typing.Any]) -> None:
 | 
			
		||||
        # push cfg index
 | 
			
		||||
        with self.__mHierarchy.safe_push(index):
 | 
			
		||||
            # extract field title and description
 | 
			
		||||
            title: str = cfg[TOKEN_SHOWCASE_CFGS_TITLE]
 | 
			
		||||
            desc: str = cfg[TOKEN_SHOWCASE_CFGS_DESC]
 | 
			
		||||
 | 
			
		||||
            # check duplication error
 | 
			
		||||
            # if "title" is equal to "desc" and they are not blank
 | 
			
		||||
            if title == desc and title != "":
 | 
			
		||||
                self.__report_duplication_error()
 | 
			
		||||
 | 
			
		||||
            # export them respectively if they are not blank
 | 
			
		||||
            if title != "":
 | 
			
		||||
                self.__add_translation(title)
 | 
			
		||||
            if desc!= "":
 | 
			
		||||
                self.__add_translation(desc)
 | 
			
		||||
 | 
			
		||||
@ -1,144 +0,0 @@
 | 
			
		||||
import typing
 | 
			
		||||
import collections
 | 
			
		||||
import termcolor
 | 
			
		||||
 | 
			
		||||
class Reporter():
 | 
			
		||||
    """
 | 
			
		||||
    General reporter with context support for convenient logging.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def __report(self, type: str, msg: str, context: str | None, color: str) -> None:
 | 
			
		||||
        # build message
 | 
			
		||||
        strl: str = f'[{type}]'
 | 
			
		||||
        if context is not None:
 | 
			
		||||
            strl += f'[{context}]'
 | 
			
		||||
        strl += ' ' + msg
 | 
			
		||||
        # output with color
 | 
			
		||||
        termcolor.cprint(strl, color)
 | 
			
		||||
 | 
			
		||||
    def error(self, msg: str, context: str | None = None) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        @brief Report an error.
 | 
			
		||||
        @param[in] msg The message to show.
 | 
			
		||||
        @param[in] context The context of this message, e.g. the file path. None if no context.
 | 
			
		||||
        """
 | 
			
		||||
        self.__report('Error', msg, context, 'red')
 | 
			
		||||
 | 
			
		||||
    def warning(self, msg: str, context: str | None = None) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        @brief Report a warning.
 | 
			
		||||
        @param[in] msg The message to show.
 | 
			
		||||
        @param[in] context The context of this message, e.g. the file path. None if no context.
 | 
			
		||||
        """
 | 
			
		||||
        self.__report('Warning', msg, context, 'yellow')
 | 
			
		||||
 | 
			
		||||
    def info(self, msg: str, context: str | None = None) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        @brief Report a info.
 | 
			
		||||
        @param[in] msg The message to show.
 | 
			
		||||
        @param[in] context The context of this message, e.g. the file path. None if no context.
 | 
			
		||||
        """
 | 
			
		||||
        self.__report('Info', msg, context, 'white')
 | 
			
		||||
 | 
			
		||||
class Hierarchy():
 | 
			
		||||
    """
 | 
			
		||||
    The hierarchy for BME validator and BME extractor.
 | 
			
		||||
    In BME validator, it build human-readable string representing the location where error happen.
 | 
			
		||||
    In BME extractor, it build the string used as the context of translation.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    __mStack: collections.deque[str]
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        self.__mStack = collections.deque()
 | 
			
		||||
 | 
			
		||||
    def push(self, item: str | int) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        @brief Add an item into the top of this hierarchy.
 | 
			
		||||
        @details
 | 
			
		||||
        If given item is string, it will be push into hierarchy directly.
 | 
			
		||||
        If given item is integer, this function will treat it as a special case, the index.
 | 
			
		||||
        Function will push it into hierarchy after formatting it (add a pair of bracket around it).
 | 
			
		||||
        @param[in] item New added item.
 | 
			
		||||
        """
 | 
			
		||||
        if isinstance(item, str):
 | 
			
		||||
            self.__mStack.append(item)
 | 
			
		||||
        elif isinstance(item, int):
 | 
			
		||||
            self.__mStack.append(f'[{item}]')
 | 
			
		||||
        else:
 | 
			
		||||
            raise Exception('Unexpected type of item when pushing into hierarchy.')
 | 
			
		||||
 | 
			
		||||
    def pop(self) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        @brief Remove the top item from hierarchy
 | 
			
		||||
        """
 | 
			
		||||
        self.__mStack.pop()
 | 
			
		||||
 | 
			
		||||
    def safe_push(self, item: str | int) -> 'HierarchyLayer':
 | 
			
		||||
        """
 | 
			
		||||
        @brief The safe version of push function.
 | 
			
		||||
        @return A with-context-supported instance which can make sure pushed item popped when leaving scope.
 | 
			
		||||
        """
 | 
			
		||||
        return HierarchyLayer(self, item)
 | 
			
		||||
 | 
			
		||||
    def clear(self) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        @brief Clear this hierarchy.
 | 
			
		||||
        """
 | 
			
		||||
        self.__mStack.clear()
 | 
			
		||||
 | 
			
		||||
    def depth(self) -> int:
 | 
			
		||||
        """
 | 
			
		||||
        @brief Return the depth of this hierarchy.
 | 
			
		||||
        @return The depth of this hierarchy.
 | 
			
		||||
        """
 | 
			
		||||
        return len(self.__mStack)
 | 
			
		||||
 | 
			
		||||
    def build_hierarchy_string(self) -> str:
 | 
			
		||||
        """
 | 
			
		||||
        @brief Build the string which can represent this hierarchy.
 | 
			
		||||
        @details It just join every items with `/` as separator.
 | 
			
		||||
        @return The built string representing this hierarchy.
 | 
			
		||||
        """
 | 
			
		||||
        return '/'.join(self.__mStack)
 | 
			
		||||
 | 
			
		||||
class HierarchyLayer():
 | 
			
		||||
    """
 | 
			
		||||
    An with-context-supported class for Hierarchy which can automatically pop item when leaving scope.
 | 
			
		||||
    This is convenient for keeping the balance of Hierarchy (avoid programmer accidently forgetting to pop item).
 | 
			
		||||
    """
 | 
			
		||||
    
 | 
			
		||||
    __mHasPop: bool
 | 
			
		||||
    __mAssocHierarchy: Hierarchy
 | 
			
		||||
 | 
			
		||||
    def __init__(self, assoc_hierarchy: Hierarchy, item: str | int):
 | 
			
		||||
        self.__mAssocHierarchy = assoc_hierarchy
 | 
			
		||||
        self.__mHasPop = False
 | 
			
		||||
        self.__mAssocHierarchy.push(item)
 | 
			
		||||
 | 
			
		||||
    def __enter__(self):
 | 
			
		||||
        return self
 | 
			
		||||
    
 | 
			
		||||
    def __exit__(self, exc_type, exc_value, traceback):
 | 
			
		||||
        self.close()
 | 
			
		||||
    
 | 
			
		||||
    def close(self) -> None:
 | 
			
		||||
        if not self.__mHasPop:
 | 
			
		||||
            self.__mAssocHierarchy.pop()
 | 
			
		||||
            self.__mHasPop = True
 | 
			
		||||
 | 
			
		||||
    def emplace(self, new_item: str | int) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        @brief Replace the content of top item in-place.
 | 
			
		||||
        @details
 | 
			
		||||
        In some cases, caller need to replace the content of top item.
 | 
			
		||||
        For example, at the beginning, we only have index info.
 | 
			
		||||
        After validating something, we can fetching a more human-readable info, such as name,
 | 
			
		||||
        now we need replace the content of top item.
 | 
			
		||||
        @param[in] new_item The new content of top item.
 | 
			
		||||
        """
 | 
			
		||||
        self.__mAssocHierarchy.pop()
 | 
			
		||||
        self.__mAssocHierarchy.push(new_item)
 | 
			
		||||
@ -1,99 +0,0 @@
 | 
			
		||||
import typing
 | 
			
		||||
import io
 | 
			
		||||
import datetime
 | 
			
		||||
 | 
			
		||||
class PoWriter():
 | 
			
		||||
    """
 | 
			
		||||
    The simple PO file writer.
 | 
			
		||||
    This class is just served for writing POT files.
 | 
			
		||||
    It may be convenient when exporting PO file for thoese whose format can not be parsed by formal tools.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    __cEscapeCharsDict: typing.ClassVar[dict[str, str]] = {
 | 
			
		||||
        '\\': '\\\\',
 | 
			
		||||
        '"': '\\"',
 | 
			
		||||
        '\n': '\\n',
 | 
			
		||||
        '\t': '\\t',
 | 
			
		||||
    }
 | 
			
		||||
    __cEscapeCharsTable: typing.ClassVar[dict] = str.maketrans(__cEscapeCharsDict)
 | 
			
		||||
    __mPoFile: io.TextIOWrapper
 | 
			
		||||
 | 
			
		||||
    def __init__(self, po_file_path: str, project_name: str):
 | 
			
		||||
        # open file
 | 
			
		||||
        self.__mPoFile = open(po_file_path, 'w', encoding = 'utf-8')
 | 
			
		||||
        # add default header
 | 
			
		||||
        self.__add_header(project_name)
 | 
			
		||||
 | 
			
		||||
    def __enter__(self):
 | 
			
		||||
        return self
 | 
			
		||||
    
 | 
			
		||||
    def __exit__(self, exc_type, exc_value, traceback):
 | 
			
		||||
        self.close()
 | 
			
		||||
    
 | 
			
		||||
    def close(self) -> None:
 | 
			
		||||
        self.__mPoFile.close()
 | 
			
		||||
 | 
			
		||||
    def __write_line(self, val: str) -> None:
 | 
			
		||||
        self.__mPoFile.write(val)
 | 
			
		||||
        self.__mPoFile.write('\n')
 | 
			
		||||
 | 
			
		||||
    def __escape_str(self, val: str) -> str:
 | 
			
		||||
        """
 | 
			
		||||
        This function escapes a given string to make it safe to use as a C++ string literal.
 | 
			
		||||
        @param[in] val Original string
 | 
			
		||||
        @return Escaped string
 | 
			
		||||
        """
 | 
			
		||||
        return val.translate(PoWriter.__cEscapeCharsTable)
 | 
			
		||||
 | 
			
		||||
    def __add_header(self, project_name: str) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        Add default header for PO file.
 | 
			
		||||
        @param[in] project_name The project name written in file.
 | 
			
		||||
        """
 | 
			
		||||
        now_datetime = datetime.datetime.now()
 | 
			
		||||
        self.__write_line('# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.')
 | 
			
		||||
        self.__write_line('msgid ""')
 | 
			
		||||
        self.__write_line('msgstr ""')
 | 
			
		||||
        self.__write_line(f'"Project-Id-Version: {self.__escape_str(project_name)}\\n"')
 | 
			
		||||
        self.__write_line(f'"POT-Creation-Date: {now_datetime.strftime("%Y-%m-%d %H:%M%Z")}\\n"')
 | 
			
		||||
        self.__write_line('"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n"')
 | 
			
		||||
        self.__write_line('"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n"')
 | 
			
		||||
        self.__write_line('"Language-Team: LANGUAGE <LL@li.org>\\n"')
 | 
			
		||||
        self.__write_line('"Language: __POT__\\n"')
 | 
			
		||||
        self.__write_line('"MIME-Version: 1.0\\n"')
 | 
			
		||||
        self.__write_line('"Content-Type: text/plain; charset=UTF-8\\n"')
 | 
			
		||||
        self.__write_line('"Content-Transfer-Encoding: 8bit\\n"')
 | 
			
		||||
        self.__write_line('"X-Generator: simple_po.PoWriter\\n"')
 | 
			
		||||
 | 
			
		||||
    def add_entry(self, msg: str, msg_context: str | None = None, extracted_comment: str | None = None, reference: str | None = None) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        @brief Write an entry into PO file with given arguments.
 | 
			
		||||
        @details
 | 
			
		||||
        Please note this function will NOT check whether there already is a duplicated entry which has been written.
 | 
			
		||||
        You must check this on your own.
 | 
			
		||||
        @param[in] msg The message string need to be translated.
 | 
			
		||||
        @param[in] msg_context The context of this message.
 | 
			
		||||
        @param[in] extracted_comment The extracted comment of this message. None if no reference. Line breaker is not allowed.
 | 
			
		||||
        @param[in] reference The code refernece of this message. None if no reference. Line breaker is not allowed.
 | 
			
		||||
        """
 | 
			
		||||
        # empty string will not be translated
 | 
			
		||||
        if msg == '': return
 | 
			
		||||
 | 
			
		||||
        # write blank line first
 | 
			
		||||
        self.__write_line('')
 | 
			
		||||
        if extracted_comment:
 | 
			
		||||
            self.__write_line(f'#. {extracted_comment}')
 | 
			
		||||
        if reference:
 | 
			
		||||
            self.__write_line(f'#: {reference}')
 | 
			
		||||
        if msg_context:
 | 
			
		||||
            self.__write_line(f'msgctxt "{self.__escape_str(msg_context)}"')
 | 
			
		||||
        self.__write_line(f'msgid "{self.__escape_str(msg)}"')
 | 
			
		||||
        self.__write_line('msgstr ""')
 | 
			
		||||
 | 
			
		||||
    def build_code_reference(self, code_file_path: str, code_line_number: int) -> str:
 | 
			
		||||
        """
 | 
			
		||||
        A convenient function to build code reference string used when adding entry.
 | 
			
		||||
        @param[in] code_file_path The path to associated code file.
 | 
			
		||||
        @param[in] code_line_number The line number of associated code within given file.
 | 
			
		||||
        """
 | 
			
		||||
        return f'{code_file_path}:{code_line_number}'
 | 
			
		||||
		Reference in New Issue
	
	Block a user