templatefilters.py
560 lines
| 15.2 KiB
| text/x-python
|
PythonLexer
/ mercurial / templatefilters.py
Yuya Nishihara
|
r32740 | # templatefilters.py - common template expansion filters | ||
Matt Mackall
|
r5976 | # | ||
Raphaël Gomès
|
r47575 | # Copyright 2005-2008 Olivia Mackall <olivia@selenic.com> | ||
Matt Mackall
|
r5976 | # | ||
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 | |||
Matt Harbison
|
r52756 | from __future__ import annotations | ||
Gregory Szorc
|
r25983 | |||
import os | ||||
import re | ||||
import time | ||||
Yuya Nishihara
|
r37246 | from .i18n import _ | ||
Joerg Sonnenberger
|
r46729 | from .node import hex | ||
Gregory Szorc
|
r25983 | from . import ( | ||
encoding, | ||||
Augie Fackler
|
r34838 | error, | ||
Pulkit Goyal
|
r32126 | pycompat, | ||
FUJIWARA Katsunori
|
r28693 | registrar, | ||
Yuya Nishihara
|
r45081 | smartset, | ||
Yuya Nishihara
|
r36938 | templateutil, | ||
Augie Fackler
|
r34696 | url, | ||
Gregory Szorc
|
r25983 | util, | ||
) | ||||
Yuya Nishihara
|
r37102 | from .utils import ( | ||
Yuya Nishihara
|
r42164 | cborutil, | ||
Yuya Nishihara
|
r37102 | dateutil, | ||
stringutil, | ||||
) | ||||
Matt Mackall
|
r5976 | |||
timeless
|
r28883 | urlerr = util.urlerr | ||
urlreq = util.urlreq | ||||
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) | ||||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | @templatefilter(b'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. | ||
""" | ||||
Augie Fackler
|
r43347 | return text.replace(b'\n', b'<br/>\n') | ||
Dirkjan Ochtman
|
r8360 | |||
Augie Fackler
|
r43346 | |||
agescales = [ | ||||
Augie Fackler
|
r43347 | (b"year", 3600 * 24 * 365, b'Y'), | ||
(b"month", 3600 * 24 * 30, b'M'), | ||||
(b"week", 3600 * 24 * 7, b'W'), | ||||
(b"day", 3600 * 24, b'd'), | ||||
(b"hour", 3600, b'h'), | ||||
(b"minute", 60, b'm'), | ||||
(b"second", 1, b's'), | ||||
Augie Fackler
|
r43346 | ] | ||
Matt Mackall
|
r5976 | |||
Augie Fackler
|
r43347 | @templatefilter(b'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 | ||||
Augie Fackler
|
r43347 | return t + b"s" | ||
Augie Fackler
|
r43346 | |||
David Soria Parra
|
r19736 | def fmt(t, c, a): | ||
if abbrev: | ||||
Augie Fackler
|
r43347 | return b"%d%s" % (c, a) | ||
return b"%d %s" % (c, plural(t, c)) | ||||
Matt Mackall
|
r5976 | |||
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: | ||||
Augie Fackler
|
r43347 | return b'in the distant future' | ||
timeless
|
r13666 | 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: | ||
Augie Fackler
|
r43347 | return b'%s from now' % fmt(t, n, a) | ||
return b'%s ago' % fmt(t, n, a) | ||||
Matt Mackall
|
r5976 | |||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | @templatefilter(b'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) | ||
Augie Fackler
|
r43346 | |||
Yuya Nishihara
|
r45087 | def _tocborencodable(obj): | ||
if isinstance(obj, smartset.abstractsmartset): | ||||
return list(obj) | ||||
return obj | ||||
Augie Fackler
|
r43347 | @templatefilter(b'cbor') | ||
Yuya Nishihara
|
r42164 | def cbor(obj): | ||
"""Any object. Serializes the object to CBOR bytes.""" | ||||
Yuya Nishihara
|
r45087 | # cborutil is stricter about type than json() filter | ||
obj = pycompat.rapply(_tocborencodable, obj) | ||||
Yuya Nishihara
|
r42164 | return b''.join(cborutil.streamencode(obj)) | ||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | @templatefilter(b'commondir') | ||
Martin von Zweigbergk
|
r38323 | 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 "". | ||||
""" | ||||
Augie Fackler
|
r43346 | |||
Joerg Sonnenberger
|
r38198 | def common(a, b): | ||
if len(a) > len(b): | ||||
Augie Fackler
|
r43346 | a = b[: len(a)] | ||
Joerg Sonnenberger
|
r38198 | elif len(b) > len(a): | ||
Augie Fackler
|
r43346 | b = b[: len(a)] | ||
Joerg Sonnenberger
|
r38198 | if a == b: | ||
return a | ||||
Manuel Jacob
|
r50179 | for i in range(len(a)): | ||
Joerg Sonnenberger
|
r38198 | if a[i] != b[i]: | ||
return a[:i] | ||||
return a | ||||
Augie Fackler
|
r43346 | |||
Joerg Sonnenberger
|
r38198 | try: | ||
if not filelist: | ||||
Augie Fackler
|
r43347 | return b"" | ||
dirlist = [f.lstrip(b'/').split(b'/')[:-1] for f in filelist] | ||||
Joerg Sonnenberger
|
r38198 | if len(dirlist) == 1: | ||
Augie Fackler
|
r43347 | return b'/'.join(dirlist[0]) | ||
Joerg Sonnenberger
|
r38198 | 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]. | ||||
Augie Fackler
|
r43347 | return b'/'.join(common(a, b)) | ||
Joerg Sonnenberger
|
r38198 | except TypeError: | ||
Augie Fackler
|
r43347 | raise error.ParseError(_(b'argument is not a list of text')) | ||
Joerg Sonnenberger
|
r38198 | |||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | @templatefilter(b'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: | ||||
Augie Fackler
|
r43347 | raise error.ParseError(_(b'not countable')) | ||
Anton Shestakov
|
r22668 | |||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | @templatefilter(b'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) | ||||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | @templatefilter(b'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``. | ||||
""" | ||||
Augie Fackler
|
r43347 | f = author.find(b'@') | ||
Patrick Mezard
|
r13588 | if f == -1: | ||
Augie Fackler
|
r43347 | return b'' | ||
Augie Fackler
|
r43346 | author = author[f + 1 :] | ||
Augie Fackler
|
r43347 | f = author.find(b'>') | ||
Patrick Mezard
|
r13588 | if f >= 0: | ||
author = author[:f] | ||||
return author | ||||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | @templatefilter(b'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 | |||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | @templatefilter(b'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
|
r43347 | return url.escape(text.replace(b'\0', b''), True) | ||
Patrick Mezard
|
r13590 | |||
Augie Fackler
|
r43346 | |||
Matt Mackall
|
r5976 | para_re = None | ||
space_re = None | ||||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | def fill(text, width, initindent=b'', hangindent=b''): | ||
Sean Farley
|
r19228 | '''fill many paragraphs with optional indentation.''' | ||
Matt Mackall
|
r5976 | global para_re, space_re | ||
if para_re is None: | ||||
Augie Fackler
|
r43347 | para_re = re.compile(b'(\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 | ||
Augie Fackler
|
r43346 | yield ( | ||
encoding.unitolocal(uctext[:w]), | ||||
encoding.unitolocal(uctext[w:]), | ||||
) | ||||
Matt Mackall
|
r5976 | break | ||
Augie Fackler
|
r43346 | yield text[start : m.start(0)], m.group(1) | ||
Matt Mackall
|
r5976 | start = m.end(1) | ||
Augie Fackler
|
r43347 | return b"".join( | ||
Augie Fackler
|
r43346 | [ | ||
stringutil.wrap( | ||||
Augie Fackler
|
r43347 | space_re.sub(b' ', stringutil.wrap(para, width)), | ||
Augie Fackler
|
r43346 | width, | ||
initindent, | ||||
hangindent, | ||||
) | ||||
+ rest | ||||
for para, rest in findparas() | ||||
] | ||||
) | ||||
Matt Mackall
|
r5976 | |||
Augie Fackler
|
r43347 | @templatefilter(b'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) | ||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | @templatefilter(b'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) | ||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | @templatefilter(b'firstline', intype=bytes) | ||
Matt Mackall
|
r5976 | def firstline(text): | ||
FUJIWARA Katsunori
|
r28693 | """Any text. Returns the first line of text.""" | ||
Martin von Zweigbergk
|
r49885 | return stringutil.firstline(text) | ||
Matt Mackall
|
r5976 | |||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | @templatefilter(b'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. | ||
""" | ||||
Joerg Sonnenberger
|
r46729 | return hex(text) | ||
Patrick Mezard
|
r13590 | |||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | @templatefilter(b'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 | """ | ||
Augie Fackler
|
r43347 | return b"%d %d" % text | ||
Patrick Mezard
|
r13590 | |||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | @templatefilter(b'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". | ||
""" | ||||
Augie Fackler
|
r43347 | return dateutil.datestr(text, b'%Y-%m-%d %H:%M %1%2') | ||
Patrick Mezard
|
r13590 | |||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | @templatefilter(b'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. | ||||
""" | ||||
Augie Fackler
|
r43347 | return dateutil.datestr(text, b'%Y-%m-%d %H:%M:%S %1%2') | ||
Patrick Mezard
|
r13590 | |||
Augie Fackler
|
r43346 | |||
Martin von Zweigbergk
|
r44093 | def indent(text, prefix, firstline=b''): | ||
Matt Mackall
|
r5976 | '''indent each non-empty line of text after first with prefix.''' | ||
lines = text.splitlines() | ||||
num_lines = len(lines) | ||||
Augie Fackler
|
r43347 | endswithnewline = text[-1:] == b'\n' | ||
Augie Fackler
|
r43346 | |||
Matt Mackall
|
r5976 | def indenter(): | ||
Manuel Jacob
|
r50179 | for i in range(num_lines): | ||
Matt Mackall
|
r5976 | l = lines[i] | ||
Martin von Zweigbergk
|
r44093 | if l.strip(): | ||
yield prefix if i else firstline | ||||
Matt Mackall
|
r5976 | yield l | ||
Nicolas Dumazet
|
r9387 | if i < num_lines - 1 or endswithnewline: | ||
Augie Fackler
|
r43347 | yield b'\n' | ||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | return b"".join(indenter()) | ||
Matt Mackall
|
r5976 | |||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | @templatefilter(b'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: | ||
Augie Fackler
|
r43347 | return b'null' | ||
Yuya Nishihara
|
r31780 | elif obj is False: | ||
Augie Fackler
|
r43347 | return b'false' | ||
Yuya Nishihara
|
r31780 | elif obj is True: | ||
Augie Fackler
|
r43347 | return b'true' | ||
Gregory Szorc
|
r49787 | elif isinstance(obj, (int, int, float)): | ||
Pulkit Goyal
|
r32127 | return pycompat.bytestr(obj) | ||
Pulkit Goyal
|
r32128 | elif isinstance(obj, bytes): | ||
Augie Fackler
|
r43347 | return b'"%s"' % encoding.jsonescape(obj, paranoid=paranoid) | ||
Martin von Zweigbergk
|
r38044 | elif isinstance(obj, type(u'')): | ||
Augie Fackler
|
r34838 | raise error.ProgrammingError( | ||
Augie Fackler
|
r43347 | b'Mercurial only does output with bytes: %r' % obj | ||
Augie Fackler
|
r43346 | ) | ||
r51821 | elif hasattr(obj, 'keys'): | |||
Augie Fackler
|
r43346 | out = [ | ||
Augie Fackler
|
r43347 | b'"%s": %s' | ||
Augie Fackler
|
r43346 | % (encoding.jsonescape(k, paranoid=paranoid), json(v, paranoid)) | ||
Gregory Szorc
|
r49768 | for k, v in sorted(obj.items()) | ||
Augie Fackler
|
r43346 | ] | ||
Augie Fackler
|
r43347 | return b'{' + b', '.join(out) + b'}' | ||
r51821 | elif hasattr(obj, '__iter__'): | |||
Yuya Nishihara
|
r32743 | out = [json(i, paranoid) for i in obj] | ||
Augie Fackler
|
r43347 | return b'[' + b', '.join(out) + b']' | ||
raise error.ProgrammingError(b'cannot encode %r' % obj) | ||||
Dirkjan Ochtman
|
r6691 | |||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | @templatefilter(b'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) | ||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | @templatefilter(b'nonempty', intype=bytes) | ||
Pulkit Goyal
|
r36570 | def nonempty(text): | ||
FUJIWARA Katsunori
|
r28693 | """Any text. Returns '(none)' if the string is empty.""" | ||
Augie Fackler
|
r43347 | return text or b"(none)" | ||
Patrick Mezard
|
r13588 | |||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | @templatefilter(b'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. | ||
""" | ||||
Gregory Szorc
|
r49789 | text = str(text, pycompat.sysstr(encoding.encoding), r'replace') | ||
Augie Fackler
|
r43347 | return b''.join([b'&#%d;' % ord(c) for c in text]) | ||
Patrick Mezard
|
r13588 | |||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | @templatefilter(b'permissions', intype=bytes) | ||
Patrick Mezard
|
r13588 | def permissions(flags): | ||
Augie Fackler
|
r43347 | if b"l" in flags: | ||
return b"lrwxrwxrwx" | ||||
if b"x" in flags: | ||||
return b"-rwxr-xr-x" | ||||
return b"-rw-r--r--" | ||||
Patrick Mezard
|
r13588 | |||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | @templatefilter(b'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 | |||
Augie Fackler
|
r43346 | |||
Manuel Jacob
|
r50389 | @templatefilter(b'reverse') | ||
def reverse(list_): | ||||
"""List. Reverses the order of list items.""" | ||||
if isinstance(list_, list): | ||||
return templateutil.hybridlist(list_[::-1], name=b'item') | ||||
raise error.ParseError(_(b'not reversible')) | ||||
Augie Fackler
|
r43347 | @templatefilter(b'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". | ||||
""" | ||||
Augie Fackler
|
r43347 | return urlreq.quote(text, safe=b'/@').replace(b'/', b'%252F') | ||
r25778 | ||||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | @templatefilter(b'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". | ||
""" | ||||
Augie Fackler
|
r43347 | return dateutil.datestr(text, b"%Y-%m-%dT%H:%M:%S%1:%2") | ||
Patrick Mezard
|
r13590 | |||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | @templatefilter(b'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". | ||
""" | ||||
Augie Fackler
|
r43347 | return dateutil.datestr(text, b"%a, %d %b %Y %H:%M:%S %1%2") | ||
Patrick Mezard
|
r13590 | |||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | @templatefilter(b'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] | ||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | @templatefilter(b'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() | ||
Augie Fackler
|
r43347 | return b' ' | ||
"Yann E. MORIN"
|
r15155 | |||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | @templatefilter(b'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 | |||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | @templatefilter(b'slashpath', intype=bytes) | ||
Yuya Nishihara
|
r35460 | def slashpath(path): | ||
"""Any text. Replaces the native path separator with slash.""" | ||||
return util.pconvert(path) | ||||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | @templatefilter(b'splitlines', intype=bytes) | ||
Ryan McElroy
|
r21820 | def splitlines(text): | ||
FUJIWARA Katsunori
|
r28693 | """Any text. Split text into a list of lines.""" | ||
Augie Fackler
|
r43347 | return templateutil.hybridlist(text.splitlines(), name=b'line') | ||
Ryan McElroy
|
r21820 | |||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | @templatefilter(b'stringescape', intype=bytes) | ||
Patrick Mezard
|
r13590 | def stringescape(text): | ||
Yuya Nishihara
|
r37102 | return stringutil.escapestr(text) | ||
Patrick Mezard
|
r13590 | |||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | @templatefilter(b'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 | |||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | @templatefilter(b'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) | ||
Augie Fackler
|
r43347 | if dir == b"": | ||
Aleix Conchillo Flaque
|
r8158 | return os.path.basename(text) | ||
else: | ||||
return dir | ||||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | @templatefilter(b'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 | """ | ||
Augie Fackler
|
r43347 | return indent(text, b'\t') | ||
Patrick Mezard
|
r13590 | |||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | @templatefilter(b'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) | ||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | @templatefilter(b'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 | |||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | @templatefilter(b'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 | |||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | @templatefilter(b'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 | |||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | @templatefilter(b'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) | ||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | @templatefilter(b'xmlescape', intype=bytes) | ||
Patrick Mezard
|
r13588 | def xmlescape(text): | ||
Augie Fackler
|
r43346 | text = ( | ||
Augie Fackler
|
r43347 | text.replace(b'&', b'&') | ||
.replace(b'<', b'<') | ||||
.replace(b'>', b'>') | ||||
.replace(b'"', b'"') | ||||
.replace(b"'", b''') | ||||
Augie Fackler
|
r43346 | ) # ' invalid in HTML | ||
Augie Fackler
|
r43347 | return re.sub(b'[\x00-\x08\x0B\x0C\x0E-\x1F]', b' ', text) | ||
Rocco Rutte
|
r8234 | |||
Augie Fackler
|
r43346 | |||
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 | ||||
Augie Fackler
|
r43346 | |||
FUJIWARA Katsunori
|
r28692 | def loadfilter(ui, extname, registrarobj): | ||
Augie Fackler
|
r46554 | """Load template filter from specified registrarobj""" | ||
Gregory Szorc
|
r49768 | for name, func in registrarobj._table.items(): | ||
FUJIWARA Katsunori
|
r28692 | filters[name] = func | ||
Augie Fackler
|
r43346 | |||
Patrick Mezard
|
r13591 | # tell hggettext to extract docstrings from these functions: | ||
i18nfunctions = filters.values() | ||||