#! /usr/bin/env python3 # encoding: utf-8 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. Any contained dicts are automatically converted to Config instances as well. Supports sequential overwriting - so one can load a default config and then 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=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): 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() def values(self): return self.raw.values() def items(self): return self.raw.items() def __getitem__(self, name): return self.raw[name] def __setitem__(self, name, value): if self.readonly: raise AttributeError("config is read-only") self.raw[name] = value def __setattr__(self, name, value): if self.readonly: raise AttributeError("config is read-only") self.raw[name] = value def __getattr__(self, name): matches = [key for key in self.raw if key.replace("-", "_") == name] if matches: assert len(matches) == 1 value = self.raw[matches[0]] if type(value) == dict: return Config(value) else: return value else: raise KeyError(name) def __contains__(self, name): return name in self.raw def __repr__(self): return "Config(%s)" %(" ".join("%s=%s" %(k, "…" if type(v) == dict else v) for k, v in self.raw.items()))