1#
2# BitBake Toaster Implementation
3#
4# Copyright (C) 2013        Intel Corporation
5#
6# SPDX-License-Identifier: GPL-2.0-only
7#
8
9from datetime import timedelta
10from os.path import relpath
11import re
12from django import template
13from django.utils import timezone
14from django.template.defaultfilters import filesizeformat
15import json as JsonLib
16from django.utils.safestring import mark_safe
17
18register = template.Library()
19
20@register.simple_tag
21def time_difference(start_time, end_time):
22    return end_time - start_time
23
24@register.filter(name = 'sectohms')
25def sectohms(time):
26    try:
27        tdsec = int(time)
28    except ValueError:
29        tdsec = 0
30    hours = int(tdsec / 3600)
31    return "%02d:%02d:%02d" % (hours, int((tdsec - (hours * 3600))/ 60), int(tdsec) % 60)
32
33
34@register.filter(name = 'get_tasks')
35def get_tasks(queryset):
36    return list(target + ':' + task if task else target \
37                    for target, task in queryset.values_list('target', 'task'))
38
39
40@register.filter(name = "json")
41def json(value, default = None):
42    # JSON spec says that "\/" is functionally identical to "/" to allow for HTML-tag embedding in JSON strings
43    # unfortunately, I can't find any option in the json module to turn on forward-slash escaping, so we do
44    # it manually here
45    return mark_safe(JsonLib.dumps(value, indent=2, default = default, ensure_ascii=False).replace('</', '<\\/'))
46
47@register.simple_tag
48def query(qs, **kwargs):
49    """ template tag which allows queryset filtering. Usage:
50          {% query books author=author as mybooks %}
51          {% for book in mybooks %}
52            ...
53          {% endfor %}
54    """
55    return qs.filter(**kwargs)
56
57
58@register.filter("whitespace_slice")
59def whitespace_space_filter(value, arg):
60    try:
61        bits = []
62        for x in arg.split(":"):
63            if len(x) == 0:
64                bits.append(None)
65            else:
66                # convert numeric value to the first whitespace after
67                first_whitespace = value.find(" ", int(x))
68                if first_whitespace == -1:
69                    bits.append(int(x))
70                else:
71                    bits.append(first_whitespace)
72        return value[slice(*bits)]
73    except (ValueError, TypeError):
74        raise
75
76@register.filter
77def divide(value, arg):
78    if int(arg) == 0:
79        return -1
80    return int(value) // int(arg)
81
82@register.filter
83def multiply(value, arg):
84    return int(value) * int(arg)
85
86@register.simple_tag
87def datecompute(delta, start = timezone.now()):
88    return start + timedelta(delta)
89
90
91@register.filter(name = 'sortcols')
92def sortcols(tablecols):
93    return sorted(tablecols, key = lambda t: t['name'])
94
95@register.filter
96def task_color(task_object, show_green=False):
97    """ Return css class depending on Task execution status and execution outcome.
98        By default, green is not returned for executed and successful tasks;
99        show_green argument should be True to get green color.
100    """
101    if not task_object.task_executed:
102        return 'class=text-muted'
103    elif task_object.outcome == task_object.OUTCOME_FAILED:
104        return 'class=text-danger'
105    elif task_object.outcome == task_object.OUTCOME_SUCCESS and show_green:
106        return 'class=text-success'
107    else:
108        return ''
109
110@register.filter
111def filtered_icon(options, filter):
112    """Returns btn-primary if the filter matches one of the filter options
113    """
114    for option in options:
115        if filter == option[1]:
116            return "btn-primary"
117        if ('daterange' == option[1]) and filter.startswith(option[4]):
118            return "btn-primary"
119    return ""
120
121@register.filter
122def filtered_tooltip(options, filter):
123    """Returns tooltip for the filter icon if the filter matches one of the filter options
124    """
125    for option in options:
126        if filter == option[1]:
127            return "Showing only %s"%option[0]
128        if ('daterange' == option[1]) and filter.startswith(option[4]):
129            return "Showing only %s"%option[0]
130    return ""
131
132@register.filter
133def format_none_and_zero(value):
134    """Return empty string if the value is None, zero or Not Applicable
135    """
136    return "" if (not value) or (value == 0) or (value == "0") or (value == 'Not Applicable') else value
137
138@register.filter
139def filtered_filesizeformat(value):
140    """
141    If the value is -1 return an empty string. Otherwise,
142    change output from fileformatsize to suppress trailing '.0'
143    and change 'bytes' to 'B'.
144    """
145    if value == -1:
146        return ''
147
148    return filesizeformat(value).replace("bytes", "B")
149
150@register.filter
151def filtered_packagespec(value):
152    """Strip off empty version and revision"""
153    return re.sub(r'(--$)', '', value)
154
155@register.filter
156def check_filter_status(options, filter):
157    """Check if the active filter is among the available options, and return 'checked'
158       if filter is not active.
159       Used in FilterDialog to select the first radio button if the filter is not active.
160    """
161    for option in options:
162        if filter == option[1]:
163            return ""
164    return "checked"
165
166@register.filter
167def variable_parent_name(value):
168    """ filter extended variable names to the parent name
169    """
170    value = re.sub(r'_\$.*', '', value)
171    return re.sub(r'_[a-z].*', '', value)
172
173@register.filter
174def filter_setin_files(file_list, matchstr):
175    """Filter/search the 'set in' file lists."""
176    result = []
177    search, filter = matchstr.split(':')
178    for pattern in (search, filter):
179        if pattern:
180            for fobj in file_list:
181                fname = fobj.file_name
182                if fname not in result and re.search(pattern, fname):
183                    result.append(fname)
184
185    # no filter, show last file (if any)
186    last = list(file_list)[-1].file_name
187    if not filter and last not in result:
188        result.append(last)
189
190    return result
191
192@register.filter
193def string_slice(strvar,slicevar):
194    """ slice a string with |string_slice:'[first]:[last]'
195    """
196    first,last= slicevar.partition(':')[::2]
197    if first=='':
198        return strvar[:int(last)]
199    elif last=='':
200        return strvar[int(first):]
201    else:
202        return strvar[int(first):int(last)]
203
204@register.filter
205def string_remove_regex(value,ex):
206    """ remove sub-string of string that matches regex
207    """
208    return re.sub(ex, '', value)
209
210@register.filter
211def filtered_installedsize(size, installed_size):
212    """If package.installed_size not null and not empty return it,
213       else return package.size
214    """
215    return size if (installed_size == 0) or (installed_size == "") or (installed_size is None) else installed_size
216
217@register.filter
218def filtered_packageversion(version, revision):
219    """ Emit "version-revision" if version and revision are not null
220        else "version" if version is not null
221        else ""
222    """
223    return "" if (not version or version == "") else version if (not revision or revision == "") else version + "-" + revision
224
225@register.filter
226def filter_sizeovertotal(package_object, total_size):
227    """ Return the % size of the package over the total size argument
228        formatted nicely.
229    """
230    size = package_object.installed_size
231    if size is None or size == '':
232        size = package_object.size
233
234    return '{:.1%}'.format(float(size)/float(total_size))
235
236from django.utils.safestring import mark_safe
237@register.filter
238def format_vpackage_rowclass(size):
239    if size == -1:
240        return mark_safe('class="text-muted"')
241    return ''
242
243@register.filter
244def format_vpackage_namehelp(name):
245    r =  name + '&nbsp;'
246    r += '<span class="glyphicon glyphicon-question-sign get-help hover-help"'
247    r += ' title = "' + name + ' has not been built">'
248    r += '</span>'
249    return mark_safe(r)
250
251@register.filter
252def get_dict_value(dictionary, key):
253    """ return the value of a dictionary key
254    """
255    try:
256        return dictionary[key]
257    except (KeyError, IndexError):
258        return ''
259
260@register.filter
261def is_shaid(text):
262    """ return True if text length is 40 characters and all hex-digits
263    """
264    try:
265        int(text, 16)
266        if len(text) == 40:
267            return True
268        return False
269    except ValueError:
270        return False
271
272@register.filter
273def cut_path_prefix(fullpath, prefixes):
274    """Cut path prefix from fullpath."""
275    for prefix in prefixes:
276        if fullpath.startswith(prefix):
277            return relpath(fullpath, prefix)
278    return fullpath
279
280
281@register.filter
282def for_target(package_dependencies, target):
283    """ filter the dependencies to be displayed by the supplied target
284    if no dependences are found for the target then return the predicted
285    dependences"""
286    return package_dependencies.for_target_or_none(target)
287