add inventory query options
diff --git a/reclass/core.py b/reclass/core.py
index 8082d80..6dc3254 100644
--- a/reclass/core.py
+++ b/reclass/core.py
@@ -17,7 +17,7 @@
from reclass.settings import Settings
from reclass.output.yaml_outputter import ExplicitDumper
from reclass.datatypes import Entity, Classes, Parameters, Exports
-from reclass.errors import MappingFormatError, ClassNotFound, ResolveError
+from reclass.errors import MappingFormatError, ClassNotFound, ResolveError, InvQueryError, InterpolationError
class Core(object):
@@ -101,7 +101,7 @@
class_entity = self._storage.get_class(klass, environment, self._settings)
except ClassNotFound, e:
e.set_nodename(nodename)
- raise e
+ raise
descent = self._recurse_entity(class_entity, seen=seen,
nodename=nodename, environment=environment)
@@ -123,20 +123,43 @@
else:
return Parameters({}, self._settings, '')
- def _get_inventory(self):
+ def _get_inventory(self, all_envs, environment, queries):
inventory = {}
for nodename in self._storage.enumerate_nodes():
- node = self._node_entity(nodename)
try:
- node.interpolate_exports()
- except ResolveError as e:
- e.export = nodename
- raise e
- inventory[nodename] = node.exports.as_dict()
+ node_base = self._storage.get_node(nodename, self._settings)
+ if node_base.environment == None:
+ node_base.environment = self._settings.default_environment
+ except yaml.scanner.ScannerError as e:
+ if self._settings.inventory_ignore_failed_node:
+ continue
+ else:
+ raise
+
+ if all_envs or node_base.environment == environment:
+ node = self._node_entity(nodename)
+ if queries is None:
+ try:
+ node.interpolate_exports()
+ except ResolveError as e:
+ e.nodename = nodename
+ else:
+ node.initialise_interpolation()
+ for p, q in queries:
+ try:
+ node.interpolate_single_export(q)
+ except ResolveError as e:
+ e.nodename = nodename
+ raise InvQueryError(q.contents(), e, context=p, uri=q.uri())
+ inventory[nodename] = node.exports.as_dict()
return inventory
def _node_entity(self, nodename):
- node_entity = self._storage.get_node(nodename, self._settings)
+ try:
+ node_entity = self._storage.get_node(nodename, self._settings)
+ except InterpolationError as e:
+ e.nodename = nodename
+ raise
if node_entity.environment == None:
node_entity.environment = self._settings.default_environment
base_entity = Entity(self._settings, name='base')
@@ -153,8 +176,16 @@
ret = self._node_entity(nodename)
ret.initialise_interpolation()
if ret.parameters.has_inv_query() and inventory is None:
- inventory = self._get_inventory()
- ret.interpolate(nodename, inventory)
+ try:
+ inventory = self._get_inventory(ret.parameters.needs_all_envs(), ret.environment, ret.parameters.get_inv_queries())
+ except InvQueryError as e:
+ e.nodename = nodename
+ raise e
+ try:
+ ret.interpolate(nodename, inventory)
+ except ResolveError as e:
+ e.nodename = nodename
+ raise
return ret
def _nodeinfo_as_dict(self, nodename, entity):
@@ -173,7 +204,7 @@
def inventory(self):
query_nodes = set()
entities = {}
- inventory = self._get_inventory()
+ inventory = self._get_inventory(True, '', None)
for n in self._storage.enumerate_nodes():
entities[n] = self._nodeinfo(n, inventory)
if entities[n].parameters.has_inv_query():
diff --git a/reclass/datatypes/entity.py b/reclass/datatypes/entity.py
index 5b54cf7..e88e2fa 100644
--- a/reclass/datatypes/entity.py
+++ b/reclass/datatypes/entity.py
@@ -96,6 +96,9 @@
self.initialise_interpolation()
self._exports.interpolate_from_external(self._parameters)
+ def interpolate_single_export(self, references):
+ self._exports.interpolate_single_from_external(self._parameters, references)
+
def __eq__(self, other):
return isinstance(other, type(self)) \
and self._applications == other._applications \
diff --git a/reclass/datatypes/exports.py b/reclass/datatypes/exports.py
index f8f38a1..ae5b30b 100644
--- a/reclass/datatypes/exports.py
+++ b/reclass/datatypes/exports.py
@@ -3,6 +3,9 @@
#
# This file is part of reclass (http://github.com/madduck/reclass)
#
+
+import copy
+
from parameters import Parameters
from reclass.errors import ResolveError
from reclass.values.value import Value
@@ -25,8 +28,6 @@
self.merge(overdict)
def interpolate_from_external(self, external):
- self._initialise_interpolate()
- external._initialise_interpolate()
while len(self._unrendered) > 0:
path, v = self._unrendered.iteritems().next()
value = path.get_value(self._base)
@@ -41,12 +42,38 @@
path.set_value(self._base, new)
del self._unrendered[path]
+ def interpolate_single_from_external(self, external, query):
+ paths = {}
+ for r in query.get_inv_references():
+ paths[r] = True
+ while len(paths) > 0:
+ path, v = paths.iteritems().next()
+ if path.exists_in(self._base) and path in self._unrendered:
+ value = path.get_value(self._base)
+ if not isinstance(value, (Value, ValueList)):
+ del paths[path]
+ del self._unrendered[path]
+ else:
+ try:
+ external._interpolate_references(path, value, None)
+ new = self._interpolate_render_from_external(external._base, path, value)
+ path.set_value(self._base, new)
+ except ResolveError as e:
+ if query.ignore_failed_render():
+ path.delete(self._base)
+ else:
+ raise
+ del paths[path]
+ del self._unrendered[path]
+ else:
+ del paths[path]
+
def _interpolate_render_from_external(self, context, path, value):
try:
new = value.render(context, None)
except ResolveError as e:
e.context = path
- raise e
+ raise
if isinstance(new, dict):
self._render_simple_dict(new, path)
elif isinstance(new, list):
diff --git a/reclass/datatypes/parameters.py b/reclass/datatypes/parameters.py
index b55f01a..4152d11 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
+from reclass.errors import InfiniteRecursionError, ResolveError, InterpolationError, ParseError, BadReferencesError
class Parameters(object):
'''
@@ -46,7 +46,8 @@
self._uri = uri
self._unrendered = None
self._escapes_handled = {}
- self._has_inv_query = False
+ self._inv_queries = []
+ self._needs_all_envs = False
self._keep_overrides = False
if mapping is not None:
# we initialise by merging
@@ -71,28 +72,39 @@
return not self.__eq__(other)
def has_inv_query(self):
- return self._has_inv_query
+ return len(self._inv_queries) > 0
+
+ def get_inv_queries(self):
+ return self._inv_queries
+
+ def needs_all_envs(self):
+ return self._needs_all_envs
def as_dict(self):
return self._base.copy()
- def _wrap_value(self, value):
+ def _wrap_value(self, value, path):
if isinstance(value, dict):
- return self._wrap_dict(value)
+ return self._wrap_dict(value, path)
elif isinstance(value, list):
- return self._wrap_list(value)
+ return self._wrap_list(value, path)
elif isinstance(value, (Value, ValueList)):
return value
else:
- return Value(value, self._settings, self._uri)
+ try:
+ return Value(value, self._settings, self._uri)
+ except InterpolationError as e:
+ e.context = str(path)
+ raise
- def _wrap_list(self, source):
- return [ self._wrap_value(v) for v in source ]
+ def _wrap_list(self, source, path):
+ return [ self._wrap_value(v, path.new_subpath(k)) for (k, v) in enumerate(source) ]
+ #self._wrap_value(v, path.new_subpath()) for v in source ]
- def _wrap_dict(self, source):
- return { k: self._wrap_value(v) for k, v in source.iteritems() }
+ def _wrap_dict(self, source, path):
+ return { k: self._wrap_value(v, path.new_subpath(k)) for k, v in source.iteritems() }
- def _update_value(self, cur, new, path):
+ def _update_value(self, cur, new):
if isinstance(cur, Value):
values = ValueList(cur, self._settings)
elif isinstance(cur, ValueList):
@@ -161,7 +173,7 @@
elif isinstance(new, dict) and isinstance(cur, dict):
return self._merge_dict(cur, new, path)
else:
- return self._update_value(cur, new, path)
+ return self._update_value(cur, new)
def merge(self, other):
"""Merge function (public edition).
@@ -179,9 +191,9 @@
self._unrendered = None
if isinstance(other, dict):
- wrapped = self._wrap_dict(other)
+ wrapped = self._wrap_dict(other, DictPath(self._settings.delimiter))
elif isinstance(other, self.__class__):
- wrapped = self._wrap_dict(other._base)
+ wrapped = self._wrap_dict(other._base, DictPath(self._settings.delimiter))
else:
raise TypeError('Cannot merge %s objects into %s' % (type(other),
self.__class__.__name__))
@@ -190,9 +202,12 @@
def _render_simple_container(self, container, key, value, path):
if isinstance(value, ValueList):
if value.is_complex():
- self._unrendered[path.new_subpath(key)] = True
+ p = path.new_subpath(key)
+ self._unrendered[p] = True
if value.has_inv_query():
- self._has_inv_query = True
+ self._inv_queries.append((p, value))
+ if value.needs_all_envs():
+ self._needs_all_envs = True
return
else:
value = value.merge()
@@ -206,9 +221,12 @@
container[key] = value
elif isinstance(value, Value):
if value.is_complex():
- self._unrendered[path.new_subpath(key)] = True
+ p = path.new_subpath(key)
+ self._unrendered[p] = True
if value.has_inv_query():
- self._has_inv_query = True
+ self._inv_queries.append((p, value))
+ if value.needs_all_envs():
+ self._needs_all_envs = True
else:
container[key] = value.render(None, None)
@@ -236,7 +254,8 @@
def _initialise_interpolate(self):
if self._unrendered is None:
self._unrendered = {}
- self._has_inv_query = False
+ self._inv_queries = []
+ self._needs_all_envs = False
self._render_simple_dict(self._base, DictPath(self._settings.delimiter))
def _interpolate_inner(self, path, inventory):
@@ -258,7 +277,7 @@
new = value.render(self._base, inventory)
except ResolveError as e:
e.context = path
- raise e
+ raise
if isinstance(new, dict):
self._render_simple_dict(new, path)
@@ -299,4 +318,4 @@
old = len(value.get_references())
value.assembleRefs(self._base)
if old == len(value.get_references()):
- raise InterpolationError('Bad references: {0}, for path: {1}'.format(value.get_references(), str(path)))
+ raise BadReferencesError(value.get_references(), str(path))
diff --git a/reclass/datatypes/tests/test_parameters.py b/reclass/datatypes/tests/test_parameters.py
index a56ba65..4dec420 100644
--- a/reclass/datatypes/tests/test_parameters.py
+++ b/reclass/datatypes/tests/test_parameters.py
@@ -257,7 +257,7 @@
self.assertEqual(p.as_dict()['foo'], v)
def test_interpolate_list(self):
- l = [41,42,43]
+ l = [41, 42, 43]
d = {'foo': 'bar'.join(SETTINGS.reference_sentinels),
'bar': l}
p = Parameters(d, SETTINGS, '')
@@ -498,7 +498,7 @@
p1 = Parameters({'alpha': {'one': 1, 'two': 2}, 'gamma': '${alpha:${beta}}'}, SETTINGS, '')
with self.assertRaises(InterpolationError) as error:
p1.interpolate()
- self.assertEqual(error.exception.message, "Bad references: ['beta'], for path: gamma")
+ self.assertEqual(error.exception.message, "=> \n Bad references: ${beta} for path: gamma")
if __name__ == '__main__':
unittest.main()
diff --git a/reclass/defaults.py b/reclass/defaults.py
index baac195..7fcb2a0 100644
--- a/reclass/defaults.py
+++ b/reclass/defaults.py
@@ -40,3 +40,5 @@
IGNORE_CLASS_NOT_FOUND = False
DEFAULT_ENVIRONMENT = 'base'
+INVENTORY_IGNORE_FAILED_NODE = False
+INVENTORY_IGNORE_FAILED_RENDER = False
diff --git a/reclass/errors.py b/reclass/errors.py
index d753306..a27919b 100644
--- a/reclass/errors.py
+++ b/reclass/errors.py
@@ -10,7 +10,7 @@
import posix, sys
import traceback
-from reclass.defaults import REFERENCE_SENTINELS
+from reclass.defaults import REFERENCE_SENTINELS, EXPORT_SENTINELS
class ReclassException(Exception):
@@ -18,7 +18,8 @@
super(ReclassException, self).__init__()
self._rc = rc
self._msg = msg
- self._traceback = traceback.format_exc()
+ self._previous_traceback = traceback.format_exc()
+ self._full_traceback = False
message = property(lambda self: self._get_message())
rc = property(lambda self: self._rc)
@@ -33,8 +34,14 @@
return 'No error message provided.'
def exit_with_message(self, out=sys.stderr):
- if self._traceback:
- print >>out, self._traceback
+ if self._full_traceback:
+ t, v, tb = sys.exc_info()
+ print >>out, 'Full Traceback:'
+ for l in traceback.format_tb(tb):
+ print >>out, l,
+ print >>out
+ if self._previous_traceback:
+ print >>out, self._previous_traceback
print >>out, self.message
print >>out
sys.exit(self.rc)
@@ -125,44 +132,74 @@
class InterpolationError(ReclassException):
- def __init__(self, msg, rc=posix.EX_DATAERR):
+ def __init__(self, msg, rc=posix.EX_DATAERR, nodename=''):
super(InterpolationError, self).__init__(rc=rc, msg=msg)
-
-
-class ResolveError(InterpolationError):
-
- def __init__(self, var, uri=None, export=None, context=None):
- super(ResolveError, self).__init__(msg=None)
- self.var = var
- self.context = context
- self.uri = uri
- self.export = export
+ self.nodename = nodename
+ self.uri = None
+ self.context = None
def _get_message(self):
+ msg = '=> {0}\n'.format(self.nodename)
+ msg += self._render_error_message(self._get_error_message(), 1)
+ msg = msg[:-1]
+ return msg
+
+ def _render_error_message(self, message_list, indent):
msg = ''
- if self.export:
- msg = '** InvQuery: %s **\n' % self.export
- msg += "Cannot resolve " + self.var.join(REFERENCE_SENTINELS)
+ for l in message_list:
+ if isinstance(l, list):
+ msg += self._render_error_message(l, indent + 1)
+ else:
+ msg += (' ' * indent * 3) + l + '\n'
+ return msg
+
+ def _add_context_and_uri(self):
+ msg = ''
if self.context:
msg += ', at %s' % self.context
if self.uri:
msg += ', in %s' % self.uri
return msg
- def set_context(self, context):
- self._context = context
+
+class ResolveError(InterpolationError):
+
+ def __init__(self, reference, uri=None, context=None):
+ super(ResolveError, self).__init__(msg=None)
+ self.reference = reference
+
+ def _get_error_message(self):
+ msg = 'Cannot resolve {0}'.format(self.reference.join(REFERENCE_SENTINELS)) + self._add_context_and_uri()
+ return [ msg ]
-class IncompleteInterpolationError(InterpolationError):
+class InvQueryError(InterpolationError):
- def __init__(self, string, end_sentinel):
- super(IncompleteInterpolationError, self).__init__(msg=None)
- self._ref = string.join(REFERENCE_SENTINELS)
- self._end_sentinel = end_sentinel
+ def __init__(self, query, resolveError, uri=None, context = None):
+ super(InvQueryError, self).__init__(msg=None)
+ self.query = query
+ self.resolveError = resolveError
+ self._traceback = self.resolveError._traceback
- def _get_message(self):
- msg = "Missing '{0}' to end reference: {1}"
- return msg.format(self._end_sentinel, self._ref)
+ def _get_error_message(self):
+ msg1 = 'Failed inv query {0}'.format(self.query.join(EXPORT_SENTINELS)) + self._add_context_and_uri()
+ msg2 = [ '--> {0}'.format(self.resolveError.nodename) ]
+ msg2.extend(self.resolveError._get_error_message())
+ return [ msg1, msg2 ]
+
+
+class ParseError(InterpolationError):
+
+ def __init__(self, msg, line, col, lineno, rc=posix.EX_DATAERR):
+ super(ParseError, self).__init__(rc=rc, msg=None)
+ self._err = msg
+ self._line = line
+ self._col = col
+ self._lineno = lineno
+
+ def _get_error_message(self):
+ msg = [ 'Parse error: {0}'.format(self._line.join(EXPORT_SENTINELS)) + self._add_context_and_uri() ]
+ msg.append('{0} at char {1}'.format(self._err, self._col - 1))
class InfiniteRecursionError(InterpolationError):
@@ -172,9 +209,32 @@
self._path = path
self._ref = ref.join(REFERENCE_SENTINELS)
- def _get_message(self):
+ def _get_error_message(self):
msg = "Infinite recursion while resolving {0} at {1}"
- return msg.format(self._ref, self._path)
+ return [ msg.format(self._ref, self._path) ]
+
+
+class BadReferencesError(InterpolationError):
+
+ def __init__(self, refs, path):
+ super(BadReferencesError, self).__init__(msg=None)
+ self._path = path
+ self._refs = [ r.join(REFERENCE_SENTINELS) for r in refs ]
+
+ def _get_error_message(self):
+ msg = 'Bad references: {0} for path: {1}'
+ return [ msg.format(", ".join(self._refs), self._path) ]
+
+
+class ExpressionError(InterpolationError):
+
+ def __init__(self, msg, rc=posix.EX_DATAERR):
+ super(ExpressionError, self).__init__(rc=rc, msg=None)
+ self._error_msg = msg
+
+ def _get_error_message(self):
+ msg = [ 'Expression error: {0}'.format(self._error_msg) + self._add_context_and_uri() ]
+ return msg
class MappingError(ReclassException):
@@ -220,22 +280,3 @@
"definition in '{3}'. Nodes can only be defined once " \
"per inventory."
return msg.format(self._storage, self._name, self._uris[1], self._uris[0])
-
-
-class ParseError(ReclassException):
-
- def __init__(self, msg, line, col, lineno, rc=posix.EX_DATAERR):
- super(ParseError, self).__init__(rc=rc, msg=None)
- self._err = msg
- self._line = line
- self._col = col
- self._lineno = lineno
-
- def _get_message(self):
- msg = "Parse error: {0} : {1} at char {2}"
- return msg.format(self._line, self._err, self._col - 1)
-
-class ExpressionError(ReclassException):
-
- def __init__(self, msg, rc=posix.EX_DATAERR):
- super(ExpressionError, self).__init__(rc=rc, msg=msg)
diff --git a/reclass/settings.py b/reclass/settings.py
index 4b5928f..d0332d0 100644
--- a/reclass/settings.py
+++ b/reclass/settings.py
@@ -14,6 +14,8 @@
self.dict_key_override_prefix = options.get('dict_key_override_prefix', reclass.defaults.PARAMETER_DICT_KEY_OVERRIDE_PREFIX)
self.escape_character = options.get('escape_character', reclass.defaults.ESCAPE_CHARACTER)
self.export_sentinels = options.get('export_sentinels', reclass.defaults.EXPORT_SENTINELS)
+ self.inventory_ignore_failed_node = options.get('inventory_ignore_failed_node', reclass.defaults.INVENTORY_IGNORE_FAILED_NODE)
+ self.inventory_ignore_failed_render = options.get('inventory_ignore_failed_render', reclass.defaults.INVENTORY_IGNORE_FAILED_RENDER)
self.reference_sentinels = options.get('reference_sentinels', reclass.defaults.REFERENCE_SENTINELS)
self.ref_parser = reclass.values.parser_funcs.get_ref_parser(self.escape_character, self.reference_sentinels, self.export_sentinels)
diff --git a/reclass/utils/dictpath.py b/reclass/utils/dictpath.py
index 0d23c96..1466a5e 100644
--- a/reclass/utils/dictpath.py
+++ b/reclass/utils/dictpath.py
@@ -134,6 +134,9 @@
del self._parts[0]
return self
+ def delete(self, base):
+ del self._get_innermost_container(base)[self._get_key()]
+
def exists_in(self, container):
item = container
for i in self._parts:
diff --git a/reclass/values/invitem.py b/reclass/values/invitem.py
index 024ef99..cb55fc3 100644
--- a/reclass/values/invitem.py
+++ b/reclass/values/invitem.py
@@ -16,6 +16,7 @@
_TEST = 'TEST'
_LIST_TEST = 'LIST_TEST'
_LOGICAL = 'LOGICAL'
+_OPTION = 'OPTION'
_VALUE = 'VALUE'
_IF = 'IF'
@@ -25,6 +26,8 @@
_EQUAL = '=='
_NOT_EQUAL = '!='
+_IGNORE_ERRORS = '+IgnoreErrors'
+_ALL_ENVS = '+AllEnvs'
class Element(object):
@@ -35,7 +38,13 @@
self._parameter_value = None
self._export_path, self._parameter_path, self._parameter_value = self._get_vars(expression[0][1], self._export_path, self._parameter_path, self._parameter_value)
self._export_path, self._parameter_path, self._parameter_value = self._get_vars(expression[2][1], self._export_path, self._parameter_path, self._parameter_value)
- self._export_path.drop_first()
+
+ try:
+ self._export_path.drop_first()
+ except AttributeError:
+ raise ExpressionError('No export')
+
+ self._inv_refs = [ self._export_path ]
self._test = expression[1][1]
if self._parameter_path is not None:
@@ -47,12 +56,15 @@
def refs(self):
return self._refs
+ def inv_refs(self):
+ return self._inv_refs
+
def value(self, context, items):
if self._parameter_path is not None:
self._parameter_value = self._resolve(self._parameter_path, context)
- if self._export_path is None or self._parameter_value is None or self._test is None:
- ExpressionError('Failed to render %s' % str(self))
+ if self._parameter_value is None or self._test is None:
+ raise ExpressionError('Failed to render %s' % str(self))
if self._export_path.exists_in(items):
result = False
@@ -100,11 +112,13 @@
self._operators = []
self._delimiter = delimiter
self._refs = []
+ self._inv_refs = []
i = 0
while i < len(expression):
e = Element(expression[i:], self._delimiter)
self._elements.append(e)
self._refs.extend(e.refs())
+ self._inv_refs.extend(e.inv_refs())
i += 3
if i < len(expression):
self._operators.append(expression[i][1])
@@ -113,6 +127,9 @@
def refs(self):
return self._refs
+ def inv_refs(self):
+ return self._inv_refs
+
def value(self, context, items):
if len(self._elements) == 0:
return True
@@ -153,6 +170,10 @@
token = tokens[0]
tokens[0] = (_OBJ, token)
+ def _option(string, location, tokens):
+ token = tokens[0]
+ tokens[0] = (_OPTION, token)
+
def _test(string, location, tokens):
token = tokens[0]
tokens[0] = (_TEST, token)
@@ -179,6 +200,10 @@
white_space = pp.White().suppress()
end = pp.StringEnd()
+ ignore_errors = pp.CaselessLiteral(_IGNORE_ERRORS)
+ all_envs = pp.CaselessLiteral(_ALL_ENVS)
+ option = (ignore_errors | all_envs).setParseAction(_option)
+ options = pp.Group(pp.ZeroOrMore(option + white_space))
operator_test = (pp.Literal(_EQUAL) | pp.Literal(_NOT_EQUAL)).setParseAction(_test)
operator_logical = (pp.CaselessLiteral(_AND) | pp.CaselessLiteral(_OR)).setParseAction(_logical)
begin_if = pp.CaselessLiteral(_IF, ).setParseAction(_if)
@@ -191,15 +216,19 @@
expr_var = pp.Group(obj + pp.Optional(white_space) + end).setParseAction(_expr_var)
expr_test = pp.Group(obj + white_space + begin_if + single_test + pp.ZeroOrMore(additional_test) + end).setParseAction(_expr_test)
expr_list_test = pp.Group(begin_if + single_test + pp.ZeroOrMore(additional_test) + end).setParseAction(_expr_list_test)
- expr = pp.Optional(white_space) + (expr_test | expr_var | expr_list_test)
- return expr
+ expr = (expr_test | expr_var | expr_list_test)
+ line = options + expr + end
+ return line
_parser = _get_parser()
def __init__(self, item, settings):
self.type = Item.INV_QUERY
self._settings = settings
- self._parse_expression(item.render(None, None))
+ self._needs_all_envs = False
+ self._ignore_failed_render = self._settings.inventory_ignore_failed_render
+ self._expr_text = item.render(None, None)
+ self._parse_expression(self._expr_text)
def _parse_expression(self, expr):
try:
@@ -210,21 +239,33 @@
if len(tokens) == 1:
self._expr_type = tokens[0][0]
self._expr = list(tokens[0][1])
+ elif len(tokens) == 2:
+ for opt in tokens[0]:
+ if opt[1] == _IGNORE_ERRORS:
+ self._ignore_failed_render = True
+ elif opt[1] == _ALL_ENVS:
+ self._needs_all_envs = True
+ self._expr_type = tokens[1][0]
+ self._expr = list(tokens[1][1])
else:
- raise ExpressionError('Failed to parse %s' % str(self._expr))
+ raise ExpressionError('Failed to parse %s' % str(tokens))
if self._expr_type == _VALUE:
self._value_path = DictPath(self._settings.delimiter, self._expr[0][1]).drop_first()
self._question = Question([], self._settings.delimiter)
self._refs = []
+ self._inv_refs = [ self._value_path ]
elif self._expr_type == _TEST:
self._value_path = DictPath(self._settings.delimiter, self._expr[0][1]).drop_first()
self._question = Question(self._expr[2:], self._settings.delimiter)
self._refs = self._question.refs()
+ self._inv_refs = self._question.inv_refs()
+ self._inv_refs.append(self._value_path)
elif self._expr_type == _LIST_TEST:
self._value_path = None
self._question = Question(self._expr[1:], self._settings.delimiter)
self._refs = self._question.refs()
+ self._inv_refs = self._question.inv_refs()
else:
raise ExpressionError('Unknown expression type: %s' % self._expr_type)
@@ -232,7 +273,7 @@
return
def contents(self):
- return self._expr
+ return self._expr_text
def has_inv_query(self):
return True
@@ -243,6 +284,15 @@
def get_references(self):
return self._question.refs()
+ def get_inv_references(self):
+ return self._inv_refs
+
+ def needs_all_envs(self):
+ return self._needs_all_envs
+
+ def ignore_failed_render(self):
+ return self._ignore_failed_render
+
def _resolve(self, path, dictionary):
try:
return path.get_value(dictionary)
diff --git a/reclass/values/parser.py b/reclass/values/parser.py
index 1b0ba4b..bdd881d 100644
--- a/reclass/values/parser.py
+++ b/reclass/values/parser.py
@@ -26,7 +26,7 @@
# speed up: try a simple reference
try:
tokens = self._settings.simple_ref_parser.leaveWhitespace().parseString(value).asList()
- except pp.ParseException as e:
+ except pp.ParseException:
# fall back on the full parser
try:
tokens = self._settings.ref_parser.leaveWhitespace().parseString(value).asList()
diff --git a/reclass/values/tests/test_value.py b/reclass/values/tests/test_value.py
index 51425cc..84403d3 100644
--- a/reclass/values/tests/test_value.py
+++ b/reclass/values/tests/test_value.py
@@ -11,8 +11,7 @@
from reclass.settings import Settings
from reclass.values.value import Value
-from reclass.errors import ResolveError, \
- IncompleteInterpolationError, ParseError
+from reclass.errors import ResolveError, ParseError
import unittest
SETTINGS = Settings()
diff --git a/reclass/values/value.py b/reclass/values/value.py
index 23401fb..cbd5ce2 100644
--- a/reclass/values/value.py
+++ b/reclass/values/value.py
@@ -8,7 +8,7 @@
from dictitem import DictItem
from listitem import ListItem
from scaitem import ScaItem
-from reclass.errors import ResolveError
+from reclass.errors import InterpolationError
class Value(object):
@@ -18,7 +18,11 @@
self._settings = settings
self._uri = uri
if isinstance(value, str):
- self._item = self._parser.parse(value, self._settings)
+ try:
+ self._item = self._parser.parse(value, self._settings)
+ except InterpolationError as e:
+ e.uri = self._uri
+ raise
elif isinstance(value, list):
self._item = ListItem(value, self._settings)
elif isinstance(value, dict):
@@ -26,6 +30,9 @@
else:
self._item = ScaItem(value, self._settings)
+ def uri(self):
+ return self._uri
+
def is_container(self):
return self._item.is_container()
@@ -38,12 +45,24 @@
def has_inv_query(self):
return self._item.has_inv_query()
+ def needs_all_envs(self):
+ if self._item.has_inv_query():
+ return self._item.needs_all_envs()
+ else:
+ return False
+
+ def ignore_failed_render(self):
+ return self._item.ignore_failed_render()
+
def is_complex(self):
return self._item.is_complex()
def get_references(self):
return self._item.get_references()
+ def get_inv_references(self):
+ return self._item.get_inv_references()
+
def assembleRefs(self, context):
if self._item.has_references():
self._item.assembleRefs(context)
@@ -51,9 +70,9 @@
def render(self, context, inventory):
try:
return self._item.render(context, inventory)
- except ResolveError as e:
+ except InterpolationError as e:
e.uri = self._uri
- raise e
+ raise
def contents(self):
return self._item.contents()
diff --git a/reclass/values/valuelist.py b/reclass/values/valuelist.py
index 64a7cb5..f89a0c3 100644
--- a/reclass/values/valuelist.py
+++ b/reclass/values/valuelist.py
@@ -13,7 +13,9 @@
self._refs = []
self._allRefs = True
self._values = [ value ]
+ self._inv_refs = []
self._has_inv_query = False
+ self._ignore_failed_render = False
self._update()
def append(self, value):
@@ -25,7 +27,6 @@
self._update()
def _update(self):
- self._has_inv_query = False
self.assembleRefs()
self._check_for_inv_query()
@@ -35,6 +36,9 @@
def has_inv_query(self):
return self._has_inv_query
+ def get_inv_references(self):
+ return self._inv_refs
+
def is_complex(self):
return (self.has_references() | self.has_inv_query())
@@ -44,11 +48,20 @@
def allRefs(self):
return self._allRefs
+ def ignore_failed_render(self):
+ return self._ignore_failed_render
+
def _check_for_inv_query(self):
self._has_inv_query = False
+ self._ignore_failed_render = True
for value in self._values:
if value.has_inv_query():
+ self._inv_refs.extend(value.get_inv_references)
self._has_inv_query = True
+ if vale.ignore_failed_render() is False:
+ self._ignore_failed_render = False
+ if self._has_inv_query is False:
+ self._ignore_failed_render = False
def assembleRefs(self, context={}):
self._refs = []