group resolve errors together and report them at the end of the interpolation
diff --git a/README-extentions.rst b/README-extentions.rst
index ebbc8a0..554a62d 100644
--- a/README-extentions.rst
+++ b/README-extentions.rst
@@ -160,6 +160,23 @@
ignore_overwritten_missing_reference: True
+Print summary of missed references
+----------------------------------
+
+Instead of failing on the first undefinded reference error all missing reference errors are printed at once.
+
+.. code-block:: yaml
+ reclass --nodeinfo mynode
+ -> dontpanic
+ Cannot resolve ${_param:kkk}, at mkkek3:tree:to:fail, in yaml_fs:///test/classes/third.yml
+ Cannot resolve ${_param:kkk}, at mkkek3:tree:another:xxxx, in yaml_fs:///test/classes/third.yml
+ Cannot resolve ${_param:kkk}, at mykey2:tree:to:fail, in yaml_fs:///test/classes/third.yml
+
+.. code-block:: yaml
+
+ group_errors: True
+
+
Inventory Queries
-----------------
diff --git a/reclass/config.py b/reclass/config.py
index 5eff1a4..e9bb43b 100644
--- a/reclass/config.py
+++ b/reclass/config.py
@@ -50,6 +50,11 @@
ret.add_option('-r', '--no-refs', dest='no_refs', action="store_true",
default=defaults.get('no_refs', OPT_NO_REFS),
help='output all key values do not use yaml references [%default]')
+ ret.add_option('-1', '--single-error', dest='group_errors', action="store_false",
+ default=defaults.get('group_errors', OPT_GROUP_ERRORS),
+ help='throw errors immediately instead of grouping them together')
+ ret.add_option('-0', '--multiple-errors', dest='group_errors', action="store_true",
+ help='were possible report any errors encountered as a group')
return ret
diff --git a/reclass/datatypes/parameters.py b/reclass/datatypes/parameters.py
index 9605efd..e7199ab 100644
--- a/reclass/datatypes/parameters.py
+++ b/reclass/datatypes/parameters.py
@@ -14,7 +14,7 @@
from reclass.utils.dictpath import DictPath
from reclass.values.value import Value
from reclass.values.valuelist import ValueList
-from reclass.errors import InfiniteRecursionError, ResolveError, InterpolationError, ParseError, BadReferencesError
+from reclass.errors import InfiniteRecursionError, ResolveError, ResolveErrorList, InterpolationError, ParseError, BadReferencesError
class Parameters(object):
'''
@@ -47,6 +47,7 @@
self._unrendered = None
self._escapes_handled = {}
self._inv_queries = []
+ self._resolve_errors = ResolveErrorList()
self._needs_all_envs = False
self._keep_overrides = False
if mapping is not None:
@@ -80,6 +81,9 @@
def needs_all_envs(self):
return self._needs_all_envs
+ def resolve_errors(self):
+ return self._resolve_errors
+
def as_dict(self):
return self._base.copy()
@@ -245,6 +249,8 @@
# processing them, so we cannot just iterate the dict
path, v = self._unrendered.iteritems().next()
self._interpolate_inner(path, inventory)
+ if self._resolve_errors.have_errors():
+ raise self._resolve_errors
def initialise_interpolation(self):
self._unrendered = None
@@ -255,6 +261,7 @@
self._unrendered = {}
self._inv_queries = []
self._needs_all_envs = False
+ self._resolve_errors = ResolveErrorList()
self._render_simple_dict(self._base, DictPath(self._settings.delimiter))
def _interpolate_inner(self, path, inventory):
@@ -276,7 +283,11 @@
new = value.render(self._base, inventory)
except ResolveError as e:
e.context = path
- raise
+ if self._settings.group_errors:
+ self._resolve_errors.add(e)
+ new = None
+ else:
+ raise
if isinstance(new, dict):
self._render_simple_dict(new, path)
diff --git a/reclass/datatypes/tests/test_parameters.py b/reclass/datatypes/tests/test_parameters.py
index 07786c1..405f757 100644
--- a/reclass/datatypes/tests/test_parameters.py
+++ b/reclass/datatypes/tests/test_parameters.py
@@ -11,7 +11,7 @@
from reclass.settings import Settings
from reclass.datatypes import Parameters
-from reclass.errors import InfiniteRecursionError, InterpolationError
+from reclass.errors import InfiniteRecursionError, InterpolationError, ResolveError, ResolveErrorList
import unittest
try:
import unittest.mock as mock
@@ -513,6 +513,20 @@
p1.interpolate()
self.assertEqual(error.exception.message, "-> \n Bad references, at gamma\n ${beta}")
+ def test_multiple_resolve_errors(self):
+ p1 = Parameters({'alpha': '${gamma}', 'beta': '${gamma}'}, SETTINGS, '')
+ with self.assertRaises(ResolveErrorList) as error:
+ p1.interpolate()
+ self.assertEqual(error.exception.message, "-> \n Cannot resolve ${gamma}, at alpha\n Cannot resolve ${gamma}, at beta")
+
+ def test_force_single_resolve_error(self):
+ settings = copy.deepcopy(SETTINGS)
+ settings.group_errors = False
+ p1 = Parameters({'alpha': '${gamma}', 'beta': '${gamma}'}, settings, '')
+ with self.assertRaises(ResolveError) as error:
+ p1.interpolate()
+ self.assertEqual(error.exception.message, "-> \n Cannot resolve ${gamma}, at alpha")
+
def test_ignore_overwriten_missing_reference(self):
settings = copy.deepcopy(SETTINGS)
settings.ignore_overwritten_missing_references = True
diff --git a/reclass/defaults.py b/reclass/defaults.py
index 4c20795..ac8aa34 100644
--- a/reclass/defaults.py
+++ b/reclass/defaults.py
@@ -15,6 +15,7 @@
OPT_NODES_URI = 'nodes'
OPT_CLASSES_URI = 'classes'
OPT_PRETTY_PRINT = True
+OPT_GROUP_ERRORS = True
OPT_NO_REFS = False
OPT_OUTPUT = 'yaml'
diff --git a/reclass/errors.py b/reclass/errors.py
index 901b196..a96c47b 100644
--- a/reclass/errors.py
+++ b/reclass/errors.py
@@ -182,6 +182,24 @@
msg = 'Cannot resolve {0}'.format(self.reference.join(REFERENCE_SENTINELS)) + self._add_context_and_uri()
return [ msg ]
+class ResolveErrorList(InterpolationError):
+ def __init__(self):
+ super(ResolveErrorList, self).__init__(msg=None)
+ self.resolve_errors = []
+ self._traceback = False
+
+ def add(self, resolve_error):
+ self.resolve_errors.append(resolve_error)
+
+ def have_errors(self):
+ return len(self.resolve_errors) > 0
+
+ def _get_error_message(self):
+ msgs = []
+ for e in self.resolve_errors:
+ msgs.extend(e._get_error_message())
+ return msgs
+
class InvQueryError(InterpolationError):
diff --git a/reclass/settings.py b/reclass/settings.py
index 44078ad..a1e203e 100644
--- a/reclass/settings.py
+++ b/reclass/settings.py
@@ -25,9 +25,10 @@
self.ignore_class_notfound_regexp = [ self.ignore_class_notfound_regexp ]
self.ignore_class_notfound_warning = options.get('ignore_class_notfound_warning', OPT_IGNORE_CLASS_NOTFOUND_WARNING)
-
self.ignore_overwritten_missing_references = options.get('ignore_overwritten_missing_references', OPT_IGNORE_OVERWRITTEN_MISSING_REFERENCES)
+ self.group_errors = options.get('group_errors', OPT_GROUP_ERRORS)
+
self.ref_parser = reclass.values.parser_funcs.get_ref_parser(self.escape_character, self.reference_sentinels, self.export_sentinels)
self.simple_ref_parser = reclass.values.parser_funcs.get_simple_ref_parser(self.escape_character, self.reference_sentinels, self.export_sentinels)