Limit class names to not contain spaces

Signed-off-by: martin f. krafft <madduck@madduck.net>
diff --git a/doc/source/changelog.rst b/doc/source/changelog.rst
index 96514ad..4604c6a 100644
--- a/doc/source/changelog.rst
+++ b/doc/source/changelog.rst
@@ -5,6 +5,7 @@
 ========= ========== ========================================================
 Version   Date       Changes
 ========= ========== ========================================================
+                     * Class names must not contain spaces
 1.1       2013-08-28 Salt adapter: fix interface to include minion_id, filter
                      output accordingly; fixes master_tops
 1.0.2     2013-08-27 Fix incorrect versioning in setuptools
diff --git a/doc/source/concepts.rst b/doc/source/concepts.rst
index 67816d8..76b5818 100644
--- a/doc/source/concepts.rst
+++ b/doc/source/concepts.rst
@@ -25,6 +25,8 @@
 A class consists of zero or more parent classes, zero or more applications,
 and any number of parameters.
 
+A class name must not contain spaces.
+
 A node is almost equivalent to a class, except that it usually does not (but
 can) specify applications.
 
diff --git a/reclass/datatypes/classes.py b/reclass/datatypes/classes.py
index dcb6852..132db31 100644
--- a/reclass/datatypes/classes.py
+++ b/reclass/datatypes/classes.py
@@ -8,6 +8,9 @@
 #
 
 import types
+from reclass.errors import InvalidClassnameError
+
+INVALID_CHARACTERS_FOR_CLASSNAMES = ' '
 
 class Classes(object):
     '''
@@ -51,12 +54,19 @@
             raise TypeError('%s instances can only contain strings, '\
                             'not %s' % (self.__class__.__name__, type(item)))
 
+    def _assert_valid_characters(self, item):
+        for c in INVALID_CHARACTERS_FOR_CLASSNAMES:
+            if c in item:
+                raise InvalidClassnameError("Invalid character '{0}' "
+                                            "in class name '{1}'.".format(c, item))
+
     def _append_if_new(self, item):
         if item not in self._items:
             self._items.append(item)
 
     def append_if_new(self, item):
         self._assert_is_string(item)
+        self._assert_valid_characters(item)
         self._append_if_new(item)
 
     def __repr__(self):
diff --git a/reclass/datatypes/tests/test_classes.py b/reclass/datatypes/tests/test_classes.py
index 7f55185..c15a6d1 100644
--- a/reclass/datatypes/tests/test_classes.py
+++ b/reclass/datatypes/tests/test_classes.py
@@ -7,11 +7,13 @@
 # Released under the terms of the Artistic Licence 2.0
 #
 from reclass.datatypes import Classes
+from reclass.datatypes.classes import INVALID_CHARACTERS_FOR_CLASSNAMES
 import unittest
 try:
     import unittest.mock as mock
 except ImportError:
     import mock
+from reclass.errors import InvalidClassnameError
 
 TESTLIST1 = ['one', 'two', 'three']
 TESTLIST2 = ['red', 'green', 'blue']
@@ -67,6 +69,12 @@
         with self.assertRaises(TypeError):
             c.append_if_new(0)
 
+    def test_append_invalid_characters(self):
+        c = Classes()
+        invalid_name = ' '.join(('foo', 'bar'))
+        with self.assertRaises(InvalidClassnameError):
+            c.append_if_new(invalid_name)
+
     def test_merge_unique(self):
         c = Classes(TESTLIST1)
         c.merge_unique(TESTLIST2)
diff --git a/reclass/errors.py b/reclass/errors.py
index 5bec2c1..b7940d6 100644
--- a/reclass/errors.py
+++ b/reclass/errors.py
@@ -101,3 +101,15 @@
         msg = "Infinite recursion while resolving %s at %s" \
                 % (ref.join(PARAMETER_INTERPOLATION_SENTINELS), path)
         super(InfiniteRecursionError, self).__init__(msg)
+
+
+class NameError(ReclassException):
+
+    def __init__(self, msg, rc=posix.EX_DATAERR):
+        super(NameError, self).__init__(msg, rc)
+
+
+class InvalidClassnameError(NameError):
+
+    def __init__(self, msg):
+        super(InvalidClassnameError, self).__init__(msg)