python/mozbuild/mozpack/errors.py

changeset 0
6474c204b198
     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()

mercurial