From 84344e0b7503fe15723577b81997dd9c607cbfc2 Mon Sep 17 00:00:00 2001 From: Jett Jones Date: Sat, 13 Mar 2021 12:08:51 -0800 Subject: [PATCH] #256 - Process substitution overrides in order --- pyhocon/config_parser.py | 11 ++++++++++- pyhocon/config_tree.py | 3 +++ tests/test_config_parser.py | 14 ++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/pyhocon/config_parser.py b/pyhocon/config_parser.py index 9241de87..3297dd83 100644 --- a/pyhocon/config_parser.py +++ b/pyhocon/config_parser.py @@ -619,9 +619,10 @@ def _do_substitute(cls, substitution, resolved_value, is_optional_resolved=True) if transformation is None and not is_optional_resolved \ else transformation + # When the result is None, remove the key. if result is None and config_values.key in config_values.parent: del config_values.parent[config_values.key] - else: + elif result is not None: config_values.parent[config_values.key] = result s = cls._find_substitutions(result) if s: @@ -670,6 +671,12 @@ def resolve_substitutions(cls, config, accept_unresolved=False): _substitutions = substitutions[:] for substitution in _substitutions: + # If this substitution is an override, and the parent is still being processed, + # skip this entry, it will be processed on the next loop. + if substitution.parent.overriden_value: + if substitution.parent.overriden_value in [s.parent for s in substitutions]: + continue + is_optional_resolved, resolved_value = cls._resolve_variable(config, substitution) # if the substitution is optional @@ -695,6 +702,8 @@ def resolve_substitutions(cls, config, accept_unresolved=False): unresolved, new_substitutions, result = cls._do_substitute(substitution, resolved_value, is_optional_resolved) any_unresolved = unresolved or any_unresolved + # Detected substitutions may already be listed to process + new_substitutions = [n for n in new_substitutions if n not in substitutions] substitutions.extend(new_substitutions) if not isinstance(result, ConfigValues): substitutions.remove(substitution) diff --git a/pyhocon/config_tree.py b/pyhocon/config_tree.py index 511fc1ce..ac6dabc8 100644 --- a/pyhocon/config_tree.py +++ b/pyhocon/config_tree.py @@ -473,9 +473,12 @@ def has_substitution(self): return len(self.get_substitutions()) > 0 def get_substitutions(self): + # Returns a list of ConfigSubstitution tokens, in string order lst = [] node = self while node: + # walking up the override chain and append overrides to the front. + # later, parent overrides will be processed first, followed by children lst = [token for token in node.tokens if isinstance(token, ConfigSubstitution)] + lst if hasattr(node, 'overriden_value'): node = node.overriden_value diff --git a/tests/test_config_parser.py b/tests/test_config_parser.py index a33e8029..d3d8630a 100644 --- a/tests/test_config_parser.py +++ b/tests/test_config_parser.py @@ -1427,6 +1427,20 @@ def test_substitution_flat_override(self): assert config['database.name'] == 'peopledb' assert config['database.pass'] == 'peoplepass' + def test_substitution_multiple_override(self): + config = ConfigFactory.parse_string( + """ + a: 1 + b: foo + c: ${a} ${b} + c: ${b} ${a} + d: ${a} ${b} + d: ${a} bar + """) + + assert config['c'] == 'foo 1' + assert config['d'] == '1 bar' + def test_substitution_nested_override(self): config = ConfigFactory.parse_string( """