framework for $[] export parameters
diff --git a/reclass/datatypes/entity.py b/reclass/datatypes/entity.py
index 1aea3b3..c6c3b28 100644
--- a/reclass/datatypes/entity.py
+++ b/reclass/datatypes/entity.py
@@ -17,13 +17,15 @@
     uri of the Entity that is being merged.
     '''
     def __init__(self, classes=None, applications=None, parameters=None,
-                 uri=None, name=None, environment=None):
+                 exports=None, uri=None, name=None, environment=None):
         if classes is None: classes = Classes()
         self._set_classes(classes)
         if applications is None: applications = Applications()
         self._set_applications(applications)
         if parameters is None: parameters = Parameters()
+        if exports is None: exports = Parameters()
         self._set_parameters(parameters)
+        self._set_exports(exports)
         self._uri = uri or ''
         self._name = name or ''
         self._environment = environment or ''
@@ -35,6 +37,7 @@
     classes = property(lambda s: s._classes)
     applications = property(lambda s: s._applications)
     parameters = property(lambda s: s._parameters)
+    exports = property(lambda s: s._exports)
 
     def _set_classes(self, classes):
         if not isinstance(classes, Classes):
@@ -54,10 +57,17 @@
                             'instance of type %s' % type(parameters))
         self._parameters = parameters
 
+    def _set_exports(self, exports):
+        if not isinstance(exports, Parameters):
+            raise TypeError('Entity.exports cannot be set to '\
+                            'instance of type %s' % type(exports))
+        self._exports = exports
+
     def merge(self, other):
         self._classes.merge_unique(other._classes)
         self._applications.merge_unique(other._applications)
         self._parameters.merge(other._parameters)
+        self._exports.merge(other._exports)
         self._name = other.name
         self._uri = other.uri
         self._environment = other.environment
@@ -66,13 +76,15 @@
         self._parameters.merge(params)
 
     def interpolate(self):
-        self._parameters.interpolate()
+        self._exports.interpolate_from_external(self._parameters)
+        self._parameters.interpolate(exports={ self._name: self._exports.as_dict() })
 
     def __eq__(self, other):
         return isinstance(other, type(self)) \
                 and self._applications == other._applications \
                 and self._classes == other._classes \
                 and self._parameters == other._parameters \
+                and self._exports == other._exports \
                 and self._name == other._name \
                 and self._uri == other._uri
 
@@ -84,6 +96,7 @@
                                                     self.classes,
                                                     self.applications,
                                                     self.parameters,
+                                                    self.exports,
                                                     self.uri,
                                                     self.name)
 
@@ -91,5 +104,6 @@
         return {'classes': self._classes.as_list(),
                 'applications': self._applications.as_list(),
                 'parameters': self._parameters.as_dict(),
+                'exports': self._exports.as_dict(),
                 'environment': self._environment
                }
diff --git a/reclass/datatypes/parameters.py b/reclass/datatypes/parameters.py
index 91016d2..3af80ff 100644
--- a/reclass/datatypes/parameters.py
+++ b/reclass/datatypes/parameters.py
@@ -49,7 +49,7 @@
             delimiter = Parameters.DEFAULT_PATH_DELIMITER
         self._delimiter = delimiter
         self._base = {}
-        self._unrendered = {}
+        self._unrendered = None
         self._escapes_handled = {}
         if mapping is not None:
             # we initialise by merging, otherwise the list of references might
@@ -199,6 +199,7 @@
 
         """
 
+        self._unrendered = None
         if isinstance(other, dict):
             wrapped = copy.deepcopy(other)
             self._wrap_dict(wrapped)
@@ -213,17 +214,15 @@
             raise TypeError('Cannot merge %s objects into %s' % (type(other),
                             self.__class__.__name__))
 
-    def has_unresolved_refs(self):
-        return len(self._unrendered) > 0
-
     def render_simple(self, options=None):
         if options is None:
             options = MergeOptions()
+        self._unrendered = {}
         self._render_simple_dict(self._base, DictPath(self.delimiter), options)
 
     def _render_simple_container(self, container, key, value, path, options):
             if isinstance(value, ValueList):
-                if value.has_references():
+                if value.has_references() or value.has_exports():
                     self._unrendered[path.new_subpath(key)] = True
                     return
                 else:
@@ -237,10 +236,10 @@
                 self._render_simple_list(value, path.new_subpath(key), options)
                 container[key] = value
             elif isinstance(value, Value):
-                if value.has_references():
+                if value.has_references() or value.has_exports():
                     self._unrendered[path.new_subpath(key)] = True
                 else:
-                    container[key] = value.render({}, options)
+                    container[key] = value.render(None, None, options)
 
     def _render_simple_dict(self, dictionary, path, options):
         for key, value in dictionary.iteritems():
@@ -250,19 +249,35 @@
         for n, value in enumerate(item_list):
             self._render_simple_container(item_list, n, value, path, options)
 
-    def interpolate(self, options=None):
+    def _ensure_render_simple(self, options):
+        if self._unrendered is None:
+            self.render_simple(options)
+
+    def interpolate_from_external(self, external, options=None):
+        if self._unrendered is None:
+            options = MergeOptions()
+        self._ensure_render_simple(options)
+        external._ensure_render_simple(options)
+        while len(self._unrendered) > 0:
+            path, v = self._unrendered.iteritems().next()
+            value = path.get_value(self._base)
+            external._interpolate_references(path, value, None, options)
+            new = value.render(external._base, options)
+            path.set_value(self._base, new)
+            del self._unrendered[path]
+
+    def interpolate(self, exports=None, options=None):
         if options is None:
             options = MergeOptions()
-        self._unrendered = {}
-        self.render_simple(options)
-        while self.has_unresolved_refs():
+        self._ensure_render_simple(options)
+        while len(self._unrendered) > 0:
             # we could use a view here, but this is simple enough:
             # _interpolate_inner removes references from the refs hash after
             # processing them, so we cannot just iterate the dict
-            path, value = self._unrendered.iteritems().next()
-            self._interpolate_inner(path, options)
+            path, v = self._unrendered.iteritems().next()
+            self._interpolate_inner(path, exports, options)
 
-    def _interpolate_inner(self, path, options):
+    def _interpolate_inner(self, path, exports, options):
         value = path.get_value(self._base)
         if not isinstance(value, (Value, ValueList)):
             # references to lists and dicts are only deepcopied when merged
@@ -270,50 +285,55 @@
             # list or dict has already been visited by _interpolate_inner
             del self._unrendered[path]
             return
-        self._unrendered[path] = False  # mark as seen
-        for ref in value.get_references():
-            path_from_ref = DictPath(self.delimiter, ref)
+        self._unrendered[path] = False
+        self._interpolate_references(path, value, exports, options)
+        new = self._interpolate_render_value(path, value, exports, options)
+        path.set_value(self._base, new)
+        del self._unrendered[path]
 
-            if path_from_ref in self._unrendered:
-                if self._unrendered[path_from_ref] is False:
-                    # every call to _interpolate_inner replaces the value of
-                    # self._unrendered[path] with False
-                    # Therefore, if we encounter False instead of True,
-                    # it means that we have already processed it and are now
-                    # faced with a cyclical reference.
-                    raise InfiniteRecursionError(path, ref)
+    def _interpolate_render_value(self, path, value, exports, options):
+        try:
+            new = value.render(self._base, exports, options)
+        except UndefinedVariableError as e:
+            raise UndefinedVariableError(e.var, path)
+
+        if isinstance(new, dict):
+            self._render_simple_dict(new, path, options)
+        elif isinstance(new, list):
+            self._render_simple_list(new, path, options)
+        return new
+
+    def _interpolate_references(self, path, value, exports, options):
+        all_refs = False
+        while not all_refs:
+            for ref in value.get_references():
+                path_from_ref = DictPath(self.delimiter, ref)
+
+                if path_from_ref in self._unrendered:
+                    if self._unrendered[path_from_ref] is False:
+                        # every call to _interpolate_inner replaces the value of
+                        # self._unrendered[path] with False
+                        # Therefore, if we encounter False instead of True,
+                        # it means that we have already processed it and are now
+                        # faced with a cyclical reference.
+                        raise InfiniteRecursionError(path, ref)
+                    else:
+                        self._interpolate_inner(path_from_ref, exports, options)
                 else:
-                    self._interpolate_inner(path_from_ref, options)
+                    # ensure ancestor keys are already dereferenced
+                    ancestor = DictPath(self.delimiter)
+                    for k in path_from_ref.key_parts():
+                        ancestor = ancestor.new_subpath(k)
+                        if ancestor in self._unrendered:
+                            self._interpolate_inner(ancestor, exports, options)
+            if value.allRefs():
+                all_refs = True
             else:
-                # ensure ancestor keys are already dereferenced
-                ancestor = DictPath(self.delimiter)
-                for k in path_from_ref.key_parts():
-                    ancestor = ancestor.new_subpath(k)
-                    if ancestor in self._unrendered:
-                        self._interpolate_inner(ancestor, options)
-
-        if value.allRefs():
-            # all references have been deferenced so render value
-            try:
-                new = value.render(self._base, options)
-                if isinstance(new, dict):
-                    self._render_simple_dict(new, path, options)
-                elif isinstance(new, list):
-                    self._render_simple_list(new, path, options)
-                path.set_value(self._base, new)
-
-                # remove the reference from the unrendered list
-                del self._unrendered[path]
-            except UndefinedVariableError as e:
-                raise UndefinedVariableError(e.var, path)
-        else:
-            # not all references in the value could be calculated previously so
-            # try recalculating references with current context and recursively
-            # call _interpolate_inner if the number of references has increased
-            # Otherwise raise an error
-            old = len(value.get_references())
-            value.assembleRefs(self._base)
-            if old != len(value.get_references()):
-                self._interpolate_inner(path, options)
-            else:
-                raise InterpolationError('Bad reference count, path:' + repr(path))
+                # not all references in the value could be calculated previously so
+                # try recalculating references with current context and recursively
+                # call _interpolate_inner if the number of references has increased
+                # Otherwise raise an error
+                old = len(value.get_references())
+                value.assembleRefs(self._base)
+                if old == len(value.get_references()):
+                    raise InterpolationError('Bad reference count, path:' + repr(path))
diff --git a/reclass/datatypes/tests/test_entity.py b/reclass/datatypes/tests/test_entity.py
index 17ec9e8..4976dd3 100644
--- a/reclass/datatypes/tests/test_entity.py
+++ b/reclass/datatypes/tests/test_entity.py
@@ -14,12 +14,11 @@
     import mock
 
 @mock.patch.multiple('reclass.datatypes', autospec=True, Classes=mock.DEFAULT,
-                     Applications=mock.DEFAULT,
-                     Parameters=mock.DEFAULT)
+                     Applications=mock.DEFAULT, Parameters=mock.DEFAULT)
 class TestEntity(unittest.TestCase):
 
     def _make_instances(self, Classes, Applications, Parameters):
-        return Classes(), Applications(), Parameters()
+        return Classes(), Applications(), Parameters(), mock.MagicMock(spec=Parameters)
 
     def test_constructor_default(self, **mocks):
         # Actually test the real objects by calling the default constructor,
@@ -30,19 +29,22 @@
         self.assertIsInstance(e.classes, Classes)
         self.assertIsInstance(e.applications, Applications)
         self.assertIsInstance(e.parameters, Parameters)
+        self.assertIsInstance(e.exports, Parameters)
 
     def test_constructor_empty(self, **types):
         instances = self._make_instances(**types)
         e = Entity(*instances)
         self.assertEqual(e.name, '')
         self.assertEqual(e.uri, '')
-        cl, al, pl = [getattr(i, '__len__') for i in instances]
+        cl, al, pl, ex = [getattr(i, '__len__') for i in instances]
         self.assertEqual(len(e.classes), cl.return_value)
         cl.assert_called_once_with()
         self.assertEqual(len(e.applications), al.return_value)
         al.assert_called_once_with()
         self.assertEqual(len(e.parameters), pl.return_value)
         pl.assert_called_once_with()
+        self.assertEqual(len(e.exports), pl.return_value)
+        ex.assert_called_once_with()
 
     def test_constructor_empty_named(self, **types):
         name = 'empty'
@@ -147,6 +149,7 @@
         comp['classes'] = instances[0].as_list()
         comp['applications'] = instances[1].as_list()
         comp['parameters'] = instances[2].as_dict()
+        comp['exports'] = instances[3].as_dict()
         comp['environment'] = 'test'
         d = entity.as_dict()
         self.assertDictEqual(d, comp)
diff --git a/reclass/datatypes/tests/test_parameters.py b/reclass/datatypes/tests/test_parameters.py
index e33f3b8..7dc2d7c 100644
--- a/reclass/datatypes/tests/test_parameters.py
+++ b/reclass/datatypes/tests/test_parameters.py
@@ -7,7 +7,7 @@
 # Released under the terms of the Artistic Licence 2.0
 #
 from reclass.datatypes import Parameters
-from reclass.defaults import PARAMETER_INTERPOLATION_SENTINELS, ESCAPE_CHARACTER
+from reclass.defaults import REFERENCE_SENTINELS, ESCAPE_CHARACTER
 from reclass.errors import InfiniteRecursionError
 from reclass.utils.mergeoptions import MergeOptions
 import unittest
@@ -234,7 +234,7 @@
 
     def test_interpolate_single(self):
         v = 42
-        d = {'foo': 'bar'.join(PARAMETER_INTERPOLATION_SENTINELS),
+        d = {'foo': 'bar'.join(REFERENCE_SENTINELS),
              'bar': v}
         p = Parameters(d)
         p.interpolate()
@@ -242,7 +242,7 @@
 
     def test_interpolate_multiple(self):
         v = '42'
-        d = {'foo': 'bar'.join(PARAMETER_INTERPOLATION_SENTINELS) + 'meep'.join(PARAMETER_INTERPOLATION_SENTINELS),
+        d = {'foo': 'bar'.join(REFERENCE_SENTINELS) + 'meep'.join(REFERENCE_SENTINELS),
              'bar': v[0],
              'meep': v[1]}
         p = Parameters(d)
@@ -251,8 +251,8 @@
 
     def test_interpolate_multilevel(self):
         v = 42
-        d = {'foo': 'bar'.join(PARAMETER_INTERPOLATION_SENTINELS),
-             'bar': 'meep'.join(PARAMETER_INTERPOLATION_SENTINELS),
+        d = {'foo': 'bar'.join(REFERENCE_SENTINELS),
+             'bar': 'meep'.join(REFERENCE_SENTINELS),
              'meep': v}
         p = Parameters(d)
         p.interpolate()
@@ -260,7 +260,7 @@
 
     def test_interpolate_list(self):
         l = [41,42,43]
-        d = {'foo': 'bar'.join(PARAMETER_INTERPOLATION_SENTINELS),
+        d = {'foo': 'bar'.join(REFERENCE_SENTINELS),
              'bar': l}
         p = Parameters(d)
         p.interpolate()
@@ -268,8 +268,8 @@
 
     def test_interpolate_infrecursion(self):
         v = 42
-        d = {'foo': 'bar'.join(PARAMETER_INTERPOLATION_SENTINELS),
-             'bar': 'foo'.join(PARAMETER_INTERPOLATION_SENTINELS)}
+        d = {'foo': 'bar'.join(REFERENCE_SENTINELS),
+             'bar': 'foo'.join(REFERENCE_SENTINELS)}
         p = Parameters(d)
         with self.assertRaises(InfiniteRecursionError):
             p.interpolate()
@@ -355,8 +355,8 @@
         self.assertEqual(p1.as_dict(), r)
 
     def test_interpolate_escaping(self):
-        v = 'bar'.join(PARAMETER_INTERPOLATION_SENTINELS)
-        d = {'foo': ESCAPE_CHARACTER + 'bar'.join(PARAMETER_INTERPOLATION_SENTINELS),
+        v = 'bar'.join(REFERENCE_SENTINELS)
+        d = {'foo': ESCAPE_CHARACTER + 'bar'.join(REFERENCE_SENTINELS),
              'bar': 'unused'}
         p = Parameters(d)
         p.render_simple()
@@ -364,7 +364,7 @@
 
     def test_interpolate_double_escaping(self):
         v = ESCAPE_CHARACTER + 'meep'
-        d = {'foo': ESCAPE_CHARACTER + ESCAPE_CHARACTER + 'bar'.join(PARAMETER_INTERPOLATION_SENTINELS),
+        d = {'foo': ESCAPE_CHARACTER + ESCAPE_CHARACTER + 'bar'.join(REFERENCE_SENTINELS),
              'bar': 'meep'}
         p = Parameters(d)
         p.interpolate()
@@ -379,7 +379,7 @@
             # Escape character followed by escape character
             '2', ESCAPE_CHARACTER + ESCAPE_CHARACTER,
             # Escape character followed by interpolation end sentinel
-            '3', ESCAPE_CHARACTER + PARAMETER_INTERPOLATION_SENTINELS[1],
+            '3', ESCAPE_CHARACTER + REFERENCE_SENTINELS[1],
             # Escape character at the end of the string
             '4', ESCAPE_CHARACTER
             ])
diff --git a/reclass/defaults.py b/reclass/defaults.py
index 2bc27a4..557d511 100644
--- a/reclass/defaults.py
+++ b/reclass/defaults.py
@@ -25,7 +25,8 @@
                           ]
 CONFIG_FILE_NAME = RECLASS_NAME + '-config.yml'
 
-PARAMETER_INTERPOLATION_SENTINELS = ('${', '}')
+REFERENCE_SENTINELS = ('${', '}')
+EXPORT_SENTINELS = ('$[', ']')
 PARAMETER_INTERPOLATION_DELIMITER = ':'
 PARAMETER_DICT_KEY_OVERRIDE_PREFIX = '~'
 ESCAPE_CHARACTER = '\\'
diff --git a/reclass/errors.py b/reclass/errors.py
index d6936d3..b1504b8 100644
--- a/reclass/errors.py
+++ b/reclass/errors.py
@@ -10,7 +10,7 @@
 import posix, sys
 import traceback
 
-from reclass.defaults import PARAMETER_INTERPOLATION_SENTINELS
+from reclass.defaults import REFERENCE_SENTINELS
 
 class ReclassException(Exception):
 
@@ -135,7 +135,7 @@
     context = property(lambda self: self._context)
 
     def _get_message(self):
-        msg = "Cannot resolve " + self._var.join(PARAMETER_INTERPOLATION_SENTINELS)
+        msg = "Cannot resolve " + self._var.join(REFERENCE_SENTINELS)
         if self._context:
             msg += ' in the context of %s' % self._context
         return msg
@@ -148,7 +148,7 @@
 
     def __init__(self, string, end_sentinel):
         super(IncompleteInterpolationError, self).__init__(msg=None)
-        self._ref = string.join(PARAMETER_INTERPOLATION_SENTINELS)
+        self._ref = string.join(REFERENCE_SENTINELS)
         self._end_sentinel = end_sentinel
 
     def _get_message(self):
@@ -161,7 +161,7 @@
     def __init__(self, path, ref):
         super(InfiniteRecursionError, self).__init__(msg=None)
         self._path = path
-        self._ref = ref.join(PARAMETER_INTERPOLATION_SENTINELS)
+        self._ref = ref.join(REFERENCE_SENTINELS)
 
     def _get_message(self):
         msg = "Infinite recursion while resolving {0} at {1}"
diff --git a/reclass/storage/yaml_fs/yamlfile.py b/reclass/storage/yaml_fs/yamlfile.py
index 717a911..03a5c11 100644
--- a/reclass/storage/yaml_fs/yamlfile.py
+++ b/reclass/storage/yaml_fs/yamlfile.py
@@ -47,12 +47,17 @@
             parameters = {}
         parameters = datatypes.Parameters(parameters)
 
+        exports = self._data.get('exports')
+        if exports is None:
+            exports = {}
+        exports = datatypes.Parameters(exports)
+
         env = self._data.get('environment', default_environment)
 
         if name is None:
             name = self._path
 
-        return datatypes.Entity(classes, applications, parameters,
+        return datatypes.Entity(classes, applications, parameters, exports,
                                 name=name, environment=env,
                                 uri='yaml_fs://{0}'.format(self._path))
 
diff --git a/reclass/utils/compitem.py b/reclass/utils/compitem.py
index 2e41f92..12845f7 100644
--- a/reclass/utils/compitem.py
+++ b/reclass/utils/compitem.py
@@ -35,14 +35,17 @@
     def get_references(self):
         return self._refs
 
-    def render(self, context):
+    def has_exports(self):
+        return False
+
+    def render(self, context, exports):
         # Preserve type if only one item
         if len(self._items) == 1:
-            return self._items[0].render(context)
+            return self._items[0].render(context, exports)
         # Multiple items
         string = ''
         for item in self._items:
-            string += str(item.render(context))
+            string += str(item.render(context, exports))
         return string
 
     def __repr__(self):
diff --git a/reclass/utils/dictitem.py b/reclass/utils/dictitem.py
index d612480..1782e70 100644
--- a/reclass/utils/dictitem.py
+++ b/reclass/utils/dictitem.py
@@ -40,6 +40,9 @@
     def has_references(self):
         return len(self._refs) > 0
 
+    def has_exports(self):
+        return False
+
     def get_references(self):
         return self._refs
 
@@ -53,7 +56,7 @@
                 raise TypeError('allow dict over scalar = False: cannot merge %s onto %s' % (repr(self), repr(item)))
         raise TypeError('Cannot merge %s over %s' % (repr(self), repr(item)))
 
-    def render(self, context):
+    def render(self, context, exports):
         return self._dict
 
     def __repr__(self):
diff --git a/reclass/utils/dictpath.py b/reclass/utils/dictpath.py
index 7395679..a7353b1 100644
--- a/reclass/utils/dictpath.py
+++ b/reclass/utils/dictpath.py
@@ -132,3 +132,20 @@
 
     def set_value(self, base, value):
         self._get_innermost_container(base)[self._get_key()] = value
+
+    def drop_first(self):
+        del self._parts[0]
+
+    def exists_in(self, base):
+        container = base
+        for i in self._parts:
+            if isinstance(container, (dict, list)) and i in container:
+                if isinstance(container, dict):
+                    container = container[i]
+                elif isinstance(container, list):
+                    container = container[int(i)]
+            elif i == self._parts[-1]:
+                return True
+            else:
+                return False
+        return True
diff --git a/reclass/utils/expitem.py b/reclass/utils/expitem.py
new file mode 100644
index 0000000..fe2bf31
--- /dev/null
+++ b/reclass/utils/expitem.py
@@ -0,0 +1,50 @@
+#
+# -*- coding: utf-8 -*-
+#
+# This file is part of reclass
+#
+
+from reclass.utils.dictpath import DictPath
+from reclass.errors import UndefinedVariableError
+
+class ExpItem(object):
+
+    def __init__(self, items, delimiter):
+        self._delimiter = delimiter
+        self._items = items
+
+    def contents(self):
+        return self._items
+
+    def has_references(self):
+        return False
+
+    def has_exports(self):
+        return True
+
+    def _resolve(self, path, key, dictionary):
+        try:
+            return path.get_value(dictionary)
+        except KeyError as e:
+            raise UndefinedVariableError(key)
+
+    def _key(self):
+        if len(self._items) == 1:
+            return self._items[0].contents()
+        string = ''
+        for item in self._items:
+            string += item.contents()
+        return string
+
+    def render(self, context, exports):
+        result = []
+        exp_key = self._key()
+        exp_path = DictPath(self._delimiter, exp_key)
+        exp_path.drop_first()
+        for node, items in exports.iteritems():
+            if exp_path.exists_in(items):
+                result.append(self._resolve(exp_path, exp_key, items))
+        return result
+
+    def __repr__(self):
+        return 'ExpItem(%r)' % self._items
diff --git a/reclass/utils/listitem.py b/reclass/utils/listitem.py
index fa17491..d80ebc9 100644
--- a/reclass/utils/listitem.py
+++ b/reclass/utils/listitem.py
@@ -34,10 +34,13 @@
     def has_references(self):
         return len(self._refs) > 0
 
+    def has_exports(self):
+        return False
+
     def get_references(self):
         return self._refs
 
-    def render(self, context):
+    def render(self, context, exports):
         return self._list
 
     def merge_over(self, item, options):
diff --git a/reclass/utils/refitem.py b/reclass/utils/refitem.py
index b14514d..7f1f759 100644
--- a/reclass/utils/refitem.py
+++ b/reclass/utils/refitem.py
@@ -25,7 +25,7 @@
                 item.assembleRefs(context)
                 self._refs.extend(item.get_references())
             try:
-                value += str(item.render(context))
+                value += str(item.render(context, None))
             except UndefinedVariableError as e:
                 self._allRefs = False
         if self._allRefs:
@@ -40,6 +40,9 @@
     def has_references(self):
         return len(self._refs) > 0
 
+    def has_exports(self):
+        return False
+
     def get_references(self):
         return self._refs
 
@@ -50,14 +53,12 @@
         except KeyError as e:
             raise UndefinedVariableError(ref)
 
-    def render(self, context):
-        # Preserve type if only one item
+    def render(self, context, exports):
         if len(self._items) == 1:
-            return self._resolve(self._items[0].render(context), context)
-        # Multiple items
+            return self._resolve(self._items[0].render(context, exports), context)
         string = ''
         for item in self._items:
-            string += str(item.render(context))
+            string += str(item.render(context, exports))
         return self._resolve(string, context)
 
     def __repr__(self):
diff --git a/reclass/utils/scaitem.py b/reclass/utils/scaitem.py
index b9e49f8..421afdc 100644
--- a/reclass/utils/scaitem.py
+++ b/reclass/utils/scaitem.py
@@ -21,6 +21,9 @@
     def has_references(self):
         return False
 
+    def has_exports(self):
+        return False
+
     def contents(self):
         return self._value
 
@@ -42,7 +45,7 @@
                 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):
+    def render(self, context, exports):
         return self._value
 
     def __repr__(self):
diff --git a/reclass/utils/tests/test_value.py b/reclass/utils/tests/test_value.py
index 5e11ef4..f4ac296 100644
--- a/reclass/utils/tests/test_value.py
+++ b/reclass/utils/tests/test_value.py
@@ -10,15 +10,15 @@
 import pyparsing as pp
 
 from reclass.utils.value import Value
-from reclass.defaults import PARAMETER_INTERPOLATION_SENTINELS, \
+from reclass.defaults import REFERENCE_SENTINELS, \
         PARAMETER_INTERPOLATION_DELIMITER
 from reclass.errors import UndefinedVariableError, \
         IncompleteInterpolationError, ParseError
 import unittest
 
 def _var(s):
-    return '%s%s%s' % (PARAMETER_INTERPOLATION_SENTINELS[0], s,
-                       PARAMETER_INTERPOLATION_SENTINELS[1])
+    return '%s%s%s' % (REFERENCE_SENTINELS[0], s,
+                       REFERENCE_SENTINELS[1])
 
 CONTEXT = {'favcolour':'yellow',
            'motd':{'greeting':'Servus!',
@@ -39,12 +39,12 @@
         s = 'my cat likes to hide in boxes'
         tv = Value(s)
         self.assertFalse(tv.has_references())
-        self.assertEquals(tv.render(CONTEXT), s)
+        self.assertEquals(tv.render(CONTEXT, None), s)
 
     def _test_solo_ref(self, key):
         s = _var(key)
         tv = Value(s)
-        res = tv.render(CONTEXT)
+        res = tv.render(CONTEXT, None)
         self.assertTrue(tv.has_references())
         self.assertEqual(res, CONTEXT[key])
 
@@ -67,7 +67,7 @@
         s = 'I like ' + _var('favcolour') + ' and I like it'
         tv = Value(s)
         self.assertTrue(tv.has_references())
-        self.assertEqual(tv.render(CONTEXT),
+        self.assertEqual(tv.render(CONTEXT, None),
                          _poor_mans_template(s, 'favcolour',
                                              CONTEXT['favcolour']))
 
@@ -75,7 +75,7 @@
         s = _var('favcolour') + ' is my favourite colour'
         tv = Value(s)
         self.assertTrue(tv.has_references())
-        self.assertEqual(tv.render(CONTEXT),
+        self.assertEqual(tv.render(CONTEXT, None),
                          _poor_mans_template(s, 'favcolour',
                                              CONTEXT['favcolour']))
 
@@ -83,7 +83,7 @@
         s = 'I like ' + _var('favcolour')
         tv = Value(s)
         self.assertTrue(tv.has_references())
-        self.assertEqual(tv.render(CONTEXT),
+        self.assertEqual(tv.render(CONTEXT, None),
                          _poor_mans_template(s, 'favcolour',
                                              CONTEXT['favcolour']))
 
@@ -92,7 +92,7 @@
         s = _var(var)
         tv = Value(s)
         self.assertTrue(tv.has_references())
-        self.assertEqual(tv.render(CONTEXT),
+        self.assertEqual(tv.render(CONTEXT, None),
                          _poor_mans_template(s, var,
                                              CONTEXT['motd']['greeting']))
 
@@ -103,7 +103,7 @@
         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), want)
+        self.assertEqual(tv.render(CONTEXT, None), want)
 
     def test_multiple_subst_flush(self):
         greet = PARAMETER_INTERPOLATION_DELIMITER.join(('motd', 'greeting'))
@@ -112,16 +112,16 @@
         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), want)
+        self.assertEqual(tv.render(CONTEXT, None), want)
 
     def test_undefined_variable(self):
         s = _var('no_such_variable')
         tv = Value(s)
         with self.assertRaises(UndefinedVariableError):
-            tv.render(CONTEXT)
+            tv.render(CONTEXT, None)
 
     def test_incomplete_variable(self):
-        s = PARAMETER_INTERPOLATION_SENTINELS[0] + 'incomplete'
+        s = REFERENCE_SENTINELS[0] + 'incomplete'
         with self.assertRaises(ParseError):
             tv = Value(s)
 
diff --git a/reclass/utils/value.py b/reclass/utils/value.py
index 7af16c1..65dbe8d 100644
--- a/reclass/utils/value.py
+++ b/reclass/utils/value.py
@@ -9,25 +9,40 @@
 from reclass.utils.mergeoptions import MergeOptions
 from reclass.utils.compitem import CompItem
 from reclass.utils.dictitem import DictItem
+from reclass.utils.expitem import ExpItem
 from reclass.utils.listitem import ListItem
 from reclass.utils.refitem import RefItem
 from reclass.utils.scaitem import ScaItem
-from reclass.defaults import PARAMETER_INTERPOLATION_DELIMITER, PARAMETER_INTERPOLATION_SENTINELS, ESCAPE_CHARACTER
+from reclass.defaults import PARAMETER_INTERPOLATION_DELIMITER, ESCAPE_CHARACTER, REFERENCE_SENTINELS, EXPORT_SENTINELS
 from reclass.errors import *
 
 
 _STR = 'STR'
 _REF = 'REF'
-_OPEN = PARAMETER_INTERPOLATION_SENTINELS[0]
-_CLOSE = PARAMETER_INTERPOLATION_SENTINELS[1]
-_CLOSE_FIRST = _CLOSE[0]
+_EXP = 'EXP'
+
 _ESCAPE = ESCAPE_CHARACTER
 _DOUBLE_ESCAPE = _ESCAPE + _ESCAPE
-_ESCAPE_OPEN = _ESCAPE + _OPEN
-_ESCAPE_CLOSE = _ESCAPE + _CLOSE
-_DOUBLE_ESCAPE_OPEN = _DOUBLE_ESCAPE + _OPEN
-_DOUBLE_ESCAPE_CLOSE = _DOUBLE_ESCAPE + _CLOSE
-_EXCLUDES = _ESCAPE + _OPEN + _CLOSE
+
+_REF_OPEN = REFERENCE_SENTINELS[0]
+_REF_CLOSE = REFERENCE_SENTINELS[1]
+_REF_CLOSE_FIRST = _REF_CLOSE[0]
+_REF_ESCAPE_OPEN = _ESCAPE + _REF_OPEN
+_REF_ESCAPE_CLOSE = _ESCAPE + _REF_CLOSE
+_REF_DOUBLE_ESCAPE_OPEN = _DOUBLE_ESCAPE + _REF_OPEN
+_REF_DOUBLE_ESCAPE_CLOSE = _DOUBLE_ESCAPE + _REF_CLOSE
+_REF_EXCLUDES = _ESCAPE + _REF_OPEN + _REF_CLOSE
+
+_EXP_OPEN = EXPORT_SENTINELS[0]
+_EXP_CLOSE = EXPORT_SENTINELS[1]
+_EXP_CLOSE_FIRST = _EXP_CLOSE[0]
+_EXP_ESCAPE_OPEN = _ESCAPE + _EXP_OPEN
+_EXP_ESCAPE_CLOSE = _ESCAPE + _EXP_CLOSE
+_EXP_DOUBLE_ESCAPE_OPEN = _DOUBLE_ESCAPE + _EXP_OPEN
+_EXP_DOUBLE_ESCAPE_CLOSE = _DOUBLE_ESCAPE + _EXP_CLOSE
+_EXP_EXCLUDES = _ESCAPE + _EXP_OPEN + _EXP_CLOSE
+
+_EXCLUDES = _ESCAPE + _REF_OPEN + _REF_CLOSE + _EXP_OPEN + _EXP_CLOSE
 
 
 class Value(object):
@@ -42,28 +57,44 @@
             token = list(tokens[0])
             tokens[0] = (_REF, token)
 
-        ref_open = pp.Literal(_OPEN).suppress()
-        ref_close = pp.Literal(_CLOSE).suppress()
-        not_open = ~pp.Literal(_OPEN) + ~pp.Literal(_ESCAPE_OPEN) + ~pp.Literal(_DOUBLE_ESCAPE_OPEN)
-        not_close = ~pp.Literal(_CLOSE) + ~pp.Literal(_ESCAPE_CLOSE) + ~pp.Literal(_DOUBLE_ESCAPE_CLOSE)
-        escape_open = pp.Literal(_ESCAPE_OPEN).setParseAction(pp.replaceWith(_OPEN))
-        escape_close = pp.Literal(_ESCAPE_CLOSE).setParseAction(pp.replaceWith(_CLOSE))
-        double_escape = pp.Combine(pp.Literal(_DOUBLE_ESCAPE) + pp.MatchFirst([pp.FollowedBy(_OPEN), pp.FollowedBy(_CLOSE)])).setParseAction(pp.replaceWith(_ESCAPE))
-        text = pp.MatchFirst([pp.Word(pp.printables, excludeChars=_EXCLUDES), pp.CharsNotIn('', exact=1)])
-        text_ref = pp.MatchFirst([pp.Word(pp.printables, excludeChars=_EXCLUDES), pp.CharsNotIn(_CLOSE_FIRST, exact=1)])
+        def _export(string, location, tokens):
+            token = list(tokens[0])
+            tokens[0] = (_EXP, token)
+
         white_space = pp.White()
+        double_escape = pp.Combine(pp.Literal(_DOUBLE_ESCAPE) + pp.MatchFirst([pp.FollowedBy(_REF_OPEN), pp.FollowedBy(_REF_CLOSE)])).setParseAction(pp.replaceWith(_ESCAPE))
 
-        content = pp.Combine(pp.OneOrMore(not_open + text))
-        ref_content = pp.Combine(pp.OneOrMore(not_open + not_close + text_ref))
-        string = pp.MatchFirst([double_escape, escape_open, content, white_space]).setParseAction(_string)
-        refString = pp.MatchFirst([double_escape, escape_open, escape_close, ref_content, white_space]).setParseAction(_string)
+        ref_open = pp.Literal(_REF_OPEN).suppress()
+        ref_close = pp.Literal(_REF_CLOSE).suppress()
+        ref_not_open = ~pp.Literal(_REF_OPEN) + ~pp.Literal(_REF_ESCAPE_OPEN) + ~pp.Literal(_REF_DOUBLE_ESCAPE_OPEN)
+        ref_not_close = ~pp.Literal(_REF_CLOSE) + ~pp.Literal(_REF_ESCAPE_CLOSE) + ~pp.Literal(_REF_DOUBLE_ESCAPE_CLOSE)
+        ref_escape_open = pp.Literal(_REF_ESCAPE_OPEN).setParseAction(pp.replaceWith(_REF_OPEN))
+        ref_escape_close = pp.Literal(_REF_ESCAPE_CLOSE).setParseAction(pp.replaceWith(_REF_CLOSE))
+        ref_text = pp.MatchFirst([pp.Word(pp.printables, excludeChars=_REF_EXCLUDES), pp.CharsNotIn(_REF_CLOSE_FIRST, exact=1)])
+        ref_content = pp.Combine(pp.OneOrMore(ref_not_open + ref_not_close + ref_text))
+        ref_string = pp.MatchFirst([double_escape, ref_escape_open, ref_escape_close, ref_content, white_space]).setParseAction(_string)
+        ref_item = pp.Forward()
+        ref_items = pp.OneOrMore(ref_item)
+        reference = (ref_open + pp.Group(ref_items) + ref_close).setParseAction(_reference)
+        ref_item << (reference | ref_string)
 
-        refItem = pp.Forward()
-        refItems = pp.OneOrMore(refItem)
-        reference = (ref_open + pp.Group(refItems) + ref_close).setParseAction(_reference)
-        refItem << (reference | refString)
+        exp_open = pp.Literal(_EXP_OPEN).suppress()
+        exp_close = pp.Literal(_EXP_CLOSE).suppress()
+        exp_not_open = ~pp.Literal(_EXP_OPEN) + ~pp.Literal(_EXP_ESCAPE_OPEN) + ~pp.Literal(_EXP_DOUBLE_ESCAPE_OPEN)
+        exp_not_close = ~pp.Literal(_EXP_CLOSE) + ~pp.Literal(_EXP_ESCAPE_CLOSE) + ~pp.Literal(_EXP_DOUBLE_ESCAPE_CLOSE)
+        exp_escape_open = pp.Literal(_EXP_ESCAPE_OPEN).setParseAction(pp.replaceWith(_EXP_OPEN))
+        exp_escape_close = pp.Literal(_EXP_ESCAPE_CLOSE).setParseAction(pp.replaceWith(_EXP_CLOSE))
+        exp_text = pp.MatchFirst([pp.Word(pp.printables, excludeChars=_EXP_EXCLUDES), pp.CharsNotIn(_EXP_CLOSE_FIRST, exact=1)])
+        exp_content = pp.Combine(pp.OneOrMore(exp_not_open + exp_not_close + exp_text))
+        exp_string = pp.MatchFirst([double_escape, exp_escape_open, exp_escape_close, exp_content, white_space]).setParseAction(_string)
+        exp_items = pp.OneOrMore(exp_string)
+        export = (exp_open + pp.Group(exp_items) + exp_close).setParseAction(_export)
 
-        item = reference | string
+        text = pp.MatchFirst([pp.Word(pp.printables, excludeChars=_EXCLUDES), pp.CharsNotIn('', exact=1)])
+        content = pp.Combine(pp.OneOrMore(ref_not_open + exp_not_open + text))
+        string = pp.MatchFirst([double_escape, ref_escape_open, exp_escape_open, content, white_space]).setParseAction(_string)
+
+        item = pp.MatchFirst([reference, export, string])
         line = pp.OneOrMore(item) + pp.StringEnd()
         return line
 
@@ -103,6 +134,12 @@
                 items.append(self._createRef(token[1]))
         return RefItem(items, self._delimiter)
 
+    def _createExp(self, tokens):
+        items = []
+        for token in tokens:
+            items.append(ScaItem(token[1]))
+        return ExpItem(items, self._delimiter)
+
     def _createItems(self, tokens):
         items = []
         for token in tokens:
@@ -110,6 +147,8 @@
                 items.append(ScaItem(token[1]))
             elif token[0] == _REF:
                 items.append(self._createRef(token[1]))
+            elif token[0] == _EXP:
+                items.append(self._createExp(token[1]))
         return items
 
     def assembleRefs(self, context={}):
@@ -130,11 +169,14 @@
     def has_references(self):
         return len(self._refs) > 0
 
+    def has_exports(self):
+        return self._item.has_exports()
+
     def get_references(self):
         return self._refs
 
-    def render(self, context, dummy=None):
-        return self._item.render(context)
+    def render(self, context, exports, dummy=None):
+        return self._item.render(context, exports)
 
     def contents(self):
         return self._item.contents()
diff --git a/reclass/utils/valuelist.py b/reclass/utils/valuelist.py
index 0fa7d0a..29d6d68 100644
--- a/reclass/utils/valuelist.py
+++ b/reclass/utils/valuelist.py
@@ -17,6 +17,8 @@
         if value is not None:
             self._values.append(value)
         self.assembleRefs()
+        self._has_exports = False
+        self._check_for_exports()
 
     def append(self, value):
         self._values.append(value)
@@ -31,12 +33,21 @@
     def has_references(self):
         return len(self._refs) > 0
 
+    def has_exports(self):
+        return self._has_exports
+
     def get_references(self):
         return self._refs
 
     def allRefs(self):
         return self._allRefs
 
+    def _check_for_exports(self):
+        self._has_exports = False
+        for value in self._values:
+            if value.has_exports():
+                self._has_exports = True
+
     def assembleRefs(self, context={}):
         self._refs = []
         self._allRefs = True
@@ -58,7 +69,7 @@
                 output = value.merge_over(output, options)
         return output
 
-    def render(self, context, options=None):
+    def render(self, context, exports, options=None):
         from reclass.datatypes.parameters import Parameters
 
         if options is None:
@@ -67,10 +78,10 @@
         deepCopied = False
         for n, value in enumerate(self._values):
             if output is None:
-                output = self._values[n].render(context, options)
+                output = self._values[n].render(context, exports, options)
                 deepCopied = False
             else:
-                new = value.render(context, options)
+                new = value.render(context, exports, options)
                 if isinstance(output, dict) and isinstance(new, dict):
                     p1 = Parameters(output, value._delimiter)
                     p2 = Parameters(new, value._delimiter)