Fri, 16 Jan 2015 18:13:44 +0100
Integrate suggestion from review to improve consistency with existing code.
1 # This Source Code Form is subject to the terms of the Mozilla Public
2 # License, v. 2.0. If a copy of the MPL was not distributed with this
3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 import sys
6 from contextlib import contextmanager
9 class ErrorMessage(Exception):
10 '''Exception type raised from errors.error() and errors.fatal()'''
13 class AccumulatedErrors(Exception):
14 '''Exception type raised from errors.accumulate()'''
17 class ErrorCollector(object):
18 '''
19 Error handling/logging class. A global instance, errors, is provided for
20 convenience.
22 Warnings, errors and fatal errors may be logged by calls to the following
23 functions:
24 errors.warn(message)
25 errors.error(message)
26 errors.fatal(message)
28 Warnings only send the message on the logging output, while errors and
29 fatal errors send the message and throw an ErrorMessage exception. The
30 exception, however, may be deferred. See further below.
32 Errors may be ignored by calling:
33 errors.ignore_errors()
35 After calling that function, only fatal errors throw an exception.
37 The warnings, errors or fatal errors messages may be augmented with context
38 information when a context is provided. Context is defined by a pair
39 (filename, linenumber), and may be set with errors.context() used as a
40 context manager:
41 with errors.context(filename, linenumber):
42 errors.warn(message)
44 Arbitrary nesting is supported, both for errors.context calls:
45 with errors.context(filename1, linenumber1):
46 errors.warn(message)
47 with errors.context(filename2, linenumber2):
48 errors.warn(message)
50 as well as for function calls:
51 def func():
52 errors.warn(message)
53 with errors.context(filename, linenumber):
54 func()
56 Errors and fatal errors can have their exception thrown at a later time,
57 allowing for several different errors to be reported at once before
58 throwing. This is achieved with errors.accumulate() as a context manager:
59 with errors.accumulate():
60 if test1:
61 errors.error(message1)
62 if test2:
63 errors.error(message2)
65 In such cases, a single AccumulatedErrors exception is thrown, but doesn't
66 contain information about the exceptions. The logged messages do.
67 '''
68 out = sys.stderr
69 WARN = 1
70 ERROR = 2
71 FATAL = 3
72 _level = ERROR
73 _context = []
74 _count = None
76 def ignore_errors(self, ignore=True):
77 if ignore:
78 self._level = self.FATAL
79 else:
80 self._level = self.ERROR
82 def _full_message(self, level, msg):
83 if level >= self._level:
84 level = 'Error'
85 else:
86 level = 'Warning'
87 if self._context:
88 file, line = self._context[-1]
89 return "%s: %s:%d: %s" % (level, file, line, msg)
90 return "%s: %s" % (level, msg)
92 def _handle(self, level, msg):
93 msg = self._full_message(level, msg)
94 if level >= self._level:
95 if self._count is None:
96 raise ErrorMessage(msg)
97 self._count += 1
98 print >>self.out, msg
100 def fatal(self, msg):
101 self._handle(self.FATAL, msg)
103 def error(self, msg):
104 self._handle(self.ERROR, msg)
106 def warn(self, msg):
107 self._handle(self.WARN, msg)
109 def get_context(self):
110 if self._context:
111 return self._context[-1]
113 @contextmanager
114 def context(self, file, line):
115 if file and line:
116 self._context.append((file, line))
117 yield
118 if file and line:
119 self._context.pop()
121 @contextmanager
122 def accumulate(self):
123 assert self._count is None
124 self._count = 0
125 yield
126 count = self._count
127 self._count = None
128 if count:
129 raise AccumulatedErrors()
131 @property
132 def count(self):
133 # _count can be None.
134 return self._count if self._count else 0
137 errors = ErrorCollector()