0
0
mirror of https://gitlab.nic.cz/labs/bird.git synced 2024-12-23 02:01:55 +00:00

Python Config Generator: allow comments everywhere

This commit is contained in:
Maria Matejka 2023-05-19 10:00:19 +02:00
parent 49ac4bd161
commit 8496c4b469
3 changed files with 67 additions and 41 deletions

View File

@ -1,22 +1,34 @@
from datetime import datetime from datetime import datetime
from weakref import WeakKeyDictionary from weakref import WeakKeyDictionary
import itertools
class ConfigObject: class ConfigObject:
def __init__(self): def __init__(self, comment=None):
self.symbols = {} self.symbols = {}
self.config = WeakKeyDictionary() self.config = WeakKeyDictionary()
self.comment = None self.comment = comment
def __str__(self): def isp(self, indent):
return "" if self.comment is None else f"# {self.comment}\n" 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): class Timestamp(ConfigObject):
def __init__(self, comment): def __init__(self, comment):
super().__init__() super().__init__(comment=f"{comment} at {datetime.now()}")
self.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): if not isinstance(value, _type):
raise Exception("BlockOption value doesn't match declared type") raise Exception("BlockOption value doesn't match declared type")
@ -24,47 +36,59 @@ class BlockOption:
self._type = _type self._type = _type
self.value = value self.value = value
def set(self, value): def set(self, value, **kwargs):
if value == self.value: if value == self.value and len(kwargs) == 0:
return self return self
else: 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 = { options = {
"disabled": BlockOption("disabled", bool, False), "disabled": BlockOption("disabled", bool, False),
} }
def __init__(self, name=None, template=None, **kwargs): def __init__(self, name=None, template=None, **kwargs):
super().__init__() super().__init__(**kwargs)
self.name = name self.name = name
if template is not None: if template is not None:
raise NotImplementedError() raise NotImplementedError()
self.options_set = {} def block_header(self):
for k in kwargs: return f"protocol {self.protocol_type}{'' if self.name is None else ' ' + self.name }"
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"
class DeviceProtocolConfig(ProtocolConfig): class DeviceProtocolConfig(ProtocolConfig):
name_prefix = "device" name_prefix = "device"

View File

@ -23,7 +23,7 @@ class Config:
def __enter__(self): def __enter__(self):
if self.config.auto_device: 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.begin = Timestamp("Config dump started")
self.config.add(self.begin) self.config.add(self.begin)
@ -37,12 +37,12 @@ class Config:
if isinstance(i, DeviceProtocolConfig): if isinstance(i, DeviceProtocolConfig):
self.auto_device = None self.auto_device = None
_file.write(str(i)) i.writelines(_file)
if self.auto_device is not None: 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): def __exit__(self, *args):
self.config.remove(self.begin) self.config.remove(self.begin)

View File

@ -2,6 +2,8 @@ from BIRD import Config
from BIRD.Config import DeviceProtocolConfig from BIRD.Config import DeviceProtocolConfig
cf = Config() 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") cf.write("test.conf")