|
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/. |
|
4 |
|
5 import sys |
|
6 from contextlib import contextmanager |
|
7 |
|
8 |
|
9 class ErrorMessage(Exception): |
|
10 '''Exception type raised from errors.error() and errors.fatal()''' |
|
11 |
|
12 |
|
13 class AccumulatedErrors(Exception): |
|
14 '''Exception type raised from errors.accumulate()''' |
|
15 |
|
16 |
|
17 class ErrorCollector(object): |
|
18 ''' |
|
19 Error handling/logging class. A global instance, errors, is provided for |
|
20 convenience. |
|
21 |
|
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) |
|
27 |
|
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. |
|
31 |
|
32 Errors may be ignored by calling: |
|
33 errors.ignore_errors() |
|
34 |
|
35 After calling that function, only fatal errors throw an exception. |
|
36 |
|
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) |
|
43 |
|
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) |
|
49 |
|
50 as well as for function calls: |
|
51 def func(): |
|
52 errors.warn(message) |
|
53 with errors.context(filename, linenumber): |
|
54 func() |
|
55 |
|
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) |
|
64 |
|
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 |
|
75 |
|
76 def ignore_errors(self, ignore=True): |
|
77 if ignore: |
|
78 self._level = self.FATAL |
|
79 else: |
|
80 self._level = self.ERROR |
|
81 |
|
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) |
|
91 |
|
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 |
|
99 |
|
100 def fatal(self, msg): |
|
101 self._handle(self.FATAL, msg) |
|
102 |
|
103 def error(self, msg): |
|
104 self._handle(self.ERROR, msg) |
|
105 |
|
106 def warn(self, msg): |
|
107 self._handle(self.WARN, msg) |
|
108 |
|
109 def get_context(self): |
|
110 if self._context: |
|
111 return self._context[-1] |
|
112 |
|
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() |
|
120 |
|
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() |
|
130 |
|
131 @property |
|
132 def count(self): |
|
133 # _count can be None. |
|
134 return self._count if self._count else 0 |
|
135 |
|
136 |
|
137 errors = ErrorCollector() |