swc-installation-test-2.py: Print full DependencyError chain
authorW. Trevor King <wking@tremily.us>
Sun, 27 Jan 2013 19:33:46 +0000 (14:33 -0500)
committerW. Trevor King <wking@tremily.us>
Sun, 27 Jan 2013 19:51:23 +0000 (14:51 -0500)
If a requirement is not satisfied due to an and_dependency or
or_dependency failure, raise a new DependencyError and attach the
original error as its cause (this would be easier if we didn't have to
preserve Python 2.x support).  This way we can print the whole chain
(e.g. virtual-shell failed because sh is not installed), instead of
just the lowest level error.

Also indent the messages, because:

  check for virtual-editor failed:
    or-dependency not satisfied for virtual-editor
    For instructions on installing an up-to-date version, see
    http://software-carpentry.org/setup/
    cause:
    check for Notepad++ (notepad++) failed:
      could not find 'notepad++' executable
      For instructions on installing an up-to-date version, see
      http://software-carpentry.org/setup/

is more readable than:

  check for virtual-editor failed:
  or-dependency not satisfied for virtual-editor
  For instructions on installing an up-to-date version, see
  http://software-carpentry.org/setup/
  cause:
  check for Notepad++ (notepad++) failed:
  could not find 'notepad++' executable
  For instructions on installing an up-to-date version, see
  http://software-carpentry.org/setup/

setup/swc-installation-test-2.py

index 4cd22e27da8466cc0ac0a83ef2a54c9bdb0742d1..35e3d5cad45f25420e4738aeab7a9dbecb02e78f 100755 (executable)
@@ -110,17 +110,24 @@ class DependencyError (Exception):
         self._message = message
     message = property(_get_message, _set_message)
 
-    def __init__(self, checker, message):
+    def __init__(self, checker, message, cause=None):
         super(DependencyError, self).__init__(message)
         self.checker = checker
         self.message = message
+        self.cause = cause
 
     def __str__(self):
         url = 'http://software-carpentry.org/setup/'  # TODO: per-package URL
-        return 'check for {0} failed:\n{1}\n{2}\n{3}'.format(
-            self.checker.full_name(), self.message,
-            'For instructions on installing an up-to-date version, see',
-            url)
+        lines = [
+            'check for {0} failed:'.format(self.checker.full_name()),
+            '  ' + self.message,
+            '  For instructions on installing an up-to-date version, see',
+            '  ' + url,
+            ]
+        if self.cause:
+            lines.append('  cause:')
+            lines.extend('  ' + line for line in str(self.cause).splitlines())
+        return '\n'.join(lines)
 
 
 def check(checks=None):
@@ -199,7 +206,14 @@ class Dependency (object):
         for dependency in self.and_dependencies:
             if not hasattr(dependency, 'check'):
                 dependency = CHECKER[dependency]
-            dependency.check()
+            try:
+                dependency.check()
+            except DependencyError as e:
+                raise DependencyError(
+                    checker=self,
+                    message='and-dependency not satisfied for {0}'.format(
+                        self.full_name()),
+                    cause=e)
         self.or_pass = or_error = None
         for dependency in self.or_dependencies:
             if not hasattr(dependency, 'check'):
@@ -215,7 +229,11 @@ class Dependency (object):
                     }
                 break  # no need to test other dependencies
         if self.or_dependencies and not self.or_pass:
-            raise or_error
+            raise DependencyError(
+                checker=self,
+                message='or-dependency not satisfied for {0}'.format(
+                    self.full_name()),
+                    cause=or_error)
 
     def _check(self):
         version = self._get_version()