libx52/daemon/x52d_map_config.py

198 lines
7.4 KiB
Python
Executable File

#!/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()