diff --git a/dcd/aux.py b/dcd/aux.py index 39af873..c33f900 100644 --- a/dcd/aux.py +++ b/dcd/aux.py @@ -77,3 +77,32 @@ def hex_to_raw(text, separator=" "): return bytearray(output) # ---------------------------------------------------------------- + +class DeleteOverlay: + pass + +def overlay_tree(base, overlay): + """ + For every key path in `overlay`, sets the leaf value of the same + key path in `base`. Any missing non-leaf keys will be created. + + To delete a key, set its overlay value to DeleteOverlay. + """ + + stack = [(base, overlay)] + + while stack: + base_p, over_p = stack.pop() + + for k, v in over_p.items(): + if v is DeleteOverlay: + del base_p[k] + else: + if type(v) is dict: + if k not in base_p or type(base_p[k]) is not dict: + base_p[k] = {} + + stack.append((base_p[k], over_p[k])) + + else: + base_p[k] = v diff --git a/dcd/cfg.py b/dcd/cfg.py index 4f0053a..b062463 100644 --- a/dcd/cfg.py +++ b/dcd/cfg.py @@ -4,6 +4,18 @@ import jsmin import json +from . import aux + +def _read_json(path): + """ + Reads a data structure from a JSON file pointed to by `path`. + + Removes any C/JS-like comments from it. + """ + + with open(path) as f: + return json.loads(jsmin.jsmin(f.read())) + class Config: """ A hierarchical key-value container that can be loaded from JSON or a Python dict. @@ -11,48 +23,30 @@ class Config: Any contained dicts are automatically converted to Config instances as well. Supports sequential overwriting - so one can load a default config and then - progressively overwrite it with overlays: - - - - - - - - >>> raw -{'a': 1, 'b': {'ba': 2, 'bb': 3, 'bc': {'bca': None}}, 'c': 4} ->>> overlay -{'b': {'bc': {'bca': 42}}} - - - - while stack: - current = stack[-1] - - ... - - stack.pop() - -for key in overlay.keys(): - - + progressively overlay more on top: + >>> c = Config.from_file("/usr/lib/app/default.cfg") + >>> c.update_from_file("/etc/app/config.cfg") + >>> c.update({"key", value, "subtree": {"key": "value"}}) """ - def __init__(self, d, readonly=False): + def __init__(self, d=None, readonly=False): + if d is None: + d = {} + assert type(d) == dict object.__setattr__(self, "raw", d) object.__setattr__(self, "readonly", readonly) @classmethod def from_file(cls, path, readonly=False): - with open(path) as f: - initial = json.loads(jsmin.jsmin(f.read())) - - return cls(initial, readonly) + return cls(_read_json(path), readonly) def update(self, d): - + aux.overlay_tree(self.raw, d) + + def update_from_file(self, path): + self.update(_read_json(path)) def keys(self): return self.raw.keys() diff --git a/setup.py b/setup.py old mode 100644 new mode 100755