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):