diff --git a/README-extensions.rst b/README-extensions.rst index da9ce85b..eb40f054 100644 --- a/README-extensions.rst +++ b/README-extensions.rst @@ -203,6 +203,46 @@ The ``${beta:${alpha:two}}`` construct first resolves the ``${alpha:two}`` refer the reference ``${beta:a}`` to the value 99. +Default values +----------------- + +References can now have a default value, if the reference couldn't get resolved, for example: + +.. code-block:: yaml + + # nodes/node1.yml + parameters: + myvalue: ${not:existing:ref||default} + +``reclass.py --nodeinfo node1`` then gives: + +.. code-block:: yaml + + parameters: + myvalue: default + +The ``${not:existing:ref||default}`` construct searches for a value at ``not:existing:ref``, and then because it can't find any, it will take the default value. + +This works for nested references as well: + +.. code-block:: yaml + + # nodes/node1.yml + parameters: + default: 123 + a: ${not:existing:ref||${default}} + b: ${not:existing:ref||${not:existing:default||fallback}} + +``reclass.py --nodeinfo node1`` then gives: + +.. code-block:: yaml + + parameters: + default: 123 + a: 123 + b: fallback + + Ignore overwritten missing references ------------------------------------- diff --git a/reclass/datatypes/tests/test_parameters.py b/reclass/datatypes/tests/test_parameters.py index 80fd8de1..d32dba3b 100644 --- a/reclass/datatypes/tests/test_parameters.py +++ b/reclass/datatypes/tests/test_parameters.py @@ -414,6 +414,20 @@ def test_nested_deep_references(self): p.interpolate() self.assertEqual(p.as_dict(), r) + def test_nested_references_default_exists(self): + values = {"a": "value", "b": "${not:existing||${a}}"} + reference = {"a": "value", "b": "value"} + parameters = Parameters(values, SETTINGS, '') + parameters.interpolate() + self.assertEqual(parameters.as_dict(), reference) + + def test_nested_references_default_exists(self): + values = {"a": "value", "b": "${not:existing||${again||fallback}}"} + reference = {"a": "value", "b": "fallback"} + parameters = Parameters(values, SETTINGS, '') + parameters.interpolate() + self.assertEqual(parameters.as_dict(), reference) + def test_stray_occurrence_overwrites_during_interpolation(self): p1 = Parameters({'r' : 1, 'b': '${r}'}, SETTINGS, '') p2 = Parameters({'b' : 2}, SETTINGS, '') diff --git a/reclass/utils/dictpath.py b/reclass/utils/dictpath.py index 70c7bb51..2afd60fb 100644 --- a/reclass/utils/dictpath.py +++ b/reclass/utils/dictpath.py @@ -67,6 +67,8 @@ def __init__(self, delim, contents=None): elif isinstance(contents, list): self._parts = contents elif isinstance(contents, six.string_types): + # parse the default value from contents, strip default section (||...) from parts + contents, self.default = self._split_default(contents) self._parts = self._split_string(contents) elif isinstance(contents, tuple): self._parts = list(contents) @@ -115,6 +117,18 @@ def _get_innermost_container(self, base): def _split_string(self, string): return re.split(r'(?