"""
Add a few specific filters to Jinja2.
"""
import re
from functools import wraps
import datetime
from pytz import utc
from calendar import timegm
from babel.dates import DateTimePattern, format_timedelta, parse_pattern
import bleach
from werkzeug.routing import BuildError
from jinja2 import Markup, escape, evalcontextfilter
from flask import Flask
from flask.ext import babel
from ..core.util import local_dt, utc_dt, slugify
from .util import url_for
[docs]def autoescape(filter_func):
"""
Decorator to autoescape result from filters.
"""
@evalcontextfilter
@wraps(filter_func)
def _autoescape(eval_ctx, *args, **kwargs):
result = filter_func(*args, **kwargs)
if eval_ctx.autoescape:
result = Markup(result)
return result
return _autoescape
@autoescape
[docs]def nl2br(value):
"""
Replace newlines with <br />.
"""
result = escape(value).replace(u'\n', Markup(u'<br />\n'))
return result
_PARAGRAPH_RE = re.compile(r'(?:\r\n|\r|\n){2,}')
@autoescape
[docs]def paragraphs(value):
"""
Blank lines delimitates paragraphs.
"""
result = u'\n\n'.join(
(u'<p>{}</p>'.format(p.strip().replace('\n', Markup('<br />\n')))
for p in _PARAGRAPH_RE.split(escape(value))))
return result
[docs]def labelize(s):
return u" ".join([ w.capitalize() for w in s.split(u"_") ])
[docs]def filesize(d):
if not isinstance(d, int):
d = int(d)
if d < 1000:
s = "%d B" % d
elif d < 1e4:
s = "%.1f kB" % (d / 1e3)
elif d < 1e6:
s = "%.0f kB" % (d / 1e3)
elif d < 1e7:
s = "%.1f MB" % (d / 1e6)
elif d < 1e9:
s = "%.0f MB" % (d / 1e6)
elif d < 1e10:
s = "%.1f GB" % (d / 1e9)
else:
s = "%.0f GB" % (d / 1e9)
return Markup(s)
[docs]def roughsize(size, above=20, mod=10):
"""
6 -> '6'
15 -> '15'
134 -> '130+'
"""
if size < above:
return unicode(size)
return u'{:d}+'.format(size - size % mod)
[docs]def age(dt, now=None):
# Fail silently for now XXX
if not dt:
return ""
if not now:
now = datetime.datetime.utcnow()
dt = utc_dt(dt)
now = utc_dt(now)
# don't use (flask.ext.)babel.format_timedelta: as of Flask-Babel 0.9 it
# doesn't support "threshold" arg.
return format_timedelta((dt - now),
locale=babel.get_locale(),
granularity='minute',
threshold=0.9,
add_direction=True)
[docs]def date_age(dt, now=None):
# Fail silently for now XXX
if not dt:
return ""
formatted_date = babel.format_datetime(dt, format='yyyy-MM-dd HH:mm')
return u"{} ({})".format(formatted_date, age(dt, now))
[docs]def date(value, format="EE, d MMMM y"):
if isinstance(value, datetime.date):
return babel.format_date(value, format)
else:
return babel.format_date(local_dt(value), format)
[docs]def babel2datepicker(pattern):
"""
Converts date format from babel
(http://babel.pocoo.org/docs/dates/#date-fields)) to a format understood by
bootstrap-datepicker.
"""
if not isinstance(pattern, DateTimePattern):
pattern = parse_pattern(pattern)
map_fmt = {
# days
'd': 'dd',
'dd': 'dd',
'EEE': 'D',
'EEEE': 'DD',
'EEEEE': 'D', # narrow name => short name
# months
'M': 'mm',
'MM': 'mm',
'MMM': 'M',
'MMMM': 'MM',
# years
'y': 'yyyy',
'yy': 'yyyy',
'yyy': 'yyyy',
'yyyy': 'yyyy',
# time picker format
# hours
'h': '%I',
'hh': '%I',
'H': '%H',
'HH': '%H',
# minutes,
'm': '%M',
'mm': '%M',
# seconds
's': '%S',
'ss': '%S',
# am/pm
'a': '%p',
}
return pattern.format % map_fmt
# Doesn't work yet. TZ issues.
[docs]def to_timestamp(dt):
utc_datetime = dt.astimezone(utc)
return timegm(utc_datetime.timetuple()) + utc_datetime.microsecond / 1e6
[docs]def abbrev(s, max_size):
if len(s) <= max_size:
return s
else:
h = max_size / 2 - 1
return s[0:h] + "..." + s[-h:]
@autoescape
[docs]def linkify(s):
return Markup(bleach.linkify(s))
[docs]def obj_to_url(obj):
"""
Find url for obj using :func:`url_for`, return empty string is not found.
:func:`url_for` is also provided in jinja context, the filtering version is
forgiving when `obj` has no default view set.
"""
try:
return url_for(obj)
except BuildError:
return u''
[docs]def init_filters(env):
if isinstance(env, Flask):
# old api for init_filters: we used to pass flask application
env = env.jinja_env
env.filters['nl2br'] = nl2br
env.filters['paragraphs'] = paragraphs
env.filters['date_age'] = date_age
env.filters['age'] = age
env.filters['date'] = date
env.filters['babel2datepicker'] = babel2datepicker
env.filters['to_timestamp'] = to_timestamp
env.filters['url_for'] = obj_to_url
env.filters['abbrev'] = abbrev
env.filters['filesize'] = filesize
env.filters['roughsize'] = roughsize
env.filters['labelize'] = labelize
env.filters['linkify'] = linkify
env.filters['toslug'] = slugify