Merge pull request #43 from salt-formulas/pr/38

Pr/38 - rebased
diff --git a/reclass/datatypes/parameters.py b/reclass/datatypes/parameters.py
index cbee11f..6ad0d27 100644
--- a/reclass/datatypes/parameters.py
+++ b/reclass/datatypes/parameters.py
@@ -44,7 +44,7 @@
     functionality and does not try to be a really mapping object.
     '''
 
-    def __init__(self, mapping, settings, uri, merge_initialise = True):
+    def __init__(self, mapping, settings, uri, parse_strings=True):
         self._settings = settings
         self._base = {}
         self._uri = uri
@@ -54,14 +54,12 @@
         self._resolve_errors = ResolveErrorList()
         self._needs_all_envs = False
         self._keep_overrides = False
+        self._parse_strings = parse_strings
         if mapping is not None:
-            if merge_initialise:
-                # we initialise by merging
-                self._keep_overrides = True
-                self.merge(mapping)
-                self._keep_overrides = False
-            else:
-                self._base = copy.deepcopy(mapping)
+            # we initialise by merging
+            self._keep_overrides = True
+            self.merge(mapping)
+            self._keep_overrides = False
 
     #delimiter = property(lambda self: self._delimiter)
 
@@ -103,7 +101,7 @@
             return value
         else:
             try:
-                return Value(value, self._settings, self._uri)
+                return Value(value, self._settings, self._uri, parse_string=self._parse_strings)
             except InterpolationError as e:
                 e.context = str(path)
                 raise
@@ -127,7 +125,7 @@
         elif isinstance(new, ValueList):
             values.extend(new)
         else:
-            values.append(Value(new, self._settings, self._uri))
+            values.append(Value(new, self._settings, self._uri, parse_string=self._parse_strings))
 
         return values
 
@@ -154,7 +152,7 @@
         for (key, newvalue) in iteritems(new):
             if key.startswith(self._settings.dict_key_override_prefix) and not self._keep_overrides:
                 if not isinstance(newvalue, Value):
-                    newvalue = Value(newvalue, self._settings, self._uri)
+                    newvalue = Value(newvalue, self._settings, self._uri, parse_string=self._parse_strings)
                 newvalue.overwrite = True
                 ret[key.lstrip(self._settings.dict_key_override_prefix)] = newvalue
             else:
@@ -187,7 +185,7 @@
         else:
             return self._update_value(cur, new)
 
-    def merge(self, other, wrap=True):
+    def merge(self, other):
         """Merge function (public edition).
 
         Call _merge_recurse on self with either another Parameter object or a
@@ -203,15 +201,9 @@
 
         self._unrendered = None
         if isinstance(other, dict):
-            if wrap:
-                wrapped = self._wrap_dict(other, DictPath(self._settings.delimiter))
-            else:
-                wrapped = copy.deepcopy(other)
+            wrapped = self._wrap_dict(other, DictPath(self._settings.delimiter))
         elif isinstance(other, self.__class__):
-            if wrap:
-                wrapped = self._wrap_dict(other._base, DictPath(self._settings.delimiter))
-            else:
-                wrapped = copy.deepcopy(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__))
diff --git a/reclass/datatypes/tests/test_parameters.py b/reclass/datatypes/tests/test_parameters.py
index ecd112c..9b788dc 100644
--- a/reclass/datatypes/tests/test_parameters.py
+++ b/reclass/datatypes/tests/test_parameters.py
@@ -655,6 +655,26 @@
         p1.interpolate()
         self.assertEqual(p1.as_dict(), r)
 
+    def test_escaped_string_overwrites(self):
+        p1 = Parameters({ 'test': '\${not_a_ref}' }, SETTINGS, '')
+        p2 = Parameters({ 'test': '\${also_not_a_ref}' }, SETTINGS, '')
+        r = { 'test': '${also_not_a_ref}' }
+        p1.merge(p2)
+        p1.interpolate()
+        self.assertEqual(p1.as_dict(), r)
+
+    def test_escaped_string_in_ref_dict_overwrite(self):
+        p1 = Parameters({'a': { 'one': '\${not_a_ref}' }, 'b': { 'two': '\${also_not_a_ref}' }}, SETTINGS, '')
+        p2 = Parameters({'c': '${a}'}, SETTINGS, '')
+        p3 = Parameters({'c': '${b}'}, SETTINGS, '')
+        p4 = Parameters({'c': { 'one': '\${again_not_a_ref}' } }, SETTINGS, '')
+        r = {'a': {'one': '${not_a_ref}'}, 'b': {'two': '${also_not_a_ref}'}, 'c': {'one': '${again_not_a_ref}', 'two': '${also_not_a_ref}'}}
+        p1.merge(p2)
+        p1.merge(p3)
+        p1.merge(p4)
+        p1.interpolate()
+        self.assertEqual(p1.as_dict(), r)
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/reclass/values/compitem.py b/reclass/values/compitem.py
index 765b323..c6e8863 100644
--- a/reclass/values/compitem.py
+++ b/reclass/values/compitem.py
@@ -39,6 +39,21 @@
     def get_references(self):
         return self._refs
 
+    def merge_over(self, item):
+        if item.type == Item.SCALAR or item.type == Item.COMPOSITE:
+            return self
+        elif item.type == Item.LIST:
+            if self._settings.allow_scalar_over_list or (self._settings.allow_none_override and self._value in [None, 'none', 'None']):
+                return self
+            else:
+                raise TypeError('allow scalar over list = False: cannot merge %s over %s' % (repr(self), repr(item)))
+        elif item.type == Item.DICTIONARY:
+            if self._settings.allow_scalar_over_dict or (self._settings.allow_none_override and self._value in [None, 'none', 'None']):
+                return self
+            else:
+                raise TypeError('allow scalar over dict = False: cannot merge %s over %s' % (repr(self), repr(item)))
+        raise TypeError('Cannot merge %s over %s' % (repr(self), repr(item)))
+
     def render(self, context, inventory):
         # Preserve type if only one item
         if len(self._items) == 1:
diff --git a/reclass/values/scaitem.py b/reclass/values/scaitem.py
index 9de5681..f4265b5 100644
--- a/reclass/values/scaitem.py
+++ b/reclass/values/scaitem.py
@@ -18,7 +18,7 @@
         return self._value
 
     def merge_over(self, item):
-        if item.type == Item.SCALAR:
+        if item.type == Item.SCALAR or item.type == Item.COMPOSITE:
             return self
         elif item.type == Item.LIST:
             if self._settings.allow_scalar_over_list or (self._settings.allow_none_override and self._value is None):
diff --git a/reclass/values/value.py b/reclass/values/value.py
index 7a855ad..1a5b450 100644
--- a/reclass/values/value.py
+++ b/reclass/values/value.py
@@ -14,16 +14,19 @@
 
     _parser = Parser()
 
-    def __init__(self, value, settings, uri):
+    def __init__(self, value, settings, uri, parse_string=True):
         self._settings = settings
         self._uri = uri
         self._overwrite = False
         if isinstance(value, str):
-            try:
-                self._item = self._parser.parse(value, self._settings)
-            except InterpolationError as e:
-                e.uri = self._uri
-                raise
+            if parse_string:
+                try:
+                    self._item = self._parser.parse(value, self._settings)
+                except InterpolationError as e:
+                    e.uri = self._uri
+                    raise
+            else:
+                self._item = ScaItem(value, self._settings)
         elif isinstance(value, list):
             self._item = ListItem(value, self._settings)
         elif isinstance(value, dict):
diff --git a/reclass/values/valuelist.py b/reclass/values/valuelist.py
index 460dff0..bdfa01a 100644
--- a/reclass/values/valuelist.py
+++ b/reclass/values/valuelist.py
@@ -109,9 +109,9 @@
                 deepCopied = False
             else:
                 if isinstance(output, dict) and isinstance(new, dict):
-                    p1 = Parameters(output, self._settings, None, merge_initialise = False)
-                    p2 = Parameters(new, self._settings, None, merge_initialise = False)
-                    p1.merge(p2, wrap=False)
+                    p1 = Parameters(output, self._settings, None, parse_strings=False)
+                    p2 = Parameters(new, self._settings, None, parse_strings=False)
+                    p1.merge(p2)
                     output = p1.as_dict()
                     continue
                 elif isinstance(output, list) and isinstance(new, list):