Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/chameleon/exc.py : 18%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -*- coding: utf-8 -*-
3import traceback
5from .utils import create_formatted_exception
6from .utils import safe_native
7from .tokenize import Token
8from .config import SOURCE_EXPRESSION_MARKER_LENGTH as LENGTH
11def compute_source_marker(line, column, expression, size):
12 """Computes source marker location string.
14 >>> def test(l, c, e, s):
15 ... s, marker = compute_source_marker(l, c, e, s)
16 ... out = s + '\\n' + marker
17 ...
18 ... # Replace dot with middle-dot to work around doctest ellipsis
19 ... print(out.replace('...', '···'))
21 >>> test('foo bar', 4, 'bar', 7)
22 foo bar
23 ^^^
25 >>> test('foo ${bar}', 4, 'bar', 10)
26 foo ${bar}
27 ^^^
29 >>> test(' foo bar', 6, 'bar', 6)
30 ··· oo bar
31 ^^^
33 >>> test(' foo bar baz ', 6, 'bar', 6)
34 ··· o bar ···
35 ^^^
37 The entire expression is always shown, even if ``size`` does not
38 accomodate for it.
40 >>> test(' foo bar baz ', 6, 'bar baz', 10)
41 ··· oo bar baz
42 ^^^^^^^
44 >>> test(' foo bar', 10, 'bar', 5)
45 ··· o bar
46 ^^^
48 >>> test(' foo bar', 10, 'boo', 5)
49 ··· o bar
50 ^
52 """
54 s = line.lstrip()
55 column -= len(line) - len(s)
56 s = s.rstrip()
58 try:
59 i = s[column:].index(expression)
60 except ValueError:
61 # If we can't find the expression
62 # (this shouldn't happen), simply
63 # use a standard size marker
64 marker = "^"
65 else:
66 column += i
67 marker = "^" * len(expression)
69 if len(expression) > size:
70 offset = column
71 size = len(expression)
72 else:
73 window = (size - len(expression)) / 2.0
74 offset = column - window
75 offset -= min(3, max(0, column + window + len(expression) - len(s)))
76 offset = int(offset)
78 if offset > 0:
79 s = s[offset:]
80 r = s.lstrip()
81 d = len(s) - len(r)
82 s = "... " + r
83 column += 4 - d
84 column -= offset
86 # This also adds to the displayed length
87 size += 4
89 if len(s) > size:
90 s = s[:size].rstrip() + " ..."
92 return s, column * " " + marker
95def iter_source_marker_lines(source, expression, line, column):
96 for i, l in enumerate(source):
97 if i + 1 != line:
98 continue
100 s, marker = compute_source_marker(
101 l, column, expression, LENGTH
102 )
104 yield " - Source: %s" % s
105 yield " %s" % marker
106 break
109def ellipsify(string, limit):
110 if len(string) > limit:
111 return "... " + string[-(limit - 4):]
113 return string
116class RenderError(Exception):
117 """An error raised during rendering.
119 This class is used as a mixin which is added to the original
120 exception.
121 """
124class TemplateError(Exception):
125 """An error raised by Chameleon.
127 >>> from chameleon.tokenize import Token
128 >>> token = Token('token')
129 >>> message = 'message'
131 Make sure the exceptions can be copied:
133 >>> from copy import copy
134 >>> copy(TemplateError(message, token))
135 TemplateError('message', 'token')
137 And pickle/unpickled:
139 >>> from pickle import dumps, loads
140 >>> loads(dumps(TemplateError(message, token), -1))
141 TemplateError('message', 'token')
143 """
145 def __init__(self, msg, token):
146 if not isinstance(token, Token):
147 token = Token(token, 0)
149 Exception.__init__(self, msg, token)
151 def __copy__(self):
152 inst = Exception.__new__(type(self))
153 inst.args = self.args
154 return inst
156 def __str__(self):
157 text = "%s\n\n" % self.args[0]
158 text += " - String: \"%s\"" % safe_native(self.token)
160 if self.filename:
161 text += "\n"
162 text += " - Filename: %s" % self.filename
164 line, column = self.location
165 text += "\n"
166 text += " - Location: (line %d: col %d)" % (line, column)
168 if line and column:
169 if self.token.source:
170 lines = iter_source_marker_lines(
171 self.token.source.splitlines(),
172 self.token, line, column
173 )
174 elif self.filename and not self.filename.startswith('<'):
175 try:
176 f = open(self.filename, 'r')
177 except IOError:
178 pass
179 else:
180 it = iter_source_marker_lines(
181 iter(f), self.token, line, column
182 )
183 try:
184 lines = list(lines)
185 finally:
186 f.close()
187 else:
188 lines = ()
190 # Prepend newlines.
191 for line in lines:
192 text += "\n" + line
194 return text
196 def __repr__(self):
197 try:
198 return "%s('%s', '%s')" % (
199 self.__class__.__name__, self.args[0], safe_native(self.token)
200 )
201 except AttributeError:
202 return object.__repr__(self)
204 @property
205 def token(self):
206 return self.args[1]
208 @property
209 def filename(self):
210 return self.token.filename
212 @property
213 def location(self):
214 return self.token.location
216 @property
217 def offset(self):
218 return getattr(self.token, "pos", 0)
221class ParseError(TemplateError):
222 """An error occurred during parsing.
224 Indicates an error on the structural level.
225 """
228class CompilationError(TemplateError):
229 """An error occurred during compilation.
231 Indicates a general compilation error.
232 """
235class TranslationError(TemplateError):
236 """An error occurred during translation.
238 Indicates a general translation error.
239 """
242class LanguageError(CompilationError):
243 """Language syntax error.
245 Indicates a syntactical error due to incorrect usage of the
246 template language.
247 """
250class ExpressionError(LanguageError):
251 """An error occurred compiling an expression.
253 Indicates a syntactical error in an expression.
254 """
257class ExceptionFormatter(object):
258 def __init__(self, errors, econtext, rcontext, value_repr):
259 kwargs = rcontext.copy()
260 kwargs.update(econtext)
262 for name in tuple(kwargs):
263 if name.startswith('__'):
264 del kwargs[name]
266 self._errors = errors
267 self._kwargs = kwargs
268 self._value_repr = value_repr
270 def __call__(self):
271 # Format keyword arguments; consecutive arguments are indented
272 # for readability
273 formatted = [
274 "%s: %s" % (name, self._value_repr(value))
275 for name, value in self._kwargs.items()
276 ]
278 for index, string in enumerate(formatted[1:]):
279 formatted[index + 1] = " " * 15 + string
281 out = []
283 for error in self._errors:
284 expression, line, column, filename, exc = error
286 if isinstance(exc, UnicodeDecodeError):
287 string = safe_native(exc.object)
288 s, marker = compute_source_marker(
289 string, exc.start, string[exc.start:exc.end], LENGTH
290 )
292 out.append(" - Stream: %s" % s)
293 out.append(" %s" % marker)
295 _filename = ellipsify(filename, 60) if filename else "<string>"
297 out.append(" - Expression: \"%s\"" % expression)
298 out.append(" - Filename: %s" % _filename)
299 out.append(" - Location: (line %d: col %d)" % (line, column))
301 if filename and not filename.startswith('<') and line and column:
302 try:
303 f = open(filename, 'r')
304 except IOError:
305 pass
306 else:
307 lines = iter_source_marker_lines(
308 iter(f), expression, line, column
309 )
310 try:
311 out.extend(lines)
312 finally:
313 f.close()
315 out.append(" - Arguments: %s" % "\n".join(formatted))
317 if isinstance(exc.__str__, ExceptionFormatter):
318 # This is a nested error that has already been wrapped
319 # We must unwrap it before trying to format it to prevent
320 # recursion
321 exc = create_formatted_exception(exc, type(exc), exc._original__str__)
322 formatted = traceback.format_exception_only(type(exc), exc)[-1]
323 formatted_class = "%s:" % type(exc).__name__
325 if formatted.startswith(formatted_class):
326 formatted = formatted[len(formatted_class):].lstrip()
328 return "\n".join(map(safe_native, [formatted] + out))