#!/usr/bin/env python3 """Loads a "target" YAML file and overwrites its values with values from "override" YAML files. Override files are processed in the order given. Usage: merge_settings.py [override/remove yamls] """ import sys import yaml import copy def dict_merge(target, source, remove): """Deep merge for dicts. Works like dict.update() that recursively updates/removes any dict values present in both parameters. Args: target (dict): Values to be overwritten by corresponding values from `source`. source (dict): Overriding values. Not changed by call. remove (bool): If this is true then it removes the entry provided in 'source' along with all entries under it from 'target'. Otherwise it overrides the values from 'source' Returns: `target` with values overwritten/removed from those in `source` at any and all levels of nested dicts. """ if not isinstance(source, dict): return source for k, v in source.items(): if k in target and isinstance(target[k], dict): dict_merge(target[k], v, remove) else: if remove is True and k in target: target.pop(k) else: target[k] = copy.deepcopy(v) return target if len(sys.argv) < 2: sys.exit('Argument required: target yaml') if len(sys.argv) == 2: # No overrides to handle sys.exit() target_filename = sys.argv[1] with open(target_filename) as target_file: data = yaml.safe_load(target_file) print('Loaded target YAML file ' + target_filename) for override_filename in sys.argv[2:]: if override_filename.endswith('.override.yml'): with open(override_filename) as override_file: override = yaml.safe_load(override_file) dict_merge(data, override, False) print('Merged override YAML file ' + override_filename) elif override_filename.endswith('.remove.yml'): with open(override_filename) as override_file: override = yaml.safe_load(override_file) dict_merge(data, override, True) print('Removed data from source YAML file' + override_filename) with open(target_filename, 'w') as target_file: yaml.dump(data, target_file) print('Wrote merged target YAML file ' + target_filename)