~brad-marshall/charms/trusty/apache2-wsgi/fix-haproxy-relations

« back to all changes in this revision

Viewing changes to hooks/lib/jinja2/utils.py

  • Committer: Robin Winslow
  • Date: 2014-05-27 14:00:44 UTC
  • Revision ID: robin.winslow@canonical.com-20140527140044-8rpmb3wx4djzwa83
Add all files

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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 errno
 
13
from collections import deque
 
14
from 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, ('(', '<', '&lt;'))),
 
22
        '|'.join(map(re.escape, ('.', ',', ')', '>', '\n', '&gt;')))
 
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
 
32
missing = type('MissingType', (), {'__repr__': lambda x: 'missing'})()
 
33
 
 
34
# internal code
 
35
internal_code = set()
 
36
 
 
37
concat = u''.join
 
38
 
 
39
 
 
40
def 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
 
 
56
def 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
 
 
69
def 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
 
 
79
def internalcode(f):
 
80
    """Marks the function as internally used"""
 
81
    internal_code.add(f.__code__)
 
82
    return f
 
83
 
 
84
 
 
85
def 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
 
 
101
def consume(iterable):
 
102
    """Consumes an iterable without doing anything with it."""
 
103
    for event in iterable:
 
104
        pass
 
105
 
 
106
 
 
107
def 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
 
 
119
def 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
 
 
145
def 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
 
 
156
def 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
 
 
173
def 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
 
 
185
def 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
 
 
230
def 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
 
 
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
 
 
294
class 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
 
472
try:
 
473
    from collections import MutableMapping
 
474
    MutableMapping.register(LRUCache)
 
475
except ImportError:
 
476
    pass
 
477
 
 
478
 
 
479
@implements_iterator
 
480
class 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
 
 
505
class 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
 
520
from markupsafe import Markup, escape, soft_unicode