]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
Python Config Generator: allow comments everywhere
authorMaria Matejka <mq@ucw.cz>
Fri, 19 May 2023 08:00:19 +0000 (10:00 +0200)
committerMaria Matejka <mq@ucw.cz>
Tue, 23 May 2023 11:44:48 +0000 (13:44 +0200)
python/BIRD/Config.py
python/BIRD/__init__.py
python/conftest.py

index 79208f5ff6960e3e5aeb78ff1caa66edb65589bc..5daf3c54e60cdd4835169eb22caf694d5edd788b 100644 (file)
@@ -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 isp(self, indent):
+        return "  " * indent
+
+    def append(self):
+        return "" if self.comment is None else f" # {self.comment}"
 
-    def __str__(self):
-        return "" if self.comment is None else f"# {self.comment}\n"
+    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)
+
+    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(ConfigObject):
+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"
index 74eff5534282f38ec2b6aa47ae56c85c07044a1b..2d49ef49f5050afded9e6efffcc8725a142ae995 100644 (file)
@@ -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)
index 97cb68c98c8ccb85c902ecc2598c3c8983e17895..5f1c1b6aa6160770ba3666b54180349ab3e09601 100644 (file)
@@ -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")