Refactoring pt. 1
In this patch parser rules are made more strict,
some boilerplate from parser and other tools is removed,
and a bug with override in composite item is partially
addressed. Also an attempt is made to enhance test coverage.
diff --git a/reclass/core.py b/reclass/core.py
index ed5a392..2facfbe 100644
--- a/reclass/core.py
+++ b/reclass/core.py
@@ -210,8 +210,8 @@
try:
node = self._node_entity(nodename)
node.initialise_interpolation()
- if node.parameters.has_inv_query() and inventory is None:
- inventory = self._get_inventory(node.parameters.needs_all_envs(), node.environment, node.parameters.get_inv_queries())
+ if node.parameters.has_inv_query and inventory is None:
+ inventory = self._get_inventory(node.parameters.needs_all_envs, node.environment, node.parameters.get_inv_queries())
node.interpolate(inventory)
return node
except InterpolationError as e:
@@ -237,7 +237,7 @@
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():
+ if entities[n].parameters.has_inv_query:
nodes.add(n)
for n in query_nodes:
entities[n] = self._nodeinfo(n, inventory)
diff --git a/reclass/datatypes/parameters.py b/reclass/datatypes/parameters.py
index fa0f379..4bac31a 100644
--- a/reclass/datatypes/parameters.py
+++ b/reclass/datatypes/parameters.py
@@ -29,8 +29,9 @@
from reclass.utils.parameterlist import ParameterList
from reclass.values.value import Value
from reclass.values.valuelist import ValueList
-from reclass.errors import InfiniteRecursionError, ResolveError, ResolveErrorList, InterpolationError, BadReferencesError
-
+from reclass.errors import InfiniteRecursionError, ResolveError
+from reclass.errors import ResolveErrorList, InterpolationError, ParseError
+from reclass.errors import BadReferencesError
class Parameters(object):
'''
@@ -84,12 +85,14 @@
def __ne__(self, other):
return not self.__eq__(other)
+ @property
def has_inv_query(self):
return len(self._inv_queries) > 0
def get_inv_queries(self):
return self._inv_queries
+ @property
def needs_all_envs(self):
return self._needs_all_envs
@@ -108,7 +111,8 @@
return self._wrap_list(value)
else:
try:
- return Value(value, self._settings, self._uri, parse_string=self._parse_strings)
+ return Value(value, self._settings, self._uri,
+ parse_string=self._parse_strings)
except InterpolationError as e:
e.context = DictPath(self._settings.delimiter)
raise
@@ -154,7 +158,8 @@
uri = new.uri
else:
uri = self._uri
- values.append(Value(new, self._settings, uri, parse_string=self._parse_strings))
+ values.append(Value(new, self._settings, uri,
+ parse_string=self._parse_strings))
return values
@@ -246,37 +251,37 @@
self._base = self._merge_recurse(self._base, wrapped)
def _render_simple_container(self, container, key, value, path):
- if isinstance(value, ValueList):
- if value.is_complex():
- p = path.new_subpath(key)
- self._unrendered[p] = True
- container[key] = value
- if value.has_inv_query():
- self._inv_queries.append((p, value))
- if value.needs_all_envs():
- self._needs_all_envs = True
- return
- else:
- value = value.merge()
- if isinstance(value, Value) and value.is_container():
- value = value.contents()
- if isinstance(value, dict):
- container[key] = self._render_simple_dict(value, path.new_subpath(key))
- elif isinstance(value, list):
- container[key] = self._render_simple_list(value, path.new_subpath(key))
- elif isinstance(value, Value):
- if value.is_complex():
- p = path.new_subpath(key)
- self._unrendered[p] = True
- container[key] = value
- if value.has_inv_query():
- self._inv_queries.append((p, value))
- if value.needs_all_envs():
- self._needs_all_envs = True
- else:
- container[key] = value.render(None, None)
- else:
+ if isinstance(value, ValueList):
+ if value.is_complex:
+ p = path.new_subpath(key)
+ self._unrendered[p] = True
container[key] = value
+ if value.has_inv_query:
+ self._inv_queries.append((p, value))
+ if value.needs_all_envs():
+ self._needs_all_envs = True
+ return
+ else:
+ value = value.merge()
+ if isinstance(value, Value) and value.is_container():
+ value = value.contents
+ if isinstance(value, dict):
+ container[key] = self._render_simple_dict(value, path.new_subpath(key))
+ elif isinstance(value, list):
+ container[key] = self._render_simple_list(value, path.new_subpath(key))
+ elif isinstance(value, Value):
+ if value.is_complex:
+ p = path.new_subpath(key)
+ self._unrendered[p] = True
+ container[key] = value
+ if value.has_inv_query:
+ self._inv_queries.append((p, value))
+ if value.needs_all_envs():
+ self._needs_all_envs = True
+ else:
+ container[key] = value.render(None, None)
+ else:
+ container[key] = value
def _render_simple_dict(self, dictionary, path):
new_dict = {}
@@ -311,7 +316,8 @@
self._inv_queries = []
self._needs_all_envs = False
self._resolve_errors = ResolveErrorList()
- self._base = self._render_simple_dict(self._base, DictPath(self._settings.delimiter))
+ self._base = self._render_simple_dict(self._base,
+ DictPath(self._settings.delimiter))
def _interpolate_inner(self, path, inventory):
value = path.get_value(self._base)
@@ -370,7 +376,7 @@
ancestor = ancestor.new_subpath(k)
if ancestor in self._unrendered:
self._interpolate_inner(ancestor, inventory)
- if value.allRefs():
+ if value.allRefs:
all_refs = True
else:
# not all references in the value could be calculated previously so
diff --git a/reclass/datatypes/tests/test_exports.py b/reclass/datatypes/tests/test_exports.py
index 8ccd6df..2184517 100644
--- a/reclass/datatypes/tests/test_exports.py
+++ b/reclass/datatypes/tests/test_exports.py
@@ -89,11 +89,16 @@
self.assertEqual(p.as_dict(), r)
def test_list_if_expr_invquery_with_and_missing(self):
- e = {'node1': {'a': 1, 'b': 2, 'c': 'green'}, 'node2': {'a': 3, 'b': 3}, 'node3': {'a': 3, 'b': 2}}
- p = Parameters({'exp': '$[ if exports:b == 2 and exports:c == green ]'}, SETTINGS, '')
- r = {'exp': ['node1']}
- p.interpolate(e)
- self.assertEqual(p.as_dict(), r)
+ inventory = {'node1': {'a': 1, 'b': 2, 'c': 'green'},
+ 'node2': {'a': 3, 'b': 3},
+ 'node3': {'a': 3, 'b': 2}}
+ mapping = {'exp': '$[ if exports:b == 2 and exports:c == green ]'}
+ expected = {'exp': ['node1']}
+
+ pars = Parameters(mapping, SETTINGS, '')
+ pars.interpolate(inventory)
+
+ self.assertEqual(pars.as_dict(), expected)
def test_list_if_expr_invquery_with_and(self):
e = {'node1': {'a': 1, 'b': 2}, 'node2': {'a': 3, 'b': 3}, 'node3': {'a': 3, 'b': 4}}
diff --git a/reclass/errors.py b/reclass/errors.py
index 0c9d48f..330ad4c 100644
--- a/reclass/errors.py
+++ b/reclass/errors.py
@@ -359,3 +359,10 @@
"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 MissingModuleError(ReclassException):
+
+ def __init__(self, modname):
+ msg = "Module %s is missing" % modname
+ super(MissingModuleError, self).__init__(rc=posix.EX_DATAERR, msg=msg)
diff --git a/reclass/storage/yaml_git/__init__.py b/reclass/storage/yaml_git/__init__.py
index c26ef77..38de092 100644
--- a/reclass/storage/yaml_git/__init__.py
+++ b/reclass/storage/yaml_git/__init__.py
@@ -17,7 +17,13 @@
import warnings
with warnings.catch_warnings():
warnings.simplefilter('ignore')
- import pygit2
+ try:
+ # NOTE: in some distros pygit2 could require special effort to acquire.
+ # It is not a problem per se, but it breaks tests for no real reason.
+ # This try block is for keeping tests sane.
+ import pygit2
+ except ImportError:
+ pygit2 = None
from six import iteritems
@@ -70,6 +76,8 @@
class GitRepo(object):
def __init__(self, uri):
+ if pygit2 is None:
+ raise errors.MissingModuleError('pygit2')
self.transport, _, self.url = uri.repo.partition('://')
self.name = self.url.replace('/', '_')
self.credentials = None
diff --git a/reclass/utils/dictpath.py b/reclass/utils/dictpath.py
index 32831cf..6bf152a 100644
--- a/reclass/utils/dictpath.py
+++ b/reclass/utils/dictpath.py
@@ -93,9 +93,9 @@
def __hash__(self):
return hash(str(self))
- def _get_path(self):
+ @property
+ def path(self):
return self._parts
- path = property(_get_path)
def _get_key(self):
if len(self._parts) == 0:
@@ -114,17 +114,8 @@
def _split_string(self, string):
return re.split(r'(?<!\\)' + re.escape(self._delim), string)
- def _escape_string(self, string):
- return string.replace(self._delim, '\\' + self._delim)
-
- def has_ancestors(self):
- return len(self._parts) > 1
-
def key_parts(self):
- if self.has_ancestors():
- return self._parts[:-1]
- else:
- return []
+ return self._parts[:-1]
def new_subpath(self, key):
return DictPath(self._delim, self._parts + [key])
diff --git a/reclass/values/__init__.py b/reclass/values/__init__.py
index 9aaaf25..ec0f882 100644
--- a/reclass/values/__init__.py
+++ b/reclass/values/__init__.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8
+# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
diff --git a/reclass/values/compitem.py b/reclass/values/compitem.py
index 183bc43..704ac69 100644
--- a/reclass/values/compitem.py
+++ b/reclass/values/compitem.py
@@ -17,26 +17,29 @@
self.type = Item.COMPOSITE
self._items = items
self._settings = settings
- self._refs = []
- self._allRefs = False
self.assembleRefs()
+ # TODO: possibility of confusion. Looks like 'assemble' should be either
+ # 'gather' or 'extract'.
def assembleRefs(self, context={}):
self._refs = []
self._allRefs = True
for item in self._items:
- if item.has_references():
+ if item.has_references:
item.assembleRefs(context)
self._refs.extend(item.get_references())
- if item.allRefs() is False:
+ if item.allRefs is False:
self._allRefs = False
+ @property
def contents(self):
return self._items
+ @property
def allRefs(self):
return self._allRefs
+ @property
def has_references(self):
return len(self._refs) > 0
diff --git a/reclass/values/dictitem.py b/reclass/values/dictitem.py
index d5272b9..b96875f 100644
--- a/reclass/values/dictitem.py
+++ b/reclass/values/dictitem.py
@@ -18,6 +18,7 @@
self._dict = item
self._settings = settings
+ @property
def contents(self):
return self._dict
diff --git a/reclass/values/invitem.py b/reclass/values/invitem.py
index 37a35cf..15b66c0 100644
--- a/reclass/values/invitem.py
+++ b/reclass/values/invitem.py
@@ -9,9 +9,11 @@
from __future__ import unicode_literals
import copy
+import functools
import pyparsing as pp
-from six import iteritems, string_types
+from six import iteritems
+from six import string_types
from .item import Item
from reclass.settings import Settings
@@ -35,15 +37,68 @@
_IGNORE_ERRORS = '+IgnoreErrors'
_ALL_ENVS = '+AllEnvs'
+
+def _get_parser():
+ def tag_with(tag, transform=lambda x:x):
+ def inner(tag, string, location, tokens):
+ token = transform(tokens[0])
+ tokens[0] = (tag, token)
+ return functools.partial(inner, tag)
+
+ _object = tag_with(_OBJ)
+ _option = tag_with(_OPTION)
+ _expr_list_test = tag_with(_LIST_TEST)
+ _test = tag_with(_TEST)
+ _logical = tag_with(_LOGICAL)
+ _if = tag_with(_IF)
+ _expr_var = tag_with(_VALUE)
+ _expr_test = tag_with(_TEST)
+ _integer = tag_with(_OBJ, int)
+ _number = tag_with(_OBJ, float)
+
+ 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))
+ 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)
+ obj = pp.Word(pp.printables).setParseAction(_object)
+ sign = pp.Optional(pp.Literal('-'))
+ number = pp.Word(pp.nums)
+ dpoint = pp.Literal('.')
+ integer = pp.Combine(sign + number + pp.WordEnd()).setParseAction(_integer)
+ real = pp.Combine(sign +
+ ((number + dpoint + number) |
+ (dpoint + number) |
+ (number + dpoint))
+ ).setParseAction(_number)
+ item = integer | real | obj
+
+ single_test = item + operator_test + item
+ additional_test = operator_logical + single_test
+ expr_var = pp.Group(obj + end).setParseAction(_expr_var)
+ expr_test = pp.Group(obj + 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 = expr_test | expr_var | expr_list_test
+ line = options + expr + end
+ return line
+
+
class Element(object):
def __init__(self, expression, delimiter):
self._delimiter = delimiter
- self._export_path = None
- self._parameter_path = None
- 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)
+ # TODO: this double sommersault must be cleaned
+ _ = self._get_vars(expression[2][1], *self._get_vars(expression[0][1]))
+ self._export_path, self._parameter_path, self._parameter_value = _
try:
self._export_path.drop_first()
@@ -82,7 +137,8 @@
if export_value != self._parameter_value:
result = True
else:
- raise ExpressionError('Unknown test {0}'.format(self._test), tbFlag=False)
+ raise ExpressionError('Unknown test {0}'.format(self._test),
+ tbFlag=False)
return result
else:
return False
@@ -93,7 +149,7 @@
except KeyError as e:
raise ResolveError(str(path))
- def _get_vars(self, var, export, parameter, value):
+ def _get_vars(self, var, export=None, parameter=None, value=None):
if isinstance(var, string_types):
path = DictPath(self._delimiter, var)
if path.path[0].lower() == 'exports':
@@ -150,81 +206,14 @@
elif self._operators[i] == _OR:
result = result or next_result
else:
- raise ExpressionError('Unknown operator {0} {1}'.format(self._operators[i], self.elements), tbFlag=False)
+ emsg = 'Unknown operator {0} {1}'.format(
+ self._operators[i], self.elements)
+ raise ExpressionError(emsg, tbFlag=False)
return result
class InvItem(Item):
- def _get_parser():
-
- def _object(string, location, tokens):
- token = tokens[0]
- tokens[0] = (_OBJ, token)
-
- def _integer(string, location, tokens):
- try:
- token = int(tokens[0])
- except ValueError:
- token = tokens[0]
- tokens[0] = (_OBJ, token)
-
- def _number(string, location, tokens):
- try:
- token = float(tokens[0])
- except ValueError:
- 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)
-
- def _logical(string, location, tokens):
- token = tokens[0]
- tokens[0] = (_LOGICAL, token)
-
- def _if(string, location, tokens):
- token = tokens[0]
- tokens[0] = (_IF, token)
-
- def _expr_var(string, location, tokens):
- token = tokens[0]
- tokens[0] = (_VALUE, token)
-
- def _expr_test(string, location, tokens):
- token = tokens[0]
- tokens[0] = (_TEST, token)
-
- def _expr_list_test(string, location, tokens):
- token = tokens[0]
- tokens[0] = (_LIST_TEST, token)
-
- 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)
- obj = pp.Word(pp.printables).setParseAction(_object)
- integer = pp.Word('0123456789-').setParseAction(_integer)
- number = pp.Word('0123456789-.').setParseAction(_number)
- item = integer | number | obj
- single_test = white_space + item + white_space + operator_test + white_space + item
- additional_test = white_space + operator_logical + single_test
- 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 = (expr_test | expr_var | expr_list_test)
- line = options + expr + end
- return line
_parser = _get_parser()
@@ -238,7 +227,7 @@
def _parse_expression(self, expr):
try:
- tokens = InvItem._parser.parseString(expr).asList()
+ tokens = self._parser.parseString(expr).asList()
except pp.ParseException as e:
raise ParseError(e.msg, e.line, e.col, e.lineno)
@@ -278,12 +267,15 @@
def assembleRefs(self, context):
return
+ @property
def contents(self):
return self._expr_text
+ @property
def has_inv_query(self):
return True
+ @property
def has_references(self):
return len(self._question.refs()) > 0
diff --git a/reclass/values/item.py b/reclass/values/item.py
index cad3684..4ab3f68 100644
--- a/reclass/values/item.py
+++ b/reclass/values/item.py
@@ -12,6 +12,8 @@
class Item(object):
+ # TODO: use enum.Enum
+ # TODO: consider DotMap
COMPOSITE = 1
DICTIONARY = 2
INV_QUERY = 3
@@ -26,18 +28,22 @@
def allRefs(self):
return True
+ @property
def has_references(self):
return False
+ @property
def has_inv_query(self):
return False
def is_container(self):
return False
+ @property
def is_complex(self):
- return (self.has_references() | self.has_inv_query())
+ return (self.has_references | self.has_inv_query)
+ @property
def contents(self):
msg = "Item class {0} does not implement contents()"
raise NotImplementedError(msg.format(self.__class__.__name__))
diff --git a/reclass/values/listitem.py b/reclass/values/listitem.py
index 41c02dd..0f0ee60 100644
--- a/reclass/values/listitem.py
+++ b/reclass/values/listitem.py
@@ -18,6 +18,7 @@
self._list = item
self._settings = settings
+ @property
def contents(self):
return self._list
diff --git a/reclass/values/refitem.py b/reclass/values/refitem.py
index d24eeee..5713346 100644
--- a/reclass/values/refitem.py
+++ b/reclass/values/refitem.py
@@ -21,18 +21,16 @@
self.type = Item.REFERENCE
self._settings = settings
self._items = items
- self._refs = []
- self._allRefs = False
self.assembleRefs()
def assembleRefs(self, context={}):
self._refs = []
self._allRefs = True
for item in self._items:
- if item.has_references():
+ if item.has_references:
item.assembleRefs(context)
self._refs.extend(item.get_references())
- if item.allRefs() == False:
+ if item.allRefs == False:
self._allRefs = False
try:
strings = [ str(i.render(context, None)) for i in self._items ]
@@ -41,12 +39,15 @@
except ResolveError as e:
self._allRefs = False
+ @property
def contents(self):
return self._items
+ @property
def allRefs(self):
return self._allRefs
+ @property
def has_references(self):
return len(self._refs) > 0
diff --git a/reclass/values/scaitem.py b/reclass/values/scaitem.py
index c16ab45..c65f302 100644
--- a/reclass/values/scaitem.py
+++ b/reclass/values/scaitem.py
@@ -18,6 +18,7 @@
self._value = value
self._settings = settings
+ @property
def contents(self):
return self._value
diff --git a/reclass/values/tests/__init__.py b/reclass/values/tests/__init__.py
index 16d1248..e69de29 100644
--- a/reclass/values/tests/__init__.py
+++ b/reclass/values/tests/__init__.py
@@ -1,7 +0,0 @@
-#
-# -*- coding: utf-8
-#
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-from __future__ import unicode_literals
diff --git a/reclass/values/tests/test_compitem.py b/reclass/values/tests/test_compitem.py
new file mode 100644
index 0000000..3d63d3b
--- /dev/null
+++ b/reclass/values/tests/test_compitem.py
@@ -0,0 +1,184 @@
+from reclass.settings import Settings
+from reclass.values.value import Value
+from reclass.values.compitem import CompItem
+from reclass.values.scaitem import ScaItem
+from reclass.values.valuelist import ValueList
+from reclass.values.listitem import ListItem
+from reclass.values.dictitem import DictItem
+import unittest
+
+SETTINGS = Settings()
+
+class TestCompItem(unittest.TestCase):
+
+ def test_assembleRefs_no_items(self):
+ composite = CompItem([], SETTINGS)
+
+ self.assertFalse(composite.has_references)
+
+ def test_assembleRefs_one_item_without_refs(self):
+ val1 = Value('foo', SETTINGS, '')
+
+ composite = CompItem([val1], SETTINGS)
+
+ self.assertFalse(composite.has_references)
+
+ def test_assembleRefs_one_item_with_one_ref(self):
+ val1 = Value('${foo}', SETTINGS, '')
+ expected_refs = ['foo']
+
+ composite = CompItem([val1], SETTINGS)
+
+ self.assertTrue(composite.has_references)
+ self.assertEquals(composite.get_references(), expected_refs)
+
+ def test_assembleRefs_one_item_with_two_refs(self):
+ val1 = Value('${foo}${bar}', SETTINGS, '')
+ expected_refs = ['foo', 'bar']
+
+ composite = CompItem([val1], SETTINGS)
+
+ self.assertTrue(composite.has_references)
+ self.assertEquals(composite.get_references(), expected_refs)
+
+ def test_assembleRefs_two_items_one_with_one_ref_one_without(self):
+ val1 = Value('${foo}bar', SETTINGS, '')
+ val2 = Value('baz', SETTINGS, '')
+ expected_refs = ['foo']
+
+ composite = CompItem([val1, val2], SETTINGS)
+
+ self.assertTrue(composite.has_references)
+ self.assertEquals(composite.get_references(), expected_refs)
+
+ def test_assembleRefs_two_items_both_with_one_ref(self):
+ val1 = Value('${foo}', SETTINGS, '')
+ val2 = Value('${bar}', SETTINGS, '')
+ expected_refs = ['foo', 'bar']
+
+ composite = CompItem([val1, val2], SETTINGS)
+
+ self.assertTrue(composite.has_references)
+ self.assertEquals(composite.get_references(), expected_refs)
+
+ def test_assembleRefs_two_items_with_two_refs(self):
+ val1 = Value('${foo}${baz}', SETTINGS, '')
+ val2 = Value('${bar}${meep}', SETTINGS, '')
+ expected_refs = ['foo', 'baz', 'bar', 'meep']
+
+ composite = CompItem([val1, val2], SETTINGS)
+
+ self.assertTrue(composite.has_references)
+ self.assertEquals(composite.get_references(), expected_refs)
+
+ def test_render_single_item(self):
+ val1 = Value('${foo}', SETTINGS, '')
+
+ composite = CompItem([val1], SETTINGS)
+
+ self.assertEquals(1, composite.render({'foo': 1}, None))
+
+
+ def test_render_multiple_items(self):
+ val1 = Value('${foo}', SETTINGS, '')
+ val2 = Value('${bar}', SETTINGS, '')
+
+ composite = CompItem([val1, val2], SETTINGS)
+
+ self.assertEquals('12', composite.render({'foo': 1, 'bar': 2}, None))
+
+ def test_merge_over_merge_scalar(self):
+ val1 = Value(None, SETTINGS, '')
+ scalar = ScaItem(1, SETTINGS)
+ composite = CompItem([val1], SETTINGS)
+
+ result = composite.merge_over(scalar)
+
+ self.assertEquals(result, composite)
+
+
+ def test_merge_over_merge_composite(self):
+ val1 = Value(None, SETTINGS, '')
+ val2 = Value(None, SETTINGS, '')
+ composite1 = CompItem([val1], SETTINGS)
+ composite2 = CompItem([val2], SETTINGS)
+
+ result = composite2.merge_over(composite1)
+
+ self.assertEquals(result, composite2)
+
+ @unittest.skip("self._value bug")
+ def test_merge_over_merge_list_scalar_allowed(self):
+ # This nice bunch of lines below breaks merge because fuck you that's
+ # why. Seriously so, merger_over simply is not working for Composites
+ sets = Settings()
+ sets.allow_scalar_override = True
+ val1 = Value(None, SETTINGS, '')
+ listitem = ListItem([1], SETTINGS)
+ composite = CompItem([val1], sets)
+
+ result = composite.merge_over(listitem)
+
+ self.assertEquals(result, composite2)
+
+ @unittest.skip("self._value bug")
+ def test_merge_over_merge_list_override_allowed(self):
+ sets = Settings()
+ sets.allow_none_override = True
+ val1 = Value(None, SETTINGS, '')
+ listitem = ListItem([1], SETTINGS)
+ composite = CompItem([val1], sets)
+
+ result = composite.merge_over(listitem)
+
+ self.assertEquals(result, composite2)
+
+ def test_merge_over_merge_list_not_allowed(self):
+ val1 = Value(None, SETTINGS, '')
+ listitem = ListItem([1], SETTINGS)
+ composite = CompItem([val1], SETTINGS)
+
+ self.assertRaises(RuntimeError, composite.merge_over, listitem)
+
+
+ @unittest.skip("self._value bug")
+ def test_merge_dict_scalar_allowed(self):
+ sets = Settings()
+ sets.allow_scalar_override = True
+ val1 = Value(None, SETTINGS, '')
+ dictitem = DictItem({'foo': 'bar'}, SETTINGS)
+ composite = CompItem([val1], sets)
+
+ result = composite.merge_over(dictitem)
+
+ self.assertEquals(result, composite)
+
+ @unittest.skip("self._value bug")
+ def test_merge_dict_override_allowed(self):
+ sets = Settings()
+ sets.allow_none_override = True
+ val1 = Value(None, SETTINGS, '')
+ dictitem = DictItem([1], SETTINGS)
+ composite = CompItem([val1], sets)
+
+ result = composite.merge_over(dictitem)
+
+ self.assertEquals(result, composite)
+
+ def test_merge_dict_dict_not_allowed(self):
+ val1 = Value(None, SETTINGS, '')
+ dictitem = DictItem({'foo': 'bar'}, SETTINGS)
+ composite = CompItem([val1], SETTINGS)
+
+ self.assertRaises(RuntimeError, composite.merge_over, dictitem)
+
+ def test_merge_other_types_not_allowed(self):
+ other = type('Other', (object,), {'type': 34})
+ val1 = Value(None, SETTINGS, '')
+ composite = CompItem([val1], SETTINGS)
+
+ self.assertRaises(RuntimeError, composite.merge_over, other)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/reclass/values/tests/test_value.py b/reclass/values/tests/test_value.py
index 4853340..a06d220 100644
--- a/reclass/values/tests/test_value.py
+++ b/reclass/values/tests/test_value.py
@@ -11,8 +11,6 @@
from __future__ import print_function
from __future__ import unicode_literals
-import pyparsing as pp
-
from reclass.settings import Settings
from reclass.values.value import Value
from reclass.errors import ResolveError, ParseError
@@ -42,14 +40,14 @@
def test_simple_string(self):
s = 'my cat likes to hide in boxes'
tv = Value(s, SETTINGS, '')
- self.assertFalse(tv.has_references())
+ self.assertFalse(tv.has_references)
self.assertEquals(tv.render(CONTEXT, None), s)
def _test_solo_ref(self, key):
s = _var(key)
tv = Value(s, SETTINGS, '')
res = tv.render(CONTEXT, None)
- self.assertTrue(tv.has_references())
+ self.assertTrue(tv.has_references)
self.assertEqual(res, CONTEXT[key])
def test_solo_ref_string(self):
@@ -70,7 +68,7 @@
def test_single_subst_bothends(self):
s = 'I like ' + _var('favcolour') + ' and I like it'
tv = Value(s, SETTINGS, '')
- self.assertTrue(tv.has_references())
+ self.assertTrue(tv.has_references)
self.assertEqual(tv.render(CONTEXT, None),
_poor_mans_template(s, 'favcolour',
CONTEXT['favcolour']))
@@ -78,7 +76,7 @@
def test_single_subst_start(self):
s = _var('favcolour') + ' is my favourite colour'
tv = Value(s, SETTINGS, '')
- self.assertTrue(tv.has_references())
+ self.assertTrue(tv.has_references)
self.assertEqual(tv.render(CONTEXT, None),
_poor_mans_template(s, 'favcolour',
CONTEXT['favcolour']))
@@ -86,7 +84,7 @@
def test_single_subst_end(self):
s = 'I like ' + _var('favcolour')
tv = Value(s, SETTINGS, '')
- self.assertTrue(tv.has_references())
+ self.assertTrue(tv.has_references)
self.assertEqual(tv.render(CONTEXT, None),
_poor_mans_template(s, 'favcolour',
CONTEXT['favcolour']))
@@ -95,7 +93,7 @@
motd = SETTINGS.delimiter.join(('motd', 'greeting'))
s = _var(motd)
tv = Value(s, SETTINGS, '')
- self.assertTrue(tv.has_references())
+ self.assertTrue(tv.has_references)
self.assertEqual(tv.render(CONTEXT, None),
_poor_mans_template(s, motd,
CONTEXT['motd']['greeting']))
@@ -104,7 +102,7 @@
greet = SETTINGS.delimiter.join(('motd', 'greeting'))
s = _var(greet) + ' I like ' + _var('favcolour') + '!'
tv = Value(s, SETTINGS, '')
- self.assertTrue(tv.has_references())
+ self.assertTrue(tv.has_references)
want = _poor_mans_template(s, greet, CONTEXT['motd']['greeting'])
want = _poor_mans_template(want, 'favcolour', CONTEXT['favcolour'])
self.assertEqual(tv.render(CONTEXT, None), want)
@@ -113,7 +111,7 @@
greet = SETTINGS.delimiter.join(('motd', 'greeting'))
s = _var(greet) + ' I like ' + _var('favcolour')
tv = Value(s, SETTINGS, '')
- self.assertTrue(tv.has_references())
+ self.assertTrue(tv.has_references)
want = _poor_mans_template(s, greet, CONTEXT['motd']['greeting'])
want = _poor_mans_template(want, 'favcolour', CONTEXT['favcolour'])
self.assertEqual(tv.render(CONTEXT, None), want)
diff --git a/reclass/values/value.py b/reclass/values/value.py
index 4e86274..613d553 100644
--- a/reclass/values/value.py
+++ b/reclass/values/value.py
@@ -70,17 +70,20 @@
def is_container(self):
return self._item.is_container()
+ @property
def allRefs(self):
- return self._item.allRefs()
+ return self._item.allRefs
+ @property
def has_references(self):
- return self._item.has_references()
+ return self._item.has_references
+ @property
def has_inv_query(self):
- return self._item.has_inv_query()
+ return self._item.has_inv_query
def needs_all_envs(self):
- if self._item.has_inv_query():
+ if self._item.has_inv_query:
return self._item.needs_all_envs()
else:
return False
@@ -88,8 +91,9 @@
def ignore_failed_render(self):
return self._item.ignore_failed_render()
+ @property
def is_complex(self):
- return self._item.is_complex()
+ return self._item.is_complex
def get_references(self):
return self._item.get_references()
@@ -98,7 +102,7 @@
return self._item.get_inv_references()
def assembleRefs(self, context):
- if self._item.has_references():
+ if self._item.has_references:
self._item.assembleRefs(context)
def render(self, context, inventory):
@@ -108,8 +112,9 @@
e.uri = self._uri
raise
+ @property
def contents(self):
- return self._item.contents()
+ return self._item.contents
def merge_over(self, value):
self._item = self._item.merge_over(value._item)
diff --git a/reclass/values/valuelist.py b/reclass/values/valuelist.py
index 9c1e1fa..b4a089d 100644
--- a/reclass/values/valuelist.py
+++ b/reclass/values/valuelist.py
@@ -48,24 +48,28 @@
self._is_complex = False
item_type = self._values[0].item_type()
for v in self._values:
- if v.is_complex() or v.constant or v.overwrite or v.item_type() != item_type:
+ if v.is_complex or v.constant or v.overwrite or v.item_type() != item_type:
self._is_complex = True
+ @property
def has_references(self):
return len(self._refs) > 0
+ @property
def has_inv_query(self):
return self._has_inv_query
def get_inv_references(self):
return self._inv_refs
+ @property
def is_complex(self):
return self._is_complex
def get_references(self):
return self._refs
+ @property
def allRefs(self):
return self._allRefs
@@ -76,7 +80,7 @@
self._has_inv_query = False
self._ignore_failed_render = True
for value in self._values:
- if value.has_inv_query():
+ 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:
@@ -89,9 +93,9 @@
self._allRefs = True
for value in self._values:
value.assembleRefs(context)
- if value.has_references():
+ if value.has_references:
self._refs.extend(value.get_references())
- if value.allRefs() is False:
+ if value.allRefs is False:
self._allRefs = False
def merge(self):