michael@0: # This Source Code Form is subject to the terms of the Mozilla Public michael@0: # License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: # file, You can obtain one at http://mozilla.org/MPL/2.0/. michael@0: michael@0: import sys michael@0: from contextlib import contextmanager michael@0: michael@0: michael@0: class ErrorMessage(Exception): michael@0: '''Exception type raised from errors.error() and errors.fatal()''' michael@0: michael@0: michael@0: class AccumulatedErrors(Exception): michael@0: '''Exception type raised from errors.accumulate()''' michael@0: michael@0: michael@0: class ErrorCollector(object): michael@0: ''' michael@0: Error handling/logging class. A global instance, errors, is provided for michael@0: convenience. michael@0: michael@0: Warnings, errors and fatal errors may be logged by calls to the following michael@0: functions: michael@0: errors.warn(message) michael@0: errors.error(message) michael@0: errors.fatal(message) michael@0: michael@0: Warnings only send the message on the logging output, while errors and michael@0: fatal errors send the message and throw an ErrorMessage exception. The michael@0: exception, however, may be deferred. See further below. michael@0: michael@0: Errors may be ignored by calling: michael@0: errors.ignore_errors() michael@0: michael@0: After calling that function, only fatal errors throw an exception. michael@0: michael@0: The warnings, errors or fatal errors messages may be augmented with context michael@0: information when a context is provided. Context is defined by a pair michael@0: (filename, linenumber), and may be set with errors.context() used as a michael@0: context manager: michael@0: with errors.context(filename, linenumber): michael@0: errors.warn(message) michael@0: michael@0: Arbitrary nesting is supported, both for errors.context calls: michael@0: with errors.context(filename1, linenumber1): michael@0: errors.warn(message) michael@0: with errors.context(filename2, linenumber2): michael@0: errors.warn(message) michael@0: michael@0: as well as for function calls: michael@0: def func(): michael@0: errors.warn(message) michael@0: with errors.context(filename, linenumber): michael@0: func() michael@0: michael@0: Errors and fatal errors can have their exception thrown at a later time, michael@0: allowing for several different errors to be reported at once before michael@0: throwing. This is achieved with errors.accumulate() as a context manager: michael@0: with errors.accumulate(): michael@0: if test1: michael@0: errors.error(message1) michael@0: if test2: michael@0: errors.error(message2) michael@0: michael@0: In such cases, a single AccumulatedErrors exception is thrown, but doesn't michael@0: contain information about the exceptions. The logged messages do. michael@0: ''' michael@0: out = sys.stderr michael@0: WARN = 1 michael@0: ERROR = 2 michael@0: FATAL = 3 michael@0: _level = ERROR michael@0: _context = [] michael@0: _count = None michael@0: michael@0: def ignore_errors(self, ignore=True): michael@0: if ignore: michael@0: self._level = self.FATAL michael@0: else: michael@0: self._level = self.ERROR michael@0: michael@0: def _full_message(self, level, msg): michael@0: if level >= self._level: michael@0: level = 'Error' michael@0: else: michael@0: level = 'Warning' michael@0: if self._context: michael@0: file, line = self._context[-1] michael@0: return "%s: %s:%d: %s" % (level, file, line, msg) michael@0: return "%s: %s" % (level, msg) michael@0: michael@0: def _handle(self, level, msg): michael@0: msg = self._full_message(level, msg) michael@0: if level >= self._level: michael@0: if self._count is None: michael@0: raise ErrorMessage(msg) michael@0: self._count += 1 michael@0: print >>self.out, msg michael@0: michael@0: def fatal(self, msg): michael@0: self._handle(self.FATAL, msg) michael@0: michael@0: def error(self, msg): michael@0: self._handle(self.ERROR, msg) michael@0: michael@0: def warn(self, msg): michael@0: self._handle(self.WARN, msg) michael@0: michael@0: def get_context(self): michael@0: if self._context: michael@0: return self._context[-1] michael@0: michael@0: @contextmanager michael@0: def context(self, file, line): michael@0: if file and line: michael@0: self._context.append((file, line)) michael@0: yield michael@0: if file and line: michael@0: self._context.pop() michael@0: michael@0: @contextmanager michael@0: def accumulate(self): michael@0: assert self._count is None michael@0: self._count = 0 michael@0: yield michael@0: count = self._count michael@0: self._count = None michael@0: if count: michael@0: raise AccumulatedErrors() michael@0: michael@0: @property michael@0: def count(self): michael@0: # _count can be None. michael@0: return self._count if self._count else 0 michael@0: michael@0: michael@0: errors = ErrorCollector()