summaryrefslogtreecommitdiff
path: root/lib/spack/external/jinja2/filters.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/spack/external/jinja2/filters.py')
-rw-r--r--lib/spack/external/jinja2/filters.py203
1 files changed, 160 insertions, 43 deletions
diff --git a/lib/spack/external/jinja2/filters.py b/lib/spack/external/jinja2/filters.py
index 76e04db665..267ddddaa0 100644
--- a/lib/spack/external/jinja2/filters.py
+++ b/lib/spack/external/jinja2/filters.py
@@ -10,9 +10,10 @@
"""
import re
import math
+import random
+import warnings
-from random import choice
-from itertools import groupby
+from itertools import groupby, chain
from collections import namedtuple
from jinja2.utils import Markup, escape, pformat, urlize, soft_unicode, \
unicode_urlencode, htmlsafe_json_dumps
@@ -52,22 +53,34 @@ def environmentfilter(f):
return f
-def make_attrgetter(environment, attribute):
+def ignore_case(value):
+ """For use as a postprocessor for :func:`make_attrgetter`. Converts strings
+ to lowercase and returns other types as-is."""
+ return value.lower() if isinstance(value, string_types) else value
+
+
+def make_attrgetter(environment, attribute, postprocess=None):
"""Returns a callable that looks up the given attribute from a
passed object with the rules of the environment. Dots are allowed
to access attributes of attributes. Integer parts in paths are
looked up as integers.
"""
- if not isinstance(attribute, string_types) \
- or ('.' not in attribute and not attribute.isdigit()):
- return lambda x: environment.getitem(x, attribute)
- attribute = attribute.split('.')
+ if attribute is None:
+ attribute = []
+ elif isinstance(attribute, string_types):
+ attribute = [int(x) if x.isdigit() else x for x in attribute.split('.')]
+ else:
+ attribute = [attribute]
+
def attrgetter(item):
for part in attribute:
- if part.isdigit():
- part = int(part)
item = environment.getitem(item, part)
+
+ if postprocess is not None:
+ item = postprocess(item)
+
return item
+
return attrgetter
@@ -190,7 +203,7 @@ def do_title(s):
if item])
-def do_dictsort(value, case_sensitive=False, by='key'):
+def do_dictsort(value, case_sensitive=False, by='key', reverse=False):
"""Sort a dict and yield (key, value) pairs. Because python dicts are
unsorted you may want to use this function to order them by either
key or value:
@@ -200,6 +213,9 @@ def do_dictsort(value, case_sensitive=False, by='key'):
{% for item in mydict|dictsort %}
sort the dict by key, case insensitive
+ {% for item in mydict|dictsort(reverse=true) %}
+ sort the dict by key, case insensitive, reverse order
+
{% for item in mydict|dictsort(true) %}
sort the dict by key, case sensitive
@@ -211,20 +227,25 @@ def do_dictsort(value, case_sensitive=False, by='key'):
elif by == 'value':
pos = 1
else:
- raise FilterArgumentError('You can only sort by either '
- '"key" or "value"')
+ raise FilterArgumentError(
+ 'You can only sort by either "key" or "value"'
+ )
+
def sort_func(item):
value = item[pos]
- if isinstance(value, string_types) and not case_sensitive:
- value = value.lower()
+
+ if not case_sensitive:
+ value = ignore_case(value)
+
return value
- return sorted(value.items(), key=sort_func)
+ return sorted(value.items(), key=sort_func, reverse=reverse)
@environmentfilter
-def do_sort(environment, value, reverse=False, case_sensitive=False,
- attribute=None):
+def do_sort(
+ environment, value, reverse=False, case_sensitive=False, attribute=None
+):
"""Sort an iterable. Per default it sorts ascending, if you pass it
true as first argument it will reverse the sorting.
@@ -250,18 +271,85 @@ def do_sort(environment, value, reverse=False, case_sensitive=False,
.. versionchanged:: 2.6
The `attribute` parameter was added.
"""
- if not case_sensitive:
- def sort_func(item):
- if isinstance(item, string_types):
- item = item.lower()
- return item
- else:
- sort_func = None
- if attribute is not None:
- getter = make_attrgetter(environment, attribute)
- def sort_func(item, processor=sort_func or (lambda x: x)):
- return processor(getter(item))
- return sorted(value, key=sort_func, reverse=reverse)
+ key_func = make_attrgetter(
+ environment, attribute,
+ postprocess=ignore_case if not case_sensitive else None
+ )
+ return sorted(value, key=key_func, reverse=reverse)
+
+
+@environmentfilter
+def do_unique(environment, value, case_sensitive=False, attribute=None):
+ """Returns a list of unique items from the the given iterable.
+
+ .. sourcecode:: jinja
+
+ {{ ['foo', 'bar', 'foobar', 'FooBar']|unique }}
+ -> ['foo', 'bar', 'foobar']
+
+ The unique items are yielded in the same order as their first occurrence in
+ the iterable passed to the filter.
+
+ :param case_sensitive: Treat upper and lower case strings as distinct.
+ :param attribute: Filter objects with unique values for this attribute.
+ """
+ getter = make_attrgetter(
+ environment, attribute,
+ postprocess=ignore_case if not case_sensitive else None
+ )
+ seen = set()
+
+ for item in value:
+ key = getter(item)
+
+ if key not in seen:
+ seen.add(key)
+ yield item
+
+
+def _min_or_max(environment, value, func, case_sensitive, attribute):
+ it = iter(value)
+
+ try:
+ first = next(it)
+ except StopIteration:
+ return environment.undefined('No aggregated item, sequence was empty.')
+
+ key_func = make_attrgetter(
+ environment, attribute,
+ ignore_case if not case_sensitive else None
+ )
+ return func(chain([first], it), key=key_func)
+
+
+@environmentfilter
+def do_min(environment, value, case_sensitive=False, attribute=None):
+ """Return the smallest item from the sequence.
+
+ .. sourcecode:: jinja
+
+ {{ [1, 2, 3]|min }}
+ -> 1
+
+ :param case_sensitive: Treat upper and lower case strings as distinct.
+ :param attribute: Get the object with the max value of this attribute.
+ """
+ return _min_or_max(environment, value, min, case_sensitive, attribute)
+
+
+@environmentfilter
+def do_max(environment, value, case_sensitive=False, attribute=None):
+ """Return the largest item from the sequence.
+
+ .. sourcecode:: jinja
+
+ {{ [1, 2, 3]|max }}
+ -> 3
+
+ :param case_sensitive: Treat upper and lower case strings as distinct.
+ :param attribute: Get the object with the max value of this attribute.
+ """
+ return _min_or_max(environment, value, max, case_sensitive, attribute)
def do_default(value, default_value=u'', boolean=False):
@@ -359,13 +447,13 @@ def do_last(environment, seq):
return environment.undefined('No last item, sequence was empty.')
-@environmentfilter
-def do_random(environment, seq):
+@contextfilter
+def do_random(context, seq):
"""Return a random item from the sequence."""
try:
- return choice(seq)
+ return random.choice(seq)
except IndexError:
- return environment.undefined('No random item, sequence was empty.')
+ return context.environment.undefined('No random item, sequence was empty.')
def do_filesizeformat(value, binary=False):
@@ -445,21 +533,44 @@ def do_urlize(eval_ctx, value, trim_url_limit=None, nofollow=False,
return rv
-def do_indent(s, width=4, indentfirst=False):
- """Return a copy of the passed string, each line indented by
- 4 spaces. The first line is not indented. If you want to
- change the number of spaces or indent the first line too
- you can pass additional parameters to the filter:
+def do_indent(
+ s, width=4, first=False, blank=False, indentfirst=None
+):
+ """Return a copy of the string with each line indented by 4 spaces. The
+ first line and blank lines are not indented by default.
- .. sourcecode:: jinja
+ :param width: Number of spaces to indent by.
+ :param first: Don't skip indenting the first line.
+ :param blank: Don't skip indenting empty lines.
- {{ mytext|indent(2, true) }}
- indent by two spaces and indent the first line too.
+ .. versionchanged:: 2.10
+ Blank lines are not indented by default.
+
+ Rename the ``indentfirst`` argument to ``first``.
"""
+ if indentfirst is not None:
+ warnings.warn(DeprecationWarning(
+ 'The "indentfirst" argument is renamed to "first".'
+ ), stacklevel=2)
+ first = indentfirst
+
+ s += u'\n' # this quirk is necessary for splitlines method
indention = u' ' * width
- rv = (u'\n' + indention).join(s.splitlines())
- if indentfirst:
+
+ if blank:
+ rv = (u'\n' + indention).join(s.splitlines())
+ else:
+ lines = s.splitlines()
+ rv = lines.pop(0)
+
+ if lines:
+ rv += u'\n' + u'\n'.join(
+ indention + line if line else line for line in lines
+ )
+
+ if first:
rv = indention + rv
+
return rv
@@ -865,6 +976,9 @@ def do_select(*args, **kwargs):
{{ numbers|select("odd") }}
{{ numbers|select("odd") }}
+ {{ numbers|select("divisibleby", 3) }}
+ {{ numbers|select("lessthan", 42) }}
+ {{ strings|select("equalto", "mystring") }}
.. versionadded:: 2.7
"""
@@ -1045,6 +1159,8 @@ FILTERS = {
'list': do_list,
'lower': do_lower,
'map': do_map,
+ 'min': do_min,
+ 'max': do_max,
'pprint': do_pprint,
'random': do_random,
'reject': do_reject,
@@ -1063,6 +1179,7 @@ FILTERS = {
'title': do_title,
'trim': do_trim,
'truncate': do_truncate,
+ 'unique': do_unique,
'upper': do_upper,
'urlencode': do_urlencode,
'urlize': do_urlize,