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

1import os 

2import re 

3import sys 

4import codecs 

5import logging 

6 

7from copy import copy 

8 

9version = sys.version_info[:3] 

10 

11try: 

12 import ast as _ast 

13except ImportError: 

14 from chameleon import ast25 as _ast 

15 

16 

17class ASTProxy(object): 

18 aliases = { 

19 # Python 3.3 

20 'TryExcept': 'Try', 

21 'TryFinally': 'Try', 

22 } 

23 

24 def __getattr__(self, name): 

25 if name.startswith('__'): 

26 raise AttributeError(name) 

27 return _ast.__dict__.get(name) or getattr(_ast, self.aliases[name]) 

28 

29 

30ast = ASTProxy() 

31log = logging.getLogger('chameleon.utils') 

32marker = object() 

33 

34# Python 2 

35if version < (3, 0, 0): 

36 import htmlentitydefs 

37 import __builtin__ as builtins 

38 

39 from .py25 import raise_with_traceback 

40 

41 chr = unichr 

42 native_string = str 

43 decode_string = unicode 

44 encode_string = str 

45 unicode_string = unicode 

46 string_type = basestring 

47 byte_string = str 

48 

49 def safe_native(s, encoding='utf-8'): 

50 if not isinstance(s, unicode): 

51 s = decode_string(s, encoding, 'replace') 

52 

53 return s.encode(encoding) 

54 

55# Python 3 

56else: 

57 from html import entities as htmlentitydefs 

58 import builtins 

59 

60 byte_string = bytes 

61 string_type = str 

62 native_string = str 

63 decode_string = bytes.decode 

64 encode_string = lambda s: bytes(s, 'utf-8') 

65 unicode_string = str 

66 

67 def safe_native(s, encoding='utf-8'): 

68 if not isinstance(s, str): 

69 s = decode_string(s, encoding, 'replace') 

70 

71 return s 

72 

73 def raise_with_traceback(exc, tb): 

74 exc.__traceback__ = tb 

75 raise exc 

76 

77def text_(s, encoding='latin-1', errors='strict'): 

78 """ If ``s`` is an instance of ``byte_string``, return 

79 ``s.decode(encoding, errors)``, otherwise return ``s``""" 

80 if isinstance(s, byte_string): 

81 return s.decode(encoding, errors) 

82 return s 

83 

84entity_re = re.compile(r'&(#?)(x?)(\d{1,5}|\w{1,8});') 

85 

86module_cache = {} 

87 

88xml_prefixes = ( 

89 (codecs.BOM_UTF8, 'utf-8-sig'), 

90 (codecs.BOM_UTF16_BE, 'utf-16-be'), 

91 (codecs.BOM_UTF16_LE, 'utf-16-le'), 

92 (codecs.BOM_UTF16, 'utf-16'), 

93 (codecs.BOM_UTF32_BE, 'utf-32-be'), 

94 (codecs.BOM_UTF32_LE, 'utf-32-le'), 

95 (codecs.BOM_UTF32, 'utf-32'), 

96 ) 

97 

98 

99def _has_encoding(encoding): 

100 try: 

101 "".encode(encoding) 

102 except LookupError: 

103 return False 

104 else: 

105 return True 

106 

107 

108# Precomputed prefix table 

109_xml_prefixes = tuple( 

110 (bom, str('<?xml').encode(encoding), encoding) 

111 for bom, encoding in reversed(xml_prefixes) 

112 if _has_encoding(encoding) 

113 ) 

114 

115_xml_decl = encode_string("<?xml") 

116 

117RE_META = re.compile( 

118 r'\s*<meta\s+http-equiv=["\']?Content-Type["\']?' 

119 r'\s+content=["\']?([^;]+);\s*charset=([^"\']+)["\']?\s*/?\s*>\s*', 

120 re.IGNORECASE 

121 ) 

122 

123RE_ENCODING = re.compile( 

124 r'encoding\s*=\s*(?:"|\')(?P<encoding>[\w\-]+)(?:"|\')'.encode('ascii'), 

125 re.IGNORECASE 

126 ) 

127 

128 

129def read_encoded(data): 

130 return read_bytes(data, "utf-8")[0] 

131 

132 

133def read_bytes(body, default_encoding): 

134 for bom, prefix, encoding in _xml_prefixes: 

135 if body.startswith(bom): 

136 document = body.decode(encoding) 

137 return document, encoding, \ 

138 "text/xml" if document.startswith("<?xml") else None 

139 

140 if prefix != encode_string('<?xml') and body.startswith(prefix): 

141 return body.decode(encoding), encoding, "text/xml" 

142 

143 if body.startswith(_xml_decl): 

144 content_type = "text/xml" 

145 

146 encoding = read_xml_encoding(body) or default_encoding 

147 else: 

148 content_type, encoding = detect_encoding(body, default_encoding) 

149 

150 return body.decode(encoding), encoding, content_type 

151 

152 

153def detect_encoding(body, default_encoding): 

154 if not isinstance(body, str): 

155 body = body.decode('ascii', 'ignore') 

156 

157 match = RE_META.search(body) 

158 if match is not None: 

159 return match.groups() 

160 

161 return None, default_encoding 

162 

163 

164def read_xml_encoding(body): 

165 if body.startswith('<?xml'.encode('ascii')): 

166 match = RE_ENCODING.search(body) 

167 if match is not None: 

168 return match.group('encoding').decode('ascii') 

169 

170 

171def mangle(filename): 

172 """Mangles template filename into top-level Python module name. 

173 

174 >>> mangle('hello_world.pt') 

175 'hello_world' 

176 

177 >>> mangle('foo.bar.baz.pt') 

178 'foo_bar_baz' 

179 

180 >>> mangle('foo-bar-baz.pt') 

181 'foo_bar_baz' 

182 

183 """ 

184 

185 base, ext = os.path.splitext(filename) 

186 return base.replace('.', '_').replace('-', '_') 

187 

188 

189def char2entity(c): 

190 cp = ord(c) 

191 name = htmlentitydefs.codepoint2name.get(cp) 

192 return '&%s;' % name if name is not None else '&#%d;' % cp 

193 

194 

195def substitute_entity(match, n2cp=htmlentitydefs.name2codepoint): 

196 ent = match.group(3) 

197 

198 if match.group(1) == "#": 

199 if match.group(2) == '': 

200 return chr(int(ent)) 

201 elif match.group(2) == 'x': 

202 return chr(int('0x' + ent, 16)) 

203 else: 

204 cp = n2cp.get(ent) 

205 

206 if cp: 

207 return chr(cp) 

208 else: 

209 return match.group() 

210 

211 

212def create_formatted_exception(exc, cls, formatter, base=Exception): 

213 try: 

214 try: 

215 new = type(cls.__name__, (cls, base), { 

216 '__str__': formatter, 

217 '_original__str__': exc.__str__, 

218 '__new__': BaseException.__new__, 

219 '__module__': cls.__module__, 

220 }) 

221 except TypeError: 

222 new = cls 

223 

224 try: 

225 inst = BaseException.__new__(new) 

226 except TypeError: 

227 inst = cls.__new__(new) 

228 

229 BaseException.__init__(inst, *exc.args) 

230 inst.__dict__ = exc.__dict__ 

231 

232 return inst 

233 except ValueError: 

234 name = type(exc).__name__ 

235 log.warn("Unable to copy exception of type '%s'." % name) 

236 raise TypeError(exc) 

237 

238 

239def unescape(string): 

240 for name in ('lt', 'gt', 'quot'): 

241 cp = htmlentitydefs.name2codepoint[name] 

242 string = string.replace('&%s;' % name, chr(cp)) 

243 

244 return string 

245 

246 

247_concat = unicode_string("").join 

248 

249 

250def join(stream): 

251 """Concatenate stream. 

252 

253 >>> print(join(('Hello', ' ', 'world'))) 

254 Hello world 

255 

256 >>> join(('Hello', 0)) 

257 Traceback (most recent call last): 

258 ... 

259 TypeError: ... expected ... 

260 

261 """ 

262 

263 try: 

264 return _concat(stream) 

265 except: 

266 # Loop through stream and coerce each element into unicode; 

267 # this should raise an exception 

268 for element in stream: 

269 unicode_string(element) 

270 

271 # In case it didn't, re-raise the original exception 

272 raise 

273 

274 

275def decode_htmlentities(string): 

276 """ 

277 >>> native_string(decode_htmlentities('&amp;amp;')) 

278 '&amp;' 

279 

280 """ 

281 

282 decoded = entity_re.subn(substitute_entity, string)[0] 

283 

284 # preserve input token data 

285 return string.replace(string, decoded) 

286 

287 

288# Taken from zope.dottedname 

289def _resolve_dotted(name, module=None): 

290 name = name.split('.') 

291 if not name[0]: 

292 if module is None: 

293 raise ValueError("relative name without base module") 

294 module = module.split('.') 

295 name.pop(0) 

296 while not name[0]: 

297 module.pop() 

298 name.pop(0) 

299 name = module + name 

300 

301 used = name.pop(0) 

302 found = __import__(used) 

303 for n in name: 

304 used += '.' + n 

305 try: 

306 found = getattr(found, n) 

307 except AttributeError: 

308 __import__(used) 

309 found = getattr(found, n) 

310 

311 return found 

312 

313 

314def resolve_dotted(dotted): 

315 if not dotted in module_cache: 

316 resolved = _resolve_dotted(dotted) 

317 module_cache[dotted] = resolved 

318 return module_cache[dotted] 

319 

320 

321def limit_string(s, max_length=53): 

322 if len(s) > max_length: 

323 return s[:max_length - 3] + '...' 

324 

325 return s 

326 

327 

328def value_repr(value): 

329 if isinstance(value, string_type): 

330 short = limit_string(value) 

331 return short.replace('\n', '\\n') 

332 if isinstance(value, (int, float)): 

333 return value 

334 if isinstance(value, dict): 

335 return '{...} (%d)' % len(value) 

336 

337 try: 

338 name = str(getattr(value, '__name__', None)), 

339 except: 

340 name = '-' 

341 

342 return '<%s %s at %s>' % (type(value).__name__, name, hex(abs(id(value)))) 

343 

344 

345class callablestr(str): 

346 __slots__ = () 

347 

348 def __call__(self): 

349 return self 

350 

351 

352class callableint(int): 

353 __slots__ = () 

354 

355 def __call__(self): 

356 return self 

357 

358 

359class descriptorstr(object): 

360 __slots__ = "function", "__name__" 

361 

362 def __init__(self, function): 

363 self.function = function 

364 self.__name__ = function.__name__ 

365 

366 def __get__(self, context, cls): 

367 return callablestr(self.function(context)) 

368 

369 

370class descriptorint(object): 

371 __slots__ = "function", "__name__" 

372 

373 def __init__(self, function): 

374 self.function = function 

375 self.__name__ = function.__name__ 

376 

377 def __get__(self, context, cls): 

378 return callableint(self.function(context)) 

379 

380 

381class DebuggingOutputStream(list): 

382 def append(self, value): 

383 if not isinstance(value, string_type): 

384 raise TypeError(value) 

385 

386 unicode_string(value) 

387 list.append(self, value) 

388 

389 

390class Scope(dict): 

391 """ 

392 >>> scope = Scope() 

393 >>> scope['a'] = 1 

394 >>> copy = scope.copy() 

395 

396 Setting a local value and then a global value, we expect the local value 

397 to take precedence. 

398 

399 >>> copy['a'] = 2 

400 >>> copy.set_global('a', 3) 

401 >>> assert copy['a'] == 2 

402 

403 However, setting a new global value should be immediately visible. 

404 

405 >>> copy.set_global('b', 1) 

406 >>> assert copy['b'] == 1 

407 

408 Make sure the objects are reference-counted, not requiring a full 

409 collection to be disposed of. 

410 

411 >>> import gc 

412 >>> _ = gc.collect() 

413 >>> del copy 

414 >>> del scope 

415 >>> import platform 

416 >>> assert gc.collect() == ( 

417 ... 0 if platform.python_implementation() == 'CPython' else None 

418 ... ) 

419 """ 

420 

421 __slots__ = "_root", 

422 

423 set_local = dict.__setitem__ 

424 

425 def __getitem__(self, key): 

426 value = dict.get(self, key, marker) 

427 if value is not marker: 

428 return value 

429 

430 root = getattr(self, "_root", marker) 

431 if root is not marker: 

432 value = dict.get(root, key, marker) 

433 

434 if value is not marker: 

435 return value 

436 

437 raise NameError(key) 

438 

439 @property 

440 def vars(self): 

441 return self 

442 

443 def copy(self): 

444 inst = Scope(self) 

445 root = getattr(self, "_root", self) 

446 inst._root = root 

447 return inst 

448 

449 def set_global(self, name, value): 

450 root = getattr(self, "_root", self) 

451 root[name] = value 

452 

453 setLocal = set_local 

454 setGlobal = set_global 

455 

456 

457class ListDictProxy(object): 

458 def __init__(self, l): 

459 self._l = l 

460 

461 def get(self, key): 

462 return self._l[-1].get(key) 

463 

464 

465class Markup(unicode_string): 

466 """Wraps a string to always render as structure. 

467 

468 >>> Markup('<br />') 

469 s'<br />' 

470 """ 

471 

472 def __html__(self): 

473 return unicode_string(self) 

474 

475 def __repr__(self): 

476 return "s'%s'" % self 

477 

478 

479class ImportableMarker(object): 

480 def __init__(self, module, name): 

481 self.__module__ = module 

482 self.name = name 

483 

484 @property 

485 def __name__(self): 

486 return "%s_MARKER" % self.name 

487 

488 def __repr__(self): 

489 return '<%s>' % self.name 

490