1# -*- coding: utf-8 -*- 2""" 3 jinja2.utils 4 ~~~~~~~~~~~~ 5 6 Utility functions. 7 8 :copyright: (c) 2010 by the Jinja Team. 9 :license: BSD, see LICENSE for more details. 10""" 11import re 12import errno 13from collections import deque 14from jinja2._compat import text_type, string_types, implements_iterator, \ 15 allocate_lock, url_quote 16 17 18_word_split_re = re.compile(r'(\s+)') 19_punctuation_re = re.compile( 20 '^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % ( 21 '|'.join(map(re.escape, ('(', '<', '<'))), 22 '|'.join(map(re.escape, ('.', ',', ')', '>', '\n', '>'))) 23 ) 24) 25_simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$') 26_striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)') 27_entity_re = re.compile(r'&([^;]+);') 28_letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' 29_digits = '0123456789' 30 31# special singleton representing missing values for the runtime 32missing = type('MissingType', (), {'__repr__': lambda x: 'missing'})() 33 34# internal code 35internal_code = set() 36 37concat = u''.join 38 39 40def contextfunction(f): 41 """This decorator can be used to mark a function or method context callable. 42 A context callable is passed the active :class:`Context` as first argument when 43 called from the template. This is useful if a function wants to get access 44 to the context or functions provided on the context object. For example 45 a function that returns a sorted list of template variables the current 46 template exports could look like this:: 47 48 @contextfunction 49 def get_exported_names(context): 50 return sorted(context.exported_vars) 51 """ 52 f.contextfunction = True 53 return f 54 55 56def evalcontextfunction(f): 57 """This decorator can be used to mark a function or method as an eval 58 context callable. This is similar to the :func:`contextfunction` 59 but instead of passing the context, an evaluation context object is 60 passed. For more information about the eval context, see 61 :ref:`eval-context`. 62 63 .. versionadded:: 2.4 64 """ 65 f.evalcontextfunction = True 66 return f 67 68 69def environmentfunction(f): 70 """This decorator can be used to mark a function or method as environment 71 callable. This decorator works exactly like the :func:`contextfunction` 72 decorator just that the first argument is the active :class:`Environment` 73 and not context. 74 """ 75 f.environmentfunction = True 76 return f 77 78 79def internalcode(f): 80 """Marks the function as internally used""" 81 internal_code.add(f.__code__) 82 return f 83 84 85def is_undefined(obj): 86 """Check if the object passed is undefined. This does nothing more than 87 performing an instance check against :class:`Undefined` but looks nicer. 88 This can be used for custom filters or tests that want to react to 89 undefined variables. For example a custom default filter can look like 90 this:: 91 92 def default(var, default=''): 93 if is_undefined(var): 94 return default 95 return var 96 """ 97 from jinja2.runtime import Undefined 98 return isinstance(obj, Undefined) 99 100 101def consume(iterable): 102 """Consumes an iterable without doing anything with it.""" 103 for event in iterable: 104 pass 105 106 107def clear_caches(): 108 """Jinja2 keeps internal caches for environments and lexers. These are 109 used so that Jinja2 doesn't have to recreate environments and lexers all 110 the time. Normally you don't have to care about that but if you are 111 messuring memory consumption you may want to clean the caches. 112 """ 113 from jinja2.environment import _spontaneous_environments 114 from jinja2.lexer import _lexer_cache 115 _spontaneous_environments.clear() 116 _lexer_cache.clear() 117 118 119def import_string(import_name, silent=False): 120 """Imports an object based on a string. This is useful if you want to 121 use import paths as endpoints or something similar. An import path can 122 be specified either in dotted notation (``xml.sax.saxutils.escape``) 123 or with a colon as object delimiter (``xml.sax.saxutils:escape``). 124 125 If the `silent` is True the return value will be `None` if the import 126 fails. 127 128 :return: imported object 129 """ 130 try: 131 if ':' in import_name: 132 module, obj = import_name.split(':', 1) 133 elif '.' in import_name: 134 items = import_name.split('.') 135 module = '.'.join(items[:-1]) 136 obj = items[-1] 137 else: 138 return __import__(import_name) 139 return getattr(__import__(module, None, None, [obj]), obj) 140 except (ImportError, AttributeError): 141 if not silent: 142 raise 143 144 145def open_if_exists(filename, mode='rb'): 146 """Returns a file descriptor for the filename if that file exists, 147 otherwise `None`. 148 """ 149 try: 150 return open(filename, mode) 151 except IOError as e: 152 if e.errno not in (errno.ENOENT, errno.EISDIR): 153 raise 154 155 156def object_type_repr(obj): 157 """Returns the name of the object's type. For some recognized 158 singletons the name of the object is returned instead. (For 159 example for `None` and `Ellipsis`). 160 """ 161 if obj is None: 162 return 'None' 163 elif obj is Ellipsis: 164 return 'Ellipsis' 165 # __builtin__ in 2.x, builtins in 3.x 166 if obj.__class__.__module__ in ('__builtin__', 'builtins'): 167 name = obj.__class__.__name__ 168 else: 169 name = obj.__class__.__module__ + '.' + obj.__class__.__name__ 170 return '%s object' % name 171 172 173def pformat(obj, verbose=False): 174 """Prettyprint an object. Either use the `pretty` library or the 175 builtin `pprint`. 176 """ 177 try: 178 from pretty import pretty 179 return pretty(obj, verbose=verbose) 180 except ImportError: 181 from pprint import pformat 182 return pformat(obj) 183 184 185def urlize(text, trim_url_limit=None, nofollow=False): 186 """Converts any URLs in text into clickable links. Works on http://, 187 https:// and www. links. Links can have trailing punctuation (periods, 188 commas, close-parens) and leading punctuation (opening parens) and 189 it'll still do the right thing. 190 191 If trim_url_limit is not None, the URLs in link text will be limited 192 to trim_url_limit characters. 193 194 If nofollow is True, the URLs in link text will get a rel="nofollow" 195 attribute. 196 """ 197 trim_url = lambda x, limit=trim_url_limit: limit is not None \ 198 and (x[:limit] + (len(x) >=limit and '...' 199 or '')) or x 200 words = _word_split_re.split(text_type(escape(text))) 201 nofollow_attr = nofollow and ' rel="nofollow"' or '' 202 for i, word in enumerate(words): 203 match = _punctuation_re.match(word) 204 if match: 205 lead, middle, trail = match.groups() 206 if middle.startswith('www.') or ( 207 '@' not in middle and 208 not middle.startswith('http://') and 209 not middle.startswith('https://') and 210 len(middle) > 0 and 211 middle[0] in _letters + _digits and ( 212 middle.endswith('.org') or 213 middle.endswith('.net') or 214 middle.endswith('.com') 215 )): 216 middle = '<a href="http://%s"%s>%s</a>' % (middle, 217 nofollow_attr, trim_url(middle)) 218 if middle.startswith('http://') or \ 219 middle.startswith('https://'): 220 middle = '<a href="%s"%s>%s</a>' % (middle, 221 nofollow_attr, trim_url(middle)) 222 if '@' in middle and not middle.startswith('www.') and \ 223 not ':' in middle and _simple_email_re.match(middle): 224 middle = '<a href="mailto:%s">%s</a>' % (middle, middle) 225 if lead + middle + trail != word: 226 words[i] = lead + middle + trail 227 return u''.join(words) 228 229 230def generate_lorem_ipsum(n=5, html=True, min=20, max=100): 231 """Generate some lorem impsum for the template.""" 232 from jinja2.constants import LOREM_IPSUM_WORDS 233 from random import choice, randrange 234 words = LOREM_IPSUM_WORDS.split() 235 result = [] 236 237 for _ in range(n): 238 next_capitalized = True 239 last_comma = last_fullstop = 0 240 word = None 241 last = None 242 p = [] 243 244 # each paragraph contains out of 20 to 100 words. 245 for idx, _ in enumerate(range(randrange(min, max))): 246 while True: 247 word = choice(words) 248 if word != last: 249 last = word 250 break 251 if next_capitalized: 252 word = word.capitalize() 253 next_capitalized = False 254 # add commas 255 if idx - randrange(3, 8) > last_comma: 256 last_comma = idx 257 last_fullstop += 2 258 word += ',' 259 # add end of sentences 260 if idx - randrange(10, 20) > last_fullstop: 261 last_comma = last_fullstop = idx 262 word += '.' 263 next_capitalized = True 264 p.append(word) 265 266 # ensure that the paragraph ends with a dot. 267 p = u' '.join(p) 268 if p.endswith(','): 269 p = p[:-1] + '.' 270 elif not p.endswith('.'): 271 p += '.' 272 result.append(p) 273 274 if not html: 275 return u'\n\n'.join(result) 276 return Markup(u'\n'.join(u'<p>%s</p>' % escape(x) for x in result)) 277 278 279def 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 294class LRUCache(object): 295 """A simple LRU Cache implementation.""" 296 297 # this is fast for small capacities (something below 1000) but doesn't 298 # scale. But as long as it's only used as storage for templates this 299 # won't do any harm. 300 301 def __init__(self, capacity): 302 self.capacity = capacity 303 self._mapping = {} 304 self._queue = deque() 305 self._postinit() 306 307 def _postinit(self): 308 # alias all queue methods for faster lookup 309 self._popleft = self._queue.popleft 310 self._pop = self._queue.pop 311 self._remove = self._queue.remove 312 self._wlock = allocate_lock() 313 self._append = self._queue.append 314 315 def __getstate__(self): 316 return { 317 'capacity': self.capacity, 318 '_mapping': self._mapping, 319 '_queue': self._queue 320 } 321 322 def __setstate__(self, d): 323 self.__dict__.update(d) 324 self._postinit() 325 326 def __getnewargs__(self): 327 return (self.capacity,) 328 329 def copy(self): 330 """Return a shallow copy of the instance.""" 331 rv = self.__class__(self.capacity) 332 rv._mapping.update(self._mapping) 333 rv._queue = deque(self._queue) 334 return rv 335 336 def get(self, key, default=None): 337 """Return an item from the cache dict or `default`""" 338 try: 339 return self[key] 340 except KeyError: 341 return default 342 343 def setdefault(self, key, default=None): 344 """Set `default` if the key is not in the cache otherwise 345 leave unchanged. Return the value of this key. 346 """ 347 self._wlock.acquire() 348 try: 349 try: 350 return self[key] 351 except KeyError: 352 self[key] = default 353 return default 354 finally: 355 self._wlock.release() 356 357 def clear(self): 358 """Clear the cache.""" 359 self._wlock.acquire() 360 try: 361 self._mapping.clear() 362 self._queue.clear() 363 finally: 364 self._wlock.release() 365 366 def __contains__(self, key): 367 """Check if a key exists in this cache.""" 368 return key in self._mapping 369 370 def __len__(self): 371 """Return the current size of the cache.""" 372 return len(self._mapping) 373 374 def __repr__(self): 375 return '<%s %r>' % ( 376 self.__class__.__name__, 377 self._mapping 378 ) 379 380 def __getitem__(self, key): 381 """Get an item from the cache. Moves the item up so that it has the 382 highest priority then. 383 384 Raise a `KeyError` if it does not exist. 385 """ 386 self._wlock.acquire() 387 try: 388 rv = self._mapping[key] 389 if self._queue[-1] != key: 390 try: 391 self._remove(key) 392 except ValueError: 393 # if something removed the key from the container 394 # when we read, ignore the ValueError that we would 395 # get otherwise. 396 pass 397 self._append(key) 398 return rv 399 finally: 400 self._wlock.release() 401 402 def __setitem__(self, key, value): 403 """Sets the value for an item. Moves the item up so that it 404 has the highest priority then. 405 """ 406 self._wlock.acquire() 407 try: 408 if key in self._mapping: 409 self._remove(key) 410 elif len(self._mapping) == self.capacity: 411 del self._mapping[self._popleft()] 412 self._append(key) 413 self._mapping[key] = value 414 finally: 415 self._wlock.release() 416 417 def __delitem__(self, key): 418 """Remove an item from the cache dict. 419 Raise a `KeyError` if it does not exist. 420 """ 421 self._wlock.acquire() 422 try: 423 del self._mapping[key] 424 try: 425 self._remove(key) 426 except ValueError: 427 # __getitem__ is not locked, it might happen 428 pass 429 finally: 430 self._wlock.release() 431 432 def items(self): 433 """Return a list of items.""" 434 result = [(key, self._mapping[key]) for key in list(self._queue)] 435 result.reverse() 436 return result 437 438 def iteritems(self): 439 """Iterate over all items.""" 440 return iter(self.items()) 441 442 def values(self): 443 """Return a list of all values.""" 444 return [x[1] for x in self.items()] 445 446 def itervalue(self): 447 """Iterate over all values.""" 448 return iter(self.values()) 449 450 def keys(self): 451 """Return a list of all keys ordered by most recent usage.""" 452 return list(self) 453 454 def iterkeys(self): 455 """Iterate over all keys in the cache dict, ordered by 456 the most recent usage. 457 """ 458 return reversed(tuple(self._queue)) 459 460 __iter__ = iterkeys 461 462 def __reversed__(self): 463 """Iterate over the values in the cache dict, oldest items 464 coming first. 465 """ 466 return iter(tuple(self._queue)) 467 468 __copy__ = copy 469 470 471# register the LRU cache as mutable mapping if possible 472try: 473 from collections import MutableMapping 474 MutableMapping.register(LRUCache) 475except ImportError: 476 pass 477 478 479@implements_iterator 480class Cycler(object): 481 """A cycle helper for templates.""" 482 483 def __init__(self, *items): 484 if not items: 485 raise RuntimeError('at least one item has to be provided') 486 self.items = items 487 self.reset() 488 489 def reset(self): 490 """Resets the cycle.""" 491 self.pos = 0 492 493 @property 494 def current(self): 495 """Returns the current item.""" 496 return self.items[self.pos] 497 498 def __next__(self): 499 """Goes one item ahead and returns it.""" 500 rv = self.current 501 self.pos = (self.pos + 1) % len(self.items) 502 return rv 503 504 505class Joiner(object): 506 """A joining helper for templates.""" 507 508 def __init__(self, sep=u', '): 509 self.sep = sep 510 self.used = False 511 512 def __call__(self): 513 if not self.used: 514 self.used = True 515 return u'' 516 return self.sep 517 518 519# Imported here because that's where it was in the past 520from markupsafe import Markup, escape, soft_unicode 521