########################################################################### # # Copyright (c) 2021-2024 Diality Inc. - All Rights Reserved. # # THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN # WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. # # @file enum_updater.py # # @author (last) Zoltan Miskolci # @date (last) 05-Aug-2026 # @author (original) Zoltan Miskolci # @date (original) 05-Aug-2026 # ############################################################################ # Module imports from clang.cindex import Index, CursorKind, Config from datetime import datetime from os import path import re import subprocess from typing import List Config.set_library_file("/usr/lib/llvm-18/lib/libclang.so") class EnumUpdater: @staticmethod def execute() -> None: """ Read the enums from the C++ file and then update the Python Enum definitions """ cpp_repo_loc = path.abspath(f'{path.dirname(__file__)}/../../leahi-common') files_to_check = [] files_to_check.append(f'{cpp_repo_loc}/AlarmDefs.h') files_to_check.append(f'{cpp_repo_loc}/MsgDefs.h') _update_cpp_repo(cpp_repo_loc) common_location = path.abspath(f'{path.dirname(__file__)}/../common') file_to_update = {'AlarmList': (f'{common_location}/alarm_defs.py', 'AlarmEnum', False), 'RequestRejectReasons': (f'{common_location}/msg_defs.py', 'DialinEnum', False), 'MsgIds': (f'{common_location}/msg_ids.py', 'DialinEnum', True), # 'TestConfig': (f'{common_location}/test_config_defs.py', 'DialinEnum', False) } for file in files_to_check: if path.exists(file): extracted_cpp_enums = _extract_cpp_enums(file) for enum_name in extracted_cpp_enums: if enum_name in file_to_update: # Extract information from the dictionary python_file = file_to_update[enum_name][0] enum_type_name = file_to_update[enum_name][1] is_enum_hex = file_to_update[enum_name][2] # Extract the original Python enums from the file original_py_enums = _extract_python_enums(python_file) new_enum = list(extracted_cpp_enums[enum_name]) # Get the enum member list cpp_enum_values = [ item['value'] for item in extracted_cpp_enums[enum_name] ] # Add the original Python enum members that are not present in the C++ enum new_enum.extend( item for item in original_py_enums.get(enum_name, []) if item['value'] not in cpp_enum_values) # Sort the new enum list by the value to maintain order new_enum.sort(key=lambda x: x['value']) # Generate the updated Python enum file _generate_python_enum_file(file_location = python_file, enum_name = enum_name, enum_member_list = new_enum, enum_type = enum_type_name, is_hex = is_enum_hex) print(f'Updated enum: {enum_name}') # ============================================================ Private Methods ============================================================ def _extract_cpp_enums(cpp_file): """ Extract the enums from the provided C++ file :param cpp_file: (String) The C++ file with full location :return: (Dictionary) The collected enums """ index = Index.create() tu = index.parse(cpp_file) enums = {} for cursor in tu.cursor.walk_preorder(): if cursor.kind == CursorKind.ENUM_DECL: # Update name to be CamelCase enum_name = "".join(part.capitalize() for part in cursor.spelling.split("_") if part) enums[enum_name] = [] for item in cursor.get_children(): if item.kind == CursorKind.ENUM_CONSTANT_DECL: if item.spelling in ['MSG_ID_FIRST_TD_TESTER_MESSAGE', 'MSG_ID_FIRST_DD_TESTER_MESSAGE', 'MSG_ID_FIRST_FP_TESTER_MESSAGE']: continue enums[enum_name].append({ "member": item.spelling, "value": item.enum_value, "comment": item.brief_comment, }) return enums def _extract_python_enums(py_file): """ Extract the enums from the provided Python file :param py_file: (String) The Python file with full location :return: (Dictionary) The collected enums """ result = {} inside_enum = False enum_name = None with open(py_file, "r", encoding="utf-8") as f: for line in f: # If we are not inside an enum, check if this line declares an enum class if not inside_enum: match = re.match(r"class (\w+)\(\w+Enum\):", line) if match: enum_name = match.group(1) result[enum_name] = [] inside_enum = True continue # If we are inside an enum, process the enum members else: # Skip empty lines within the enum definition if line.strip() == '': continue # If the line is not indented, it means the enum definition has ended if not line.startswith(' '): inside_enum = False continue match = re.match(r"\s*(\w+)\s*=\s*([0-9A-Fa-fx]+)\s*#\s*(.*)", line ) if match: member, value, comment = match.groups() result[enum_name].append( { "member": member, "value": int(value, 0), "comment": comment, } ) return result def _generate_python_enum_file(file_location: str, enum_name: str, enum_member_list: List[dict], enum_type: str='Enum', is_hex: bool=False) -> None: """ Generate the enum file content :param file_location: (String) The enum file with full location :param enum_name: (String) The enum's name :param enum_member_list: (List of Dictionaries) The information of the enum members :param enum_type: (String) The enum type to be the parent :param is_hex: (Boolean) The member's values are hex or not :return: None """ now = datetime.now().strftime("%d-%b-%Y") file_name = file_location.split('/')[-1] lines = [] lines.append('###########################################################################') lines.append('#') lines.append('# Copyright (c) 2020-2024 Diality Inc. - All Rights Reserved.') lines.append('#') lines.append('# THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM IN PART OR IN') lines.append('# WHOLE WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER.') lines.append('#') lines.append(f'# @file {file_name}') lines.append('#') lines.append('# @author (last) Enum Updater Script') lines.append(f'# @date (last) {now}') lines.append('# @author (original) Zoltan Miskolci') lines.append('# @date (original) 31-Jul-2026') lines.append('#') lines.append('############################################################################') lines.append('from enum import unique') lines.append(f'from leahi_dialin.utils.enums import {enum_type}') lines.append('') lines.append('') lines.append('@unique') lines.append(f'class {enum_name}({enum_type}):') # Add the members for i in range(len(enum_member_list)): member = enum_member_list[i] if i != 0 and member['value'] != enum_member_list[i-1]['value'] + 1: lines.append('') value = member['value'] if is_hex: value = hex(member['value']).upper().replace('X','x') lines.append(f' {member["member"]:<75} = {value:<10} # {member["comment"]}') lines.append('') # Add cutom code for specific enum or enum types lines.extend(_custom_code_generator(enum_name, enum_type)) # Write into the file with open(file_location, "w") as f: f.write("\n".join(lines)) def _custom_code_generator(enum_name:str, enum_type:str) -> List[str]: """ Add cutom code for specific enum or enum types :param enum_name: (String) The enum's name :param enum_type: (String) The enum type to be the parent :return: (List[str]) The custom code lines """ lines = [] # Add the _str_list for python 3.6 compatibility if enum_type == 'DialinEnum': lines.append(f'{enum_name}._str_list = ' + '{ }') lines.append('') if enum_name == 'RequestRejectReasons': lines.append(f'ACK_NOT_REQUIRED = [ ]') lines.append('') return lines def _update_cpp_repo(cpp_repo: str) -> None: """ Update the C++ repository to ensure the latest changes are available for parsing. :param cpp_repo: (String) The C++ folder with full location :return: None """ try: # # Switch to the staging branch # subprocess.run( # ["git", "-C", cpp_repo, "checkout", "staging"], # check=True # ) # Get latest refs from remote subprocess.run( ["git", "-C", cpp_repo, "fetch", "origin"], check=True ) # Discard all local changes subprocess.run( ["git", "-C", cpp_repo, "reset", "--hard", "HEAD"], check=True ) subprocess.run( ["git", "-C", cpp_repo, "pull"], check=True, capture_output=True, text=True ) print(f"Successfully updated repository: {cpp_repo}") except subprocess.CalledProcessError as e: print(f"Failed to update repository: {cpp_repo}") print(e.stderr) # ============================================================ Main ============================================================ if __name__ == '__main__': EnumUpdater.execute()