codekingpro/portable-devtools
114k
1# mako/util.py2# Copyright 2006-2024 the Mako authors and contributors <see AUTHORS file>3#4# This module is part of Mako and is released under5# the MIT License: http://www.opensource.org/licenses/mit-license.php6from ast import parse7import codecs8import collections9import operator10import os11import re12import timeit13 14from .compat import importlib_metadata_get15 16 17def update_wrapper(decorated, fn):18 decorated.__wrapped__ = fn19 decorated.__name__ = fn.__name__20 return decorated21 22 23class PluginLoader:24 def __init__(self, group):25 self.group = group26 self.impls = {}27 28 def load(self, name):29 if name in self.impls:30 return self.impls[name]()31 32 for impl in importlib_metadata_get(self.group):33 if impl.name == name:34 self.impls[name] = impl.load35 return impl.load()36 37 from mako import exceptions38 39 raise exceptions.RuntimeException(40 "Can't load plugin %s %s" % (self.group, name)41 )42 43 def register(self, name, modulepath, objname):44 def load():45 mod = __import__(modulepath)46 for token in modulepath.split(".")[1:]:47 mod = getattr(mod, token)48 return getattr(mod, objname)49 50 self.impls[name] = load51 52 53def verify_directory(dir_):54 """create and/or verify a filesystem directory."""55 56 tries = 057 58 while not os.path.exists(dir_):59 try:60 tries += 161 os.makedirs(dir_, 0o755)62 except:63 if tries > 5:64 raise65 66 67def to_list(x, default=None):68 if x is None:69 return default70 if not isinstance(x, (list, tuple)):71 return [x]72 else:73 return x74 75 76class memoized_property:77 78 """A read-only @property that is only evaluated once."""79 80 def __init__(self, fget, doc=None):81 self.fget = fget82 self.__doc__ = doc or fget.__doc__83 self.__name__ = fget.__name__84 85 def __get__(self, obj, cls):86 if obj is None:87 return self88 obj.__dict__[self.__name__] = result = self.fget(obj)89 return result90 91 92class memoized_instancemethod:93 94 """Decorate a method memoize its return value.95 96 Best applied to no-arg methods: memoization is not sensitive to97 argument values, and will always return the same value even when98 called with different arguments.99 100 """101 102 def __init__(self, fget, doc=None):103 self.fget = fget104 self.__doc__ = doc or fget.__doc__105 self.__name__ = fget.__name__106 107 def __get__(self, obj, cls):108 if obj is None:109 return self110 111 def oneshot(*args, **kw):112 result = self.fget(obj, *args, **kw)113 114 def memo(*a, **kw):115 return result116 117 memo.__name__ = self.__name__118 memo.__doc__ = self.__doc__119 obj.__dict__[self.__name__] = memo120 return result121 122 oneshot.__name__ = self.__name__123 oneshot.__doc__ = self.__doc__124 return oneshot125 126 127class SetLikeDict(dict):128 129 """a dictionary that has some setlike methods on it"""130 131 def union(self, other):132 """produce a 'union' of this dict and another (at the key level).133 134 values in the second dict take precedence over that of the first"""135 x = SetLikeDict(**self)136 x.update(other)137 return x138 139 140class FastEncodingBuffer:141 142 """a very rudimentary buffer that is faster than StringIO,143 and supports unicode data."""144 145 def __init__(self, encoding=None, errors="strict"):146 self.data = collections.deque()147 self.encoding = encoding148 self.delim = ""149 self.errors = errors150 self.write = self.data.append151 152 def truncate(self):153 self.data = collections.deque()154 self.write = self.data.append155 156 def getvalue(self):157 if self.encoding:158 return self.delim.join(self.data).encode(159 self.encoding, self.errors160 )161 else:162 return self.delim.join(self.data)163 164 165class LRUCache(dict):166 167 """A dictionary-like object that stores a limited number of items,168 discarding lesser used items periodically.169 170 this is a rewrite of LRUCache from Myghty to use a periodic timestamp-based171 paradigm so that synchronization is not really needed. the size management172 is inexact.173 """174 175 class _Item:176 def __init__(self, key, value):177 self.key = key178 self.value = value179 self.timestamp = timeit.default_timer()180 181 def __repr__(self):182 return repr(self.value)183 184 def __init__(self, capacity, threshold=0.5):185 self.capacity = capacity186 self.threshold = threshold187 188 def __getitem__(self, key):189 item = dict.__getitem__(self, key)190 item.timestamp = timeit.default_timer()191 return item.value192 193 def values(self):194 return [i.value for i in dict.values(self)]195 196 def setdefault(self, key, value):197 if key in self:198 return self[key]199 self[key] = value200 return value201 202 def __setitem__(self, key, value):203 item = dict.get(self, key)204 if item is None:205 item = self._Item(key, value)206 dict.__setitem__(self, key, item)207 else:208 item.value = value209 self._manage_size()210 211 def _manage_size(self):212 while len(self) > self.capacity + self.capacity * self.threshold:213 bytime = sorted(214 dict.values(self),215 key=operator.attrgetter("timestamp"),216 reverse=True,217 )218 for item in bytime[self.capacity :]:219 try:220 del self[item.key]221 except KeyError:222 # if we couldn't find a key, most likely some other thread223 # broke in on us. loop around and try again224 break225 226 227# Regexp to match python magic encoding line228_PYTHON_MAGIC_COMMENT_re = re.compile(229 r"[ \t\f]* \# .* coding[=:][ \t]*([-\w.]+)", re.VERBOSE230)231 232 233def parse_encoding(fp):234 """Deduce the encoding of a Python source file (binary mode) from magic235 comment.236 237 It does this in the same way as the `Python interpreter`__238 239 .. __: http://docs.python.org/ref/encodings.html240 241 The ``fp`` argument should be a seekable file object in binary mode.242 """243 pos = fp.tell()244 fp.seek(0)245 try:246 line1 = fp.readline()247 has_bom = line1.startswith(codecs.BOM_UTF8)248 if has_bom:249 line1 = line1[len(codecs.BOM_UTF8) :]250 251 m = _PYTHON_MAGIC_COMMENT_re.match(line1.decode("ascii", "ignore"))252 if not m:253 try:254 parse(line1.decode("ascii", "ignore"))255 except (ImportError, SyntaxError):256 # Either it's a real syntax error, in which case the source257 # is not valid python source, or line2 is a continuation of258 # line1, in which case we don't want to scan line2 for a magic259 # comment.260 pass261 else:262 line2 = fp.readline()263 m = _PYTHON_MAGIC_COMMENT_re.match(264 line2.decode("ascii", "ignore")265 )266 267 if has_bom:268 if m:269 raise SyntaxError(270 "python refuses to compile code with both a UTF8"271 " byte-order-mark and a magic encoding comment"272 )273 return "utf_8"274 elif m:275 return m.group(1)276 else:277 return None278 finally:279 fp.seek(pos)280 281 282def sorted_dict_repr(d):283 """repr() a dictionary with the keys in order.284 285 Used by the lexer unit test to compare parse trees based on strings.286 287 """288 keys = list(d.keys())289 keys.sort()290 return "{" + ", ".join("%r: %r" % (k, d[k]) for k in keys) + "}"291 292 293def restore__ast(_ast):294 """Attempt to restore the required classes to the _ast module if it295 appears to be missing them296 """297 if hasattr(_ast, "AST"):298 return299 _ast.PyCF_ONLY_AST = 2 << 9300 m = compile(301 """\302def foo(): pass303class Bar: pass304if False: pass305baz = 'mako'3061 + 2 - 3 * 4 / 53076 // 7 % 8 << 9 >> 1030811 & 12 ^ 13 | 1430915 and 16 or 17310-baz + (not +18) - ~17311baz and 'foo' or 'bar'312(mako is baz == baz) is not baz != mako313mako > baz < mako >= baz <= mako314mako in baz not in mako""",315 "<unknown>",316 "exec",317 _ast.PyCF_ONLY_AST,318 )319 _ast.Module = type(m)320 321 for cls in _ast.Module.__mro__:322 if cls.__name__ == "mod":323 _ast.mod = cls324 elif cls.__name__ == "AST":325 _ast.AST = cls326 327 _ast.FunctionDef = type(m.body[0])328 _ast.ClassDef = type(m.body[1])329 _ast.If = type(m.body[2])330 331 _ast.Name = type(m.body[3].targets[0])332 _ast.Store = type(m.body[3].targets[0].ctx)333 _ast.Str = type(m.body[3].value)334 335 _ast.Sub = type(m.body[4].value.op)336 _ast.Add = type(m.body[4].value.left.op)337 _ast.Div = type(m.body[4].value.right.op)338 _ast.Mult = type(m.body[4].value.right.left.op)339 340 _ast.RShift = type(m.body[5].value.op)341 _ast.LShift = type(m.body[5].value.left.op)342 _ast.Mod = type(m.body[5].value.left.left.op)343 _ast.FloorDiv = type(m.body[5].value.left.left.left.op)344 345 _ast.BitOr = type(m.body[6].value.op)346 _ast.BitXor = type(m.body[6].value.left.op)347 _ast.BitAnd = type(m.body[6].value.left.left.op)348 349 _ast.Or = type(m.body[7].value.op)350 _ast.And = type(m.body[7].value.values[0].op)351 352 _ast.Invert = type(m.body[8].value.right.op)353 _ast.Not = type(m.body[8].value.left.right.op)354 _ast.UAdd = type(m.body[8].value.left.right.operand.op)355 _ast.USub = type(m.body[8].value.left.left.op)356 357 _ast.Or = type(m.body[9].value.op)358 _ast.And = type(m.body[9].value.values[0].op)359 360 _ast.IsNot = type(m.body[10].value.ops[0])361 _ast.NotEq = type(m.body[10].value.ops[1])362 _ast.Is = type(m.body[10].value.left.ops[0])363 _ast.Eq = type(m.body[10].value.left.ops[1])364 365 _ast.Gt = type(m.body[11].value.ops[0])366 _ast.Lt = type(m.body[11].value.ops[1])367 _ast.GtE = type(m.body[11].value.ops[2])368 _ast.LtE = type(m.body[11].value.ops[3])369 370 _ast.In = type(m.body[12].value.ops[0])371 _ast.NotIn = type(m.body[12].value.ops[1])372 373 374def read_file(path, mode="rb"):375 with open(path, mode) as fp:376 return fp.read()377 378 379def read_python_file(path):380 fp = open(path, "rb")381 try:382 encoding = parse_encoding(fp)383 data = fp.read()384 if encoding:385 data = data.decode(encoding)386 return data387 finally:388 fp.close()389 