templatefilters.py
475 lines
| 14.5 KiB
| text/x-python
|
PythonLexer
/ mercurial / templatefilters.py
Yuya Nishihara
|
r32740 | # templatefilters.py - common template expansion filters | ||
Matt Mackall
|
r5976 | # | ||
# Copyright 2005-2008 Matt Mackall <mpm@selenic.com> | ||||
# | ||||
Martin Geisler
|
r8225 | # This software may be used and distributed according to the terms of the | ||
Matt Mackall
|
r10263 | # GNU General Public License version 2 or any later version. | ||
Matt Mackall
|
r5976 | |||
Gregory Szorc
|
r25983 | from __future__ import absolute_import | ||
import os | ||||
import re | ||||
import time | ||||
Yuya Nishihara
|
r37246 | from .i18n import _ | ||
Gregory Szorc
|
r25983 | from . import ( | ||
encoding, | ||||
Augie Fackler
|
r34838 | error, | ||
Gregory Szorc
|
r25983 | node, | ||
Pulkit Goyal
|
r32126 | pycompat, | ||
FUJIWARA Katsunori
|
r28693 | registrar, | ||
Yuya Nishihara
|
r36938 | templateutil, | ||
Augie Fackler
|
r34696 | url, | ||
Gregory Szorc
|
r25983 | util, | ||
) | ||||
Yuya Nishihara
|
r37102 | from .utils import ( | ||
dateutil, | ||||
stringutil, | ||||
) | ||||
Matt Mackall
|
r5976 | |||
timeless
|
r28883 | urlerr = util.urlerr | ||
urlreq = util.urlreq | ||||
Pulkit Goyal
|
r32126 | if pycompat.ispy3: | ||
long = int | ||||
FUJIWARA Katsunori
|
r28693 | # filters are callables like: | ||
# fn(obj) | ||||
# with: | ||||
# obj - object to be filtered (text, date, list and so on) | ||||
filters = {} | ||||
templatefilter = registrar.templatefilter(filters) | ||||
Yuya Nishihara
|
r37240 | @templatefilter('addbreaks', intype=bytes) | ||
Patrick Mezard
|
r13588 | def addbreaks(text): | ||
FUJIWARA Katsunori
|
r28693 | """Any text. Add an XHTML "<br />" tag before the end of | ||
Patrick Mezard
|
r13591 | every line except the last. | ||
""" | ||||
Patrick Mezard
|
r13588 | return text.replace('\n', '<br/>\n') | ||
Dirkjan Ochtman
|
r8360 | |||
David Soria Parra
|
r19736 | agescales = [("year", 3600 * 24 * 365, 'Y'), | ||
("month", 3600 * 24 * 30, 'M'), | ||||
("week", 3600 * 24 * 7, 'W'), | ||||
("day", 3600 * 24, 'd'), | ||||
("hour", 3600, 'h'), | ||||
("minute", 60, 'm'), | ||||
("second", 1, 's')] | ||||
Matt Mackall
|
r5976 | |||
Yuya Nishihara
|
r37244 | @templatefilter('age', intype=templateutil.date) | ||
David Soria Parra
|
r19736 | def age(date, abbrev=False): | ||
FUJIWARA Katsunori
|
r28693 | """Date. Returns a human-readable date/time difference between the | ||
Patrick Mezard
|
r13591 | given date/time and the current date/time. | ||
""" | ||||
Matt Mackall
|
r5976 | |||
def plural(t, c): | ||||
if c == 1: | ||||
return t | ||||
return t + "s" | ||||
David Soria Parra
|
r19736 | def fmt(t, c, a): | ||
if abbrev: | ||||
return "%d%s" % (c, a) | ||||
Matt Mackall
|
r5976 | return "%d %s" % (c, plural(t, c)) | ||
now = time.time() | ||||
then = date[0] | ||||
timeless
|
r13666 | future = False | ||
Dirkjan Ochtman
|
r7682 | if then > now: | ||
timeless
|
r13666 | future = True | ||
delta = max(1, int(then - now)) | ||||
if delta > agescales[0][1] * 30: | ||||
return 'in the distant future' | ||||
else: | ||||
delta = max(1, int(now - then)) | ||||
if delta > agescales[0][1] * 2: | ||||
Boris Feld
|
r36625 | return dateutil.shortdate(date) | ||
Dirkjan Ochtman
|
r9722 | |||
David Soria Parra
|
r19736 | for t, s, a in agescales: | ||
Alejandro Santos
|
r9029 | n = delta // s | ||
Matt Mackall
|
r5976 | if n >= 2 or s == 1: | ||
timeless
|
r13666 | if future: | ||
David Soria Parra
|
r19736 | return '%s from now' % fmt(t, n, a) | ||
return '%s ago' % fmt(t, n, a) | ||||
Matt Mackall
|
r5976 | |||
Yuya Nishihara
|
r37240 | @templatefilter('basename', intype=bytes) | ||
Patrick Mezard
|
r13590 | def basename(path): | ||
FUJIWARA Katsunori
|
r28693 | """Any text. Treats the text as a path, and returns the last | ||
Yuya Nishihara
|
r35577 | component of the path after splitting by the path separator. | ||
For example, "foo/bar/baz" becomes "baz" and "foo/bar//" becomes "". | ||||
Patrick Mezard
|
r13591 | """ | ||
Patrick Mezard
|
r13590 | return os.path.basename(path) | ||
Martin von Zweigbergk
|
r38323 | @templatefilter('commondir') | ||
def commondir(filelist): | ||||
Joerg Sonnenberger
|
r38198 | """List of text. Treats each list item as file name with / | ||
as path separator and returns the longest common directory | ||||
prefix shared by all list items. | ||||
Returns the empty string if no common prefix exists. | ||||
The list items are not normalized, i.e. "foo/../bar" is handled as | ||||
file "bar" in the directory "foo/..". Leading slashes are ignored. | ||||
For example, ["foo/bar/baz", "foo/baz/bar"] becomes "foo" and | ||||
["foo/bar", "baz"] becomes "". | ||||
""" | ||||
def common(a, b): | ||||
if len(a) > len(b): | ||||
a = b[:len(a)] | ||||
elif len(b) > len(a): | ||||
b = b[:len(a)] | ||||
if a == b: | ||||
return a | ||||
Gregory Szorc
|
r38806 | for i in pycompat.xrange(len(a)): | ||
Joerg Sonnenberger
|
r38198 | if a[i] != b[i]: | ||
return a[:i] | ||||
return a | ||||
try: | ||||
if not filelist: | ||||
return "" | ||||
dirlist = [f.lstrip('/').split('/')[:-1] for f in filelist] | ||||
if len(dirlist) == 1: | ||||
return '/'.join(dirlist[0]) | ||||
a = min(dirlist) | ||||
b = max(dirlist) | ||||
# The common prefix of a and b is shared with all | ||||
# elements of the list since Python sorts lexicographical | ||||
# and [1, x] after [1]. | ||||
return '/'.join(common(a, b)) | ||||
except TypeError: | ||||
raise error.ParseError(_('argument is not a list of text')) | ||||
FUJIWARA Katsunori
|
r28693 | @templatefilter('count') | ||
Anton Shestakov
|
r22668 | def count(i): | ||
FUJIWARA Katsunori
|
r28693 | """List or text. Returns the length as an integer.""" | ||
Yuya Nishihara
|
r37246 | try: | ||
return len(i) | ||||
except TypeError: | ||||
raise error.ParseError(_('not countable')) | ||||
Anton Shestakov
|
r22668 | |||
Yuya Nishihara
|
r37240 | @templatefilter('dirname', intype=bytes) | ||
Yuya Nishihara
|
r36260 | def dirname(path): | ||
"""Any text. Treats the text as a path, and strips the last | ||||
component of the path after splitting by the path separator. | ||||
""" | ||||
return os.path.dirname(path) | ||||
Yuya Nishihara
|
r37240 | @templatefilter('domain', intype=bytes) | ||
Patrick Mezard
|
r13588 | def domain(author): | ||
FUJIWARA Katsunori
|
r28693 | """Any text. Finds the first string that looks like an email | ||
Patrick Mezard
|
r13591 | address, and extracts just the domain component. Example: ``User | ||
<user@example.com>`` becomes ``example.com``. | ||||
""" | ||||
Patrick Mezard
|
r13588 | f = author.find('@') | ||
if f == -1: | ||||
return '' | ||||
author = author[f + 1:] | ||||
f = author.find('>') | ||||
if f >= 0: | ||||
author = author[:f] | ||||
return author | ||||
Yuya Nishihara
|
r37240 | @templatefilter('email', intype=bytes) | ||
Patrick Mezard
|
r13590 | def email(text): | ||
FUJIWARA Katsunori
|
r28693 | """Any text. Extracts the first string that looks like an email | ||
Patrick Mezard
|
r13591 | address. Example: ``User <user@example.com>`` becomes | ||
``user@example.com``. | ||||
""" | ||||
Yuya Nishihara
|
r37102 | return stringutil.email(text) | ||
Patrick Mezard
|
r13590 | |||
Yuya Nishihara
|
r37240 | @templatefilter('escape', intype=bytes) | ||
Patrick Mezard
|
r13590 | def escape(text): | ||
FUJIWARA Katsunori
|
r28693 | """Any text. Replaces the special XML/XHTML characters "&", "<" | ||
Siddharth Agarwal
|
r17772 | and ">" with XML entities, and filters out NUL characters. | ||
Patrick Mezard
|
r13591 | """ | ||
Augie Fackler
|
r34696 | return url.escape(text.replace('\0', ''), True) | ||
Patrick Mezard
|
r13590 | |||
Matt Mackall
|
r5976 | para_re = None | ||
space_re = None | ||||
Mads Kiilerich
|
r19872 | def fill(text, width, initindent='', hangindent=''): | ||
Sean Farley
|
r19228 | '''fill many paragraphs with optional indentation.''' | ||
Matt Mackall
|
r5976 | global para_re, space_re | ||
if para_re is None: | ||||
para_re = re.compile('(\n\n|\n\\s*[-*]\\s*)', re.M) | ||||
Yuya Nishihara
|
r36516 | space_re = re.compile(br' +') | ||
Matt Mackall
|
r5976 | |||
def findparas(): | ||||
start = 0 | ||||
while True: | ||||
m = para_re.search(text, start) | ||||
if not m: | ||||
Yuya Nishihara
|
r36515 | uctext = encoding.unifromlocal(text[start:]) | ||
FUJIWARA Katsunori
|
r11297 | w = len(uctext) | ||
Martin von Zweigbergk
|
r40065 | while w > 0 and uctext[w - 1].isspace(): | ||
Matt Mackall
|
r10282 | w -= 1 | ||
Yuya Nishihara
|
r36515 | yield (encoding.unitolocal(uctext[:w]), | ||
encoding.unitolocal(uctext[w:])) | ||||
Matt Mackall
|
r5976 | break | ||
yield text[start:m.start(0)], m.group(1) | ||||
start = m.end(1) | ||||
Yuya Nishihara
|
r37102 | return "".join([stringutil.wrap(space_re.sub(' ', | ||
stringutil.wrap(para, width)), | ||||
width, initindent, hangindent) + rest | ||||
Matt Mackall
|
r5976 | for para, rest in findparas()]) | ||
Yuya Nishihara
|
r37240 | @templatefilter('fill68', intype=bytes) | ||
Patrick Mezard
|
r13590 | def fill68(text): | ||
FUJIWARA Katsunori
|
r28693 | """Any text. Wraps the text to fit in 68 columns.""" | ||
Patrick Mezard
|
r13590 | return fill(text, 68) | ||
Yuya Nishihara
|
r37240 | @templatefilter('fill76', intype=bytes) | ||
Patrick Mezard
|
r13590 | def fill76(text): | ||
FUJIWARA Katsunori
|
r28693 | """Any text. Wraps the text to fit in 76 columns.""" | ||
Patrick Mezard
|
r13590 | return fill(text, 76) | ||
Yuya Nishihara
|
r37240 | @templatefilter('firstline', intype=bytes) | ||
Matt Mackall
|
r5976 | def firstline(text): | ||
FUJIWARA Katsunori
|
r28693 | """Any text. Returns the first line of text.""" | ||
Matt Mackall
|
r5976 | try: | ||
Nicolas Dumazet
|
r9136 | return text.splitlines(True)[0].rstrip('\r\n') | ||
Matt Mackall
|
r5976 | except IndexError: | ||
return '' | ||||
Yuya Nishihara
|
r37240 | @templatefilter('hex', intype=bytes) | ||
Patrick Mezard
|
r13590 | def hexfilter(text): | ||
FUJIWARA Katsunori
|
r28693 | """Any text. Convert a binary Mercurial node identifier into | ||
Patrick Mezard
|
r13591 | its long hexadecimal representation. | ||
""" | ||||
Patrick Mezard
|
r13590 | return node.hex(text) | ||
Yuya Nishihara
|
r37244 | @templatefilter('hgdate', intype=templateutil.date) | ||
Patrick Mezard
|
r13590 | def hgdate(text): | ||
FUJIWARA Katsunori
|
r28693 | """Date. Returns the date as a pair of numbers: "1157407993 | ||
Yuya Nishihara
|
r38319 | 25200" (Unix timestamp, timezone offset). | ||
Patrick Mezard
|
r13591 | """ | ||
Patrick Mezard
|
r13590 | return "%d %d" % text | ||
Yuya Nishihara
|
r37244 | @templatefilter('isodate', intype=templateutil.date) | ||
Patrick Mezard
|
r13590 | def isodate(text): | ||
FUJIWARA Katsunori
|
r28693 | """Date. Returns the date in ISO 8601 format: "2009-08-18 13:00 | ||
Patrick Mezard
|
r13591 | +0200". | ||
""" | ||||
Boris Feld
|
r36625 | return dateutil.datestr(text, '%Y-%m-%d %H:%M %1%2') | ||
Patrick Mezard
|
r13590 | |||
Yuya Nishihara
|
r37244 | @templatefilter('isodatesec', intype=templateutil.date) | ||
Patrick Mezard
|
r13590 | def isodatesec(text): | ||
FUJIWARA Katsunori
|
r28693 | """Date. Returns the date in ISO 8601 format, including | ||
Patrick Mezard
|
r13591 | seconds: "2009-08-18 13:00:13 +0200". See also the rfc3339date | ||
filter. | ||||
""" | ||||
Boris Feld
|
r36625 | return dateutil.datestr(text, '%Y-%m-%d %H:%M:%S %1%2') | ||
Patrick Mezard
|
r13590 | |||
Matt Mackall
|
r5976 | def indent(text, prefix): | ||
'''indent each non-empty line of text after first with prefix.''' | ||||
lines = text.splitlines() | ||||
num_lines = len(lines) | ||||
Nicolas Dumazet
|
r9387 | endswithnewline = text[-1:] == '\n' | ||
Matt Mackall
|
r5976 | def indenter(): | ||
Gregory Szorc
|
r38806 | for i in pycompat.xrange(num_lines): | ||
Matt Mackall
|
r5976 | l = lines[i] | ||
if i and l.strip(): | ||||
yield prefix | ||||
yield l | ||||
Nicolas Dumazet
|
r9387 | if i < num_lines - 1 or endswithnewline: | ||
Matt Mackall
|
r5976 | yield '\n' | ||
return "".join(indenter()) | ||||
FUJIWARA Katsunori
|
r28693 | @templatefilter('json') | ||
Yuya Nishihara
|
r31782 | def json(obj, paranoid=True): | ||
Yuya Nishihara
|
r37967 | """Any object. Serializes the object to a JSON formatted text.""" | ||
Yuya Nishihara
|
r31780 | if obj is None: | ||
return 'null' | ||||
elif obj is False: | ||||
return 'false' | ||||
elif obj is True: | ||||
return 'true' | ||||
Matt Harbison
|
r31728 | elif isinstance(obj, (int, long, float)): | ||
Pulkit Goyal
|
r32127 | return pycompat.bytestr(obj) | ||
Pulkit Goyal
|
r32128 | elif isinstance(obj, bytes): | ||
Yuya Nishihara
|
r31782 | return '"%s"' % encoding.jsonescape(obj, paranoid=paranoid) | ||
Martin von Zweigbergk
|
r38044 | elif isinstance(obj, type(u'')): | ||
Augie Fackler
|
r34838 | raise error.ProgrammingError( | ||
Martin von Zweigbergk
|
r38044 | 'Mercurial only does output with bytes: %r' % obj) | ||
Augie Fackler
|
r14967 | elif util.safehasattr(obj, 'keys'): | ||
Yuya Nishihara
|
r32743 | out = ['"%s": %s' % (encoding.jsonescape(k, paranoid=paranoid), | ||
json(v, paranoid)) | ||||
Yuya Nishihara
|
r31781 | for k, v in sorted(obj.iteritems())] | ||
Dirkjan Ochtman
|
r6691 | return '{' + ', '.join(out) + '}' | ||
Augie Fackler
|
r14944 | elif util.safehasattr(obj, '__iter__'): | ||
Yuya Nishihara
|
r32743 | out = [json(i, paranoid) for i in obj] | ||
Dirkjan Ochtman
|
r6691 | return '[' + ', '.join(out) + ']' | ||
Yuya Nishihara
|
r37247 | raise error.ProgrammingError('cannot encode %r' % obj) | ||
Dirkjan Ochtman
|
r6691 | |||
Yuya Nishihara
|
r37240 | @templatefilter('lower', intype=bytes) | ||
Yuya Nishihara
|
r24566 | def lower(text): | ||
FUJIWARA Katsunori
|
r28693 | """Any text. Converts the text to lowercase.""" | ||
Yuya Nishihara
|
r24566 | return encoding.lower(text) | ||
Yuya Nishihara
|
r37240 | @templatefilter('nonempty', intype=bytes) | ||
Pulkit Goyal
|
r36570 | def nonempty(text): | ||
FUJIWARA Katsunori
|
r28693 | """Any text. Returns '(none)' if the string is empty.""" | ||
Pulkit Goyal
|
r36570 | return text or "(none)" | ||
Patrick Mezard
|
r13588 | |||
Yuya Nishihara
|
r37240 | @templatefilter('obfuscate', intype=bytes) | ||
Patrick Mezard
|
r13588 | def obfuscate(text): | ||
FUJIWARA Katsunori
|
r28693 | """Any text. Returns the input text rendered as a sequence of | ||
Patrick Mezard
|
r13591 | XML entities. | ||
""" | ||||
Augie Fackler
|
r36581 | text = unicode(text, pycompat.sysstr(encoding.encoding), r'replace') | ||
Patrick Mezard
|
r13588 | return ''.join(['&#%d;' % ord(c) for c in text]) | ||
Yuya Nishihara
|
r37240 | @templatefilter('permissions', intype=bytes) | ||
Patrick Mezard
|
r13588 | def permissions(flags): | ||
if "l" in flags: | ||||
return "lrwxrwxrwx" | ||||
if "x" in flags: | ||||
return "-rwxr-xr-x" | ||||
return "-rw-r--r--" | ||||
Yuya Nishihara
|
r37240 | @templatefilter('person', intype=bytes) | ||
Patrick Mezard
|
r13588 | def person(author): | ||
FUJIWARA Katsunori
|
r28693 | """Any text. Returns the name before an email address, | ||
"Yann E. MORIN"
|
r16235 | interpreting it as per RFC 5322. | ||
""" | ||||
Connor Sheehan
|
r37173 | return stringutil.person(author) | ||
Patrick Mezard
|
r13588 | |||
Yuya Nishihara
|
r37240 | @templatefilter('revescape', intype=bytes) | ||
r25778 | def revescape(text): | |||
FUJIWARA Katsunori
|
r28693 | """Any text. Escapes all "special" characters, except @. | ||
r25778 | Forward slashes are escaped twice to prevent web servers from prematurely | |||
unescaping them. For example, "@foo bar/baz" becomes "@foo%20bar%252Fbaz". | ||||
""" | ||||
timeless
|
r28883 | return urlreq.quote(text, safe='/@').replace('/', '%252F') | ||
r25778 | ||||
Yuya Nishihara
|
r37244 | @templatefilter('rfc3339date', intype=templateutil.date) | ||
Patrick Mezard
|
r13590 | def rfc3339date(text): | ||
FUJIWARA Katsunori
|
r28693 | """Date. Returns a date using the Internet date format | ||
Patrick Mezard
|
r13591 | specified in RFC 3339: "2009-08-18T13:00:13+02:00". | ||
""" | ||||
Boris Feld
|
r36625 | return dateutil.datestr(text, "%Y-%m-%dT%H:%M:%S%1:%2") | ||
Patrick Mezard
|
r13590 | |||
Yuya Nishihara
|
r37244 | @templatefilter('rfc822date', intype=templateutil.date) | ||
Patrick Mezard
|
r13590 | def rfc822date(text): | ||
FUJIWARA Katsunori
|
r28693 | """Date. Returns a date using the same format used in email | ||
Patrick Mezard
|
r13591 | headers: "Tue, 18 Aug 2009 13:00:13 +0200". | ||
""" | ||||
Boris Feld
|
r36625 | return dateutil.datestr(text, "%a, %d %b %Y %H:%M:%S %1%2") | ||
Patrick Mezard
|
r13590 | |||
Yuya Nishihara
|
r37240 | @templatefilter('short', intype=bytes) | ||
Patrick Mezard
|
r13590 | def short(text): | ||
FUJIWARA Katsunori
|
r28693 | """Changeset hash. Returns the short form of a changeset hash, | ||
Patrick Mezard
|
r13591 | i.e. a 12 hexadecimal digit string. | ||
""" | ||||
Patrick Mezard
|
r13590 | return text[:12] | ||
Yuya Nishihara
|
r37240 | @templatefilter('shortbisect', intype=bytes) | ||
Yuya Nishihara
|
r36848 | def shortbisect(label): | ||
"""Any text. Treats `label` as a bisection status, and | ||||
"Yann E. MORIN"
|
r15155 | returns a single-character representing the status (G: good, B: bad, | ||
S: skipped, U: untested, I: ignored). Returns single space if `text` | ||||
is not a valid bisection status. | ||||
""" | ||||
Yuya Nishihara
|
r36848 | if label: | ||
Yuya Nishihara
|
r36849 | return label[0:1].upper() | ||
Yuya Nishihara
|
r36848 | return ' ' | ||
"Yann E. MORIN"
|
r15155 | |||
Yuya Nishihara
|
r37244 | @templatefilter('shortdate', intype=templateutil.date) | ||
Patrick Mezard
|
r13590 | def shortdate(text): | ||
FUJIWARA Katsunori
|
r28693 | """Date. Returns a date like "2006-09-18".""" | ||
Boris Feld
|
r36625 | return dateutil.shortdate(text) | ||
Patrick Mezard
|
r13590 | |||
Yuya Nishihara
|
r37240 | @templatefilter('slashpath', intype=bytes) | ||
Yuya Nishihara
|
r35460 | def slashpath(path): | ||
"""Any text. Replaces the native path separator with slash.""" | ||||
return util.pconvert(path) | ||||
Yuya Nishihara
|
r37240 | @templatefilter('splitlines', intype=bytes) | ||
Ryan McElroy
|
r21820 | def splitlines(text): | ||
FUJIWARA Katsunori
|
r28693 | """Any text. Split text into a list of lines.""" | ||
Yuya Nishihara
|
r36939 | return templateutil.hybridlist(text.splitlines(), name='line') | ||
Ryan McElroy
|
r21820 | |||
Yuya Nishihara
|
r37240 | @templatefilter('stringescape', intype=bytes) | ||
Patrick Mezard
|
r13590 | def stringescape(text): | ||
Yuya Nishihara
|
r37102 | return stringutil.escapestr(text) | ||
Patrick Mezard
|
r13590 | |||
Yuya Nishihara
|
r37239 | @templatefilter('stringify', intype=bytes) | ||
Patrick Mezard
|
r13588 | def stringify(thing): | ||
FUJIWARA Katsunori
|
r28693 | """Any type. Turns the value into text by converting values into | ||
Patrick Mezard
|
r13591 | text and concatenating them. | ||
""" | ||||
Yuya Nishihara
|
r37239 | return thing # coerced by the intype | ||
Patrick Mezard
|
r13588 | |||
Yuya Nishihara
|
r37240 | @templatefilter('stripdir', intype=bytes) | ||
Aleix Conchillo Flaque
|
r8158 | def stripdir(text): | ||
FUJIWARA Katsunori
|
r28693 | """Treat the text as path and strip a directory level, if | ||
Patrick Mezard
|
r13591 | possible. For example, "foo" and "foo/bar" becomes "foo". | ||
""" | ||||
Aleix Conchillo Flaque
|
r8158 | dir = os.path.dirname(text) | ||
if dir == "": | ||||
return os.path.basename(text) | ||||
else: | ||||
return dir | ||||
Yuya Nishihara
|
r37240 | @templatefilter('tabindent', intype=bytes) | ||
Patrick Mezard
|
r13590 | def tabindent(text): | ||
FUJIWARA Katsunori
|
r28693 | """Any text. Returns the text, with every non-empty line | ||
Matt Mackall
|
r19467 | except the first starting with a tab character. | ||
Patrick Mezard
|
r13591 | """ | ||
Patrick Mezard
|
r13590 | return indent(text, '\t') | ||
Yuya Nishihara
|
r37240 | @templatefilter('upper', intype=bytes) | ||
Yuya Nishihara
|
r24566 | def upper(text): | ||
FUJIWARA Katsunori
|
r28693 | """Any text. Converts the text to uppercase.""" | ||
Yuya Nishihara
|
r24566 | return encoding.upper(text) | ||
Yuya Nishihara
|
r37240 | @templatefilter('urlescape', intype=bytes) | ||
Patrick Mezard
|
r13590 | def urlescape(text): | ||
FUJIWARA Katsunori
|
r28693 | """Any text. Escapes all "special" characters. For example, | ||
Patrick Mezard
|
r13591 | "foo bar" becomes "foo%20bar". | ||
""" | ||||
timeless
|
r28883 | return urlreq.quote(text) | ||
Patrick Mezard
|
r13590 | |||
Yuya Nishihara
|
r37240 | @templatefilter('user', intype=bytes) | ||
Patrick Mezard
|
r13590 | def userfilter(text): | ||
FUJIWARA Katsunori
|
r28693 | """Any text. Returns a short representation of a user name or email | ||
Matteo Capobianco
|
r16360 | address.""" | ||
Yuya Nishihara
|
r37102 | return stringutil.shortuser(text) | ||
Patrick Mezard
|
r13590 | |||
Yuya Nishihara
|
r37240 | @templatefilter('emailuser', intype=bytes) | ||
Matteo Capobianco
|
r16360 | def emailuser(text): | ||
FUJIWARA Katsunori
|
r28693 | """Any text. Returns the user portion of an email address.""" | ||
Yuya Nishihara
|
r37102 | return stringutil.emailuser(text) | ||
Matteo Capobianco
|
r16360 | |||
Yuya Nishihara
|
r37240 | @templatefilter('utf8', intype=bytes) | ||
Yuya Nishihara
|
r28209 | def utf8(text): | ||
FUJIWARA Katsunori
|
r28693 | """Any text. Converts from the local character encoding to UTF-8.""" | ||
Yuya Nishihara
|
r28209 | return encoding.fromlocal(text) | ||
Yuya Nishihara
|
r37240 | @templatefilter('xmlescape', intype=bytes) | ||
Patrick Mezard
|
r13588 | def xmlescape(text): | ||
text = (text | ||||
.replace('&', '&') | ||||
.replace('<', '<') | ||||
.replace('>', '>') | ||||
.replace('"', '"') | ||||
.replace("'", ''')) # ' invalid in HTML | ||||
return re.sub('[\x00-\x08\x0B\x0C\x0E-\x1F]', ' ', text) | ||||
Rocco Rutte
|
r8234 | |||
Angel Ezquerra
|
r18627 | def websub(text, websubtable): | ||
""":websub: Any text. Only applies to hgweb. Applies the regular | ||||
expression replacements defined in the websub section. | ||||
""" | ||||
if websubtable: | ||||
for regexp, format in websubtable: | ||||
text = regexp.sub(format, text) | ||||
return text | ||||
FUJIWARA Katsunori
|
r28692 | def loadfilter(ui, extname, registrarobj): | ||
"""Load template filter from specified registrarobj | ||||
""" | ||||
for name, func in registrarobj._table.iteritems(): | ||||
filters[name] = func | ||||
Patrick Mezard
|
r13591 | # tell hggettext to extract docstrings from these functions: | ||
i18nfunctions = filters.values() | ||||