- Introduce the concept of the graph state bitset: This is a set of flags that indica...
[libfirm] / scripts / jinja2 / utils.py
1 # -*- coding: utf-8 -*-
2 """
3     jinja2.utils
4     ~~~~~~~~~~~~
5
6     Utility functions.
7
8     :copyright: 2008 by Armin Ronacher.
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
39 # concatenate a list of strings and convert them to unicode.
40 # unfortunately there is a bug in python 2.4 and lower that causes
41 # unicode.join trash the traceback.
42 _concat = u''.join
43 try:
44     def _test_gen_bug():
45         raise TypeError(_test_gen_bug)
46         yield None
47     _concat(_test_gen_bug())
48 except TypeError, _error:
49     if not _error.args or _error.args[0] is not _test_gen_bug:
50         def concat(gen):
51             try:
52                 return _concat(list(gen))
53             except:
54                 # this hack is needed so that the current frame
55                 # does not show up in the traceback.
56                 exc_type, exc_value, tb = sys.exc_info()
57                 raise exc_type, exc_value, tb.tb_next
58     else:
59         concat = _concat
60     del _test_gen_bug, _error
61
62
63 # ironpython without stdlib doesn't have keyword
64 try:
65     from keyword import iskeyword as is_python_keyword
66 except ImportError:
67     _py_identifier_re = re.compile(r'^[a-zA-Z_][a-zA-Z0-9]*$')
68     def is_python_keyword(name):
69         if _py_identifier_re.search(name) is None:
70             return False
71         try:
72             exec name + " = 42"
73         except SyntaxError:
74             return False
75         return True
76
77
78 # common types.  These do exist in the special types module too which however
79 # does not exist in IronPython out of the box.
80 class _C(object):
81     def method(self): pass
82 def _func():
83     yield None
84 FunctionType = type(_func)
85 GeneratorType = type(_func())
86 MethodType = type(_C.method)
87 CodeType = type(_C.method.func_code)
88 try:
89     raise TypeError()
90 except TypeError:
91     _tb = sys.exc_info()[2]
92     TracebackType = type(_tb)
93     FrameType = type(_tb.tb_frame)
94 del _C, _tb, _func
95
96
97 def contextfunction(f):
98     """This decorator can be used to mark a function or method context callable.
99     A context callable is passed the active :class:`Context` as first argument when
100     called from the template.  This is useful if a function wants to get access
101     to the context or functions provided on the context object.  For example
102     a function that returns a sorted list of template variables the current
103     template exports could look like this::
104
105         @contextfunction
106         def get_exported_names(context):
107             return sorted(context.exported_vars)
108     """
109     f.contextfunction = True
110     return f
111
112
113 def environmentfunction(f):
114     """This decorator can be used to mark a function or method as environment
115     callable.  This decorator works exactly like the :func:`contextfunction`
116     decorator just that the first argument is the active :class:`Environment`
117     and not context.
118     """
119     f.environmentfunction = True
120     return f
121
122
123 def is_undefined(obj):
124     """Check if the object passed is undefined.  This does nothing more than
125     performing an instance check against :class:`Undefined` but looks nicer.
126     This can be used for custom filters or tests that want to react to
127     undefined variables.  For example a custom default filter can look like
128     this::
129
130         def default(var, default=''):
131             if is_undefined(var):
132                 return default
133             return var
134     """
135     from jinja2.runtime import Undefined
136     return isinstance(obj, Undefined)
137
138
139 def consume(iterable):
140     """Consumes an iterable without doing anything with it."""
141     for event in iterable:
142         pass
143
144
145 def clear_caches():
146     """Jinja2 keeps internal caches for environments and lexers.  These are
147     used so that Jinja2 doesn't have to recreate environments and lexers all
148     the time.  Normally you don't have to care about that but if you are
149     messuring memory consumption you may want to clean the caches.
150     """
151     from jinja2.environment import _spontaneous_environments
152     from jinja2.lexer import _lexer_cache
153     _spontaneous_environments.clear()
154     _lexer_cache.clear()
155
156
157 def import_string(import_name, silent=False):
158     """Imports an object based on a string.  This use useful if you want to
159     use import paths as endpoints or something similar.  An import path can
160     be specified either in dotted notation (``xml.sax.saxutils.escape``)
161     or with a colon as object delimiter (``xml.sax.saxutils:escape``).
162
163     If the `silent` is True the return value will be `None` if the import
164     fails.
165
166     :return: imported object
167     """
168     try:
169         if ':' in import_name:
170             module, obj = import_name.split(':', 1)
171         elif '.' in import_name:
172             items = import_name.split('.')
173             module = '.'.join(items[:-1])
174             obj = items[-1]
175         else:
176             return __import__(import_name)
177         return getattr(__import__(module, None, None, [obj]), obj)
178     except (ImportError, AttributeError):
179         if not silent:
180             raise
181
182
183 def open_if_exists(filename, mode='r'):
184     """Returns a file descriptor for the filename if that file exists,
185     otherwise `None`.
186     """
187     try:
188         return file(filename, mode)
189     except IOError, e:
190         if e.errno not in (errno.ENOENT, errno.EISDIR):
191             raise
192
193
194 def pformat(obj, verbose=False):
195     """Prettyprint an object.  Either use the `pretty` library or the
196     builtin `pprint`.
197     """
198     try:
199         from pretty import pretty
200         return pretty(obj, verbose=verbose)
201     except ImportError:
202         from pprint import pformat
203         return pformat(obj)
204
205
206 def urlize(text, trim_url_limit=None, nofollow=False):
207     """Converts any URLs in text into clickable links. Works on http://,
208     https:// and www. links. Links can have trailing punctuation (periods,
209     commas, close-parens) and leading punctuation (opening parens) and
210     it'll still do the right thing.
211
212     If trim_url_limit is not None, the URLs in link text will be limited
213     to trim_url_limit characters.
214
215     If nofollow is True, the URLs in link text will get a rel="nofollow"
216     attribute.
217     """
218     trim_url = lambda x, limit=trim_url_limit: limit is not None \
219                          and (x[:limit] + (len(x) >=limit and '...'
220                          or '')) or x
221     words = _word_split_re.split(unicode(escape(text)))
222     nofollow_attr = nofollow and ' rel="nofollow"' or ''
223     for i, word in enumerate(words):
224         match = _punctuation_re.match(word)
225         if match:
226             lead, middle, trail = match.groups()
227             if middle.startswith('www.') or (
228                 '@' not in middle and
229                 not middle.startswith('http://') and
230                 len(middle) > 0 and
231                 middle[0] in _letters + _digits and (
232                     middle.endswith('.org') or
233                     middle.endswith('.net') or
234                     middle.endswith('.com')
235                 )):
236                 middle = '<a href="http://%s"%s>%s</a>' % (middle,
237                     nofollow_attr, trim_url(middle))
238             if middle.startswith('http://') or \
239                middle.startswith('https://'):
240                 middle = '<a href="%s"%s>%s</a>' % (middle,
241                     nofollow_attr, trim_url(middle))
242             if '@' in middle and not middle.startswith('www.') and \
243                not ':' in middle and _simple_email_re.match(middle):
244                 middle = '<a href="mailto:%s">%s</a>' % (middle, middle)
245             if lead + middle + trail != word:
246                 words[i] = lead + middle + trail
247     return u''.join(words)
248
249
250 def generate_lorem_ipsum(n=5, html=True, min=20, max=100):
251     """Generate some lorem impsum for the template."""
252     from jinja2.constants import LOREM_IPSUM_WORDS
253     from random import choice, random, randrange
254     words = LOREM_IPSUM_WORDS.split()
255     result = []
256
257     for _ in xrange(n):
258         next_capitalized = True
259         last_comma = last_fullstop = 0
260         word = None
261         last = None
262         p = []
263
264         # each paragraph contains out of 20 to 100 words.
265         for idx, _ in enumerate(xrange(randrange(min, max))):
266             while True:
267                 word = choice(words)
268                 if word != last:
269                     last = word
270                     break
271             if next_capitalized:
272                 word = word.capitalize()
273                 next_capitalized = False
274             # add commas
275             if idx - randrange(3, 8) > last_comma:
276                 last_comma = idx
277                 last_fullstop += 2
278                 word += ','
279             # add end of sentences
280             if idx - randrange(10, 20) > last_fullstop:
281                 last_comma = last_fullstop = idx
282                 word += '.'
283                 next_capitalized = True
284             p.append(word)
285
286         # ensure that the paragraph ends with a dot.
287         p = u' '.join(p)
288         if p.endswith(','):
289             p = p[:-1] + '.'
290         elif not p.endswith('.'):
291             p += '.'
292         result.append(p)
293
294     if not html:
295         return u'\n\n'.join(result)
296     return Markup(u'\n'.join(u'<p>%s</p>' % escape(x) for x in result))
297
298
299 class Markup(unicode):
300     r"""Marks a string as being safe for inclusion in HTML/XML output without
301     needing to be escaped.  This implements the `__html__` interface a couple
302     of frameworks and web applications use.  :class:`Markup` is a direct
303     subclass of `unicode` and provides all the methods of `unicode` just that
304     it escapes arguments passed and always returns `Markup`.
305
306     The `escape` function returns markup objects so that double escaping can't
307     happen.  If you want to use autoescaping in Jinja just enable the
308     autoescaping feature in the environment.
309
310     The constructor of the :class:`Markup` class can be used for three
311     different things:  When passed an unicode object it's assumed to be safe,
312     when passed an object with an HTML representation (has an `__html__`
313     method) that representation is used, otherwise the object passed is
314     converted into a unicode string and then assumed to be safe:
315
316     >>> Markup("Hello <em>World</em>!")
317     Markup(u'Hello <em>World</em>!')
318     >>> class Foo(object):
319     ...  def __html__(self):
320     ...   return '<a href="#">foo</a>'
321     ...
322     >>> Markup(Foo())
323     Markup(u'<a href="#">foo</a>')
324
325     If you want object passed being always treated as unsafe you can use the
326     :meth:`escape` classmethod to create a :class:`Markup` object:
327
328     >>> Markup.escape("Hello <em>World</em>!")
329     Markup(u'Hello &lt;em&gt;World&lt;/em&gt;!')
330
331     Operations on a markup string are markup aware which means that all
332     arguments are passed through the :func:`escape` function:
333
334     >>> em = Markup("<em>%s</em>")
335     >>> em % "foo & bar"
336     Markup(u'<em>foo &amp; bar</em>')
337     >>> strong = Markup("<strong>%(text)s</strong>")
338     >>> strong % {'text': '<blink>hacker here</blink>'}
339     Markup(u'<strong>&lt;blink&gt;hacker here&lt;/blink&gt;</strong>')
340     >>> Markup("<em>Hello</em> ") + "<foo>"
341     Markup(u'<em>Hello</em> &lt;foo&gt;')
342     """
343     __slots__ = ()
344
345     def __new__(cls, base=u'', encoding=None, errors='strict'):
346         if hasattr(base, '__html__'):
347             base = base.__html__()
348         if encoding is None:
349             return unicode.__new__(cls, base)
350         return unicode.__new__(cls, base, encoding, errors)
351
352     def __html__(self):
353         return self
354
355     def __add__(self, other):
356         if hasattr(other, '__html__') or isinstance(other, basestring):
357             return self.__class__(unicode(self) + unicode(escape(other)))
358         return NotImplemented
359
360     def __radd__(self, other):
361         if hasattr(other, '__html__') or isinstance(other, basestring):
362             return self.__class__(unicode(escape(other)) + unicode(self))
363         return NotImplemented
364
365     def __mul__(self, num):
366         if isinstance(num, (int, long)):
367             return self.__class__(unicode.__mul__(self, num))
368         return NotImplemented
369     __rmul__ = __mul__
370
371     def __mod__(self, arg):
372         if isinstance(arg, tuple):
373             arg = tuple(imap(_MarkupEscapeHelper, arg))
374         else:
375             arg = _MarkupEscapeHelper(arg)
376         return self.__class__(unicode.__mod__(self, arg))
377
378     def __repr__(self):
379         return '%s(%s)' % (
380             self.__class__.__name__,
381             unicode.__repr__(self)
382         )
383
384     def join(self, seq):
385         return self.__class__(unicode.join(self, imap(escape, seq)))
386     join.__doc__ = unicode.join.__doc__
387
388     def split(self, *args, **kwargs):
389         return map(self.__class__, unicode.split(self, *args, **kwargs))
390     split.__doc__ = unicode.split.__doc__
391
392     def rsplit(self, *args, **kwargs):
393         return map(self.__class__, unicode.rsplit(self, *args, **kwargs))
394     rsplit.__doc__ = unicode.rsplit.__doc__
395
396     def splitlines(self, *args, **kwargs):
397         return map(self.__class__, unicode.splitlines(self, *args, **kwargs))
398     splitlines.__doc__ = unicode.splitlines.__doc__
399
400     def unescape(self):
401         r"""Unescape markup again into an unicode string.  This also resolves
402         known HTML4 and XHTML entities:
403
404         >>> Markup("Main &raquo; <em>About</em>").unescape()
405         u'Main \xbb <em>About</em>'
406         """
407         from jinja2.constants import HTML_ENTITIES
408         def handle_match(m):
409             name = m.group(1)
410             if name in HTML_ENTITIES:
411                 return unichr(HTML_ENTITIES[name])
412             try:
413                 if name[:2] in ('#x', '#X'):
414                     return unichr(int(name[2:], 16))
415                 elif name.startswith('#'):
416                     return unichr(int(name[1:]))
417             except ValueError:
418                 pass
419             return u''
420         return _entity_re.sub(handle_match, unicode(self))
421
422     def striptags(self):
423         r"""Unescape markup into an unicode string and strip all tags.  This
424         also resolves known HTML4 and XHTML entities.  Whitespace is
425         normalized to one:
426
427         >>> Markup("Main &raquo;  <em>About</em>").striptags()
428         u'Main \xbb About'
429         """
430         stripped = u' '.join(_striptags_re.sub('', self).split())
431         return Markup(stripped).unescape()
432
433     @classmethod
434     def escape(cls, s):
435         """Escape the string.  Works like :func:`escape` with the difference
436         that for subclasses of :class:`Markup` this function would return the
437         correct subclass.
438         """
439         rv = escape(s)
440         if rv.__class__ is not cls:
441             return cls(rv)
442         return rv
443
444     def make_wrapper(name):
445         orig = getattr(unicode, name)
446         def func(self, *args, **kwargs):
447             args = _escape_argspec(list(args), enumerate(args))
448             _escape_argspec(kwargs, kwargs.iteritems())
449             return self.__class__(orig(self, *args, **kwargs))
450         func.__name__ = orig.__name__
451         func.__doc__ = orig.__doc__
452         return func
453
454     for method in '__getitem__', '__getslice__', 'capitalize', \
455                   'title', 'lower', 'upper', 'replace', 'ljust', \
456                   'rjust', 'lstrip', 'rstrip', 'center', 'strip', \
457                   'translate', 'expandtabs', 'swapcase', 'zfill':
458         locals()[method] = make_wrapper(method)
459
460     # new in python 2.5
461     if hasattr(unicode, 'partition'):
462         partition = make_wrapper('partition'),
463         rpartition = make_wrapper('rpartition')
464
465     # new in python 2.6
466     if hasattr(unicode, 'format'):
467         format = make_wrapper('format')
468
469     del method, make_wrapper
470
471
472 def _escape_argspec(obj, iterable):
473     """Helper for various string-wrapped functions."""
474     for key, value in iterable:
475         if hasattr(value, '__html__') or isinstance(value, basestring):
476             obj[key] = escape(value)
477     return obj
478
479
480 class _MarkupEscapeHelper(object):
481     """Helper for Markup.__mod__"""
482
483     def __init__(self, obj):
484         self.obj = obj
485
486     __getitem__ = lambda s, x: _MarkupEscapeHelper(s.obj[x])
487     __unicode__ = lambda s: unicode(escape(s.obj))
488     __str__ = lambda s: str(escape(s.obj))
489     __repr__ = lambda s: str(escape(repr(s.obj)))
490     __int__ = lambda s: int(s.obj)
491     __float__ = lambda s: float(s.obj)
492
493
494 class LRUCache(object):
495     """A simple LRU Cache implementation."""
496
497     # this is fast for small capacities (something below 1000) but doesn't
498     # scale.  But as long as it's only used as storage for templates this
499     # won't do any harm.
500
501     def __init__(self, capacity):
502         self.capacity = capacity
503         self._mapping = {}
504         self._queue = deque()
505         self._postinit()
506
507     def _postinit(self):
508         # alias all queue methods for faster lookup
509         self._popleft = self._queue.popleft
510         self._pop = self._queue.pop
511         if hasattr(self._queue, 'remove'):
512             self._remove = self._queue.remove
513         self._wlock = allocate_lock()
514         self._append = self._queue.append
515
516     def _remove(self, obj):
517         """Python 2.4 compatibility."""
518         for idx, item in enumerate(self._queue):
519             if item == obj:
520                 del self._queue[idx]
521                 break
522
523     def __getstate__(self):
524         return {
525             'capacity':     self.capacity,
526             '_mapping':     self._mapping,
527             '_queue':       self._queue
528         }
529
530     def __setstate__(self, d):
531         self.__dict__.update(d)
532         self._postinit()
533
534     def __getnewargs__(self):
535         return (self.capacity,)
536
537     def copy(self):
538         """Return an shallow copy of the instance."""
539         rv = self.__class__(self.capacity)
540         rv._mapping.update(self._mapping)
541         rv._queue = deque(self._queue)
542         return rv
543
544     def get(self, key, default=None):
545         """Return an item from the cache dict or `default`"""
546         try:
547             return self[key]
548         except KeyError:
549             return default
550
551     def setdefault(self, key, default=None):
552         """Set `default` if the key is not in the cache otherwise
553         leave unchanged. Return the value of this key.
554         """
555         try:
556             return self[key]
557         except KeyError:
558             self[key] = default
559             return default
560
561     def clear(self):
562         """Clear the cache."""
563         self._wlock.acquire()
564         try:
565             self._mapping.clear()
566             self._queue.clear()
567         finally:
568             self._wlock.release()
569
570     def __contains__(self, key):
571         """Check if a key exists in this cache."""
572         return key in self._mapping
573
574     def __len__(self):
575         """Return the current size of the cache."""
576         return len(self._mapping)
577
578     def __repr__(self):
579         return '<%s %r>' % (
580             self.__class__.__name__,
581             self._mapping
582         )
583
584     def __getitem__(self, key):
585         """Get an item from the cache. Moves the item up so that it has the
586         highest priority then.
587
588         Raise an `KeyError` if it does not exist.
589         """
590         rv = self._mapping[key]
591         if self._queue[-1] != key:
592             self._remove(key)
593             self._append(key)
594         return rv
595
596     def __setitem__(self, key, value):
597         """Sets the value for an item. Moves the item up so that it
598         has the highest priority then.
599         """
600         self._wlock.acquire()
601         try:
602             if key in self._mapping:
603                 self._remove(key)
604             elif len(self._mapping) == self.capacity:
605                 del self._mapping[self._popleft()]
606             self._append(key)
607             self._mapping[key] = value
608         finally:
609             self._wlock.release()
610
611     def __delitem__(self, key):
612         """Remove an item from the cache dict.
613         Raise an `KeyError` if it does not exist.
614         """
615         self._wlock.acquire()
616         try:
617             del self._mapping[key]
618             self._remove(key)
619         finally:
620             self._wlock.release()
621
622     def items(self):
623         """Return a list of items."""
624         result = [(key, self._mapping[key]) for key in list(self._queue)]
625         result.reverse()
626         return result
627
628     def iteritems(self):
629         """Iterate over all items."""
630         return iter(self.items())
631
632     def values(self):
633         """Return a list of all values."""
634         return [x[1] for x in self.items()]
635
636     def itervalue(self):
637         """Iterate over all values."""
638         return iter(self.values())
639
640     def keys(self):
641         """Return a list of all keys ordered by most recent usage."""
642         return list(self)
643
644     def iterkeys(self):
645         """Iterate over all keys in the cache dict, ordered by
646         the most recent usage.
647         """
648         return reversed(tuple(self._queue))
649
650     __iter__ = iterkeys
651
652     def __reversed__(self):
653         """Iterate over the values in the cache dict, oldest items
654         coming first.
655         """
656         return iter(tuple(self._queue))
657
658     __copy__ = copy
659
660
661 # register the LRU cache as mutable mapping if possible
662 try:
663     from collections import MutableMapping
664     MutableMapping.register(LRUCache)
665 except ImportError:
666     pass
667
668
669 class Cycler(object):
670     """A cycle helper for templates."""
671
672     def __init__(self, *items):
673         if not items:
674             raise RuntimeError('at least one item has to be provided')
675         self.items = items
676         self.reset()
677
678     def reset(self):
679         """Resets the cycle."""
680         self.pos = 0
681
682     @property
683     def current(self):
684         """Returns the current item."""
685         return self.items[self.pos]
686
687     def next(self):
688         """Goes one item ahead and returns it."""
689         rv = self.current
690         self.pos = (self.pos + 1) % len(self.items)
691         return rv
692
693
694 class Joiner(object):
695     """A joining helper for templates."""
696
697     def __init__(self, sep=u', '):
698         self.sep = sep
699         self.used = False
700
701     def __call__(self):
702         if not self.used:
703             self.used = True
704             return u''
705         return self.sep
706
707
708 # we have to import it down here as the speedups module imports the
709 # markup type which is define above.
710 try:
711     from jinja2._speedups import escape, soft_unicode
712 except ImportError:
713     def escape(s):
714         """Convert the characters &, <, >, ' and " in string s to HTML-safe
715         sequences.  Use this if you need to display text that might contain
716         such characters in HTML.  Marks return value as markup string.
717         """
718         if hasattr(s, '__html__'):
719             return s.__html__()
720         return Markup(unicode(s)
721             .replace('&', '&amp;')
722             .replace('>', '&gt;')
723             .replace('<', '&lt;')
724             .replace("'", '&#39;')
725             .replace('"', '&#34;')
726         )
727
728     def soft_unicode(s):
729         """Make a string unicode if it isn't already.  That way a markup
730         string is not converted back to unicode.
731         """
732         if not isinstance(s, unicode):
733             s = unicode(s)
734         return s
735
736
737 # partials
738 try:
739     from functools import partial
740 except ImportError:
741     class partial(object):
742         def __init__(self, _func, *args, **kwargs):
743             self._func = _func
744             self._args = args
745             self._kwargs = kwargs
746         def __call__(self, *args, **kwargs):
747             kwargs.update(self._kwargs)
748             return self._func(*(self._args + args), **kwargs)