Hide keyboard shortcuts

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 -*- 

2 

3import traceback 

4 

5from .utils import create_formatted_exception 

6from .utils import safe_native 

7from .tokenize import Token 

8from .config import SOURCE_EXPRESSION_MARKER_LENGTH as LENGTH 

9 

10 

11def compute_source_marker(line, column, expression, size): 

12 """Computes source marker location string. 

13 

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('...', '···')) 

20 

21 >>> test('foo bar', 4, 'bar', 7) 

22 foo bar 

23 ^^^ 

24 

25 >>> test('foo ${bar}', 4, 'bar', 10) 

26 foo ${bar} 

27 ^^^ 

28 

29 >>> test(' foo bar', 6, 'bar', 6) 

30 ··· oo bar 

31 ^^^ 

32 

33 >>> test(' foo bar baz ', 6, 'bar', 6) 

34 ··· o bar ··· 

35 ^^^ 

36 

37 The entire expression is always shown, even if ``size`` does not 

38 accomodate for it. 

39 

40 >>> test(' foo bar baz ', 6, 'bar baz', 10) 

41 ··· oo bar baz 

42 ^^^^^^^ 

43 

44 >>> test(' foo bar', 10, 'bar', 5) 

45 ··· o bar 

46 ^^^ 

47 

48 >>> test(' foo bar', 10, 'boo', 5) 

49 ··· o bar 

50 ^ 

51 

52 """ 

53 

54 s = line.lstrip() 

55 column -= len(line) - len(s) 

56 s = s.rstrip() 

57 

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) 

68 

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) 

77 

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 

85 

86 # This also adds to the displayed length 

87 size += 4 

88 

89 if len(s) > size: 

90 s = s[:size].rstrip() + " ..." 

91 

92 return s, column * " " + marker 

93 

94 

95def iter_source_marker_lines(source, expression, line, column): 

96 for i, l in enumerate(source): 

97 if i + 1 != line: 

98 continue 

99 

100 s, marker = compute_source_marker( 

101 l, column, expression, LENGTH 

102 ) 

103 

104 yield " - Source: %s" % s 

105 yield " %s" % marker 

106 break 

107 

108 

109def ellipsify(string, limit): 

110 if len(string) > limit: 

111 return "... " + string[-(limit - 4):] 

112 

113 return string 

114 

115 

116class RenderError(Exception): 

117 """An error raised during rendering. 

118 

119 This class is used as a mixin which is added to the original 

120 exception. 

121 """ 

122 

123 

124class TemplateError(Exception): 

125 """An error raised by Chameleon. 

126 

127 >>> from chameleon.tokenize import Token 

128 >>> token = Token('token') 

129 >>> message = 'message' 

130 

131 Make sure the exceptions can be copied: 

132 

133 >>> from copy import copy 

134 >>> copy(TemplateError(message, token)) 

135 TemplateError('message', 'token') 

136 

137 And pickle/unpickled: 

138 

139 >>> from pickle import dumps, loads 

140 >>> loads(dumps(TemplateError(message, token), -1)) 

141 TemplateError('message', 'token') 

142 

143 """ 

144 

145 def __init__(self, msg, token): 

146 if not isinstance(token, Token): 

147 token = Token(token, 0) 

148 

149 Exception.__init__(self, msg, token) 

150 

151 def __copy__(self): 

152 inst = Exception.__new__(type(self)) 

153 inst.args = self.args 

154 return inst 

155 

156 def __str__(self): 

157 text = "%s\n\n" % self.args[0] 

158 text += " - String: \"%s\"" % safe_native(self.token) 

159 

160 if self.filename: 

161 text += "\n" 

162 text += " - Filename: %s" % self.filename 

163 

164 line, column = self.location 

165 text += "\n" 

166 text += " - Location: (line %d: col %d)" % (line, column) 

167 

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 = () 

189 

190 # Prepend newlines. 

191 for line in lines: 

192 text += "\n" + line 

193 

194 return text 

195 

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) 

203 

204 @property 

205 def token(self): 

206 return self.args[1] 

207 

208 @property 

209 def filename(self): 

210 return self.token.filename 

211 

212 @property 

213 def location(self): 

214 return self.token.location 

215 

216 @property 

217 def offset(self): 

218 return getattr(self.token, "pos", 0) 

219 

220 

221class ParseError(TemplateError): 

222 """An error occurred during parsing. 

223 

224 Indicates an error on the structural level. 

225 """ 

226 

227 

228class CompilationError(TemplateError): 

229 """An error occurred during compilation. 

230 

231 Indicates a general compilation error. 

232 """ 

233 

234 

235class TranslationError(TemplateError): 

236 """An error occurred during translation. 

237 

238 Indicates a general translation error. 

239 """ 

240 

241 

242class LanguageError(CompilationError): 

243 """Language syntax error. 

244 

245 Indicates a syntactical error due to incorrect usage of the 

246 template language. 

247 """ 

248 

249 

250class ExpressionError(LanguageError): 

251 """An error occurred compiling an expression. 

252 

253 Indicates a syntactical error in an expression. 

254 """ 

255 

256 

257class ExceptionFormatter(object): 

258 def __init__(self, errors, econtext, rcontext, value_repr): 

259 kwargs = rcontext.copy() 

260 kwargs.update(econtext) 

261 

262 for name in tuple(kwargs): 

263 if name.startswith('__'): 

264 del kwargs[name] 

265 

266 self._errors = errors 

267 self._kwargs = kwargs 

268 self._value_repr = value_repr 

269 

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 ] 

277 

278 for index, string in enumerate(formatted[1:]): 

279 formatted[index + 1] = " " * 15 + string 

280 

281 out = [] 

282 

283 for error in self._errors: 

284 expression, line, column, filename, exc = error 

285 

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 ) 

291 

292 out.append(" - Stream: %s" % s) 

293 out.append(" %s" % marker) 

294 

295 _filename = ellipsify(filename, 60) if filename else "<string>" 

296 

297 out.append(" - Expression: \"%s\"" % expression) 

298 out.append(" - Filename: %s" % _filename) 

299 out.append(" - Location: (line %d: col %d)" % (line, column)) 

300 

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() 

314 

315 out.append(" - Arguments: %s" % "\n".join(formatted)) 

316 

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__ 

324 

325 if formatted.startswith(formatted_class): 

326 formatted = formatted[len(formatted_class):].lstrip() 

327 

328 return "\n".join(map(safe_native, [formatted] + out))