1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/python/mozbuild/mozpack/errors.py Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,137 @@ 1.4 +# This Source Code Form is subject to the terms of the Mozilla Public 1.5 +# License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 +# file, You can obtain one at http://mozilla.org/MPL/2.0/. 1.7 + 1.8 +import sys 1.9 +from contextlib import contextmanager 1.10 + 1.11 + 1.12 +class ErrorMessage(Exception): 1.13 + '''Exception type raised from errors.error() and errors.fatal()''' 1.14 + 1.15 + 1.16 +class AccumulatedErrors(Exception): 1.17 + '''Exception type raised from errors.accumulate()''' 1.18 + 1.19 + 1.20 +class ErrorCollector(object): 1.21 + ''' 1.22 + Error handling/logging class. A global instance, errors, is provided for 1.23 + convenience. 1.24 + 1.25 + Warnings, errors and fatal errors may be logged by calls to the following 1.26 + functions: 1.27 + errors.warn(message) 1.28 + errors.error(message) 1.29 + errors.fatal(message) 1.30 + 1.31 + Warnings only send the message on the logging output, while errors and 1.32 + fatal errors send the message and throw an ErrorMessage exception. The 1.33 + exception, however, may be deferred. See further below. 1.34 + 1.35 + Errors may be ignored by calling: 1.36 + errors.ignore_errors() 1.37 + 1.38 + After calling that function, only fatal errors throw an exception. 1.39 + 1.40 + The warnings, errors or fatal errors messages may be augmented with context 1.41 + information when a context is provided. Context is defined by a pair 1.42 + (filename, linenumber), and may be set with errors.context() used as a 1.43 + context manager: 1.44 + with errors.context(filename, linenumber): 1.45 + errors.warn(message) 1.46 + 1.47 + Arbitrary nesting is supported, both for errors.context calls: 1.48 + with errors.context(filename1, linenumber1): 1.49 + errors.warn(message) 1.50 + with errors.context(filename2, linenumber2): 1.51 + errors.warn(message) 1.52 + 1.53 + as well as for function calls: 1.54 + def func(): 1.55 + errors.warn(message) 1.56 + with errors.context(filename, linenumber): 1.57 + func() 1.58 + 1.59 + Errors and fatal errors can have their exception thrown at a later time, 1.60 + allowing for several different errors to be reported at once before 1.61 + throwing. This is achieved with errors.accumulate() as a context manager: 1.62 + with errors.accumulate(): 1.63 + if test1: 1.64 + errors.error(message1) 1.65 + if test2: 1.66 + errors.error(message2) 1.67 + 1.68 + In such cases, a single AccumulatedErrors exception is thrown, but doesn't 1.69 + contain information about the exceptions. The logged messages do. 1.70 + ''' 1.71 + out = sys.stderr 1.72 + WARN = 1 1.73 + ERROR = 2 1.74 + FATAL = 3 1.75 + _level = ERROR 1.76 + _context = [] 1.77 + _count = None 1.78 + 1.79 + def ignore_errors(self, ignore=True): 1.80 + if ignore: 1.81 + self._level = self.FATAL 1.82 + else: 1.83 + self._level = self.ERROR 1.84 + 1.85 + def _full_message(self, level, msg): 1.86 + if level >= self._level: 1.87 + level = 'Error' 1.88 + else: 1.89 + level = 'Warning' 1.90 + if self._context: 1.91 + file, line = self._context[-1] 1.92 + return "%s: %s:%d: %s" % (level, file, line, msg) 1.93 + return "%s: %s" % (level, msg) 1.94 + 1.95 + def _handle(self, level, msg): 1.96 + msg = self._full_message(level, msg) 1.97 + if level >= self._level: 1.98 + if self._count is None: 1.99 + raise ErrorMessage(msg) 1.100 + self._count += 1 1.101 + print >>self.out, msg 1.102 + 1.103 + def fatal(self, msg): 1.104 + self._handle(self.FATAL, msg) 1.105 + 1.106 + def error(self, msg): 1.107 + self._handle(self.ERROR, msg) 1.108 + 1.109 + def warn(self, msg): 1.110 + self._handle(self.WARN, msg) 1.111 + 1.112 + def get_context(self): 1.113 + if self._context: 1.114 + return self._context[-1] 1.115 + 1.116 + @contextmanager 1.117 + def context(self, file, line): 1.118 + if file and line: 1.119 + self._context.append((file, line)) 1.120 + yield 1.121 + if file and line: 1.122 + self._context.pop() 1.123 + 1.124 + @contextmanager 1.125 + def accumulate(self): 1.126 + assert self._count is None 1.127 + self._count = 0 1.128 + yield 1.129 + count = self._count 1.130 + self._count = None 1.131 + if count: 1.132 + raise AccumulatedErrors() 1.133 + 1.134 + @property 1.135 + def count(self): 1.136 + # _count can be None. 1.137 + return self._count if self._count else 0 1.138 + 1.139 + 1.140 +errors = ErrorCollector()