--- a/config.py Thu Jan 18 23:01:16 2018 +0000 +++ b/config.py Fri Jan 19 10:41:01 2018 +0000 @@ -7,7 +7,95 @@ import os import xdg import string +from functools import reduce +# +# Defaults +# + +defaults={ + # Default: backup every 6 hours (21600 seconds) + 'backup_interval': 21600, + # Default: retry every 15 minutes if unable to connect / unfinished backup + 'retry_interval': 900, + # borg + 'borg': { + 'executable': 'borg', + 'common_parameters': [], + 'create_parameters': [], + } +} + + +# +# Type checking etc. +# + +def error(x): + raise AssertionError(x) + +def check_string(cfg, field, descr, loc, default=None): + return check_field(cfg, field, descr, loc, default, + lambda x: isinstance(x, str)) + +def check_dict(cfg, field, descr, loc, default=None): + return check_field(cfg, field, descr, loc, default, + lambda x: isinstance(x, dict)) + +def check_list(cfg, field, descr, loc, default=None): + return check_field(cfg, field, descr, loc, default, + lambda x: isinstance(x, list)) + +def is_list_of(x, chk): + if x is None: + return True + elif isinstance(x, list): + return reduce(lambda y, z: y and chk(z), x, True) + else: + return False + +def check_list_of_dicts(cfg, field, descr, loc, default=None): + return check_field(cfg, field, descr, loc, default, + lambda x: is_list_of(x, lambda z: isinstance(z, dict))) + +def check_list_of_strings(cfg, field, descr, loc, default=None): + return check_field(cfg, field, descr, loc, default, + lambda x: is_list_of(x, lambda z: isinstance(z, str))) + +def check_nonempty_list_of_strings(cfg, field, descr, loc): + return check_list_of_strings(cfg, field, descr, loc) and cfg[field] + + +def check_nonneg_int(cfg, field, descr, loc, default=None): + return check_field(cfg, field, descr, loc, default, + lambda x: isinstance(x, int) and x>=0) + +def check_field(cfg, field, descr, loc, default, check): + if field in cfg: + tmp=cfg[field] + if not check(tmp): + error("%s is of invalid type for %s" % (field, loc)) + return tmp + else: + if default is not None: + return default + else: + error("%s is not configured for %s" % (field, loc)) + +# +# Conversion of config into command line +# + +def arglistify(args): + flatten=lambda l: [item for sublist in l for item in sublist] + if args is None: + return [] + else: + return flatten([['--' + key, str(d[key])] for d in args for key in d]) + +# +# Load config on module load +# def expand_env(cfg, env): if isinstance(cfg, dict): @@ -17,8 +105,8 @@ elif isinstance(cfg, str): out=string.Template(cfg).substitute(os.environ) else: - out=cfg - + out=cfg + return out cfgfile=os.path.join(xdg.XDG_CONFIG_HOME, "borgend", "config.yaml") @@ -29,3 +117,23 @@ with io.open(cfgfile, 'r') as file: settings=expand_env(yaml.load(file), os.environ); +# +# Verify basic settings +# + +if 'borg' not in settings: + settings['borg']=defaults['borg'] +else: + def check_and_set(cfg, field, loc, defa, fn): + cfg[field]=fn(cfg, field, field, loc, defa[field]) + return cfg + + settings['borg']=check_and_set(settings['borg'], 'executable', 'borg', + defaults['borg'], check_string) + + settings['borg']=check_and_set(settings['borg'], 'common_parameters', 'borg', + defaults['borg'], check_list_of_dicts) + + settings['borg']=check_and_set(settings['borg'], 'create_parameters', 'borg', + defaults['borg'], check_list_of_dicts) +