| OLD | NEW |
| 1 # -*- coding: utf-8 -*- | 1 # -*- coding: utf-8 -*- |
| 2 """ | 2 """ |
| 3 jinja2.utils | 3 jinja2.utils |
| 4 ~~~~~~~~~~~~ | 4 ~~~~~~~~~~~~ |
| 5 | 5 |
| 6 Utility functions. | 6 Utility functions. |
| 7 | 7 |
| 8 :copyright: (c) 2010 by the Jinja Team. | 8 :copyright: (c) 2010 by the Jinja Team. |
| 9 :license: BSD, see LICENSE for more details. | 9 :license: BSD, see LICENSE for more details. |
| 10 """ | 10 """ |
| 11 import re | 11 import re |
| 12 import sys | |
| 13 import errno | 12 import errno |
| 14 try: | |
| 15 from thread import allocate_lock | |
| 16 except ImportError: | |
| 17 from dummy_thread import allocate_lock | |
| 18 from collections import deque | 13 from collections import deque |
| 19 from itertools import imap | 14 from jinja2._compat import text_type, string_types, implements_iterator, \ |
| 15 allocate_lock, url_quote |
| 20 | 16 |
| 21 | 17 |
| 22 _word_split_re = re.compile(r'(\s+)') | 18 _word_split_re = re.compile(r'(\s+)') |
| 23 _punctuation_re = re.compile( | 19 _punctuation_re = re.compile( |
| 24 '^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % ( | 20 '^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % ( |
| 25 '|'.join(imap(re.escape, ('(', '<', '<'))), | 21 '|'.join(map(re.escape, ('(', '<', '<'))), |
| 26 '|'.join(imap(re.escape, ('.', ',', ')', '>', '\n', '>'))) | 22 '|'.join(map(re.escape, ('.', ',', ')', '>', '\n', '>'))) |
| 27 ) | 23 ) |
| 28 ) | 24 ) |
| 29 _simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$') | 25 _simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$') |
| 30 _striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)') | 26 _striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)') |
| 31 _entity_re = re.compile(r'&([^;]+);') | 27 _entity_re = re.compile(r'&([^;]+);') |
| 32 _letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' | 28 _letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' |
| 33 _digits = '0123456789' | 29 _digits = '0123456789' |
| 34 | 30 |
| 35 # special singleton representing missing values for the runtime | 31 # special singleton representing missing values for the runtime |
| 36 missing = type('MissingType', (), {'__repr__': lambda x: 'missing'})() | 32 missing = type('MissingType', (), {'__repr__': lambda x: 'missing'})() |
| 37 | 33 |
| 38 # internal code | 34 # internal code |
| 39 internal_code = set() | 35 internal_code = set() |
| 40 | 36 |
| 41 | 37 concat = u''.join |
| 42 # concatenate a list of strings and convert them to unicode. | |
| 43 # unfortunately there is a bug in python 2.4 and lower that causes | |
| 44 # unicode.join trash the traceback. | |
| 45 _concat = u''.join | |
| 46 try: | |
| 47 def _test_gen_bug(): | |
| 48 raise TypeError(_test_gen_bug) | |
| 49 yield None | |
| 50 _concat(_test_gen_bug()) | |
| 51 except TypeError, _error: | |
| 52 if not _error.args or _error.args[0] is not _test_gen_bug: | |
| 53 def concat(gen): | |
| 54 try: | |
| 55 return _concat(list(gen)) | |
| 56 except Exception: | |
| 57 # this hack is needed so that the current frame | |
| 58 # does not show up in the traceback. | |
| 59 exc_type, exc_value, tb = sys.exc_info() | |
| 60 raise exc_type, exc_value, tb.tb_next | |
| 61 else: | |
| 62 concat = _concat | |
| 63 del _test_gen_bug, _error | |
| 64 | |
| 65 | |
| 66 # for python 2.x we create outselves a next() function that does the | |
| 67 # basics without exception catching. | |
| 68 try: | |
| 69 next = next | |
| 70 except NameError: | |
| 71 def next(x): | |
| 72 return x.next() | |
| 73 | |
| 74 | |
| 75 # if this python version is unable to deal with unicode filenames | |
| 76 # when passed to encode we let this function encode it properly. | |
| 77 # This is used in a couple of places. As far as Jinja is concerned | |
| 78 # filenames are unicode *or* bytestrings in 2.x and unicode only in | |
| 79 # 3.x because compile cannot handle bytes | |
| 80 if sys.version_info < (3, 0): | |
| 81 def _encode_filename(filename): | |
| 82 if isinstance(filename, unicode): | |
| 83 return filename.encode('utf-8') | |
| 84 return filename | |
| 85 else: | |
| 86 def _encode_filename(filename): | |
| 87 assert filename is None or isinstance(filename, str), \ | |
| 88 'filenames must be strings' | |
| 89 return filename | |
| 90 | |
| 91 from keyword import iskeyword as is_python_keyword | |
| 92 | |
| 93 | |
| 94 # common types. These do exist in the special types module too which however | |
| 95 # does not exist in IronPython out of the box. Also that way we don't have | |
| 96 # to deal with implementation specific stuff here | |
| 97 class _C(object): | |
| 98 def method(self): pass | |
| 99 def _func(): | |
| 100 yield None | |
| 101 FunctionType = type(_func) | |
| 102 GeneratorType = type(_func()) | |
| 103 MethodType = type(_C.method) | |
| 104 CodeType = type(_C.method.func_code) | |
| 105 try: | |
| 106 raise TypeError() | |
| 107 except TypeError: | |
| 108 _tb = sys.exc_info()[2] | |
| 109 TracebackType = type(_tb) | |
| 110 FrameType = type(_tb.tb_frame) | |
| 111 del _C, _tb, _func | |
| 112 | 38 |
| 113 | 39 |
| 114 def contextfunction(f): | 40 def contextfunction(f): |
| 115 """This decorator can be used to mark a function or method context callable. | 41 """This decorator can be used to mark a function or method context callable. |
| 116 A context callable is passed the active :class:`Context` as first argument w
hen | 42 A context callable is passed the active :class:`Context` as first argument w
hen |
| 117 called from the template. This is useful if a function wants to get access | 43 called from the template. This is useful if a function wants to get access |
| 118 to the context or functions provided on the context object. For example | 44 to the context or functions provided on the context object. For example |
| 119 a function that returns a sorted list of template variables the current | 45 a function that returns a sorted list of template variables the current |
| 120 template exports could look like this:: | 46 template exports could look like this:: |
| 121 | 47 |
| 122 @contextfunction | 48 @contextfunction |
| 123 def get_exported_names(context): | 49 def get_exported_names(context): |
| 124 return sorted(context.exported_vars) | 50 return sorted(context.exported_vars) |
| 125 """ | 51 """ |
| 126 f.contextfunction = True | 52 f.contextfunction = True |
| 127 return f | 53 return f |
| 128 | 54 |
| 129 | 55 |
| 130 def evalcontextfunction(f): | 56 def evalcontextfunction(f): |
| 131 """This decoraotr can be used to mark a function or method as an eval | 57 """This decorator can be used to mark a function or method as an eval |
| 132 context callable. This is similar to the :func:`contextfunction` | 58 context callable. This is similar to the :func:`contextfunction` |
| 133 but instead of passing the context, an evaluation context object is | 59 but instead of passing the context, an evaluation context object is |
| 134 passed. For more information about the eval context, see | 60 passed. For more information about the eval context, see |
| 135 :ref:`eval-context`. | 61 :ref:`eval-context`. |
| 136 | 62 |
| 137 .. versionadded:: 2.4 | 63 .. versionadded:: 2.4 |
| 138 """ | 64 """ |
| 139 f.evalcontextfunction = True | 65 f.evalcontextfunction = True |
| 140 return f | 66 return f |
| 141 | 67 |
| 142 | 68 |
| 143 def environmentfunction(f): | 69 def environmentfunction(f): |
| 144 """This decorator can be used to mark a function or method as environment | 70 """This decorator can be used to mark a function or method as environment |
| 145 callable. This decorator works exactly like the :func:`contextfunction` | 71 callable. This decorator works exactly like the :func:`contextfunction` |
| 146 decorator just that the first argument is the active :class:`Environment` | 72 decorator just that the first argument is the active :class:`Environment` |
| 147 and not context. | 73 and not context. |
| 148 """ | 74 """ |
| 149 f.environmentfunction = True | 75 f.environmentfunction = True |
| 150 return f | 76 return f |
| 151 | 77 |
| 152 | 78 |
| 153 def internalcode(f): | 79 def internalcode(f): |
| 154 """Marks the function as internally used""" | 80 """Marks the function as internally used""" |
| 155 internal_code.add(f.func_code) | 81 internal_code.add(f.__code__) |
| 156 return f | 82 return f |
| 157 | 83 |
| 158 | 84 |
| 159 def is_undefined(obj): | 85 def is_undefined(obj): |
| 160 """Check if the object passed is undefined. This does nothing more than | 86 """Check if the object passed is undefined. This does nothing more than |
| 161 performing an instance check against :class:`Undefined` but looks nicer. | 87 performing an instance check against :class:`Undefined` but looks nicer. |
| 162 This can be used for custom filters or tests that want to react to | 88 This can be used for custom filters or tests that want to react to |
| 163 undefined variables. For example a custom default filter can look like | 89 undefined variables. For example a custom default filter can look like |
| 164 this:: | 90 this:: |
| 165 | 91 |
| (...skipping 18 matching lines...) Expand all Loading... |
| 184 the time. Normally you don't have to care about that but if you are | 110 the time. Normally you don't have to care about that but if you are |
| 185 messuring memory consumption you may want to clean the caches. | 111 messuring memory consumption you may want to clean the caches. |
| 186 """ | 112 """ |
| 187 from jinja2.environment import _spontaneous_environments | 113 from jinja2.environment import _spontaneous_environments |
| 188 from jinja2.lexer import _lexer_cache | 114 from jinja2.lexer import _lexer_cache |
| 189 _spontaneous_environments.clear() | 115 _spontaneous_environments.clear() |
| 190 _lexer_cache.clear() | 116 _lexer_cache.clear() |
| 191 | 117 |
| 192 | 118 |
| 193 def import_string(import_name, silent=False): | 119 def import_string(import_name, silent=False): |
| 194 """Imports an object based on a string. This use useful if you want to | 120 """Imports an object based on a string. This is useful if you want to |
| 195 use import paths as endpoints or something similar. An import path can | 121 use import paths as endpoints or something similar. An import path can |
| 196 be specified either in dotted notation (``xml.sax.saxutils.escape``) | 122 be specified either in dotted notation (``xml.sax.saxutils.escape``) |
| 197 or with a colon as object delimiter (``xml.sax.saxutils:escape``). | 123 or with a colon as object delimiter (``xml.sax.saxutils:escape``). |
| 198 | 124 |
| 199 If the `silent` is True the return value will be `None` if the import | 125 If the `silent` is True the return value will be `None` if the import |
| 200 fails. | 126 fails. |
| 201 | 127 |
| 202 :return: imported object | 128 :return: imported object |
| 203 """ | 129 """ |
| 204 try: | 130 try: |
| (...skipping 10 matching lines...) Expand all Loading... |
| 215 if not silent: | 141 if not silent: |
| 216 raise | 142 raise |
| 217 | 143 |
| 218 | 144 |
| 219 def open_if_exists(filename, mode='rb'): | 145 def open_if_exists(filename, mode='rb'): |
| 220 """Returns a file descriptor for the filename if that file exists, | 146 """Returns a file descriptor for the filename if that file exists, |
| 221 otherwise `None`. | 147 otherwise `None`. |
| 222 """ | 148 """ |
| 223 try: | 149 try: |
| 224 return open(filename, mode) | 150 return open(filename, mode) |
| 225 except IOError, e: | 151 except IOError as e: |
| 226 if e.errno not in (errno.ENOENT, errno.EISDIR): | 152 if e.errno not in (errno.ENOENT, errno.EISDIR): |
| 227 raise | 153 raise |
| 228 | 154 |
| 229 | 155 |
| 230 def object_type_repr(obj): | 156 def object_type_repr(obj): |
| 231 """Returns the name of the object's type. For some recognized | 157 """Returns the name of the object's type. For some recognized |
| 232 singletons the name of the object is returned instead. (For | 158 singletons the name of the object is returned instead. (For |
| 233 example for `None` and `Ellipsis`). | 159 example for `None` and `Ellipsis`). |
| 234 """ | 160 """ |
| 235 if obj is None: | 161 if obj is None: |
| (...skipping 28 matching lines...) Expand all Loading... |
| 264 | 190 |
| 265 If trim_url_limit is not None, the URLs in link text will be limited | 191 If trim_url_limit is not None, the URLs in link text will be limited |
| 266 to trim_url_limit characters. | 192 to trim_url_limit characters. |
| 267 | 193 |
| 268 If nofollow is True, the URLs in link text will get a rel="nofollow" | 194 If nofollow is True, the URLs in link text will get a rel="nofollow" |
| 269 attribute. | 195 attribute. |
| 270 """ | 196 """ |
| 271 trim_url = lambda x, limit=trim_url_limit: limit is not None \ | 197 trim_url = lambda x, limit=trim_url_limit: limit is not None \ |
| 272 and (x[:limit] + (len(x) >=limit and '...' | 198 and (x[:limit] + (len(x) >=limit and '...' |
| 273 or '')) or x | 199 or '')) or x |
| 274 words = _word_split_re.split(unicode(escape(text))) | 200 words = _word_split_re.split(text_type(escape(text))) |
| 275 nofollow_attr = nofollow and ' rel="nofollow"' or '' | 201 nofollow_attr = nofollow and ' rel="nofollow"' or '' |
| 276 for i, word in enumerate(words): | 202 for i, word in enumerate(words): |
| 277 match = _punctuation_re.match(word) | 203 match = _punctuation_re.match(word) |
| 278 if match: | 204 if match: |
| 279 lead, middle, trail = match.groups() | 205 lead, middle, trail = match.groups() |
| 280 if middle.startswith('www.') or ( | 206 if middle.startswith('www.') or ( |
| 281 '@' not in middle and | 207 '@' not in middle and |
| 282 not middle.startswith('http://') and | 208 not middle.startswith('http://') and |
| 209 not middle.startswith('https://') and |
| 283 len(middle) > 0 and | 210 len(middle) > 0 and |
| 284 middle[0] in _letters + _digits and ( | 211 middle[0] in _letters + _digits and ( |
| 285 middle.endswith('.org') or | 212 middle.endswith('.org') or |
| 286 middle.endswith('.net') or | 213 middle.endswith('.net') or |
| 287 middle.endswith('.com') | 214 middle.endswith('.com') |
| 288 )): | 215 )): |
| 289 middle = '<a href="http://%s"%s>%s</a>' % (middle, | 216 middle = '<a href="http://%s"%s>%s</a>' % (middle, |
| 290 nofollow_attr, trim_url(middle)) | 217 nofollow_attr, trim_url(middle)) |
| 291 if middle.startswith('http://') or \ | 218 if middle.startswith('http://') or \ |
| 292 middle.startswith('https://'): | 219 middle.startswith('https://'): |
| 293 middle = '<a href="%s"%s>%s</a>' % (middle, | 220 middle = '<a href="%s"%s>%s</a>' % (middle, |
| 294 nofollow_attr, trim_url(middle)) | 221 nofollow_attr, trim_url(middle)) |
| 295 if '@' in middle and not middle.startswith('www.') and \ | 222 if '@' in middle and not middle.startswith('www.') and \ |
| 296 not ':' in middle and _simple_email_re.match(middle): | 223 not ':' in middle and _simple_email_re.match(middle): |
| 297 middle = '<a href="mailto:%s">%s</a>' % (middle, middle) | 224 middle = '<a href="mailto:%s">%s</a>' % (middle, middle) |
| 298 if lead + middle + trail != word: | 225 if lead + middle + trail != word: |
| 299 words[i] = lead + middle + trail | 226 words[i] = lead + middle + trail |
| 300 return u''.join(words) | 227 return u''.join(words) |
| 301 | 228 |
| 302 | 229 |
| 303 def generate_lorem_ipsum(n=5, html=True, min=20, max=100): | 230 def generate_lorem_ipsum(n=5, html=True, min=20, max=100): |
| 304 """Generate some lorem impsum for the template.""" | 231 """Generate some lorem impsum for the template.""" |
| 305 from jinja2.constants import LOREM_IPSUM_WORDS | 232 from jinja2.constants import LOREM_IPSUM_WORDS |
| 306 from random import choice, randrange | 233 from random import choice, randrange |
| 307 words = LOREM_IPSUM_WORDS.split() | 234 words = LOREM_IPSUM_WORDS.split() |
| 308 result = [] | 235 result = [] |
| 309 | 236 |
| 310 for _ in xrange(n): | 237 for _ in range(n): |
| 311 next_capitalized = True | 238 next_capitalized = True |
| 312 last_comma = last_fullstop = 0 | 239 last_comma = last_fullstop = 0 |
| 313 word = None | 240 word = None |
| 314 last = None | 241 last = None |
| 315 p = [] | 242 p = [] |
| 316 | 243 |
| 317 # each paragraph contains out of 20 to 100 words. | 244 # each paragraph contains out of 20 to 100 words. |
| 318 for idx, _ in enumerate(xrange(randrange(min, max))): | 245 for idx, _ in enumerate(range(randrange(min, max))): |
| 319 while True: | 246 while True: |
| 320 word = choice(words) | 247 word = choice(words) |
| 321 if word != last: | 248 if word != last: |
| 322 last = word | 249 last = word |
| 323 break | 250 break |
| 324 if next_capitalized: | 251 if next_capitalized: |
| 325 word = word.capitalize() | 252 word = word.capitalize() |
| 326 next_capitalized = False | 253 next_capitalized = False |
| 327 # add commas | 254 # add commas |
| 328 if idx - randrange(3, 8) > last_comma: | 255 if idx - randrange(3, 8) > last_comma: |
| (...skipping 13 matching lines...) Expand all Loading... |
| 342 p = p[:-1] + '.' | 269 p = p[:-1] + '.' |
| 343 elif not p.endswith('.'): | 270 elif not p.endswith('.'): |
| 344 p += '.' | 271 p += '.' |
| 345 result.append(p) | 272 result.append(p) |
| 346 | 273 |
| 347 if not html: | 274 if not html: |
| 348 return u'\n\n'.join(result) | 275 return u'\n\n'.join(result) |
| 349 return Markup(u'\n'.join(u'<p>%s</p>' % escape(x) for x in result)) | 276 return Markup(u'\n'.join(u'<p>%s</p>' % escape(x) for x in result)) |
| 350 | 277 |
| 351 | 278 |
| 279 def unicode_urlencode(obj, charset='utf-8'): |
| 280 """URL escapes a single bytestring or unicode string with the |
| 281 given charset if applicable to URL safe quoting under all rules |
| 282 that need to be considered under all supported Python versions. |
| 283 |
| 284 If non strings are provided they are converted to their unicode |
| 285 representation first. |
| 286 """ |
| 287 if not isinstance(obj, string_types): |
| 288 obj = text_type(obj) |
| 289 if isinstance(obj, text_type): |
| 290 obj = obj.encode(charset) |
| 291 return text_type(url_quote(obj)) |
| 292 |
| 293 |
| 352 class LRUCache(object): | 294 class LRUCache(object): |
| 353 """A simple LRU Cache implementation.""" | 295 """A simple LRU Cache implementation.""" |
| 354 | 296 |
| 355 # this is fast for small capacities (something below 1000) but doesn't | 297 # this is fast for small capacities (something below 1000) but doesn't |
| 356 # scale. But as long as it's only used as storage for templates this | 298 # scale. But as long as it's only used as storage for templates this |
| 357 # won't do any harm. | 299 # won't do any harm. |
| 358 | 300 |
| 359 def __init__(self, capacity): | 301 def __init__(self, capacity): |
| 360 self.capacity = capacity | 302 self.capacity = capacity |
| 361 self._mapping = {} | 303 self._mapping = {} |
| 362 self._queue = deque() | 304 self._queue = deque() |
| 363 self._postinit() | 305 self._postinit() |
| 364 | 306 |
| 365 def _postinit(self): | 307 def _postinit(self): |
| 366 # alias all queue methods for faster lookup | 308 # alias all queue methods for faster lookup |
| 367 self._popleft = self._queue.popleft | 309 self._popleft = self._queue.popleft |
| 368 self._pop = self._queue.pop | 310 self._pop = self._queue.pop |
| 369 if hasattr(self._queue, 'remove'): | 311 self._remove = self._queue.remove |
| 370 self._remove = self._queue.remove | |
| 371 self._wlock = allocate_lock() | 312 self._wlock = allocate_lock() |
| 372 self._append = self._queue.append | 313 self._append = self._queue.append |
| 373 | 314 |
| 374 def _remove(self, obj): | |
| 375 """Python 2.4 compatibility.""" | |
| 376 for idx, item in enumerate(self._queue): | |
| 377 if item == obj: | |
| 378 del self._queue[idx] | |
| 379 break | |
| 380 | |
| 381 def __getstate__(self): | 315 def __getstate__(self): |
| 382 return { | 316 return { |
| 383 'capacity': self.capacity, | 317 'capacity': self.capacity, |
| 384 '_mapping': self._mapping, | 318 '_mapping': self._mapping, |
| 385 '_queue': self._queue | 319 '_queue': self._queue |
| 386 } | 320 } |
| 387 | 321 |
| 388 def __setstate__(self, d): | 322 def __setstate__(self, d): |
| 389 self.__dict__.update(d) | 323 self.__dict__.update(d) |
| 390 self._postinit() | 324 self._postinit() |
| 391 | 325 |
| 392 def __getnewargs__(self): | 326 def __getnewargs__(self): |
| 393 return (self.capacity,) | 327 return (self.capacity,) |
| 394 | 328 |
| 395 def copy(self): | 329 def copy(self): |
| 396 """Return an shallow copy of the instance.""" | 330 """Return a shallow copy of the instance.""" |
| 397 rv = self.__class__(self.capacity) | 331 rv = self.__class__(self.capacity) |
| 398 rv._mapping.update(self._mapping) | 332 rv._mapping.update(self._mapping) |
| 399 rv._queue = deque(self._queue) | 333 rv._queue = deque(self._queue) |
| 400 return rv | 334 return rv |
| 401 | 335 |
| 402 def get(self, key, default=None): | 336 def get(self, key, default=None): |
| 403 """Return an item from the cache dict or `default`""" | 337 """Return an item from the cache dict or `default`""" |
| 404 try: | 338 try: |
| 405 return self[key] | 339 return self[key] |
| 406 except KeyError: | 340 except KeyError: |
| 407 return default | 341 return default |
| 408 | 342 |
| 409 def setdefault(self, key, default=None): | 343 def setdefault(self, key, default=None): |
| 410 """Set `default` if the key is not in the cache otherwise | 344 """Set `default` if the key is not in the cache otherwise |
| 411 leave unchanged. Return the value of this key. | 345 leave unchanged. Return the value of this key. |
| 412 """ | 346 """ |
| 347 self._wlock.acquire() |
| 413 try: | 348 try: |
| 414 return self[key] | 349 try: |
| 415 except KeyError: | 350 return self[key] |
| 416 self[key] = default | 351 except KeyError: |
| 417 return default | 352 self[key] = default |
| 353 return default |
| 354 finally: |
| 355 self._wlock.release() |
| 418 | 356 |
| 419 def clear(self): | 357 def clear(self): |
| 420 """Clear the cache.""" | 358 """Clear the cache.""" |
| 421 self._wlock.acquire() | 359 self._wlock.acquire() |
| 422 try: | 360 try: |
| 423 self._mapping.clear() | 361 self._mapping.clear() |
| 424 self._queue.clear() | 362 self._queue.clear() |
| 425 finally: | 363 finally: |
| 426 self._wlock.release() | 364 self._wlock.release() |
| 427 | 365 |
| 428 def __contains__(self, key): | 366 def __contains__(self, key): |
| 429 """Check if a key exists in this cache.""" | 367 """Check if a key exists in this cache.""" |
| 430 return key in self._mapping | 368 return key in self._mapping |
| 431 | 369 |
| 432 def __len__(self): | 370 def __len__(self): |
| 433 """Return the current size of the cache.""" | 371 """Return the current size of the cache.""" |
| 434 return len(self._mapping) | 372 return len(self._mapping) |
| 435 | 373 |
| 436 def __repr__(self): | 374 def __repr__(self): |
| 437 return '<%s %r>' % ( | 375 return '<%s %r>' % ( |
| 438 self.__class__.__name__, | 376 self.__class__.__name__, |
| 439 self._mapping | 377 self._mapping |
| 440 ) | 378 ) |
| 441 | 379 |
| 442 def __getitem__(self, key): | 380 def __getitem__(self, key): |
| 443 """Get an item from the cache. Moves the item up so that it has the | 381 """Get an item from the cache. Moves the item up so that it has the |
| 444 highest priority then. | 382 highest priority then. |
| 445 | 383 |
| 446 Raise an `KeyError` if it does not exist. | 384 Raise a `KeyError` if it does not exist. |
| 447 """ | 385 """ |
| 448 rv = self._mapping[key] | 386 self._wlock.acquire() |
| 449 if self._queue[-1] != key: | 387 try: |
| 450 try: | 388 rv = self._mapping[key] |
| 451 self._remove(key) | 389 if self._queue[-1] != key: |
| 452 except ValueError: | 390 try: |
| 453 # if something removed the key from the container | 391 self._remove(key) |
| 454 # when we read, ignore the ValueError that we would | 392 except ValueError: |
| 455 # get otherwise. | 393 # if something removed the key from the container |
| 456 pass | 394 # when we read, ignore the ValueError that we would |
| 457 self._append(key) | 395 # get otherwise. |
| 458 return rv | 396 pass |
| 397 self._append(key) |
| 398 return rv |
| 399 finally: |
| 400 self._wlock.release() |
| 459 | 401 |
| 460 def __setitem__(self, key, value): | 402 def __setitem__(self, key, value): |
| 461 """Sets the value for an item. Moves the item up so that it | 403 """Sets the value for an item. Moves the item up so that it |
| 462 has the highest priority then. | 404 has the highest priority then. |
| 463 """ | 405 """ |
| 464 self._wlock.acquire() | 406 self._wlock.acquire() |
| 465 try: | 407 try: |
| 466 if key in self._mapping: | 408 if key in self._mapping: |
| 467 try: | 409 self._remove(key) |
| 468 self._remove(key) | |
| 469 except ValueError: | |
| 470 # __getitem__ is not locked, it might happen | |
| 471 pass | |
| 472 elif len(self._mapping) == self.capacity: | 410 elif len(self._mapping) == self.capacity: |
| 473 del self._mapping[self._popleft()] | 411 del self._mapping[self._popleft()] |
| 474 self._append(key) | 412 self._append(key) |
| 475 self._mapping[key] = value | 413 self._mapping[key] = value |
| 476 finally: | 414 finally: |
| 477 self._wlock.release() | 415 self._wlock.release() |
| 478 | 416 |
| 479 def __delitem__(self, key): | 417 def __delitem__(self, key): |
| 480 """Remove an item from the cache dict. | 418 """Remove an item from the cache dict. |
| 481 Raise an `KeyError` if it does not exist. | 419 Raise a `KeyError` if it does not exist. |
| 482 """ | 420 """ |
| 483 self._wlock.acquire() | 421 self._wlock.acquire() |
| 484 try: | 422 try: |
| 485 del self._mapping[key] | 423 del self._mapping[key] |
| 486 try: | 424 try: |
| 487 self._remove(key) | 425 self._remove(key) |
| 488 except ValueError: | 426 except ValueError: |
| 489 # __getitem__ is not locked, it might happen | 427 # __getitem__ is not locked, it might happen |
| 490 pass | 428 pass |
| 491 finally: | 429 finally: |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 531 | 469 |
| 532 | 470 |
| 533 # register the LRU cache as mutable mapping if possible | 471 # register the LRU cache as mutable mapping if possible |
| 534 try: | 472 try: |
| 535 from collections import MutableMapping | 473 from collections import MutableMapping |
| 536 MutableMapping.register(LRUCache) | 474 MutableMapping.register(LRUCache) |
| 537 except ImportError: | 475 except ImportError: |
| 538 pass | 476 pass |
| 539 | 477 |
| 540 | 478 |
| 479 @implements_iterator |
| 541 class Cycler(object): | 480 class Cycler(object): |
| 542 """A cycle helper for templates.""" | 481 """A cycle helper for templates.""" |
| 543 | 482 |
| 544 def __init__(self, *items): | 483 def __init__(self, *items): |
| 545 if not items: | 484 if not items: |
| 546 raise RuntimeError('at least one item has to be provided') | 485 raise RuntimeError('at least one item has to be provided') |
| 547 self.items = items | 486 self.items = items |
| 548 self.reset() | 487 self.reset() |
| 549 | 488 |
| 550 def reset(self): | 489 def reset(self): |
| 551 """Resets the cycle.""" | 490 """Resets the cycle.""" |
| 552 self.pos = 0 | 491 self.pos = 0 |
| 553 | 492 |
| 554 @property | 493 @property |
| 555 def current(self): | 494 def current(self): |
| 556 """Returns the current item.""" | 495 """Returns the current item.""" |
| 557 return self.items[self.pos] | 496 return self.items[self.pos] |
| 558 | 497 |
| 559 def next(self): | 498 def __next__(self): |
| 560 """Goes one item ahead and returns it.""" | 499 """Goes one item ahead and returns it.""" |
| 561 rv = self.current | 500 rv = self.current |
| 562 self.pos = (self.pos + 1) % len(self.items) | 501 self.pos = (self.pos + 1) % len(self.items) |
| 563 return rv | 502 return rv |
| 564 | 503 |
| 565 | 504 |
| 566 class Joiner(object): | 505 class Joiner(object): |
| 567 """A joining helper for templates.""" | 506 """A joining helper for templates.""" |
| 568 | 507 |
| 569 def __init__(self, sep=u', '): | 508 def __init__(self, sep=u', '): |
| 570 self.sep = sep | 509 self.sep = sep |
| 571 self.used = False | 510 self.used = False |
| 572 | 511 |
| 573 def __call__(self): | 512 def __call__(self): |
| 574 if not self.used: | 513 if not self.used: |
| 575 self.used = True | 514 self.used = True |
| 576 return u'' | 515 return u'' |
| 577 return self.sep | 516 return self.sep |
| 578 | 517 |
| 579 | 518 |
| 580 # try markupsafe first, if that fails go with Jinja2's bundled version | 519 # Imported here because that's where it was in the past |
| 581 # of markupsafe. Markupsafe was previously Jinja2's implementation of | 520 from markupsafe import Markup, escape, soft_unicode |
| 582 # the Markup object but was moved into a separate package in a patchleve | |
| 583 # release | |
| 584 try: | |
| 585 from markupsafe import Markup, escape, soft_unicode | |
| 586 except ImportError: | |
| 587 from jinja2._markupsafe import Markup, escape, soft_unicode | |
| 588 | |
| 589 | |
| 590 # partials | |
| 591 try: | |
| 592 from functools import partial | |
| 593 except ImportError: | |
| 594 class partial(object): | |
| 595 def __init__(self, _func, *args, **kwargs): | |
| 596 self._func = _func | |
| 597 self._args = args | |
| 598 self._kwargs = kwargs | |
| 599 def __call__(self, *args, **kwargs): | |
| 600 kwargs.update(self._kwargs) | |
| 601 return self._func(*(self._args + args), **kwargs) | |
| OLD | NEW |