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

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
7from copy import copy
9version = sys.version_info[:3]
11try:
12 import ast as _ast
13except ImportError:
14 from chameleon import ast25 as _ast
17class ASTProxy(object):
18 aliases = {
19 # Python 3.3
20 'TryExcept': 'Try',
21 'TryFinally': 'Try',
22 }
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])
30ast = ASTProxy()
31log = logging.getLogger('chameleon.utils')
32marker = object()
34# Python 2
35if version < (3, 0, 0):
36 import htmlentitydefs
37 import __builtin__ as builtins
39 from .py25 import raise_with_traceback
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
49 def safe_native(s, encoding='utf-8'):
50 if not isinstance(s, unicode):
51 s = decode_string(s, encoding, 'replace')
53 return s.encode(encoding)
55# Python 3
56else:
57 from html import entities as htmlentitydefs
58 import builtins
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
67 def safe_native(s, encoding='utf-8'):
68 if not isinstance(s, str):
69 s = decode_string(s, encoding, 'replace')
71 return s
73 def raise_with_traceback(exc, tb):
74 exc.__traceback__ = tb
75 raise exc
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
84entity_re = re.compile(r'&(#?)(x?)(\d{1,5}|\w{1,8});')
86module_cache = {}
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 )
99def _has_encoding(encoding):
100 try:
101 "".encode(encoding)
102 except LookupError:
103 return False
104 else:
105 return True
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 )
115_xml_decl = encode_string("<?xml")
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 )
123RE_ENCODING = re.compile(
124 r'encoding\s*=\s*(?:"|\')(?P<encoding>[\w\-]+)(?:"|\')'.encode('ascii'),
125 re.IGNORECASE
126 )
129def read_encoded(data):
130 return read_bytes(data, "utf-8")[0]
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
140 if prefix != encode_string('<?xml') and body.startswith(prefix):
141 return body.decode(encoding), encoding, "text/xml"
143 if body.startswith(_xml_decl):
144 content_type = "text/xml"
146 encoding = read_xml_encoding(body) or default_encoding
147 else:
148 content_type, encoding = detect_encoding(body, default_encoding)
150 return body.decode(encoding), encoding, content_type
153def detect_encoding(body, default_encoding):
154 if not isinstance(body, str):
155 body = body.decode('ascii', 'ignore')
157 match = RE_META.search(body)
158 if match is not None:
159 return match.groups()
161 return None, default_encoding
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')
171def mangle(filename):
172 """Mangles template filename into top-level Python module name.
174 >>> mangle('hello_world.pt')
175 'hello_world'
177 >>> mangle('foo.bar.baz.pt')
178 'foo_bar_baz'
180 >>> mangle('foo-bar-baz.pt')
181 'foo_bar_baz'
183 """
185 base, ext = os.path.splitext(filename)
186 return base.replace('.', '_').replace('-', '_')
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
195def substitute_entity(match, n2cp=htmlentitydefs.name2codepoint):
196 ent = match.group(3)
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)
206 if cp:
207 return chr(cp)
208 else:
209 return match.group()
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
224 try:
225 inst = BaseException.__new__(new)
226 except TypeError:
227 inst = cls.__new__(new)
229 BaseException.__init__(inst, *exc.args)
230 inst.__dict__ = exc.__dict__
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)
239def unescape(string):
240 for name in ('lt', 'gt', 'quot'):
241 cp = htmlentitydefs.name2codepoint[name]
242 string = string.replace('&%s;' % name, chr(cp))
244 return string
247_concat = unicode_string("").join
250def join(stream):
251 """Concatenate stream.
253 >>> print(join(('Hello', ' ', 'world')))
254 Hello world
256 >>> join(('Hello', 0))
257 Traceback (most recent call last):
258 ...
259 TypeError: ... expected ...
261 """
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)
271 # In case it didn't, re-raise the original exception
272 raise
275def decode_htmlentities(string):
276 """
277 >>> native_string(decode_htmlentities('&amp;'))
278 '&'
280 """
282 decoded = entity_re.subn(substitute_entity, string)[0]
284 # preserve input token data
285 return string.replace(string, decoded)
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
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)
311 return found
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]
321def limit_string(s, max_length=53):
322 if len(s) > max_length:
323 return s[:max_length - 3] + '...'
325 return s
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)
337 try:
338 name = str(getattr(value, '__name__', None)),
339 except:
340 name = '-'
342 return '<%s %s at %s>' % (type(value).__name__, name, hex(abs(id(value))))
345class callablestr(str):
346 __slots__ = ()
348 def __call__(self):
349 return self
352class callableint(int):
353 __slots__ = ()
355 def __call__(self):
356 return self
359class descriptorstr(object):
360 __slots__ = "function", "__name__"
362 def __init__(self, function):
363 self.function = function
364 self.__name__ = function.__name__
366 def __get__(self, context, cls):
367 return callablestr(self.function(context))
370class descriptorint(object):
371 __slots__ = "function", "__name__"
373 def __init__(self, function):
374 self.function = function
375 self.__name__ = function.__name__
377 def __get__(self, context, cls):
378 return callableint(self.function(context))
381class DebuggingOutputStream(list):
382 def append(self, value):
383 if not isinstance(value, string_type):
384 raise TypeError(value)
386 unicode_string(value)
387 list.append(self, value)
390class Scope(dict):
391 """
392 >>> scope = Scope()
393 >>> scope['a'] = 1
394 >>> copy = scope.copy()
396 Setting a local value and then a global value, we expect the local value
397 to take precedence.
399 >>> copy['a'] = 2
400 >>> copy.set_global('a', 3)
401 >>> assert copy['a'] == 2
403 However, setting a new global value should be immediately visible.
405 >>> copy.set_global('b', 1)
406 >>> assert copy['b'] == 1
408 Make sure the objects are reference-counted, not requiring a full
409 collection to be disposed of.
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 """
421 __slots__ = "_root",
423 set_local = dict.__setitem__
425 def __getitem__(self, key):
426 value = dict.get(self, key, marker)
427 if value is not marker:
428 return value
430 root = getattr(self, "_root", marker)
431 if root is not marker:
432 value = dict.get(root, key, marker)
434 if value is not marker:
435 return value
437 raise NameError(key)
439 @property
440 def vars(self):
441 return self
443 def copy(self):
444 inst = Scope(self)
445 root = getattr(self, "_root", self)
446 inst._root = root
447 return inst
449 def set_global(self, name, value):
450 root = getattr(self, "_root", self)
451 root[name] = value
453 setLocal = set_local
454 setGlobal = set_global
457class ListDictProxy(object):
458 def __init__(self, l):
459 self._l = l
461 def get(self, key):
462 return self._l[-1].get(key)
465class Markup(unicode_string):
466 """Wraps a string to always render as structure.
468 >>> Markup('<br />')
469 s'<br />'
470 """
472 def __html__(self):
473 return unicode_string(self)
475 def __repr__(self):
476 return "s'%s'" % self
479class ImportableMarker(object):
480 def __init__(self, module, name):
481 self.__module__ = module
482 self.name = name
484 @property
485 def __name__(self):
486 return "%s_MARKER" % self.name
488 def __repr__(self):
489 return '<%s>' % self.name