def apply_replacements(conf: Property) -> Property: """Apply a set of replacement values to a config file, returning a new copy. The replacements are found in a 'Replacements' block in the property. These replace %values% starting and ending with percents. A double-percent allows literal percents. Unassigned values are an error. """ replace = {} new_conf = Property(conf.real_name, []) # Strip the replacement blocks from the config, and save the values. for prop in conf: if prop.name == 'replacements': for rep_prop in prop: replace[rep_prop.name.strip('%')] = rep_prop.value else: new_conf.append(prop) def rep_func(match: Match): """Does the replacement.""" var = match.group(1) if not var: # %% becomes %. return '%' try: return replace[var.casefold()] except KeyError: raise ValueError('Unresolved variable: {!r}\n{}'.format( var, replace)) for prop in new_conf.iter_tree(blocks=True): prop.name = RE_PERCENT_VAR.sub(rep_func, prop.real_name) if not prop.has_children(): prop.value = RE_PERCENT_VAR.sub(rep_func, prop.value) return new_conf