
Config now supports full hierarchical and overlay-capable usage. A generic tree overlay function is in aux.
94 lines
2.1 KiB
Python
94 lines
2.1 KiB
Python
#! /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()))
|