diff --git a/python/BIRD/Config.py b/python/BIRD/Config.py index 79208f5f..5daf3c54 100644 --- a/python/BIRD/Config.py +++ b/python/BIRD/Config.py @@ -1,22 +1,34 @@ from datetime import datetime from weakref import WeakKeyDictionary +import itertools + class ConfigObject: - def __init__(self): + def __init__(self, comment=None): self.symbols = {} self.config = WeakKeyDictionary() - self.comment = None + self.comment = comment - def __str__(self): - return "" if self.comment is None else f"# {self.comment}\n" + def isp(self, indent): + return " " * indent + + def append(self): + return "" if self.comment is None else f" # {self.comment}" + + def lines(self, indent): + return [] if self.comment is None else [ f"{self.isp(indent)}# {self.comment}" ] + + def writelines(self, f): + f.writelines([ x + "\n" for x in self.lines(0) ]) class Timestamp(ConfigObject): def __init__(self, comment): - super().__init__() - self.comment = f"{comment} at {datetime.now()}" + super().__init__(comment=f"{comment} at {datetime.now()}") + +class BlockOption(ConfigObject): + def __init__(self, config_text, _type, value, **kwargs): + super().__init__(**kwargs) -class BlockOption: - def __init__(self, config_text, _type, value): if not isinstance(value, _type): raise Exception("BlockOption value doesn't match declared type") @@ -24,47 +36,59 @@ class BlockOption: self._type = _type self.value = value - def set(self, value): - if value == self.value: + def set(self, value, **kwargs): + if value == self.value and len(kwargs) == 0: return self else: - return type(self)(self.config_text, self._type, value) + return type(self)(self.config_text, self._type, value, **kwargs) -class ProtocolConfig(ConfigObject): + def lines(self, indent): + return [ f"{self.isp(indent)}{self.config_text} {self.value};{self.append()}" ] + +class ConfigBlock(ConfigObject): + def __init__(self, **kwargs): + super().__init__(**{ k:v for k,v in kwargs.items() if k not in self.options }) + + self.options_set = {} + for k,v in kwargs.items(): + if k in self.options: + self.options_set[k] = self.options[k].set(v) + + def set(self, arg, val, **kwargs): + if arg not in self.options: + raise NotImplementedError + + self.options_set[arg] = self.options[arg].set(val, **kwargs) + + def lines(self, indent): + inside = [ + (opt.lines(indent+1)) + for k,opt in self.options_set.items() + if opt != self.options[k] + ] + + header = self.block_header() + isp = " " * indent + + if len(inside) == 0: + return [ header + " {}" + self.append() ] + else: + return [ *super().lines(indent), isp + header + " {", *itertools.chain(*inside), isp + "}" ] + +class ProtocolConfig(ConfigBlock): options = { "disabled": BlockOption("disabled", bool, False), } def __init__(self, name=None, template=None, **kwargs): - super().__init__() + super().__init__(**kwargs) self.name = name if template is not None: raise NotImplementedError() - self.options_set = {} - for k in kwargs: - if k not in self.options: - raise NotImplementedError() - - self.options_set[k] = self.options[k].set(kwargs[k]) - - def block_inside(self, indent): - if len(self.options_set) == 0: - return None - - return ("\n" + " " * indent).join([""] + [ - f"{opt.config_text} {opt.value};" for k,opt in self.options_set.items() if opt != self.options[k] - ]) - - def __str__(self): - inside = self.block_inside(1) - header = f"protocol {self.protocol_type}{'' if self.name is None else ' ' + self.name }" - - if inside is None: - return header + " {}\n" - else: - return header + " {" + inside + "\n}\n" + def block_header(self): + return f"protocol {self.protocol_type}{'' if self.name is None else ' ' + self.name }" class DeviceProtocolConfig(ProtocolConfig): name_prefix = "device" diff --git a/python/BIRD/__init__.py b/python/BIRD/__init__.py index 74eff553..2d49ef49 100644 --- a/python/BIRD/__init__.py +++ b/python/BIRD/__init__.py @@ -23,7 +23,7 @@ class Config: def __enter__(self): if self.config.auto_device: - self.auto_device = DeviceProtocolConfig() + self.auto_device = DeviceProtocolConfig(comment="Default device protocol; set Config(auto_device=False) to remove") self.begin = Timestamp("Config dump started") self.config.add(self.begin) @@ -37,12 +37,12 @@ class Config: if isinstance(i, DeviceProtocolConfig): self.auto_device = None - _file.write(str(i)) + i.writelines(_file) if self.auto_device is not None: - _file.write(str(self.auto_device)) + self.auto_device.writelines(_file) - _file.write(str(Timestamp("Config dump finished"))) + Timestamp("Config dump finished").writelines(_file) def __exit__(self, *args): self.config.remove(self.begin) diff --git a/python/conftest.py b/python/conftest.py index 97cb68c9..5f1c1b6a 100644 --- a/python/conftest.py +++ b/python/conftest.py @@ -2,6 +2,8 @@ from BIRD import Config from BIRD.Config import DeviceProtocolConfig cf = Config() -cf.add(DeviceProtocolConfig(name="foo", scan_time=42)) +cf.add(dev := DeviceProtocolConfig(name="foo", comment="my own device protocol")) +dev.set("scan_time", 86400, comment="once a day, my interfaces never change") +#cf.add(DeviceProtocolConfig(name="foo", scan_time=42, comment="my own device protocol")) cf.write("test.conf")