#!/usr/bin/env python3 """Read the default configuration file, and create ID enums for sections and options. """ import argparse import configparser import json import os.path from collections import defaultdict from itertools import count from pprint import pprint class ConfigToEnum: """ConfigToEnum scans a configuration file, and dumps the secions and options within a section as Python Enums""" REGISTRY_COMMENT = """The configuration registry is a historic record of all configuration identifiers. Do NOT edit this file manually, or else, the communication protocol may break.""" def __init__(self, cfg_file, config_ids): """Initialize the object""" self.config = configparser.ConfigParser(default_section=None, interpolation=None) self.config.optionxform = str self.config.read(cfg_file) self.registry_file = config_ids try: with open(self.registry_file, encoding='utf-8') as regfd: self.config_ids = json.load(regfd) except Exception: # On any error, ignore it and start with a clean slate self.config_ids = {} self.sections = {} self.options = {} def parse(self): """Parse the config object and assign IDs""" self._parse_sections() for section in self.config.sections(): self._parse_options(section) def _parse_sections(self): """Assign IDs to each section""" sections = {} unassigned = [] for section in self.config.sections(): section = section.upper() section_id = self.config_ids.get('sections', {}).get(section) if section_id is None: unassigned.append(section) else: sections[section] = section_id if not sections: counter = count(1) else: counter = count(max(sections.values()) + 1) sections.update({k:v for k, v in zip(unassigned, counter)}) orig_sections = self.config_ids.get('sections', {}) sections.update({k:v for k, v in orig_sections.items() if k not in sections}) self.sections = sections def _parse_options(self, section): options = {} unassigned = [] for option in self.config.options(section): option = option.upper() section = section.upper() option_id = self.config_ids.get('options', {}).get(section, {}).get(option) if option_id is None: unassigned.append(option) else: options[option] = option_id if not options: counter = count(1) else: counter = count(max(options.values()) + 1) options.update({k:v for k, v in zip(unassigned, counter)}) orig_options = self.config_ids.get('options', {}).get(section, {}) # Make sure that we have all the entries already options.update({k:v for k, v in orig_options.items() if k not in options}) self.options[section] = options def save_registry(self): """Save the generated registry""" registry = { "_comment": self.REGISTRY_COMMENT, "sections": self.sections, "options": self.options, } with open(self.registry_file, 'w', encoding='utf-8') as regfd: json.dump(registry, regfd, indent=4) def generate_c_definitions(self, output_header, output_source): """Generate the C definitions""" with open(output_header, 'w', encoding='utf-8') as out_fd: include_guard = os.path.basename(output_header).replace('-', '_').replace('.', '_').upper() print("// Autogenerated config identifiers - DO NOT EDIT\n", file=out_fd) print(f"#ifndef {include_guard}", file=out_fd) print(f"#define {include_guard}", file=out_fd) print(file=out_fd) max_sec_val = max(self.sections.values()) + 1 max_opt_val_global = 0 for section, value in self.sections.items(): print(f"#define CFG_SECTION_{section} {value}", file=out_fd) max_opt_val = max(self.options[section].values()) + 1 max_opt_val_global = max(max_opt_val, max_opt_val_global) for option, value in self.options[section].items(): print(f"#define CFG_OPTION_{section}_{option} {value}", file=out_fd) print(f"#define CFG_OPTION_{section}_MAX_OPTIONS {max_opt_val}\n", file=out_fd) print(f"#define CFG_SECTION_MAX {max_sec_val}\n", file=out_fd) print(f"#define CFG_SECTION_MAX_OPT_VAL {max_opt_val_global}\n", file=out_fd) print("extern const char * section_names[CFG_SECTION_MAX];", file=out_fd) print("extern const char * option_names[CFG_SECTION_MAX][CFG_SECTION_MAX_OPT_VAL];", file=out_fd) print(f"#endif // !defined {include_guard}", file=out_fd) with open(output_source, 'w', encoding='utf-8') as out_fd: print("// Autogenerated config string table - DO NOT EDIT\n", file=out_fd) print(f'#include "{os.path.basename(output_header)}"', file=out_fd) print("const char * section_names[CFG_SECTION_MAX] = {", file=out_fd) for section, value in self.sections.items(): print(f' [{value}] = "{section.lower()}",', file=out_fd) print("};\n", file=out_fd) print("const char * options_names[CFG_SECTION_MAX][CFG_SECTION_MAX_OPT_VAL] = {", file=out_fd) for section, value in self.sections.items(): print(f' [{value}] =', '{', file=out_fd) for option, value in self.options[section].items(): print(f' [{value}] = "{option.lower()}",', file=out_fd) print(' },', file=out_fd) print("};\n", file=out_fd) def generate_py_definitions(self, output_file): """Generate the Python definitions""" try: out_fd = open(output_file, 'w', encoding='utf-8') print("'''Autogenerated config identifiers from x52d.conf'''", file=out_fd) print("# DO NOT EDIT\n", file=out_fd) print("from enum import Enum", file=out_fd) print("\nclass Section(Enum):", file=out_fd) print(" '''Section identifiers'''", file=out_fd) for section, value in self.sections.items(): print(f" {section} = {value}", file=out_fd) for section in self.sections.keys(): print(f"\nclass {section}(Enum):", file=out_fd) print(f" '''Section {section} identifiers'''", file=out_fd) for option, value in self.options[section].items(): print(f" {option} = {value}", file=out_fd) finally: out_fd.close() def main(): parser = argparse.ArgumentParser(description="Generate C enum and python enum for config") parser.add_argument('input_file') parser.add_argument('registry') parser.add_argument('output_c_header') parser.add_argument('output_c_strings') parser.add_argument('output_py_defs') args = parser.parse_args() c2e = ConfigToEnum(args.input_file, args.registry) c2e.parse() c2e.save_registry() c2e.generate_c_definitions(args.output_c_header, args.output_c_strings) c2e.generate_py_definitions(args.output_py_defs) if __name__ == '__main__': main()