Skip to content

Commit a4e31f1

Browse files
committed
Backport Jinja filters from v4 branch
Signed-off-by: Philippe Ombredanne <[email protected]>
1 parent b9c8532 commit a4e31f1

File tree

2 files changed

+123
-2
lines changed

2 files changed

+123
-2
lines changed

src/attributecode/attrib.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import attributecode
3333
from attributecode import ERROR
3434
from attributecode import Error
35+
from attributecode.attrib_util import get_template
3536
from attributecode.licenses import COMMON_LICENSES
3637
from attributecode.model import parse_license_expression
3738
from attributecode.util import add_unc
@@ -46,7 +47,7 @@ def generate(abouts, template_string=None, vartext_dict=None):
4647
syntax_error = check_template(template_string)
4748
if syntax_error:
4849
return 'Template validation error at line: %r: %r' % (syntax_error)
49-
template = jinja2.Template(template_string)
50+
template = get_template(template_string)
5051

5152
try:
5253
captured_license = []
@@ -119,13 +120,14 @@ def generate(abouts, template_string=None, vartext_dict=None):
119120
return rendered
120121

121122

123+
122124
def check_template(template_string):
123125
"""
124126
Check the syntax of a template. Return an error tuple (line number,
125127
message) if the template is invalid or None if it is valid.
126128
"""
127129
try:
128-
jinja2.Template(template_string)
130+
get_template(template_string)
129131
except (jinja2.TemplateSyntaxError, jinja2.TemplateAssertionError) as e:
130132
return e.lineno, e.message
131133

src/attributecode/attrib_util.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf8 -*-
3+
4+
# ============================================================================
5+
# Copyright (c) 2018 nexB Inc. http://www.nexb.com/ - All rights reserved.
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
# ============================================================================
16+
17+
from __future__ import absolute_import
18+
from __future__ import print_function
19+
from __future__ import unicode_literals
20+
21+
from jinja2 import Environment
22+
from jinja2.filters import environmentfilter
23+
from jinja2.filters import make_attrgetter
24+
from jinja2.filters import ignore_case
25+
from jinja2.filters import FilterArgumentError
26+
27+
28+
"""
29+
Extra JINJA2 custom filters and other template utilities.
30+
"""
31+
32+
33+
def get_template(template_text):
34+
"""
35+
Return a template built from a text string.
36+
Register custom templates as needed.
37+
"""
38+
env = Environment(autoescape=True)
39+
# register our custom filters
40+
env.filters.update(dict(
41+
unique_together=unique_together,
42+
multi_sort=multi_sort))
43+
return env.from_string(template_text)
44+
45+
46+
@environmentfilter
47+
def multi_sort(environment, value, reverse=False, case_sensitive=False,
48+
attributes=None):
49+
"""
50+
Sort an iterable using an "attributes" list of attribute names available on
51+
each iterable item. Sort ascending unless reverse is "true". Ignore the case
52+
of strings unless "case_sensitive" is "true".
53+
54+
.. sourcecode:: jinja
55+
56+
{% for item in iterable|multi_sort(attributes=['date', 'name']) %}
57+
...
58+
{% endfor %}
59+
"""
60+
if not attributes:
61+
raise FilterArgumentError(
62+
'The multi_sort filter requires a list of attributes as argument, '
63+
'such as in: '
64+
"for item in iterable|multi_sort(attributes=['date', 'name'])")
65+
66+
# build a list of attribute getters, one for each attribute
67+
do_ignore_case = ignore_case if not case_sensitive else None
68+
attribute_getters = []
69+
for attribute in attributes:
70+
ag = make_attrgetter(environment, attribute, postprocess=do_ignore_case)
71+
attribute_getters.append(ag)
72+
73+
# build a key function that has runs all attribute getters
74+
def key(v):
75+
return [a(v) for a in attribute_getters]
76+
77+
return sorted(value, key=key, reverse=reverse)
78+
79+
80+
@environmentfilter
81+
def unique_together(environment, value, case_sensitive=False, attributes=None):
82+
"""
83+
Return a list of unique items from an iterable. Unicity is checked when
84+
considering together all the values of an "attributes" list of attribute
85+
names available on each iterable item.. The items order is preserved. Ignore
86+
the case of strings unless "case_sensitive" is "true".
87+
.. sourcecode:: jinja
88+
89+
{% for item in iterable|unique_together(attributes=['date', 'name']) %}
90+
...
91+
{% endfor %}
92+
93+
"""
94+
if not attributes:
95+
raise FilterArgumentError(
96+
'The unique_together filter requires a list of attributes as argument, '
97+
'such as in: '
98+
"{% for item in iterable|unique_together(attributes=['date', 'name']) %} ")
99+
100+
# build a list of attribute getters, one for each attribute
101+
do_ignore_case = ignore_case if not case_sensitive else None
102+
attribute_getters = []
103+
for attribute in attributes:
104+
ag = make_attrgetter(environment, attribute, postprocess=do_ignore_case)
105+
attribute_getters.append(ag)
106+
107+
# build a unique_key function that has runs all attribute getters
108+
# and returns a hashable tuple
109+
def unique_key(v):
110+
return tuple(repr(a(v)) for a in attribute_getters)
111+
112+
unique = []
113+
seen = set()
114+
for item in value:
115+
key = unique_key(item)
116+
if key not in seen:
117+
seen.add(key)
118+
unique.append(item)
119+
return unique

0 commit comments

Comments
 (0)