1# mako/util.py 2# Copyright (C) 2006-2015 the Mako authors and contributors <see AUTHORS file> 3# 4# This module is part of Mako and is released under 5# the MIT License: http://www.opensource.org/licenses/mit-license.php 6 7import re 8import collections 9import codecs 10import os 11from mako import compat 12import operator 13 14def update_wrapper(decorated, fn): 15 decorated.__wrapped__ = fn 16 decorated.__name__ = fn.__name__ 17 return decorated 18 19 20class PluginLoader(object): 21 def __init__(self, group): 22 self.group = group 23 self.impls = {} 24 25 def load(self, name): 26 if name in self.impls: 27 return self.impls[name]() 28 else: 29 import pkg_resources 30 for impl in pkg_resources.iter_entry_points( 31 self.group, 32 name): 33 self.impls[name] = impl.load 34 return impl.load() 35 else: 36 from mako import exceptions 37 raise exceptions.RuntimeException( 38 "Can't load plugin %s %s" % 39 (self.group, name)) 40 41 def register(self, name, modulepath, objname): 42 def load(): 43 mod = __import__(modulepath) 44 for token in modulepath.split(".")[1:]: 45 mod = getattr(mod, token) 46 return getattr(mod, objname) 47 self.impls[name] = load 48 49def verify_directory(dir): 50 """create and/or verify a filesystem directory.""" 51 52 tries = 0 53 54 while not os.path.exists(dir): 55 try: 56 tries += 1 57 os.makedirs(dir, compat.octal("0775")) 58 except: 59 if tries > 5: 60 raise 61 62def to_list(x, default=None): 63 if x is None: 64 return default 65 if not isinstance(x, (list, tuple)): 66 return [x] 67 else: 68 return x 69 70 71class memoized_property(object): 72 """A read-only @property that is only evaluated once.""" 73 def __init__(self, fget, doc=None): 74 self.fget = fget 75 self.__doc__ = doc or fget.__doc__ 76 self.__name__ = fget.__name__ 77 78 def __get__(self, obj, cls): 79 if obj is None: 80 return self 81 obj.__dict__[self.__name__] = result = self.fget(obj) 82 return result 83 84class memoized_instancemethod(object): 85 """Decorate a method memoize its return value. 86 87 Best applied to no-arg methods: memoization is not sensitive to 88 argument values, and will always return the same value even when 89 called with different arguments. 90 91 """ 92 def __init__(self, fget, doc=None): 93 self.fget = fget 94 self.__doc__ = doc or fget.__doc__ 95 self.__name__ = fget.__name__ 96 97 def __get__(self, obj, cls): 98 if obj is None: 99 return self 100 def oneshot(*args, **kw): 101 result = self.fget(obj, *args, **kw) 102 memo = lambda *a, **kw: result 103 memo.__name__ = self.__name__ 104 memo.__doc__ = self.__doc__ 105 obj.__dict__[self.__name__] = memo 106 return result 107 oneshot.__name__ = self.__name__ 108 oneshot.__doc__ = self.__doc__ 109 return oneshot 110 111class SetLikeDict(dict): 112 """a dictionary that has some setlike methods on it""" 113 def union(self, other): 114 """produce a 'union' of this dict and another (at the key level). 115 116 values in the second dict take precedence over that of the first""" 117 x = SetLikeDict(**self) 118 x.update(other) 119 return x 120 121class FastEncodingBuffer(object): 122 """a very rudimentary buffer that is faster than StringIO, 123 but doesn't crash on unicode data like cStringIO.""" 124 125 def __init__(self, encoding=None, errors='strict', as_unicode=False): 126 self.data = collections.deque() 127 self.encoding = encoding 128 if as_unicode: 129 self.delim = compat.u('') 130 else: 131 self.delim = '' 132 self.as_unicode = as_unicode 133 self.errors = errors 134 self.write = self.data.append 135 136 def truncate(self): 137 self.data = collections.deque() 138 self.write = self.data.append 139 140 def getvalue(self): 141 if self.encoding: 142 return self.delim.join(self.data).encode(self.encoding, 143 self.errors) 144 else: 145 return self.delim.join(self.data) 146 147class LRUCache(dict): 148 """A dictionary-like object that stores a limited number of items, 149 discarding lesser used items periodically. 150 151 this is a rewrite of LRUCache from Myghty to use a periodic timestamp-based 152 paradigm so that synchronization is not really needed. the size management 153 is inexact. 154 """ 155 156 class _Item(object): 157 def __init__(self, key, value): 158 self.key = key 159 self.value = value 160 self.timestamp = compat.time_func() 161 def __repr__(self): 162 return repr(self.value) 163 164 def __init__(self, capacity, threshold=.5): 165 self.capacity = capacity 166 self.threshold = threshold 167 168 def __getitem__(self, key): 169 item = dict.__getitem__(self, key) 170 item.timestamp = compat.time_func() 171 return item.value 172 173 def values(self): 174 return [i.value for i in dict.values(self)] 175 176 def setdefault(self, key, value): 177 if key in self: 178 return self[key] 179 else: 180 self[key] = value 181 return value 182 183 def __setitem__(self, key, value): 184 item = dict.get(self, key) 185 if item is None: 186 item = self._Item(key, value) 187 dict.__setitem__(self, key, item) 188 else: 189 item.value = value 190 self._manage_size() 191 192 def _manage_size(self): 193 while len(self) > self.capacity + self.capacity * self.threshold: 194 bytime = sorted(dict.values(self), 195 key=operator.attrgetter('timestamp'), reverse=True) 196 for item in bytime[self.capacity:]: 197 try: 198 del self[item.key] 199 except KeyError: 200 # if we couldn't find a key, most likely some other thread 201 # broke in on us. loop around and try again 202 break 203 204# Regexp to match python magic encoding line 205_PYTHON_MAGIC_COMMENT_re = re.compile( 206 r'[ \t\f]* \# .* coding[=:][ \t]*([-\w.]+)', 207 re.VERBOSE) 208 209def parse_encoding(fp): 210 """Deduce the encoding of a Python source file (binary mode) from magic 211 comment. 212 213 It does this in the same way as the `Python interpreter`__ 214 215 .. __: http://docs.python.org/ref/encodings.html 216 217 The ``fp`` argument should be a seekable file object in binary mode. 218 """ 219 pos = fp.tell() 220 fp.seek(0) 221 try: 222 line1 = fp.readline() 223 has_bom = line1.startswith(codecs.BOM_UTF8) 224 if has_bom: 225 line1 = line1[len(codecs.BOM_UTF8):] 226 227 m = _PYTHON_MAGIC_COMMENT_re.match(line1.decode('ascii', 'ignore')) 228 if not m: 229 try: 230 import parser 231 parser.suite(line1.decode('ascii', 'ignore')) 232 except (ImportError, SyntaxError): 233 # Either it's a real syntax error, in which case the source 234 # is not valid python source, or line2 is a continuation of 235 # line1, in which case we don't want to scan line2 for a magic 236 # comment. 237 pass 238 else: 239 line2 = fp.readline() 240 m = _PYTHON_MAGIC_COMMENT_re.match( 241 line2.decode('ascii', 'ignore')) 242 243 if has_bom: 244 if m: 245 raise SyntaxError("python refuses to compile code with both a UTF8" \ 246 " byte-order-mark and a magic encoding comment") 247 return 'utf_8' 248 elif m: 249 return m.group(1) 250 else: 251 return None 252 finally: 253 fp.seek(pos) 254 255def sorted_dict_repr(d): 256 """repr() a dictionary with the keys in order. 257 258 Used by the lexer unit test to compare parse trees based on strings. 259 260 """ 261 keys = list(d.keys()) 262 keys.sort() 263 return "{" + ", ".join(["%r: %r" % (k, d[k]) for k in keys]) + "}" 264 265def restore__ast(_ast): 266 """Attempt to restore the required classes to the _ast module if it 267 appears to be missing them 268 """ 269 if hasattr(_ast, 'AST'): 270 return 271 _ast.PyCF_ONLY_AST = 2 << 9 272 m = compile("""\ 273def foo(): pass 274class Bar(object): pass 275if False: pass 276baz = 'mako' 2771 + 2 - 3 * 4 / 5 2786 // 7 % 8 << 9 >> 10 27911 & 12 ^ 13 | 14 28015 and 16 or 17 281-baz + (not +18) - ~17 282baz and 'foo' or 'bar' 283(mako is baz == baz) is not baz != mako 284mako > baz < mako >= baz <= mako 285mako in baz not in mako""", '<unknown>', 'exec', _ast.PyCF_ONLY_AST) 286 _ast.Module = type(m) 287 288 for cls in _ast.Module.__mro__: 289 if cls.__name__ == 'mod': 290 _ast.mod = cls 291 elif cls.__name__ == 'AST': 292 _ast.AST = cls 293 294 _ast.FunctionDef = type(m.body[0]) 295 _ast.ClassDef = type(m.body[1]) 296 _ast.If = type(m.body[2]) 297 298 _ast.Name = type(m.body[3].targets[0]) 299 _ast.Store = type(m.body[3].targets[0].ctx) 300 _ast.Str = type(m.body[3].value) 301 302 _ast.Sub = type(m.body[4].value.op) 303 _ast.Add = type(m.body[4].value.left.op) 304 _ast.Div = type(m.body[4].value.right.op) 305 _ast.Mult = type(m.body[4].value.right.left.op) 306 307 _ast.RShift = type(m.body[5].value.op) 308 _ast.LShift = type(m.body[5].value.left.op) 309 _ast.Mod = type(m.body[5].value.left.left.op) 310 _ast.FloorDiv = type(m.body[5].value.left.left.left.op) 311 312 _ast.BitOr = type(m.body[6].value.op) 313 _ast.BitXor = type(m.body[6].value.left.op) 314 _ast.BitAnd = type(m.body[6].value.left.left.op) 315 316 _ast.Or = type(m.body[7].value.op) 317 _ast.And = type(m.body[7].value.values[0].op) 318 319 _ast.Invert = type(m.body[8].value.right.op) 320 _ast.Not = type(m.body[8].value.left.right.op) 321 _ast.UAdd = type(m.body[8].value.left.right.operand.op) 322 _ast.USub = type(m.body[8].value.left.left.op) 323 324 _ast.Or = type(m.body[9].value.op) 325 _ast.And = type(m.body[9].value.values[0].op) 326 327 _ast.IsNot = type(m.body[10].value.ops[0]) 328 _ast.NotEq = type(m.body[10].value.ops[1]) 329 _ast.Is = type(m.body[10].value.left.ops[0]) 330 _ast.Eq = type(m.body[10].value.left.ops[1]) 331 332 _ast.Gt = type(m.body[11].value.ops[0]) 333 _ast.Lt = type(m.body[11].value.ops[1]) 334 _ast.GtE = type(m.body[11].value.ops[2]) 335 _ast.LtE = type(m.body[11].value.ops[3]) 336 337 _ast.In = type(m.body[12].value.ops[0]) 338 _ast.NotIn = type(m.body[12].value.ops[1]) 339 340 341 342def read_file(path, mode='rb'): 343 fp = open(path, mode) 344 try: 345 data = fp.read() 346 return data 347 finally: 348 fp.close() 349 350def read_python_file(path): 351 fp = open(path, "rb") 352 try: 353 encoding = parse_encoding(fp) 354 data = fp.read() 355 if encoding: 356 data = data.decode(encoding) 357 return data 358 finally: 359 fp.close() 360 361