import re import subprocess import os from enum import Enum class CfgVar: def __init__(self, key, value=None, value_type='STRING', comment=""): self.key = key self.value = value self.value_type = value_type self.comment = comment def __repr__(self): return f"CfgVar(key='{self.key}', value={self.value}, value_type='{self.value_type}', comment='{self.comment}')" TokenType = Enum('TokenType', ['BRACE_OPEN', 'BRACE_CLOSE', 'VARIABLE', 'NAMESPACE']) class Token: def __init__(self, type): self.type = type def __repr__(self): return str(self.type) class Variable(Token): def __init__(self, name, comment=""): super().__init__(TokenType.VARIABLE) self.name = name self.comment = comment def __repr__(self): return f"VAR {self.name} [{self.comment}]" class Namespace(Token): def __init__(self, name): super().__init__(TokenType.NAMESPACE) self.name = name def __repr__(self): return f"NAMESPACE {self.name}" def tokenize(file_path): text = "" if os.path.exists(file_path): text = open(file_path, "r", encoding="utf-8").read() tokens = [] it = text.__iter__() try: char = it.__next__() while True: if char == "{": tokens.append(Token(TokenType.BRACE_OPEN)) char = it.__next__() elif char == "}": tokens.append(Token(TokenType.BRACE_CLOSE)) char = it.__next__() elif char.isalpha(): name = char char = it.__next__() while char.isalnum() or char == "_" or char == ".": name += char char = it.__next__() char = it.__next__() while char in (" ", "\t"): char = it.__next__() if char == "=": tokens.append(Variable(name)) char = it.__next__() while char != ";": char = it.__next__() char = it.__next__() while char in (" ", "\t"): char = it.__next__() if char == "#": char = it.__next__() while char in (" ", "\t"): char = it.__next__() comment = "" while char != "\n": comment += char char = it.__next__() if tokens[-1].type == TokenType.VARIABLE: tokens[-1].comment = comment char = it.__next__() else: tokens.append(Namespace(name)) elif char == "#": while char != "\n": char = it.__next__() char = it.__next__() else: char = it.__next__() except StopIteration as e: return tokens def parse(file_path): stack = [] data = dict() tokens = tokenize(file_path) it = tokens.__iter__() try: while True: token = it.__next__() if token.type == TokenType.NAMESPACE: stack.append(token.name) if token.type == TokenType.BRACE_CLOSE: stack.pop() if token.type == TokenType.VARIABLE: if stack: path = '.'.join(stack) + f'.{token.name}' else: path = token.name data[path] = CfgVar(token.name, comment=token.comment) except StopIteration as e: return data def load_config(file_path): config = parse(file_path) output = subprocess.run(["config-parser/parser-cli", file_path], capture_output=True, text=True) for line in output.stdout.split("\n"): match = re.match(r" > (\w+) '(.*)' = *(.*)", line) if match: value_type, path, value = match.groups() config[path].value = get_value_from_str(value, value_type) config[path].value_type = value_type return config def construct_nested_dict(config): config_nested = dict() for path in config: nss = path.split(".") cur_ns = config_nested for ns in nss[:-1]: if ns not in cur_ns: cur_ns[ns] = {} cur_ns = cur_ns[ns] cur_ns[nss[-1]] = config[path] return config_nested def get_str_from_value(value, value_type): if not value: if value_type == 'STRING': return '""' elif value_type == 'INT': return '0' elif value_type == 'DOUBLE': return '0.0' elif value_type == 'BOOLEAN': return 'false' else: if value_type == 'STRING': return f'"{value}"' elif value_type == 'BOOLEAN': return str(value).lower() elif value_type == 'DOUBLE': return "{:.7f}".format(float(value)) return str(value) def get_value_from_str(string, value_type): if not string: if value_type == 'STRING': return "" elif value_type == 'INT': return 0 elif value_type == 'DOUBLE': return 0.0 elif value_type == 'BOOLEAN': return False else: if value_type == 'STRING': return string elif value_type == 'BOOLEAN': return string == 'true' elif value_type == 'DOUBLE': return float(string) elif value_type == 'INT': return int(string) def write_namespace(file, ns, ns_name, indent): file.write("\n") file.write(" " * indent + ns_name + "\n") file.write(" " * indent + "{\n") for key in ns: if isinstance(ns[key], dict): write_namespace(file, ns[key], key, indent + 4) else: file.write(" " * (indent + 4) + f"{key} = {get_str_from_value(ns[key].value, ns[key].value_type)};\n") file.write(" " * indent + "}\n") file.write("\n") def dump_config(config, file_path): config_nested = construct_nested_dict(config) indent = 0 with open(file_path, "w", encoding="utf-8") as file: for key in config_nested: if isinstance(config_nested[key], dict): write_namespace(file, config_nested[key], key, indent) else: file.write(f"{key} = {get_str_from_value(config_nested[key].value, config_nested[key].value_type)};\n") if __name__ == '__main__': print(load_config('config.txt'))