Rework exceptions, improve message handling

Signed-off-by: martin f. krafft <madduck@madduck.net>
diff --git a/reclass/errors.py b/reclass/errors.py
index 4ab85d6..2440178 100644
--- a/reclass/errors.py
+++ b/reclass/errors.py
@@ -14,125 +14,162 @@
 
 class ReclassException(Exception):
 
-    def __init__(self, msg, rc=posix.EX_SOFTWARE, *args):
-        super(ReclassException, self).__init__(msg, *args)
+    def __init__(self, rc=posix.EX_SOFTWARE, msg=None):
+        super(ReclassException, self).__init__()
         self._rc = rc
+        self._msg = msg
         self._traceback = traceback.format_exc()
 
-    def __str__(self):
-        return self.message
-
+    message = property(lambda self: self._get_message())
     rc = property(lambda self: self._rc)
 
+    def _get_message(self):
+        if self._msg:
+            return self._msg
+        else:
+            return 'No error message provided.'
+
     def exit_with_message(self, out=sys.stderr):
         print >>out, self.message
-        print >>out, self._traceback
+        if self._traceback:
+            print >>out, self._traceback
         sys.exit(self.rc)
 
 
 class PermissionError(ReclassException):
 
     def __init__(self, msg, rc=posix.EX_NOPERM):
-        super(PermissionError, self).__init__(msg, rc)
+        super(PermissionError, self).__init__(rc=rc, msg=msg)
 
 
 class InvocationError(ReclassException):
 
     def __init__(self, msg, rc=posix.EX_USAGE):
-        super(InvocationError, self).__init__(msg, rc)
+        super(InvocationError, self).__init__(rc=rc, msg=msg)
 
 
 class ConfigError(ReclassException):
 
     def __init__(self, msg, rc=posix.EX_CONFIG):
-        super(ConfigError, self).__init__(msg, rc)
+        super(ConfigError, self).__init__(rc=rc, msg=msg)
 
 
 class DuplicateUriError(ConfigError):
 
     def __init__(self, nodes_uri, classes_uri):
-        msg = "The inventory URIs must not be the same for nodes and classes: "
-        msg += nodes_uri
-        super(DuplicateUriError, self).__init__(msg)
+        super(DuplicateUriError, self).__init__(msg=None)
+        self._nodes_uri = nodes_uri
+        self._classes_uri = classes_uri
+
+    def _get_message(self):
+        return "The inventory URIs must not be the same " \
+               "for nodes and classes: {0}".format(self._nodes_uri)
 
 
 class UriOverlapError(ConfigError):
 
     def __init__(self, nodes_uri, classes_uri):
+        super(UriOverlapError, self).__init__(msg=None)
+        self._nodes_uri = nodes_uri
+        self._classes_uri = classes_uri
+
+    def _get_message(self):
         msg = "The URIs for the nodes and classes inventories must not " \
               "overlap, but {0} and {1} do."
-        msg = msg.format(nodes_uri, classes_uri)
-        super(UriOverlapError, self).__init__(msg)
+        return msg.format(self._nodes_uri, self._classes_uri)
 
 
 class NotFoundError(ReclassException):
 
     def __init__(self, msg, rc=posix.EX_IOERR):
-        super(NotFoundError, self).__init__(msg, rc)
+        super(NotFoundError, self).__init__(rc=rc, msg=msg)
 
 
 class NodeNotFound(NotFoundError):
 
     def __init__(self, storage, nodename, uri):
+        super(NodeNotFound, self).__init__(msg=None)
         self._storage = storage
         self._name = nodename
         self._uri = uri
-        msg = "Node '{0}' not found under {1}://{2}".format(self._name,
-                                                            self._storage,
-                                                            self._uri)
-        super(NodeNotFound, self).__init__(msg)
+
+    def _get_message(self):
+        msg = "Node '{0}' not found under {1}://{2}"
+        return msg.format(self._name, self._storage, self._uri)
 
 
 class ClassNotFound(NotFoundError):
 
-    def __init__(self, storage, classname, uri, nodename):
+    def __init__(self, storage, classname, uri, nodename=None):
+        super(ClassNotFound, self).__init__(msg=None)
         self._storage = storage
         self._name = classname
         self._uri = uri
         self._nodename = nodename
-        msg = "Class '{0}' (in ancestry of node '{1}') not found under {2}://{3}" \
-                .format(self._name, self._nodename, self._storage, self._uri)
-        super(ClassNotFound, self).__init__(msg)
+
+    def _get_message(self):
+        if self._nodename:
+            msg = "Class '{0}' (in ancestry of node '{1}') not found " \
+                  "under {2}://{3}"
+        else:
+            msg = "Class '{0}' not found under {2}://{3}"
+        return msg.format(self._name, self._nodename, self._storage, self._uri)
+
+    def set_nodename(self, nodename):
+        self._nodename = nodename
 
 
 class InterpolationError(ReclassException):
 
     def __init__(self, msg, rc=posix.EX_DATAERR):
-        super(InterpolationError, self).__init__(msg, rc)
+        super(InterpolationError, self).__init__(rc=rc, msg=msg)
 
 
 class UndefinedVariableError(InterpolationError):
 
     def __init__(self, var, context=None):
+        super(UndefinedVariableError, self).__init__(msg=None)
         self._var = var
-        msg = "Cannot resolve " + var.join(PARAMETER_INTERPOLATION_SENTINELS)
-        if context:
-            msg += ' in the context of %s' % context
-        super(UndefinedVariableError, self).__init__(msg)
+        self._context = context
 
-    var = property(lambda x: x._var)
+    def _get_message(self):
+        msg = "Cannot resolve " + var.join(PARAMETER_INTERPOLATION_SENTINELS)
+        if self._context:
+            msg += ' in the context of %s' % self._context
+        return msg
+
+    def set_context(self, context):
+        self._context = context
 
 
 class IncompleteInterpolationError(InterpolationError):
 
     def __init__(self, string, end_sentinel):
-        msg = "Missing '%s' to end reference: %s" % \
-                (end_sentinel, string.join(PARAMETER_INTERPOLATION_SENTINELS))
-        super(IncompleteInterpolationError, self).__init__(msg)
+        super(IncompleteInterpolationError, self).__init__(msg=None)
+        self._ref = string.join(PARAMETER_INTERPOLATION_SENTINELS)
+        self._end_sentinel = end_sentinel
+
+    def _get_message(self):
+        msg = "Missing '{0}' to end reference: {1}"
+        return msg.format(self._end_sentinel, self._ref)
 
 
 class InfiniteRecursionError(InterpolationError):
 
     def __init__(self, path, ref):
-        msg = "Infinite recursion while resolving %s at %s" \
-                % (ref.join(PARAMETER_INTERPOLATION_SENTINELS), path)
-        super(InfiniteRecursionError, self).__init__(msg)
+        super(InfiniteRecursionError, self).__init__(msg=None)
+        self._path = path
+        self._ref = ref.join(PARAMETER_INTERPOLATION_SENTINELS)
+
+    def _get_message(self):
+        msg = "Infinite recursion while resolving {0} at {1}"
+        return msg.format(self._ref, self._path)
 
 
 class MappingError(ReclassException):
 
     def __init__(self, msg, rc=posix.EX_DATAERR):
-        super(MappingError, self).__init__(msg, rc)
+        super(MappingError, self).__init__(rc=rc, msg=msg)
 
 
 class MappingFormatError(MappingError):
@@ -144,23 +181,31 @@
 class NameError(ReclassException):
 
     def __init__(self, msg, rc=posix.EX_DATAERR):
-        super(NameError, self).__init__(msg, rc)
+        super(NameError, self).__init__(rc=rc, msg=msg)
 
 
 class InvalidClassnameError(NameError):
 
     def __init__(self, invalid_character, classname):
-        self._invalid_character = invalid_character
+        super(InvalidClassnameError, self).__init__(msg=None)
+        self._char = invalid_character
         self._classname = classname
+
+    def _get_message(self):
         msg = "Invalid character '{0}' in class name '{1}'."
-        msg = msg.format(invalid_character, classname)
-        super(InvalidClassnameError, self).__init__(msg)
+        return msg.format(self._char, classname)
 
 
 class DuplicateNodeNameError(NameError):
 
     def __init__(self, storage, name, uri1, uri2):
+        super(DuplicateNodeNameError, self).__init__(msg=None)
+        self._storage = storage
+        self._name = name
+        self._uris = (uri1, uri2)
+
+    def _get_message(self):
         msg = "{0}: Definition of node '{1}' in '{2}' collides with " \
-              "definition in '{3}'. Nodes can only be defined once per inventory."
-        msg = msg.format(storage, name, uri2, uri1)
-        super(DuplicateNodeNameError, self).__init__(msg)
+              "definition in '{3}'. Nodes can only be defined once " \
+              "per inventory."
+        return msg.format(storage, name, uris[1], uris[0])