Merge pull request #95 from salt-formulas/develop

Release 1.7
diff --git a/README-extensions.rst b/README-extensions.rst
index e67e441..7b537cf 100644
--- a/README-extensions.rst
+++ b/README-extensions.rst
@@ -33,7 +33,7 @@
 .. code-block:: yaml
 
   ignore_class_notfound: False
-  ignore_class_regexp: ['.*']
+  ignore_class_notfound_regexp: ['.*']
 
 If you set regexp pattern to ``service.*`` all missing classes starting 'service.' will be logged with warning, but will not
 fail to return rendered reclass. Assuming all parameter interpolation passes.
@@ -691,3 +691,38 @@
         storage_type: yaml_fs
         # options for yaml_fs storage type
         uri: /srv/salt/env/pre-prod/classes
+
+
+Support to use current node parameters as references in class name
+------------------------------------------------------------------
+
+With the following reclass config:
+
+.. code-block::
+
+    => /etc/reclass/nodes/mynode.yml
+    classes:
+      - common
+    parameters:
+      project: myproject
+
+    => /etc/reclass/classes/common.yml
+    class:
+      - ${project}
+
+    => /etc/reclass/classes/myproject.yml
+    parameters:
+      some:
+        project: parameters
+
+
+Will get the following result for the parameters:
+
+.. code-block:: yaml
+
+    parameters:
+      project: myproject
+      some:
+        project: parameters
+
+
diff --git a/doc/source/changelog.rst b/doc/source/changelog.rst
index dccf34f..249b0a4 100644
--- a/doc/source/changelog.rst
+++ b/doc/source/changelog.rst
@@ -5,6 +5,10 @@
 ========= ========== ========================================================
 Version   Date       Changes
 ========= ========== ========================================================
+1.7.0     2020-10-02 Fixes and few new features:
+                     * Allow class mappings to wildcard match against either the node name and class
+                     * Support for .yaml along with .yml
+                     * Support to use current node parameters as references in class name
 1.6.0     2018-11-06 * Python code and parser refactoring by a-ovchinnikov
                      * Improvements in yaml_git and mixed setup by Andrew Pickford
                      * Relative paths in class names by Petr Michalec, Martin Polreich and Andrew Pickford
diff --git a/doc/source/operations.rst b/doc/source/operations.rst
index f744148..08b34e5 100644
--- a/doc/source/operations.rst
+++ b/doc/source/operations.rst
@@ -101,6 +101,13 @@
 can be assigned to each mapping by providing a space-separated list (class
 names cannot contain spaces anyway).
 
+By default the class mappings regex match is done against the node name. This can
+be changed to do the match against the path of the node file from the classes
+directory, but dropping the .yml extension at the end of the node file. This is
+controlled with the setting class_mappings_match_path. When False (the
+default) the match is done again the node name and when true the match is done
+against the node file path.
+
 .. warning::
 
   The class mappings do not really belong in the configuration file, as they
diff --git a/reclass/core.py b/reclass/core.py
index 3e0ab34..f35450f 100644
--- a/reclass/core.py
+++ b/reclass/core.py
@@ -25,6 +25,7 @@
 from reclass.settings import Settings
 from reclass.datatypes import Entity, Classes, Parameters, Exports
 from reclass.errors import MappingFormatError, ClassNameResolveError, ClassNotFound, InvQueryClassNameResolveError, InvQueryClassNotFound, InvQueryError, InterpolationError, ResolveError
+from reclass.values import NodeInventory
 from reclass.values.parser import Parser
 
 
@@ -72,26 +73,30 @@
             key = '/{0}/'.format(key)
         return key, list(lexer)
 
-    def _get_class_mappings_entity(self, nodename):
+    def _get_class_mappings_entity(self, entity):
         if not self._class_mappings:
             return Entity(self._settings, name='empty (class mappings)')
         c = Classes()
+        if self._settings.class_mappings_match_path:
+            matchname = entity.pathname
+        else:
+            matchname = entity.name
         for mapping in self._class_mappings:
             matched = False
             key, klasses = Core._shlex_split(mapping)
             if key[0] == ('/'):
-                matched = Core._match_regexp(key[1:-1], nodename)
+                matched = Core._match_regexp(key[1:-1], matchname)
                 if matched:
                     for klass in klasses:
                         c.append_if_new(matched.expand(klass))
 
             else:
-                if Core._match_glob(key, nodename):
+                if Core._match_glob(key, matchname):
                     for klass in klasses:
                         c.append_if_new(klass)
 
         return Entity(self._settings, classes=c,
-                      name='class mappings for node {0}'.format(nodename))
+                      name='class mappings for node {0}'.format(entity.name))
 
     def _get_input_data_entity(self):
         if not self._input_data:
@@ -167,7 +172,29 @@
         else:
             return Parameters({}, self._settings, '')
 
+    def _get_scalar_parameters(self, node_parameters):
+        if self._settings.scalar_parameters:
+            scalars = node_parameters.as_dict().get(
+                    self._settings.scalar_parameters, {})
+            return Parameters(
+                    {self._settings.scalar_parameters: scalars}, self._settings, '__scalar__')
+        else:
+            return Parameters({}, self._settings, '')
+
     def _get_inventory(self, all_envs, environment, queries):
+        '''
+            Returns a dictionary of NodeInventory objects, one per matching node. Exports
+            which are required for the given queries (or all exports if the queries is None)
+            are rendered, remaining exports are left unrendered.
+
+            Args:
+              all_envs - if True match export values from nodes in any environment
+                         else if False match only for nodes in the same environment as the
+                         environment parameter
+              environment - node environment to match against if all_envs is False
+              queries - list of inventory queries to determine required export values
+                        or if None match all exports defined by a node
+        '''
         inventory = {}
         for nodename in self._storage.enumerate_nodes():
             try:
@@ -187,6 +214,7 @@
                 except ClassNameResolveError as e:
                     raise InvQueryClassNameResolveError(e)
                 if queries is None:
+                    # This only happens if reclass is called with the --inventory option
                     try:
                         node.interpolate_exports()
                     except InterpolationError as e:
@@ -199,7 +227,7 @@
                         except InterpolationError as e:
                             e.nodename = nodename
                             raise InvQueryError(q.contents, e, context=p, uri=q.uri)
-                inventory[nodename] = node.exports.as_dict()
+                inventory[nodename] = NodeInventory(node.exports.as_dict(), node_base.environment == environment)
         return inventory
 
     def _node_entity(self, nodename):
@@ -207,9 +235,10 @@
         if node_entity.environment == None:
             node_entity.environment = self._settings.default_environment
         base_entity = Entity(self._settings, name='base')
-        base_entity.merge(self._get_class_mappings_entity(node_entity.name))
+        base_entity.merge(self._get_class_mappings_entity(node_entity))
         base_entity.merge(self._get_input_data_entity())
         base_entity.merge_parameters(self._get_automatic_parameters(nodename, node_entity.environment))
+        base_entity.merge_parameters(self._get_scalar_parameters(node_entity.parameters))
         seen = {}
         merge_base = self._recurse_entity(base_entity, seen=seen, nodename=nodename,
                                           environment=node_entity.environment)
diff --git a/reclass/datatypes/entity.py b/reclass/datatypes/entity.py
index 2e0e1e4..88b5afe 100644
--- a/reclass/datatypes/entity.py
+++ b/reclass/datatypes/entity.py
@@ -24,9 +24,10 @@
     '''
     def __init__(self, settings, classes=None, applications=None,
                  parameters=None, exports=None, uri=None, name=None,
-                 environment=None):
+                 pathname=None, environment=None):
         self._uri = '' if uri is None else uri
         self._name = '' if name is None else name
+        self._pathname = '' if pathname is None else pathname
         self._classes = self._set_field(classes, Classes)
         self._applications = self._set_field(applications, Applications)
         pars = [None, settings, uri]
@@ -36,6 +37,7 @@
 
     name = property(lambda s: s._name)
     uri = property(lambda s: s._uri)
+    pathname = property(lambda s: s._pathname)
     classes = property(lambda s: s._classes)
     applications = property(lambda s: s._applications)
     parameters = property(lambda s: s._parameters)
@@ -101,10 +103,10 @@
         return not self.__eq__(other)
 
     def __repr__(self):
-        return "%s(%r, %r, %r, %r, uri=%r, name=%r, environment=%r)" % (
+        return "%s(%r, %r, %r, %r, uri=%r, name=%r, pathname=%r, environment=%r)" % (
                    self.__class__.__name__, self.classes, self.applications,
                    self.parameters, self.exports, self.uri, self.name,
-                   self.environment)
+                   self.pathname, self.environment)
 
     def as_dict(self):
         return {'classes': self._classes.as_list(),
diff --git a/reclass/datatypes/tests/test_entity.py b/reclass/datatypes/tests/test_entity.py
index f18f3fc..a2b71b5 100644
--- a/reclass/datatypes/tests/test_entity.py
+++ b/reclass/datatypes/tests/test_entity.py
@@ -11,9 +11,12 @@
 from __future__ import print_function
 from __future__ import unicode_literals
 
+from six import iteritems
+
 from reclass.settings import Settings
 from reclass.datatypes import Entity, Classes, Parameters, Applications, Exports
 from reclass.errors import ResolveError
+from reclass.values import NodeInventory
 import unittest
 
 try:
@@ -167,6 +170,9 @@
 
 class TestEntityNoMock(unittest.TestCase):
 
+    def _make_inventory(self, nodes):
+        return { name: NodeInventory(node, True) for name, node in iteritems(nodes) }
+
     def test_interpolate_list_types(self):
         node1_exports = Exports({'exps': [ '${one}' ] }, SETTINGS, 'first')
         node1_parameters = Parameters({'alpha': [ '${two}', '${three}' ], 'one': 1, 'two': 2, 'three': 3 }, SETTINGS, 'first')
@@ -174,33 +180,37 @@
         node2_exports = Exports({'exps': '${alpha}' }, SETTINGS, 'second')
         node2_parameters = Parameters({}, SETTINGS, 'second')
         node2_entity = Entity(SETTINGS, classes=None, applications=None, parameters=node2_parameters, exports=node2_exports)
-        r = {'exps': [ 1, 2, 3 ]}
+        result = {'exps': [ 1, 2, 3 ]}
         node1_entity.merge(node2_entity)
         node1_entity.interpolate(None)
         self.assertIs(type(node1_entity.exports.as_dict()['exps']), list)
-        self.assertDictEqual(node1_entity.exports.as_dict(), r)
+        self.assertDictEqual(node1_entity.exports.as_dict(), result)
 
     def test_exports_with_refs(self):
-        inventory = {'node1': {'a': 1, 'b': 2}, 'node2': {'a': 3, 'b': 4}}
+        inventory = self._make_inventory({'node1': {'a': 1, 'b': 2}, 'node2': {'a': 3, 'b': 4}})
         node3_exports = Exports({'a': '${a}', 'b': '${b}'}, SETTINGS, '')
         node3_parameters = Parameters({'name': 'node3', 'a': '${c}', 'b': 5}, SETTINGS, '')
         node3_parameters.merge({'c': 3})
         node3_entity = Entity(SETTINGS, classes=None, applications=None, parameters=node3_parameters, exports=node3_exports)
         node3_entity.interpolate_exports()
-        inventory['node3'] = node3_entity.exports.as_dict()
-        r = {'node1': {'a': 1, 'b': 2}, 'node2': {'a': 3, 'b': 4}, 'node3': {'a': 3, 'b': 5}}
-        self.assertDictEqual(inventory, r)
+        inventory['node3'] = NodeInventory(node3_entity.exports.as_dict(), True)
+        result = { 'node1': NodeInventory({'a': 1, 'b': 2}, True),
+                   'node2': NodeInventory({'a': 3, 'b': 4}, True),
+                   'node3': NodeInventory({'a': 3, 'b': 5}, True) }
+        self.assertDictEqual(inventory, result)
 
     def test_reference_to_an_export(self):
-        inventory = {'node1': {'a': 1, 'b': 2}, 'node2': {'a': 3, 'b': 4}}
+        inventory = self._make_inventory({'node1': {'a': 1, 'b': 2}, 'node2': {'a': 3, 'b': 4}})
         node3_exports = Exports({'a': '${a}', 'b': '${b}'}, SETTINGS, '')
         node3_parameters = Parameters({'name': 'node3', 'ref': '${exp}', 'a': '${c}', 'b': 5}, SETTINGS, '')
         node3_parameters.merge({'c': 3, 'exp': '$[ exports:a ]'})
         node3_entity = Entity(SETTINGS, classes=None, applications=None, parameters=node3_parameters, exports=node3_exports)
         node3_entity.interpolate_exports()
-        inventory['node3'] = node3_entity.exports.as_dict()
+        inventory['node3'] = NodeInventory(node3_entity.exports.as_dict(), True)
         node3_entity.interpolate(inventory)
-        res_inv = {'node1': {'a': 1, 'b': 2}, 'node2': {'a': 3, 'b': 4}, 'node3': {'a': 3, 'b': 5}}
+        res_inv = { 'node1': NodeInventory({'a': 1, 'b': 2}, True),
+                    'node2': NodeInventory({'a': 3, 'b': 4}, True),
+                    'node3': NodeInventory({'a': 3, 'b': 5}, True) }
         res_params = {'a': 3, 'c': 3, 'b': 5, 'name': 'node3', 'exp': {'node1': 1, 'node3': 3, 'node2': 3}, 'ref': {'node1': 1, 'node3': 3, 'node2': 3}}
         self.assertDictEqual(node3_parameters.as_dict(), res_params)
         self.assertDictEqual(inventory, res_inv)
@@ -218,40 +228,61 @@
         for p, q in queries:
             node1_entity.interpolate_single_export(q)
             node2_entity.interpolate_single_export(q)
-        res_inv = {'node1': {'a': {'test': 1}}, 'node2': {'a': {'test': 2}}}
-        res_params = {'a': {'test': 1}, 'b': 1, 'name': 'node1', 'exp': {'node1': {'test': 1}, 'node2': {'test': 2}}}
-        inventory = {}
-        inventory['node1'] = node1_entity.exports.as_dict()
-        inventory['node2'] = node2_entity.exports.as_dict()
+        res_inv = { 'node1': NodeInventory({'a': {'test': 1}}, True),
+                    'node2': NodeInventory({'a': {'test': 2}}, True) }
+        res_params = { 'name': 'node1',
+                       'a': {'test': 1},
+                       'b': 1,
+                       'exp': {'node1': {'test': 1}, 'node2': {'test': 2}} }
+        inventory = self._make_inventory({'node1': node1_entity.exports.as_dict(), 'node2': node2_entity.exports.as_dict()})
         node1_entity.interpolate(inventory)
         self.assertDictEqual(node1_parameters.as_dict(), res_params)
         self.assertDictEqual(inventory, res_inv)
 
     def test_exports_with_ancestor_references(self):
-        inventory = {'node1': {'alpha' : {'beta': {'a': 1, 'b': 2}}}, 'node2': {'alpha' : {'beta': {'a': 3, 'b': 4}}}}
+        inventory = self._make_inventory({'node1': {'alpha' : {'beta': {'a': 1, 'b': 2}}}, 'node2': {'alpha' : {'beta': {'a': 3, 'b': 4}}}})
         node3_exports = Exports({'alpha': '${alpha}'}, SETTINGS, '')
         node3_parameters = Parameters({'name': 'node3', 'alpha': {'beta' : {'a': 5, 'b': 6}}, 'exp': '$[ exports:alpha:beta ]'}, SETTINGS, '')
         node3_entity = Entity(SETTINGS, classes=None, applications=None, parameters=node3_parameters, exports=node3_exports)
-        res_params = {'exp': {'node1': {'a': 1, 'b': 2}, 'node2': {'a': 3, 'b': 4}, 'node3': {'a': 5, 'b': 6}}, 'name': 'node3', 'alpha': {'beta': {'a': 5, 'b': 6}}}
-        res_inv = {'node1': {'alpha' : {'beta': {'a': 1, 'b': 2}}}, 'node2': {'alpha' : {'beta': {'a': 3, 'b': 4}}}, 'node3': {'alpha' : {'beta': {'a': 5, 'b': 6}}}}
+        res_params = { 'name': 'node3',
+                       'exp': {'node1': {'a': 1, 'b': 2},
+                       'node2': {'a': 3, 'b': 4},
+                       'node3': {'a': 5, 'b': 6}},
+                       'alpha': {'beta': {'a': 5, 'b': 6}} }
+        res_inv = { 'node1': NodeInventory({'alpha' : {'beta': {'a': 1, 'b': 2}}}, True),
+                    'node2': NodeInventory({'alpha' : {'beta': {'a': 3, 'b': 4}}}, True),
+                    'node3': NodeInventory({'alpha' : {'beta': {'a': 5, 'b': 6}}}, True) }
         node3_entity.initialise_interpolation()
         queries = node3_entity.parameters.get_inv_queries()
         for p, q in queries:
             node3_entity.interpolate_single_export(q)
-        inventory['node3'] = node3_entity.exports.as_dict()
+        inventory['node3'] = NodeInventory(node3_entity.exports.as_dict(), True)
         node3_entity.interpolate(inventory)
         self.assertDictEqual(node3_parameters.as_dict(), res_params)
         self.assertDictEqual(inventory, res_inv)
 
     def test_exports_with_nested_references(self):
-        inventory = {'node1': {'alpha': {'a': 1, 'b': 2}}, 'node2': {'alpha': {'a': 3, 'b': 4}}}
+        inventory = self._make_inventory({'node1': {'alpha': {'a': 1, 'b': 2}}, 'node2': {'alpha': {'a': 3, 'b': 4}}})
         node3_exports = Exports({'alpha': '${alpha}'}, SETTINGS, '')
-        node3_parameters = Parameters({'name': 'node3', 'alpha': {'a': '${one}', 'b': '${two}'}, 'beta': '$[ exports:alpha ]', 'one': '111', 'two': '${three}', 'three': '123'}, SETTINGS, '')
+        node3_parameters = Parameters({ 'name': 'node3',
+                                        'alpha': {'a': '${one}', 'b': '${two}'},
+                                        'beta': '$[ exports:alpha ]',
+                                        'one': '111',
+                                        'two': '${three}',
+                                        'three': '123'},
+                                      SETTINGS, '')
         node3_entity = Entity(SETTINGS, classes=None, applications=None, parameters=node3_parameters, exports=node3_exports)
-        res_params = {'beta': {'node1': {'a': 1, 'b': 2}, 'node3': {'a': '111', 'b': '123'}, 'node2': {'a': 3, 'b': 4}}, 'name': 'node3', 'alpha': {'a': '111', 'b': '123'}, 'three': '123', 'two': '123', 'one': '111'}
-        res_inv = {'node1': {'alpha': {'a': 1, 'b': 2}}, 'node2': {'alpha': {'a': 3, 'b': 4}}, 'node3': {'alpha': {'a': '111', 'b': '123'}}}
+        res_params = { 'name': 'node3',
+                       'alpha': { 'a': '111', 'b': '123' },
+                       'beta': { 'node1': {'a': 1, 'b': 2 }, 'node2': { 'a': 3, 'b': 4}, 'node3': { 'a': '111', 'b': '123' } },
+                       'one': '111',
+                       'two': '123',
+                       'three': '123' }
+        res_inv = { 'node1': NodeInventory({'alpha': {'a': 1, 'b': 2}}, True),
+                    'node2': NodeInventory({'alpha': {'a': 3, 'b': 4}}, True),
+                    'node3': NodeInventory({'alpha': {'a': '111', 'b': '123'}}, True) }
         node3_entity.interpolate_exports()
-        inventory['node3'] = node3_entity.exports.as_dict()
+        inventory['node3'] = NodeInventory(node3_entity.exports.as_dict(), True)
         node3_entity.interpolate(inventory)
         self.assertDictEqual(node3_parameters.as_dict(), res_params)
         self.assertDictEqual(inventory, res_inv)
@@ -285,11 +316,12 @@
         for p, q in queries:
             node1_entity.interpolate_single_export(q)
             node2_entity.interpolate_single_export(q)
-        res_inv = {'node1': {'a': 1}, 'node2': {}}
-        res_params = { 'a': 1, 'name': 'node1', 'exp': {'node1': 1} }
-        inventory = {}
-        inventory['node1'] = node1_entity.exports.as_dict()
-        inventory['node2'] = node2_entity.exports.as_dict()
+        res_inv = { 'node1': NodeInventory({'a': 1}, True),
+                    'node2': NodeInventory({}, True) }
+        res_params = { 'name': 'node1',
+                       'a': 1,
+                       'exp': {'node1': 1} }
+        inventory = self._make_inventory({'node1': node1_entity.exports.as_dict(), 'node2': node2_entity.exports.as_dict()})
         node1_entity.interpolate(inventory)
         self.assertDictEqual(node1_parameters.as_dict(), res_params)
         self.assertDictEqual(inventory, res_inv)
diff --git a/reclass/datatypes/tests/test_exports.py b/reclass/datatypes/tests/test_exports.py
index 16a45cb..e0c5cc1 100644
--- a/reclass/datatypes/tests/test_exports.py
+++ b/reclass/datatypes/tests/test_exports.py
@@ -8,33 +8,39 @@
 from __future__ import print_function
 from __future__ import unicode_literals
 
+from six import iteritems
+
 from reclass.utils.parameterdict import ParameterDict
 from reclass.utils.parameterlist import ParameterList
 from reclass.settings import Settings
 from reclass.datatypes import Exports, Parameters
 from reclass.errors import ParseError
+from reclass.values import NodeInventory
 import unittest
 
 SETTINGS = Settings()
 
 class TestInvQuery(unittest.TestCase):
 
+    def _make_inventory(self, nodes):
+        return { name: NodeInventory(node, True) for name, node in iteritems(nodes) }
+
     def test_overwrite_method(self):
-        e = Exports({'alpha': { 'one': 1, 'two': 2}}, SETTINGS, '')
-        d = {'alpha': { 'three': 3, 'four': 4}}
-        e.overwrite(d)
-        e.interpolate()
-        self.assertEqual(e.as_dict(), d)
+        exports = Exports({'alpha': { 'one': 1, 'two': 2}}, SETTINGS, '')
+        data = {'alpha': { 'three': 3, 'four': 4}}
+        exports.overwrite(data)
+        exports.interpolate()
+        self.assertEqual(exports.as_dict(), data)
 
     def test_interpolate_types(self):
-        e = Exports({'alpha': { 'one': 1, 'two': 2}, 'beta': [ 1, 2 ]}, SETTINGS, '')
-        r = {'alpha': { 'one': 1, 'two': 2}, 'beta': [ 1, 2 ]}
-        self.assertIs(type(e.as_dict()['alpha']), ParameterDict)
-        self.assertIs(type(e.as_dict()['beta']), ParameterList)
-        e.interpolate()
-        self.assertIs(type(e.as_dict()['alpha']), dict)
-        self.assertIs(type(e.as_dict()['beta']), list)
-        self.assertEqual(e.as_dict(), r)
+        exports = Exports({'alpha': { 'one': 1, 'two': 2}, 'beta': [ 1, 2 ]}, SETTINGS, '')
+        result = {'alpha': { 'one': 1, 'two': 2}, 'beta': [ 1, 2 ]}
+        self.assertIs(type(exports.as_dict()['alpha']), ParameterDict)
+        self.assertIs(type(exports.as_dict()['beta']), ParameterList)
+        exports.interpolate()
+        self.assertIs(type(exports.as_dict()['alpha']), dict)
+        self.assertIs(type(exports.as_dict()['beta']), list)
+        self.assertEqual(exports.as_dict(), result)
 
     def test_malformed_invquery(self):
         with self.assertRaises(ParseError):
@@ -51,83 +57,121 @@
             p = Parameters({'exp': '$[ exports:a if exports:b == self:test_value anddd exports:c == self:test_value2 ]'}, SETTINGS, '')
 
     def test_value_expr_invquery(self):
-        e = {'node1': {'a': 1, 'b': 2}, 'node2': {'a': 3, 'b': 4}}
-        p = Parameters({'exp': '$[ exports:a ]'}, SETTINGS, '')
-        r = {'exp': {'node1': 1, 'node2': 3}}
-        p.interpolate(e)
-        self.assertEqual(p.as_dict(), r)
+        inventory = self._make_inventory({'node1': {'a': 1, 'b': 2}, 'node2': {'a': 3, 'b': 4}})
+        parameters = Parameters({'exp': '$[ exports:a ]'}, SETTINGS, '')
+        result = {'exp': {'node1': 1, 'node2': 3}}
+        parameters.interpolate(inventory)
+        self.assertEqual(parameters.as_dict(), result)
 
     def test_if_expr_invquery(self):
-        e = {'node1': {'a': 1, 'b': 2}, 'node2': {'a': 3, 'b': 4}}
-        p = Parameters({'exp': '$[ exports:a if exports:b == 4 ]'}, SETTINGS, '')
-        r = {'exp': {'node2': 3}}
-        p.interpolate(e)
-        self.assertEqual(p.as_dict(), r)
+        inventory = self._make_inventory({'node1': {'a': 1, 'b': 2}, 'node2': {'a': 3, 'b': 4}})
+        parameters = Parameters({'exp': '$[ exports:a if exports:b == 4 ]'}, SETTINGS, '')
+        result = {'exp': {'node2': 3}}
+        parameters.interpolate(inventory)
+        self.assertEqual(parameters.as_dict(), result)
 
     def test_if_expr_invquery_with_refs(self):
-        e = {'node1': {'a': 1, 'b': 2}, 'node2': {'a': 3, 'b': 4}}
-        p = Parameters({'exp': '$[ exports:a if exports:b == self:test_value ]', 'test_value': 2}, SETTINGS, '')
-        r = {'exp': {'node1': 1}, 'test_value': 2}
-        p.interpolate(e)
-        self.assertEqual(p.as_dict(), r)
+        inventory = self._make_inventory({'node1': {'a': 1, 'b': 2}, 'node2': {'a': 3, 'b': 4}})
+        parameters = Parameters({'exp': '$[ exports:a if exports:b == self:test_value ]', 'test_value': 2}, SETTINGS, '')
+        result = {'exp': {'node1': 1}, 'test_value': 2}
+        parameters.interpolate(inventory)
+        self.assertEqual(parameters.as_dict(), result)
 
     def test_list_if_expr_invquery(self):
-        e = {'node1': {'a': 1, 'b': 2}, 'node2': {'a': 3, 'b': 3}, 'node3': {'a': 3, 'b': 2}}
-        p = Parameters({'exp': '$[ if exports:b == 2 ]'}, SETTINGS, '')
-        r1 = {'exp': ['node1', 'node3']}
-        r2 = {'exp': ['node3', 'node1']}
-        p.interpolate(e)
-        self.assertIn(p.as_dict(), [ r1, r2 ])
+        inventory = self._make_inventory({'node1': {'a': 1, 'b': 2}, 'node2': {'a': 3, 'b': 3}, 'node3': {'a': 3, 'b': 2}})
+        parameters = Parameters({'exp': '$[ if exports:b == 2 ]'}, SETTINGS, '')
+        result = {'exp': ['node1', 'node3']}
+        parameters.interpolate(inventory)
+        self.assertEqual(parameters.as_dict(), result)
 
     def test_if_expr_invquery_wth_and(self):
-        e = {'node1': {'a': 1, 'b': 4, 'c': False}, 'node2': {'a': 3, 'b': 4, 'c': True}}
-        p = Parameters({'exp': '$[ exports:a if exports:b == 4 and exports:c == True ]'}, SETTINGS, '')
-        r = {'exp': {'node2': 3}}
-        p.interpolate(e)
-        self.assertEqual(p.as_dict(), r)
+        inventory = self._make_inventory({'node1': {'a': 1, 'b': 4, 'c': False}, 'node2': {'a': 3, 'b': 4, 'c': True}})
+        parameters = Parameters({'exp': '$[ exports:a if exports:b == 4 and exports:c == True ]'}, SETTINGS, '')
+        result = {'exp': {'node2': 3}}
+        parameters.interpolate(inventory)
+        self.assertEqual(parameters.as_dict(), result)
 
     def test_if_expr_invquery_wth_or(self):
-        e = {'node1': {'a': 1, 'b': 4}, 'node2': {'a': 3, 'b': 3}}
-        p = Parameters({'exp': '$[ exports:a if exports:b == 4 or exports:b == 3 ]'}, SETTINGS, '')
-        r = {'exp': {'node1': 1, 'node2': 3}}
-        p.interpolate(e)
-        self.assertEqual(p.as_dict(), r)
+        inventory = self._make_inventory({'node1': {'a': 1, 'b': 4}, 'node2': {'a': 3, 'b': 3}})
+        parameters = Parameters({'exp': '$[ exports:a if exports:b == 4 or exports:b == 3 ]'}, SETTINGS, '')
+        result = {'exp': {'node1': 1, 'node2': 3}}
+        parameters.interpolate(inventory)
+        self.assertEqual(parameters.as_dict(), result)
 
     def test_list_if_expr_invquery_with_and(self):
-        e = {'node1': {'a': 1, 'b': 2, 'c': 'green'}, 'node2': {'a': 3, 'b': 3}, 'node3': {'a': 3, 'b': 2, 'c': 'red'}}
-        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 = self._make_inventory(
+                        { 'node1': {'a': 1, 'b': 2, 'c': 'green'},
+                          'node2': {'a': 3, 'b': 3},
+                          'node3': {'a': 3, 'b': 2, 'c': 'red'} })
+        parameters = Parameters({'exp': '$[ if exports:b == 2 and exports:c == green ]'}, SETTINGS, '')
+        result = {'exp': ['node1']}
+        parameters.interpolate(inventory)
+        self.assertEqual(parameters.as_dict(), result)
 
     def test_list_if_expr_invquery_with_and_missing(self):
-        inventory = {'node1': {'a': 1, 'b': 2, 'c': 'green'},
-                     'node2': {'a': 3, 'b': 3},
-                     'node3': {'a': 3, 'b': 2}}
+        inventory = self._make_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']}
+        parameterss = Parameters(mapping, SETTINGS, '')
+        parameterss.interpolate(inventory)
+        self.assertEqual(parameterss.as_dict(), expected)
 
-        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}}
-        p = Parameters({'exp': '$[ if exports:b == 2 or exports:b == 4 ]'}, SETTINGS, '')
-        r1 = {'exp': ['node1', 'node3']}
-        r2 = {'exp': ['node3', 'node1']}
-        p.interpolate(e)
-        self.assertIn(p.as_dict(), [ r1, r2 ])
+    def test_list_if_expr_invquery_with_or(self):
+        inventory = self._make_inventory(
+                        { 'node1': {'a': 1, 'b': 2},
+                          'node2': {'a': 3, 'b': 3},
+                          'node3': {'a': 3, 'b': 4} })
+        parameters = Parameters({'exp': '$[ if exports:b == 2 or exports:b == 4 ]'}, SETTINGS, '')
+        result = {'exp': ['node1', 'node3']}
+        parameters.interpolate(inventory)
+        self.assertEqual(parameters.as_dict(), result)
 
     def test_merging_inv_queries(self):
-        e = {'node1': {'a': 1}, 'node2': {'a': 1}, 'node3': {'a': 2}}
-        p1 = Parameters({'exp': '$[ if exports:a == 1 ]'}, SETTINGS, '')
-        p2 = Parameters({'exp': '$[ if exports:a == 2 ]'}, SETTINGS, '')
-        r = { 'exp': [ 'node1', 'node2', 'node3' ] }
-        p1.merge(p2)
-        p1.interpolate(e)
-        self.assertEqual(p1.as_dict(), r)
+        inventory = self._make_inventory({'node1': {'a': 1}, 'node2': {'a': 1}, 'node3': {'a': 2}})
+        pars1 = Parameters({'exp': '$[ if exports:a == 1 ]'}, SETTINGS, '')
+        pars2 = Parameters({'exp': '$[ if exports:a == 2 ]'}, SETTINGS, '')
+        result = { 'exp': [ 'node1', 'node2', 'node3' ] }
+        pars1.merge(pars2)
+        pars1.interpolate(inventory)
+        self.assertEqual(pars1.as_dict(), result)
+
+    def test_same_expr_invquery_different_flags(self):
+        inventory = { 'node1': NodeInventory({'a': 1}, True),
+                      'node2': NodeInventory({'a': 2}, True),
+                      'node3': NodeInventory({'a': 3}, False) }
+        parameters = Parameters({'alpha': '$[ exports:a ]', 'beta': '$[ +AllEnvs exports:a ]'}, SETTINGS, '')
+        result = { 'alpha': { 'node1': 1, 'node2': 2 },
+                   'beta': { 'node1': 1 , 'node2': 2, 'node3': 3 } }
+        parameters.interpolate(inventory)
+        self.assertEqual(parameters.as_dict(), result)
+
+    def test_same_if_expr_invquery_different_flags(self):
+        inventory = { 'node1': NodeInventory({'a': 1, 'b': 1}, True),
+                      'node2': NodeInventory({'a': 2, 'b': 2}, True),
+                      'node3': NodeInventory({'a': 3, 'b': 2}, False) }
+        parameters = Parameters(
+                         { 'alpha': '$[ exports:a if exports:b == 2 ]',
+                           'beta': '$[ +AllEnvs exports:a if exports:b == 2]' },
+                         SETTINGS, '')
+        result = { 'alpha': { 'node2': 2 },
+                   'beta': { 'node2': 2, 'node3': 3 } }
+        parameters.interpolate(inventory)
+        self.assertEqual(parameters.as_dict(), result)
+
+    def test_same_list_if_expr_invquery_different_flags(self):
+        inventory = { 'node1': NodeInventory({'a': 1}, True),
+                      'node2': NodeInventory({'a': 2}, True),
+                      'node3': NodeInventory({'a': 2}, False) }
+        parameters = Parameters(
+                         { 'alpha': '$[ if exports:a == 2 ]',
+                           'beta': '$[ +AllEnvs if exports:a == 2]' },
+                         SETTINGS, '')
+        result = { 'alpha': [ 'node2' ],
+                   'beta': [ 'node2', 'node3' ] }
+        parameters.interpolate(inventory)
+        self.assertEqual(parameters.as_dict(), result)
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/reclass/datatypes/tests/test_parameters.py b/reclass/datatypes/tests/test_parameters.py
index 79322e6..80fd8de 100644
--- a/reclass/datatypes/tests/test_parameters.py
+++ b/reclass/datatypes/tests/test_parameters.py
@@ -194,7 +194,7 @@
         with self.assertRaises(TypeMergeError) as e:
             p1.merge(p2)
             p1.interpolate()
-        self.assertEqual(e.exception.message, "-> \n   Canot merge list over scalar, at key, in ; ")
+        self.assertEqual(e.exception.message, "-> \n   Cannot merge list over scalar, at key, in ; ")
 
     def test_merge_list_into_scalar_allow(self):
         settings = Settings({'allow_list_over_scalar': True})
@@ -212,7 +212,7 @@
         with self.assertRaises(TypeMergeError) as e:
             p1.merge(p2)
             p1.interpolate()
-        self.assertEqual(e.exception.message, "-> \n   Canot merge scalar over list, at key, in ; ")
+        self.assertEqual(e.exception.message, "-> \n   Cannot merge scalar over list, at key, in ; ")
 
     def test_merge_scalar_over_list_allow(self):
         l = ['foo', 1, 2]
@@ -231,7 +231,7 @@
         with self.assertRaises(TypeMergeError) as e:
             p1.merge(p2)
             p1.interpolate()
-        self.assertEqual(e.exception.message, "-> \n   Canot merge scalar over list, at key, in ; ")
+        self.assertEqual(e.exception.message, "-> \n   Cannot merge scalar over list, at key, in ; ")
 
     def test_merge_none_over_list_allow(self):
         l = ['foo', 1, 2]
@@ -249,7 +249,7 @@
         with self.assertRaises(TypeMergeError) as e:
             p1.merge(p2)
             p1.interpolate()
-        self.assertEqual(e.exception.message, "-> \n   Canot merge dictionary over scalar, at a, in ; ")
+        self.assertEqual(e.exception.message, "-> \n   Cannot merge dictionary over scalar, at a, in ; ")
 
     def test_merge_dict_over_scalar_allow(self):
         settings = Settings({'allow_dict_over_scalar': True})
@@ -267,7 +267,7 @@
         with self.assertRaises(TypeMergeError) as e:
             p1.merge(p2)
             p1.interpolate()
-        self.assertEqual(e.exception.message, "-> \n   Canot merge scalar over dictionary, at a, in ; ")
+        self.assertEqual(e.exception.message, "-> \n   Cannot merge scalar over dictionary, at a, in ; ")
 
     def test_merge_scalar_over_dict_allow(self):
         d = { 'one': 1, 'two': 2}
@@ -284,7 +284,7 @@
         with self.assertRaises(TypeMergeError) as e:
             p1.merge(p2)
             p1.interpolate()
-        self.assertEqual(e.exception.message, "-> \n   Canot merge scalar over dictionary, at key, in ; ")
+        self.assertEqual(e.exception.message, "-> \n   Cannot merge scalar over dictionary, at key, in ; ")
 
     def test_merge_none_over_dict_allow(self):
         settings = Settings({'allow_none_override': True})
@@ -302,7 +302,7 @@
             p1.merge(p2)
             p1.merge(p3)
             p1.interpolate()
-        self.assertEqual(e.exception.message, "-> \n   Canot merge list over dictionary, at one:a, in second; third")
+        self.assertEqual(e.exception.message, "-> \n   Cannot merge list over dictionary, at one:a, in second; third")
 
     # def test_merge_bare_dict_over_dict(self):
         # settings = Settings({'allow_bare_override': True})
diff --git a/reclass/defaults.py b/reclass/defaults.py
index f240f3f..7ea797e 100644
--- a/reclass/defaults.py
+++ b/reclass/defaults.py
@@ -56,4 +56,7 @@
 ESCAPE_CHARACTER = '\\'
 
 AUTOMATIC_RECLASS_PARAMETERS = True
+SCALAR_RECLASS_PARAMETERS = False
 DEFAULT_ENVIRONMENT = 'base'
+
+CLASS_MAPPINGS_MATCH_PATH = False
diff --git a/reclass/errors.py b/reclass/errors.py
index 330ad4c..df35ef8 100644
--- a/reclass/errors.py
+++ b/reclass/errors.py
@@ -117,7 +117,7 @@
 
 class InterpolationError(ReclassException):
 
-    def __init__(self, msg, rc=posix.EX_DATAERR, nodename='', uri=None, context=None, tbFlag=True):
+    def __init__(self, msg=None, rc=posix.EX_DATAERR, nodename='', uri=None, context=None, tbFlag=True):
         super(InterpolationError, self).__init__(rc=rc, msg=msg, tbFlag=tbFlag)
         self.nodename = nodename
         self.uri = uri
@@ -291,7 +291,7 @@
         self.type2 = value2.item_type_str()
 
     def _get_error_message(self):
-        msg = [ 'Canot merge {0} over {1}'.format(self.type1, self.type2) + self._add_context_and_uri() ]
+        msg = [ 'Cannot merge {0} over {1}'.format(self.type1, self.type2) + self._add_context_and_uri() ]
         return msg
 
 
@@ -330,7 +330,7 @@
 
 class NameError(ReclassException):
 
-    def __init__(self, msg, rc=posix.EX_DATAERR):
+    def __init__(self, msg=None, rc=posix.EX_DATAERR):
         super(NameError, self).__init__(rc=rc, msg=msg)
 
 
diff --git a/reclass/settings.py b/reclass/settings.py
index b7f5252..08fdea5 100644
--- a/reclass/settings.py
+++ b/reclass/settings.py
@@ -19,6 +19,8 @@
         'allow_dict_over_scalar': defaults.OPT_ALLOW_DICT_OVER_SCALAR,
         'allow_none_override': defaults.OPT_ALLOW_NONE_OVERRIDE,
         'automatic_parameters': defaults.AUTOMATIC_RECLASS_PARAMETERS,
+        'class_mappings_match_path': defaults.CLASS_MAPPINGS_MATCH_PATH,
+        'scalar_parameters': defaults.SCALAR_RECLASS_PARAMETERS,
         'default_environment': defaults.DEFAULT_ENVIRONMENT,
         'delimiter': defaults.PARAMETER_INTERPOLATION_DELIMITER,
         'dict_key_override_prefix':
@@ -39,7 +41,7 @@
             defaults.OPT_IGNORE_CLASS_NOTFOUND_REGEXP,
         'ignore_class_notfound_warning':
             defaults.OPT_IGNORE_CLASS_NOTFOUND_WARNING,
-        'ignore_overwritten_missing_referencesdefaults.':
+        'ignore_overwritten_missing_references':
             defaults.OPT_IGNORE_OVERWRITTEN_MISSING_REFERENCES,
         'group_errors': defaults.OPT_GROUP_ERRORS,
         'compose_node_name': defaults.OPT_COMPOSE_NODE_NAME,
diff --git a/reclass/storage/yaml_fs/__init__.py b/reclass/storage/yaml_fs/__init__.py
index 20e8eec..ee49df3 100644
--- a/reclass/storage/yaml_fs/__init__.py
+++ b/reclass/storage/yaml_fs/__init__.py
@@ -12,7 +12,6 @@
 from __future__ import unicode_literals
 
 import os, sys
-import fnmatch
 import yaml
 from reclass.output.yaml_outputter import ExplicitDumper
 from reclass.storage import ExternalNodeStorageBase
@@ -21,7 +20,7 @@
 from reclass.datatypes import Entity
 import reclass.errors
 
-FILE_EXTENSION = '.yml'
+FILE_EXTENSION = ('.yml', '.yaml')
 STORAGE_NAME = 'yaml_fs'
 
 def vvv(msg):
@@ -71,7 +70,7 @@
     def _enumerate_inventory(self, basedir, name_mangler):
         ret = {}
         def register_fn(dirpath, filenames):
-            filenames = fnmatch.filter(filenames, '*{0}'.format(FILE_EXTENSION))
+            filenames = [f for f in filenames if f.endswith(FILE_EXTENSION)]
             vvv('REGISTER {0} in path {1}'.format(filenames, dirpath))
             for f in filenames:
                 name = os.path.splitext(f)[0]
@@ -96,18 +95,20 @@
         try:
             relpath = self._nodes[name]
             path = os.path.join(self.nodes_uri, relpath)
+            pathname = os.path.splitext(relpath)[0]
         except KeyError as e:
             raise reclass.errors.NodeNotFound(self.name, name, self.nodes_uri)
-        entity = YamlData.from_file(path).get_entity(name, settings)
+        entity = YamlData.from_file(path).get_entity(name, pathname, settings)
         return entity
 
     def get_class(self, name, environment, settings):
         vvv('GET CLASS {0}'.format(name))
         try:
             path = os.path.join(self.classes_uri, self._classes[name])
+            pathname = os.path.splitext(self._classes[name])[0]
         except KeyError as e:
             raise reclass.errors.ClassNotFound(self.name, name, self.classes_uri)
-        entity = YamlData.from_file(path).get_entity(name, settings)
+        entity = YamlData.from_file(path).get_entity(name, pathname, settings)
         return entity
 
     def enumerate_nodes(self):
diff --git a/reclass/storage/yaml_fs/directory.py b/reclass/storage/yaml_fs/directory.py
index a8916b3..4e11643 100644
--- a/reclass/storage/yaml_fs/directory.py
+++ b/reclass/storage/yaml_fs/directory.py
@@ -15,7 +15,7 @@
 from reclass.errors import NotFoundError
 
 SKIPDIRS = ('CVS', 'SCCS')
-FILE_EXTENSION = '.yml'
+FILE_EXTENSION = ('.yml', '.yaml')
 
 def vvv(msg):
     #print(msg, file=sys.stderr)
diff --git a/reclass/storage/yaml_git/__init__.py b/reclass/storage/yaml_git/__init__.py
index a28079b..1053a95 100644
--- a/reclass/storage/yaml_git/__init__.py
+++ b/reclass/storage/yaml_git/__init__.py
@@ -11,7 +11,6 @@
 import distutils.version
 import errno
 import fcntl
-import fnmatch
 import os
 import time
 
@@ -34,7 +33,7 @@
 from reclass.storage import ExternalNodeStorageBase
 from reclass.storage.yamldata import YamlData
 
-FILE_EXTENSION = '.yml'
+FILE_EXTENSION = ('.yml', '.yaml')
 STORAGE_NAME = 'yaml_git'
 
 def path_mangler(inventory_base_uri, nodes_uri, classes_uri):
@@ -43,7 +42,7 @@
     return nodes_uri, classes_uri
 
 
-GitMD = collections.namedtuple('GitMD', ['name', 'path', 'id'], verbose=False, rename=False)
+GitMD = collections.namedtuple('GitMD', ['name', 'path', 'id'], rename=False)
 
 
 class GitURI(object):
@@ -213,13 +212,13 @@
             branch = {}
             files = self.files_in_branch(bname)
             for file in files:
-                if fnmatch.fnmatch(file.name, '*{0}'.format(FILE_EXTENSION)):
+                if file.name.endswith(FILE_EXTENSION):
                     name = os.path.splitext(file.name)[0]
                     relpath = os.path.dirname(file.path)
                     if callable(self._class_name_mangler):
                         relpath, name = self._class_name_mangler(relpath, name)
                     if name in ret:
-                        raise reclass.errors.DuplicateNodeNameError(self.name + ' - ' + bname, name, ret[name], path)
+                        raise reclass.errors.DuplicateNodeNameError(self.url + ' - ' + bname, name, ret[name], file)
                     else:
                         branch[name] = file
             ret[bname] = branch
@@ -234,7 +233,7 @@
                 if callable(self._node_name_mangler):
                     relpath, node_name = self._node_name_mangler(relpath, node_name)
                 if node_name in ret:
-                    raise reclass.errors.DuplicateNodeNameError(self.name, name, files[name], path)
+                    raise reclass.errors.DuplicateNodeNameError(self.url, name, ret[node_name].path, file.path)
                 else:
                     ret[node_name] = file
         return ret
@@ -274,7 +273,9 @@
     def get_node(self, name, settings):
         file = self._nodes[name]
         blob = self._repos[self._nodes_uri.repo].get(file.id)
-        entity = YamlData.from_string(blob.data, 'git_fs://{0} {1} {2}'.format(self._nodes_uri.repo, self._nodes_uri.branch, file.path)).get_entity(name, settings)
+        uri = 'git_fs://{0} {1} {2}'.format(self._nodes_uri.repo, self._nodes_uri.branch, file.path)
+        pathname = os.path.splitext(file.path)[0]
+        entity = YamlData.from_string(blob.data, uri).get_entity(name, pathname, settings)
         return entity
 
     def get_class(self, name, environment, settings):
@@ -289,7 +290,9 @@
             raise reclass.errors.NotFoundError("File " + name + " missing from " + uri.repo + " branch " + uri.branch)
         file = self._repos[uri.repo].files[uri.branch][name]
         blob = self._repos[uri.repo].get(file.id)
-        entity = YamlData.from_string(blob.data, 'git_fs://{0} {1} {2}'.format(uri.repo, uri.branch, file.path)).get_entity(name, settings)
+        uri = 'git_fs://{0} {1} {2}'.format(uri.repo, uri.branch, file.path)
+        pathname = os.path.splitext(file.path)[0]
+        entity = YamlData.from_string(blob.data, uri).get_entity(name, pathname, settings)
         return entity
 
     def enumerate_nodes(self):
diff --git a/reclass/storage/yamldata.py b/reclass/storage/yamldata.py
index a38b589..f68d803 100644
--- a/reclass/storage/yamldata.py
+++ b/reclass/storage/yamldata.py
@@ -80,10 +80,7 @@
     def count_dots(self, value):
         return len(list(self.yield_dots(value)))
 
-    def get_entity(self, name, settings):
-        #if name is None:
-        #    name = self._uri
-
+    def get_entity(self, name, pathname, settings):
         classes = self._data.get('classes')
         if classes is None:
             classes = []
@@ -108,7 +105,7 @@
         env = self._data.get('environment', None)
 
         return datatypes.Entity(settings, classes=classes, applications=applications, parameters=parameters,
-                                exports=exports, name=name, environment=env, uri=self.uri)
+                                exports=exports, name=name, pathname=pathname, environment=env, uri=self.uri)
 
     def __str__(self):
         return '<{0} {1}, {2}>'.format(self.__class__.__name__, self._uri,
diff --git a/reclass/tests/data/04/classes/one.yml b/reclass/tests/data/04/classes/one.yml
new file mode 100644
index 0000000..37ee5e8
--- /dev/null
+++ b/reclass/tests/data/04/classes/one.yml
@@ -0,0 +1,2 @@
+parameters:
+  test1: 1
diff --git a/reclass/tests/data/04/classes/three.yml b/reclass/tests/data/04/classes/three.yml
new file mode 100644
index 0000000..f71f8ce
--- /dev/null
+++ b/reclass/tests/data/04/classes/three.yml
@@ -0,0 +1,2 @@
+parameters:
+  test3: 3
diff --git a/reclass/tests/data/04/classes/two.yml b/reclass/tests/data/04/classes/two.yml
new file mode 100644
index 0000000..80d5209
--- /dev/null
+++ b/reclass/tests/data/04/classes/two.yml
@@ -0,0 +1,2 @@
+parameters:
+  test2: 2
diff --git a/reclass/tests/data/04/nodes/alpha/node1.yml b/reclass/tests/data/04/nodes/alpha/node1.yml
new file mode 100644
index 0000000..f0f59f5
--- /dev/null
+++ b/reclass/tests/data/04/nodes/alpha/node1.yml
@@ -0,0 +1,2 @@
+classes:
+  - one
diff --git a/reclass/tests/test_core.py b/reclass/tests/test_core.py
index 4827177..c1e283d 100644
--- a/reclass/tests/test_core.py
+++ b/reclass/tests/test_core.py
@@ -23,13 +23,13 @@
 
 class TestCore(unittest.TestCase):
 
-    def _core(self, dataset, opts={}):
+    def _core(self, dataset, opts={}, class_mappings=[]):
         inventory_uri = os.path.dirname(os.path.abspath(__file__)) + '/data/' + dataset
         path_mangler = get_path_mangler('yaml_fs')
         nodes_uri, classes_uri = path_mangler(inventory_uri, 'nodes', 'classes')
         settings = Settings(opts)
         storage = get_storage('yaml_fs', nodes_uri, classes_uri, settings.compose_node_name)
-        return Core(storage, None, settings)
+        return Core(storage, class_mappings, settings)
 
     def test_type_conversion(self):
         reclass = self._core('01')
@@ -72,7 +72,7 @@
         self.assertEqual(node['parameters'], params)
 
     def test_compose_node_names(self):
-        reclass = self._core('03', {'compose_node_name': True})
+        reclass = self._core('03', opts={'compose_node_name': True})
         alpha_one_node = reclass.nodeinfo('alpha.one')
         alpha_one_res = {'a': 1, 'alpha': [1, 2], 'beta': {'a': 1, 'b': 2}, 'b': 2, '_reclass_': {'environment': 'base', 'name': {'full': 'alpha.one', 'short': 'alpha'}}}
         alpha_two_node = reclass.nodeinfo('alpha.two')
@@ -86,6 +86,18 @@
         self.assertEqual(beta_one_node['parameters'], beta_one_res)
         self.assertEqual(beta_two_node['parameters'], beta_two_res)
 
+    def test_class_mappings_match_path_false(self):
+        reclass = self._core('04', opts={'class_mappings_match_path': False}, class_mappings=['node*    two', 'alpha/node*    three'])
+        node = reclass.nodeinfo('node1')
+        params = { 'test1': 1, 'test2': 2, '_reclass_': {'environment': u'base', 'name': {'full': 'node1', 'short': 'node1'}}}
+        self.assertEqual(node['parameters'], params)
+
+    def test_class_mappings_match_path_true(self):
+        reclass = self._core('04', opts={'class_mappings_match_path': True}, class_mappings=['node*    two', 'alpha/node*    three'])
+        node = reclass.nodeinfo('node1')
+        params = { 'test1': 1, 'test3': 3, '_reclass_': {'environment': u'base', 'name': {'full': 'node1', 'short': 'node1'}}}
+        self.assertEqual(node['parameters'], params)
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/reclass/values/__init__.py b/reclass/values/__init__.py
index ec0f882..0458d34 100644
--- a/reclass/values/__init__.py
+++ b/reclass/values/__init__.py
@@ -3,3 +3,7 @@
 from __future__ import division
 from __future__ import print_function
 from __future__ import unicode_literals
+
+import collections
+
+NodeInventory = collections.namedtuple('NodeInventory', ['items', 'env_matches'], rename=False)
diff --git a/reclass/values/invitem.py b/reclass/values/invitem.py
index adb1cb6..d8f3874 100644
--- a/reclass/values/invitem.py
+++ b/reclass/values/invitem.py
@@ -16,8 +16,7 @@
 from six import iteritems
 from six import string_types
 
-from reclass.values import item
-from reclass.values import parser_funcs
+from reclass.values import item, parser_funcs
 from reclass.settings import Settings
 from reclass.utils.dictpath import DictPath
 from reclass.errors import ExpressionError, ParseError, ResolveError
@@ -200,10 +199,11 @@
 
     def _value_expression(self, inventory):
         results = {}
-        for (node, items) in iteritems(inventory):
-            if self._value_path.exists_in(items):
-                results[node] = copy.deepcopy(self._resolve(self._value_path,
-                                              items))
+        for name, node in iteritems(inventory):
+            if self.needs_all_envs or node.env_matches:
+                if self._value_path.exists_in(node.items):
+                    answer = self._resolve(self._value_path, node.items)
+                    results[name] = copy.deepcopy(answer)
         return results
 
     def _test_expression(self, context, inventory):
@@ -212,18 +212,21 @@
             raise ExpressionError(msg % str(self), tbFlag=False)
 
         results = {}
-        for node, items in iteritems(inventory):
-            if (self._question.value(context, items) and
-                    self._value_path.exists_in(items)):
-                results[node] = copy.deepcopy(
-                    self._resolve(self._value_path, items))
+        for name, node  in iteritems(inventory):
+            if self.needs_all_envs or node.env_matches:
+                if (self._question.value(context, node.items) and
+                    self._value_path.exists_in(node.items)):
+                    answer = self._resolve(self._value_path, node.items)
+                    results[name] = copy.deepcopy(answer)
         return results
 
     def _list_test_expression(self, context, inventory):
         results = []
-        for (node, items) in iteritems(inventory):
-            if self._question.value(context, items):
-                results.append(node)
+        for name, node in iteritems(inventory):
+            if self.needs_all_envs or node.env_matches:
+                if self._question.value(context, node.items):
+                    results.append(name)
+        results.sort()
         return results
 
     def render(self, context, inventory):
diff --git a/reclass/version.py b/reclass/version.py
index 5a40c2e..7077418 100644
--- a/reclass/version.py
+++ b/reclass/version.py
@@ -14,7 +14,7 @@
 RECLASS_NAME = 'reclass'
 DESCRIPTION = ('merge data by recursive descent down an ancestry hierarchy '
                '(forked extended version)')
-VERSION = '1.6.0'
+VERSION = '1.7.0'
 AUTHOR = 'martin f. krafft / Andrew Pickford / salt-formulas community'
 AUTHOR_EMAIL = 'salt-formulas@freelists.org'
 MAINTAINER = 'salt-formulas community'