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