import contextlib import os import yaml import sys from glob import glob @contextlib.contextmanager def pushd(newDir): previousDir = os.getcwd() os.chdir(newDir) try: yield finally: os.chdir(previousDir) class DependencyException(Exception): pass def deptree(sources, deps=None): if deps is None: deps = [] deferred = [] for name in sources.keys(): source = sources[name] if "depends" not in source: deps.append(name) else: deferred.append(name) while deferred: name = deferred.pop() depends = sources[name]["depends"] # todo - detect dependency loop if name in depends: raise DependencyException('Source {} depends upon itself'.format(name)) elif set(depends).issubset(set(deps)): deps.append(name) elif not set(depends).issubset(set(deps)): missing = ', '.join(set(depends).difference(set(deps))) raise DependencyException( 'Source {0} has missing dependencies: {1}'.format(name, missing)) else: deferred.append(name) return deps def main(args): with pushd('etc/backup.d'): with open("backup.yml") as f: config = yaml.load(f) sources = {} for source in config['sources']: source = os.path.realpath(source) for path in glob('{}/*.yml'.format(source)): path = os.path.realpath(path) with pushd(os.path.dirname(path)), open(path) as f: data = yaml.load(f) if "depends" in data: for i in range(0, len(data["depends"])): data["depends"][i] = os.path.realpath('{}.yml'.format(data["depends"][i])) sources[path] = data config['sources'] = sources import json for name in deptree(config["sources"]): source = config["sources"][name] print(json.dumps(source, indent=2)) if __name__ == '__main__': try: main(sys.argv[1:]) except DependencyException as ex: print(ex) sys.exit(1) except Exception: from traceback import format_exc msg = "Error encountered:\n" + format_exc().strip() print(msg) sys.exit(1)