def _merge_yaml(dest, source): """Merges source into dest; entries in source take precedence over dest. This routine may modify dest and should be assigned to dest, in case dest was None to begin with, e.g.: dest = _merge_yaml(dest, source) Config file authors can optionally end any attribute in a dict with `::` instead of `:`, and the key will override that of the parent instead of merging. """ def they_are(t): return isinstance(dest, t) and isinstance(source, t) # If source is None, overwrite with source. if source is None: return None # Source list is prepended (for precedence) if they_are(list): dest[:] = source + [x for x in dest if x not in source] return dest # Source dict is merged into dest. elif they_are(dict): # track keys for marking key_marks = {} for sk, sv in iteritems(source): if _override(sk) or sk not in dest: # if sk ended with ::, or if it's new, completely override dest[sk] = copy.copy(sv) else: # otherwise, merge the YAML dest[sk] = _merge_yaml(dest[sk], source[sk]) # this seems unintuitive, but see below. We need this because # Python dicts do not overwrite keys on insert, and we want # to copy mark information on source keys to dest. key_marks[sk] = sk # ensure that keys are marked in the destination. The # key_marks dict ensures we can get the actual source key # objects from dest keys for dk in list(dest.keys()): if dk in key_marks and syaml.markable(dk): syaml.mark(dk, key_marks[dk]) elif dk in key_marks: # The destination key may not be markable if it was derived # from a schema default. In this case replace the key. val = dest.pop(dk) dest[key_marks[dk]] = val return dest # If we reach here source and dest are either different types or are # not both lists or dicts: replace with source. return copy.copy(source)
def _mark_internal(data, name): """Add a simple name mark to raw YAML/JSON data. This is used by `spack config blame` to show where config lines came from. """ if isinstance(data, dict): d = syaml.syaml_dict((_mark_internal(k, name), _mark_internal(v, name)) for k, v in data.items()) elif isinstance(data, list): d = syaml.syaml_list(_mark_internal(e, name) for e in data) else: d = syaml.syaml_type(data) if syaml.markable(d): d._start_mark = yaml.Mark(name, None, None, None, None, None) d._end_mark = yaml.Mark(name, None, None, None, None, None) return d