output documentation to file on request
[libfirm] / scripts / jinja2 / utils.py
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 """
11 import re
12 import sys
13 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
19 from itertools import imap
20
21
22 _word_split_re = re.compile(r'(\s+)')
23 _punctuation_re = re.compile(
24     '^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % (
25         '|'.join(imap(re.escape, ('(', '<', '&lt;'))),
26         '|'.join(imap(re.escape, ('.', ',', ')', '>', '\n', '&gt;')))
27     )
28 )
29 _simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$')
30 _striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)')
31 _entity_re = re.compile(r'&([^;]+);')
32 _letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
33 _digits = '0123456789'
34
35 # special singleton representing missing values for the runtime
36 missing = type('MissingType', (), {'__repr__': lambda x: 'missing'})()
37
38 # internal code
39 internal_code = set()
40
41
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
113
114 def contextfunction(f):
115     """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 when
117     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
119     a function that returns a sorted list of template variables the current
120     template exports could look like this::
121
122         @contextfunction
123         def get_exported_names(context):
124             return sorted(context.exported_vars)
125     """
126     f.contextfunction = True
127     return f
128
129
130 def evalcontextfunction(f):
131     """This decoraotr can be used to mark a function or method as an eval
132     context callable.  This is similar to the :func:`contextfunction`
133     but instead of passing the context, an evaluation context object is
134     passed.  For more information about the eval context, see
135     :ref:`eval-context`.
136
137     .. versionadded:: 2.4
138     """
139     f.evalcontextfunction = True
140     return f
141
142
143 def environmentfunction(f):
144     """This decorator can be used to mark a function or method as environment
145     callable.  This decorator works exactly like the :func:`contextfunction`
146     decorator just that the first argument is the active :class:`Environment`
147     and not context.
148     """
149     f.environmentfunction = True
150     return f
151
152
153 def internalcode(f):
154     """Marks the function as internally used"""
155     internal_code.add(f.func_code)
156     return f
157
158
159 def is_undefined(obj):
160     """Check if the object passed is undefined.  This does nothing more than
161     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
163     undefined variables.  For example a custom default filter can look like
164     this::
165
166         def default(var, default=''):
167             if is_undefined(var):
168                 return default
169             return var
170     """
171     from jinja2.runtime import Undefined
172     return isinstance(obj, Undefined)
173
174
175 def consume(iterable):
176     """Consumes an iterable without doing anything with it."""
177     for event in iterable:
178         pass
179
180
181 def clear_caches():
182     """Jinja2 keeps internal caches for environments and lexers.  These are
183     used so that Jinja2 doesn't have to recreate environments and lexers all
184     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.
186     """
187     from jinja2.environment import _spontaneous_environments
188     from jinja2.lexer import _lexer_cache
189     _spontaneous_environments.clear()
190     _lexer_cache.clear()
191
192
193 def import_string(import_name, silent=False):
194     """Imports an object based on a string.  This use useful if you want to
195     use import paths as endpoints or something similar.  An import path can
196     be specified either in dotted notation (``xml.sax.saxutils.escape``)
197     or with a colon as object delimiter (``xml.sax.saxutils:escape``).
198
199     If the `silent` is True the return value will be `None` if the import
200     fails.
201
202     :return: imported object
203     """
204     try:
205         if ':' in import_name:
206             module, obj = import_name.split(':', 1)
207         elif '.' in import_name:
208             items = import_name.split('.')
209             module = '.'.join(items[:-1])
210             obj = items[-1]
211         else:
212             return __import__(import_name)
213         return getattr(__import__(module, None, None, [obj]), obj)
214     except (ImportError, AttributeError):
215         if not silent:
216             raise
217
218
219 def open_if_exists(filename, mode='rb'):
220     """Returns a file descriptor for the filename if that file exists,
221     otherwise `None`.
222     """
223     try:
224         return open(filename, mode)
225     except IOError, e:
226         if e.errno not in (errno.ENOENT, errno.EISDIR):
227             raise
228
229
230 def object_type_repr(obj):
231     """Returns the name of the object's type.  For some recognized
232     singletons the name of the object is returned instead. (For
233     example for `None` and `Ellipsis`).
234     """
235     if obj is None:
236         return 'None'
237     elif obj is Ellipsis:
238         return 'Ellipsis'
239     # __builtin__ in 2.x, builtins in 3.x
240     if obj.__class__.__module__ in ('__builtin__', 'builtins'):
241         name = obj.__class__.__name__
242     else:
243         name = obj.__class__.__module__ + '.' + obj.__class__.__name__
244     return '%s object' % name
245
246
247 def pformat(obj, verbose=False):
248     """Prettyprint an object.  Either use the `pretty` library or the
249     builtin `pprint`.
250     """
251     try:
252         from pretty import pretty
253         return pretty(obj, verbose=verbose)
254     except ImportError:
255         from pprint import pformat
256         return pformat(obj)
257
258
259 def urlize(text, trim_url_limit=None, nofollow=False):
260     """Converts any URLs in text into clickable links. Works on http://,
261     https:// and www. links. Links can have trailing punctuation (periods,
262     commas, close-parens) and leading punctuation (opening parens) and
263     it'll still do the right thing.
264
265     If trim_url_limit is not None, the URLs in link text will be limited
266     to trim_url_limit characters.
267
268     If nofollow is True, the URLs in link text will get a rel="nofollow"
269     attribute.
270     """
271     trim_url = lambda x, limit=trim_url_limit: limit is not None \
272                          and (x[:limit] + (len(x) >=limit and '...'
273                          or '')) or x
274     words = _word_split_re.split(unicode(escape(text)))
275     nofollow_attr = nofollow and ' rel="nofollow"' or ''
276     for i, word in enumerate(words):
277         match = _punctuation_re.match(word)
278         if match:
279             lead, middle, trail = match.groups()
280             if middle.startswith('www.') or (
281                 '@' not in middle and
282                 not middle.startswith('http://') and
283                 len(middle) > 0 and
284                 middle[0] in _letters + _digits and (
285                     middle.endswith('.org') or
286                     middle.endswith('.net') or
287                     middle.endswith('.com')
288                 )):
289                 middle = '<a href="http://%s"%s>%s</a>' % (middle,
290                     nofollow_attr, trim_url(middle))
291             if middle.startswith('http://') or \
292                middle.startswith('https://'):
293                 middle = '<a href="%s"%s>%s</a>' % (middle,
294                     nofollow_attr, trim_url(middle))
295             if '@' in middle and not middle.startswith('www.') and \
296                not ':' in middle and _simple_email_re.match(middle):
297                 middle = '<a href="mailto:%s">%s</a>' % (middle, middle)
298             if lead + middle + trail != word:
299                 words[i] = lead + middle + trail
300     return u''.join(words)
301
302
303 def generate_lorem_ipsum(n=5, html=True, min=20, max=100):
304     """Generate some lorem impsum for the template."""
305     from jinja2.constants import LOREM_IPSUM_WORDS
306     from random import choice, randrange
307     words = LOREM_IPSUM_WORDS.split()
308     result = []
309
310     for _ in xrange(n):
311         next_capitalized = True
312         last_comma = last_fullstop = 0
313         word = None
314         last = None
315         p = []
316
317         # each paragraph contains out of 20 to 100 words.
318         for idx, _ in enumerate(xrange(randrange(min, max))):
319             while True:
320                 word = choice(words)
321                 if word != last:
322                     last = word
323                     break
324             if next_capitalized:
325                 word = word.capitalize()
326                 next_capitalized = False
327             # add commas
328             if idx - randrange(3, 8) > last_comma:
329                 last_comma = idx
330                 last_fullstop += 2
331                 word += ','
332             # add end of sentences
333             if idx - randrange(10, 20) > last_fullstop:
334                 last_comma = last_fullstop = idx
335                 word += '.'
336                 next_capitalized = True
337             p.append(word)
338
339         # ensure that the paragraph ends with a dot.
340         p = u' '.join(p)
341         if p.endswith(','):
342             p = p[:-1] + '.'
343         elif not p.endswith('.'):
344             p += '.'
345         result.append(p)
346
347     if not html:
348         return u'\n\n'.join(result)
349     return Markup(u'\n'.join(u'<p>%s</p>' % escape(x) for x in result))
350
351
352 class LRUCache(object):
353     """A simple LRU Cache implementation."""
354
355     # 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
357     # won't do any harm.
358
359     def __init__(self, capacity):
360         self.capacity = capacity
361         self._mapping = {}
362         self._queue = deque()
363         self._postinit()
364
365     def _postinit(self):
366         # alias all queue methods for faster lookup
367         self._popleft = self._queue.popleft
368         self._pop = self._queue.pop
369         if hasattr(self._queue, 'remove'):
370             self._remove = self._queue.remove
371         self._wlock = allocate_lock()
372         self._append = self._queue.append
373
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):
382         return {
383             'capacity':     self.capacity,
384             '_mapping':     self._mapping,
385             '_queue':       self._queue
386         }
387
388     def __setstate__(self, d):
389         self.__dict__.update(d)
390         self._postinit()
391
392     def __getnewargs__(self):
393         return (self.capacity,)
394
395     def copy(self):
396         """Return an shallow copy of the instance."""
397         rv = self.__class__(self.capacity)
398         rv._mapping.update(self._mapping)
399         rv._queue = deque(self._queue)
400         return rv
401
402     def get(self, key, default=None):
403         """Return an item from the cache dict or `default`"""
404         try:
405             return self[key]
406         except KeyError:
407             return default
408
409     def setdefault(self, key, default=None):
410         """Set `default` if the key is not in the cache otherwise
411         leave unchanged. Return the value of this key.
412         """
413         try:
414             return self[key]
415         except KeyError:
416             self[key] = default
417             return default
418
419     def clear(self):
420         """Clear the cache."""
421         self._wlock.acquire()
422         try:
423             self._mapping.clear()
424             self._queue.clear()
425         finally:
426             self._wlock.release()
427
428     def __contains__(self, key):
429         """Check if a key exists in this cache."""
430         return key in self._mapping
431
432     def __len__(self):
433         """Return the current size of the cache."""
434         return len(self._mapping)
435
436     def __repr__(self):
437         return '<%s %r>' % (
438             self.__class__.__name__,
439             self._mapping
440         )
441
442     def __getitem__(self, key):
443         """Get an item from the cache. Moves the item up so that it has the
444         highest priority then.
445
446         Raise an `KeyError` if it does not exist.
447         """
448         rv = self._mapping[key]
449         if self._queue[-1] != key:
450             try:
451                 self._remove(key)
452             except ValueError:
453                 # if something removed the key from the container
454                 # when we read, ignore the ValueError that we would
455                 # get otherwise.
456                 pass
457             self._append(key)
458         return rv
459
460     def __setitem__(self, key, value):
461         """Sets the value for an item. Moves the item up so that it
462         has the highest priority then.
463         """
464         self._wlock.acquire()
465         try:
466             if key in self._mapping:
467                 try:
468                     self._remove(key)
469                 except ValueError:
470                     # __getitem__ is not locked, it might happen
471                     pass
472             elif len(self._mapping) == self.capacity:
473                 del self._mapping[self._popleft()]
474             self._append(key)
475             self._mapping[key] = value
476         finally:
477             self._wlock.release()
478
479     def __delitem__(self, key):
480         """Remove an item from the cache dict.
481         Raise an `KeyError` if it does not exist.
482         """
483         self._wlock.acquire()
484         try:
485             del self._mapping[key]
486             try:
487                 self._remove(key)
488             except ValueError:
489                 # __getitem__ is not locked, it might happen
490                 pass
491         finally:
492             self._wlock.release()
493
494     def items(self):
495         """Return a list of items."""
496         result = [(key, self._mapping[key]) for key in list(self._queue)]
497         result.reverse()
498         return result
499
500     def iteritems(self):
501         """Iterate over all items."""
502         return iter(self.items())
503
504     def values(self):
505         """Return a list of all values."""
506         return [x[1] for x in self.items()]
507
508     def itervalue(self):
509         """Iterate over all values."""
510         return iter(self.values())
511
512     def keys(self):
513         """Return a list of all keys ordered by most recent usage."""
514         return list(self)
515
516     def iterkeys(self):
517         """Iterate over all keys in the cache dict, ordered by
518         the most recent usage.
519         """
520         return reversed(tuple(self._queue))
521
522     __iter__ = iterkeys
523
524     def __reversed__(self):
525         """Iterate over the values in the cache dict, oldest items
526         coming first.
527         """
528         return iter(tuple(self._queue))
529
530     __copy__ = copy
531
532
533 # register the LRU cache as mutable mapping if possible
534 try:
535     from collections import MutableMapping
536     MutableMapping.register(LRUCache)
537 except ImportError:
538     pass
539
540
541 class Cycler(object):
542     """A cycle helper for templates."""
543
544     def __init__(self, *items):
545         if not items:
546             raise RuntimeError('at least one item has to be provided')
547         self.items = items
548         self.reset()
549
550     def reset(self):
551         """Resets the cycle."""
552         self.pos = 0
553
554     @property
555     def current(self):
556         """Returns the current item."""
557         return self.items[self.pos]
558
559     def next(self):
560         """Goes one item ahead and returns it."""
561         rv = self.current
562         self.pos = (self.pos + 1) % len(self.items)
563         return rv
564
565
566 class Joiner(object):
567     """A joining helper for templates."""
568
569     def __init__(self, sep=u', '):
570         self.sep = sep
571         self.used = False
572
573     def __call__(self):
574         if not self.used:
575             self.used = True
576             return u''
577         return self.sep
578
579
580 # try markupsafe first, if that fails go with Jinja2's bundled version
581 # of markupsafe.  Markupsafe was previously Jinja2's implementation of
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)