##// END OF EJS Templates
templates: make `firstline` filter not keep '\v', '\f' and similar...
Martin von Zweigbergk -
r49884:1138674e default
parent child Browse files
Show More
@@ -1,554 +1,554 b''
1 # templatefilters.py - common template expansion filters
1 # templatefilters.py - common template expansion filters
2 #
2 #
3 # Copyright 2005-2008 Olivia Mackall <olivia@selenic.com>
3 # Copyright 2005-2008 Olivia Mackall <olivia@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8
8
9 import os
9 import os
10 import re
10 import re
11 import time
11 import time
12
12
13 from .i18n import _
13 from .i18n import _
14 from .node import hex
14 from .node import hex
15 from . import (
15 from . import (
16 encoding,
16 encoding,
17 error,
17 error,
18 pycompat,
18 pycompat,
19 registrar,
19 registrar,
20 smartset,
20 smartset,
21 templateutil,
21 templateutil,
22 url,
22 url,
23 util,
23 util,
24 )
24 )
25 from .utils import (
25 from .utils import (
26 cborutil,
26 cborutil,
27 dateutil,
27 dateutil,
28 stringutil,
28 stringutil,
29 )
29 )
30
30
31 urlerr = util.urlerr
31 urlerr = util.urlerr
32 urlreq = util.urlreq
32 urlreq = util.urlreq
33
33
34 # filters are callables like:
34 # filters are callables like:
35 # fn(obj)
35 # fn(obj)
36 # with:
36 # with:
37 # obj - object to be filtered (text, date, list and so on)
37 # obj - object to be filtered (text, date, list and so on)
38 filters = {}
38 filters = {}
39
39
40 templatefilter = registrar.templatefilter(filters)
40 templatefilter = registrar.templatefilter(filters)
41
41
42
42
43 @templatefilter(b'addbreaks', intype=bytes)
43 @templatefilter(b'addbreaks', intype=bytes)
44 def addbreaks(text):
44 def addbreaks(text):
45 """Any text. Add an XHTML "<br />" tag before the end of
45 """Any text. Add an XHTML "<br />" tag before the end of
46 every line except the last.
46 every line except the last.
47 """
47 """
48 return text.replace(b'\n', b'<br/>\n')
48 return text.replace(b'\n', b'<br/>\n')
49
49
50
50
51 agescales = [
51 agescales = [
52 (b"year", 3600 * 24 * 365, b'Y'),
52 (b"year", 3600 * 24 * 365, b'Y'),
53 (b"month", 3600 * 24 * 30, b'M'),
53 (b"month", 3600 * 24 * 30, b'M'),
54 (b"week", 3600 * 24 * 7, b'W'),
54 (b"week", 3600 * 24 * 7, b'W'),
55 (b"day", 3600 * 24, b'd'),
55 (b"day", 3600 * 24, b'd'),
56 (b"hour", 3600, b'h'),
56 (b"hour", 3600, b'h'),
57 (b"minute", 60, b'm'),
57 (b"minute", 60, b'm'),
58 (b"second", 1, b's'),
58 (b"second", 1, b's'),
59 ]
59 ]
60
60
61
61
62 @templatefilter(b'age', intype=templateutil.date)
62 @templatefilter(b'age', intype=templateutil.date)
63 def age(date, abbrev=False):
63 def age(date, abbrev=False):
64 """Date. Returns a human-readable date/time difference between the
64 """Date. Returns a human-readable date/time difference between the
65 given date/time and the current date/time.
65 given date/time and the current date/time.
66 """
66 """
67
67
68 def plural(t, c):
68 def plural(t, c):
69 if c == 1:
69 if c == 1:
70 return t
70 return t
71 return t + b"s"
71 return t + b"s"
72
72
73 def fmt(t, c, a):
73 def fmt(t, c, a):
74 if abbrev:
74 if abbrev:
75 return b"%d%s" % (c, a)
75 return b"%d%s" % (c, a)
76 return b"%d %s" % (c, plural(t, c))
76 return b"%d %s" % (c, plural(t, c))
77
77
78 now = time.time()
78 now = time.time()
79 then = date[0]
79 then = date[0]
80 future = False
80 future = False
81 if then > now:
81 if then > now:
82 future = True
82 future = True
83 delta = max(1, int(then - now))
83 delta = max(1, int(then - now))
84 if delta > agescales[0][1] * 30:
84 if delta > agescales[0][1] * 30:
85 return b'in the distant future'
85 return b'in the distant future'
86 else:
86 else:
87 delta = max(1, int(now - then))
87 delta = max(1, int(now - then))
88 if delta > agescales[0][1] * 2:
88 if delta > agescales[0][1] * 2:
89 return dateutil.shortdate(date)
89 return dateutil.shortdate(date)
90
90
91 for t, s, a in agescales:
91 for t, s, a in agescales:
92 n = delta // s
92 n = delta // s
93 if n >= 2 or s == 1:
93 if n >= 2 or s == 1:
94 if future:
94 if future:
95 return b'%s from now' % fmt(t, n, a)
95 return b'%s from now' % fmt(t, n, a)
96 return b'%s ago' % fmt(t, n, a)
96 return b'%s ago' % fmt(t, n, a)
97
97
98
98
99 @templatefilter(b'basename', intype=bytes)
99 @templatefilter(b'basename', intype=bytes)
100 def basename(path):
100 def basename(path):
101 """Any text. Treats the text as a path, and returns the last
101 """Any text. Treats the text as a path, and returns the last
102 component of the path after splitting by the path separator.
102 component of the path after splitting by the path separator.
103 For example, "foo/bar/baz" becomes "baz" and "foo/bar//" becomes "".
103 For example, "foo/bar/baz" becomes "baz" and "foo/bar//" becomes "".
104 """
104 """
105 return os.path.basename(path)
105 return os.path.basename(path)
106
106
107
107
108 def _tocborencodable(obj):
108 def _tocborencodable(obj):
109 if isinstance(obj, smartset.abstractsmartset):
109 if isinstance(obj, smartset.abstractsmartset):
110 return list(obj)
110 return list(obj)
111 return obj
111 return obj
112
112
113
113
114 @templatefilter(b'cbor')
114 @templatefilter(b'cbor')
115 def cbor(obj):
115 def cbor(obj):
116 """Any object. Serializes the object to CBOR bytes."""
116 """Any object. Serializes the object to CBOR bytes."""
117 # cborutil is stricter about type than json() filter
117 # cborutil is stricter about type than json() filter
118 obj = pycompat.rapply(_tocborencodable, obj)
118 obj = pycompat.rapply(_tocborencodable, obj)
119 return b''.join(cborutil.streamencode(obj))
119 return b''.join(cborutil.streamencode(obj))
120
120
121
121
122 @templatefilter(b'commondir')
122 @templatefilter(b'commondir')
123 def commondir(filelist):
123 def commondir(filelist):
124 """List of text. Treats each list item as file name with /
124 """List of text. Treats each list item as file name with /
125 as path separator and returns the longest common directory
125 as path separator and returns the longest common directory
126 prefix shared by all list items.
126 prefix shared by all list items.
127 Returns the empty string if no common prefix exists.
127 Returns the empty string if no common prefix exists.
128
128
129 The list items are not normalized, i.e. "foo/../bar" is handled as
129 The list items are not normalized, i.e. "foo/../bar" is handled as
130 file "bar" in the directory "foo/..". Leading slashes are ignored.
130 file "bar" in the directory "foo/..". Leading slashes are ignored.
131
131
132 For example, ["foo/bar/baz", "foo/baz/bar"] becomes "foo" and
132 For example, ["foo/bar/baz", "foo/baz/bar"] becomes "foo" and
133 ["foo/bar", "baz"] becomes "".
133 ["foo/bar", "baz"] becomes "".
134 """
134 """
135
135
136 def common(a, b):
136 def common(a, b):
137 if len(a) > len(b):
137 if len(a) > len(b):
138 a = b[: len(a)]
138 a = b[: len(a)]
139 elif len(b) > len(a):
139 elif len(b) > len(a):
140 b = b[: len(a)]
140 b = b[: len(a)]
141 if a == b:
141 if a == b:
142 return a
142 return a
143 for i in pycompat.xrange(len(a)):
143 for i in pycompat.xrange(len(a)):
144 if a[i] != b[i]:
144 if a[i] != b[i]:
145 return a[:i]
145 return a[:i]
146 return a
146 return a
147
147
148 try:
148 try:
149 if not filelist:
149 if not filelist:
150 return b""
150 return b""
151 dirlist = [f.lstrip(b'/').split(b'/')[:-1] for f in filelist]
151 dirlist = [f.lstrip(b'/').split(b'/')[:-1] for f in filelist]
152 if len(dirlist) == 1:
152 if len(dirlist) == 1:
153 return b'/'.join(dirlist[0])
153 return b'/'.join(dirlist[0])
154 a = min(dirlist)
154 a = min(dirlist)
155 b = max(dirlist)
155 b = max(dirlist)
156 # The common prefix of a and b is shared with all
156 # The common prefix of a and b is shared with all
157 # elements of the list since Python sorts lexicographical
157 # elements of the list since Python sorts lexicographical
158 # and [1, x] after [1].
158 # and [1, x] after [1].
159 return b'/'.join(common(a, b))
159 return b'/'.join(common(a, b))
160 except TypeError:
160 except TypeError:
161 raise error.ParseError(_(b'argument is not a list of text'))
161 raise error.ParseError(_(b'argument is not a list of text'))
162
162
163
163
164 @templatefilter(b'count')
164 @templatefilter(b'count')
165 def count(i):
165 def count(i):
166 """List or text. Returns the length as an integer."""
166 """List or text. Returns the length as an integer."""
167 try:
167 try:
168 return len(i)
168 return len(i)
169 except TypeError:
169 except TypeError:
170 raise error.ParseError(_(b'not countable'))
170 raise error.ParseError(_(b'not countable'))
171
171
172
172
173 @templatefilter(b'dirname', intype=bytes)
173 @templatefilter(b'dirname', intype=bytes)
174 def dirname(path):
174 def dirname(path):
175 """Any text. Treats the text as a path, and strips the last
175 """Any text. Treats the text as a path, and strips the last
176 component of the path after splitting by the path separator.
176 component of the path after splitting by the path separator.
177 """
177 """
178 return os.path.dirname(path)
178 return os.path.dirname(path)
179
179
180
180
181 @templatefilter(b'domain', intype=bytes)
181 @templatefilter(b'domain', intype=bytes)
182 def domain(author):
182 def domain(author):
183 """Any text. Finds the first string that looks like an email
183 """Any text. Finds the first string that looks like an email
184 address, and extracts just the domain component. Example: ``User
184 address, and extracts just the domain component. Example: ``User
185 <user@example.com>`` becomes ``example.com``.
185 <user@example.com>`` becomes ``example.com``.
186 """
186 """
187 f = author.find(b'@')
187 f = author.find(b'@')
188 if f == -1:
188 if f == -1:
189 return b''
189 return b''
190 author = author[f + 1 :]
190 author = author[f + 1 :]
191 f = author.find(b'>')
191 f = author.find(b'>')
192 if f >= 0:
192 if f >= 0:
193 author = author[:f]
193 author = author[:f]
194 return author
194 return author
195
195
196
196
197 @templatefilter(b'email', intype=bytes)
197 @templatefilter(b'email', intype=bytes)
198 def email(text):
198 def email(text):
199 """Any text. Extracts the first string that looks like an email
199 """Any text. Extracts the first string that looks like an email
200 address. Example: ``User <user@example.com>`` becomes
200 address. Example: ``User <user@example.com>`` becomes
201 ``user@example.com``.
201 ``user@example.com``.
202 """
202 """
203 return stringutil.email(text)
203 return stringutil.email(text)
204
204
205
205
206 @templatefilter(b'escape', intype=bytes)
206 @templatefilter(b'escape', intype=bytes)
207 def escape(text):
207 def escape(text):
208 """Any text. Replaces the special XML/XHTML characters "&", "<"
208 """Any text. Replaces the special XML/XHTML characters "&", "<"
209 and ">" with XML entities, and filters out NUL characters.
209 and ">" with XML entities, and filters out NUL characters.
210 """
210 """
211 return url.escape(text.replace(b'\0', b''), True)
211 return url.escape(text.replace(b'\0', b''), True)
212
212
213
213
214 para_re = None
214 para_re = None
215 space_re = None
215 space_re = None
216
216
217
217
218 def fill(text, width, initindent=b'', hangindent=b''):
218 def fill(text, width, initindent=b'', hangindent=b''):
219 '''fill many paragraphs with optional indentation.'''
219 '''fill many paragraphs with optional indentation.'''
220 global para_re, space_re
220 global para_re, space_re
221 if para_re is None:
221 if para_re is None:
222 para_re = re.compile(b'(\n\n|\n\\s*[-*]\\s*)', re.M)
222 para_re = re.compile(b'(\n\n|\n\\s*[-*]\\s*)', re.M)
223 space_re = re.compile(br' +')
223 space_re = re.compile(br' +')
224
224
225 def findparas():
225 def findparas():
226 start = 0
226 start = 0
227 while True:
227 while True:
228 m = para_re.search(text, start)
228 m = para_re.search(text, start)
229 if not m:
229 if not m:
230 uctext = encoding.unifromlocal(text[start:])
230 uctext = encoding.unifromlocal(text[start:])
231 w = len(uctext)
231 w = len(uctext)
232 while w > 0 and uctext[w - 1].isspace():
232 while w > 0 and uctext[w - 1].isspace():
233 w -= 1
233 w -= 1
234 yield (
234 yield (
235 encoding.unitolocal(uctext[:w]),
235 encoding.unitolocal(uctext[:w]),
236 encoding.unitolocal(uctext[w:]),
236 encoding.unitolocal(uctext[w:]),
237 )
237 )
238 break
238 break
239 yield text[start : m.start(0)], m.group(1)
239 yield text[start : m.start(0)], m.group(1)
240 start = m.end(1)
240 start = m.end(1)
241
241
242 return b"".join(
242 return b"".join(
243 [
243 [
244 stringutil.wrap(
244 stringutil.wrap(
245 space_re.sub(b' ', stringutil.wrap(para, width)),
245 space_re.sub(b' ', stringutil.wrap(para, width)),
246 width,
246 width,
247 initindent,
247 initindent,
248 hangindent,
248 hangindent,
249 )
249 )
250 + rest
250 + rest
251 for para, rest in findparas()
251 for para, rest in findparas()
252 ]
252 ]
253 )
253 )
254
254
255
255
256 @templatefilter(b'fill68', intype=bytes)
256 @templatefilter(b'fill68', intype=bytes)
257 def fill68(text):
257 def fill68(text):
258 """Any text. Wraps the text to fit in 68 columns."""
258 """Any text. Wraps the text to fit in 68 columns."""
259 return fill(text, 68)
259 return fill(text, 68)
260
260
261
261
262 @templatefilter(b'fill76', intype=bytes)
262 @templatefilter(b'fill76', intype=bytes)
263 def fill76(text):
263 def fill76(text):
264 """Any text. Wraps the text to fit in 76 columns."""
264 """Any text. Wraps the text to fit in 76 columns."""
265 return fill(text, 76)
265 return fill(text, 76)
266
266
267
267
268 @templatefilter(b'firstline', intype=bytes)
268 @templatefilter(b'firstline', intype=bytes)
269 def firstline(text):
269 def firstline(text):
270 """Any text. Returns the first line of text."""
270 """Any text. Returns the first line of text."""
271 try:
271 try:
272 return text.splitlines(True)[0].rstrip(b'\r\n')
272 return text.splitlines()[0]
273 except IndexError:
273 except IndexError:
274 return b''
274 return b''
275
275
276
276
277 @templatefilter(b'hex', intype=bytes)
277 @templatefilter(b'hex', intype=bytes)
278 def hexfilter(text):
278 def hexfilter(text):
279 """Any text. Convert a binary Mercurial node identifier into
279 """Any text. Convert a binary Mercurial node identifier into
280 its long hexadecimal representation.
280 its long hexadecimal representation.
281 """
281 """
282 return hex(text)
282 return hex(text)
283
283
284
284
285 @templatefilter(b'hgdate', intype=templateutil.date)
285 @templatefilter(b'hgdate', intype=templateutil.date)
286 def hgdate(text):
286 def hgdate(text):
287 """Date. Returns the date as a pair of numbers: "1157407993
287 """Date. Returns the date as a pair of numbers: "1157407993
288 25200" (Unix timestamp, timezone offset).
288 25200" (Unix timestamp, timezone offset).
289 """
289 """
290 return b"%d %d" % text
290 return b"%d %d" % text
291
291
292
292
293 @templatefilter(b'isodate', intype=templateutil.date)
293 @templatefilter(b'isodate', intype=templateutil.date)
294 def isodate(text):
294 def isodate(text):
295 """Date. Returns the date in ISO 8601 format: "2009-08-18 13:00
295 """Date. Returns the date in ISO 8601 format: "2009-08-18 13:00
296 +0200".
296 +0200".
297 """
297 """
298 return dateutil.datestr(text, b'%Y-%m-%d %H:%M %1%2')
298 return dateutil.datestr(text, b'%Y-%m-%d %H:%M %1%2')
299
299
300
300
301 @templatefilter(b'isodatesec', intype=templateutil.date)
301 @templatefilter(b'isodatesec', intype=templateutil.date)
302 def isodatesec(text):
302 def isodatesec(text):
303 """Date. Returns the date in ISO 8601 format, including
303 """Date. Returns the date in ISO 8601 format, including
304 seconds: "2009-08-18 13:00:13 +0200". See also the rfc3339date
304 seconds: "2009-08-18 13:00:13 +0200". See also the rfc3339date
305 filter.
305 filter.
306 """
306 """
307 return dateutil.datestr(text, b'%Y-%m-%d %H:%M:%S %1%2')
307 return dateutil.datestr(text, b'%Y-%m-%d %H:%M:%S %1%2')
308
308
309
309
310 def indent(text, prefix, firstline=b''):
310 def indent(text, prefix, firstline=b''):
311 '''indent each non-empty line of text after first with prefix.'''
311 '''indent each non-empty line of text after first with prefix.'''
312 lines = text.splitlines()
312 lines = text.splitlines()
313 num_lines = len(lines)
313 num_lines = len(lines)
314 endswithnewline = text[-1:] == b'\n'
314 endswithnewline = text[-1:] == b'\n'
315
315
316 def indenter():
316 def indenter():
317 for i in pycompat.xrange(num_lines):
317 for i in pycompat.xrange(num_lines):
318 l = lines[i]
318 l = lines[i]
319 if l.strip():
319 if l.strip():
320 yield prefix if i else firstline
320 yield prefix if i else firstline
321 yield l
321 yield l
322 if i < num_lines - 1 or endswithnewline:
322 if i < num_lines - 1 or endswithnewline:
323 yield b'\n'
323 yield b'\n'
324
324
325 return b"".join(indenter())
325 return b"".join(indenter())
326
326
327
327
328 @templatefilter(b'json')
328 @templatefilter(b'json')
329 def json(obj, paranoid=True):
329 def json(obj, paranoid=True):
330 """Any object. Serializes the object to a JSON formatted text."""
330 """Any object. Serializes the object to a JSON formatted text."""
331 if obj is None:
331 if obj is None:
332 return b'null'
332 return b'null'
333 elif obj is False:
333 elif obj is False:
334 return b'false'
334 return b'false'
335 elif obj is True:
335 elif obj is True:
336 return b'true'
336 return b'true'
337 elif isinstance(obj, (int, int, float)):
337 elif isinstance(obj, (int, int, float)):
338 return pycompat.bytestr(obj)
338 return pycompat.bytestr(obj)
339 elif isinstance(obj, bytes):
339 elif isinstance(obj, bytes):
340 return b'"%s"' % encoding.jsonescape(obj, paranoid=paranoid)
340 return b'"%s"' % encoding.jsonescape(obj, paranoid=paranoid)
341 elif isinstance(obj, type(u'')):
341 elif isinstance(obj, type(u'')):
342 raise error.ProgrammingError(
342 raise error.ProgrammingError(
343 b'Mercurial only does output with bytes: %r' % obj
343 b'Mercurial only does output with bytes: %r' % obj
344 )
344 )
345 elif util.safehasattr(obj, b'keys'):
345 elif util.safehasattr(obj, b'keys'):
346 out = [
346 out = [
347 b'"%s": %s'
347 b'"%s": %s'
348 % (encoding.jsonescape(k, paranoid=paranoid), json(v, paranoid))
348 % (encoding.jsonescape(k, paranoid=paranoid), json(v, paranoid))
349 for k, v in sorted(obj.items())
349 for k, v in sorted(obj.items())
350 ]
350 ]
351 return b'{' + b', '.join(out) + b'}'
351 return b'{' + b', '.join(out) + b'}'
352 elif util.safehasattr(obj, b'__iter__'):
352 elif util.safehasattr(obj, b'__iter__'):
353 out = [json(i, paranoid) for i in obj]
353 out = [json(i, paranoid) for i in obj]
354 return b'[' + b', '.join(out) + b']'
354 return b'[' + b', '.join(out) + b']'
355 raise error.ProgrammingError(b'cannot encode %r' % obj)
355 raise error.ProgrammingError(b'cannot encode %r' % obj)
356
356
357
357
358 @templatefilter(b'lower', intype=bytes)
358 @templatefilter(b'lower', intype=bytes)
359 def lower(text):
359 def lower(text):
360 """Any text. Converts the text to lowercase."""
360 """Any text. Converts the text to lowercase."""
361 return encoding.lower(text)
361 return encoding.lower(text)
362
362
363
363
364 @templatefilter(b'nonempty', intype=bytes)
364 @templatefilter(b'nonempty', intype=bytes)
365 def nonempty(text):
365 def nonempty(text):
366 """Any text. Returns '(none)' if the string is empty."""
366 """Any text. Returns '(none)' if the string is empty."""
367 return text or b"(none)"
367 return text or b"(none)"
368
368
369
369
370 @templatefilter(b'obfuscate', intype=bytes)
370 @templatefilter(b'obfuscate', intype=bytes)
371 def obfuscate(text):
371 def obfuscate(text):
372 """Any text. Returns the input text rendered as a sequence of
372 """Any text. Returns the input text rendered as a sequence of
373 XML entities.
373 XML entities.
374 """
374 """
375 text = str(text, pycompat.sysstr(encoding.encoding), r'replace')
375 text = str(text, pycompat.sysstr(encoding.encoding), r'replace')
376 return b''.join([b'&#%d;' % ord(c) for c in text])
376 return b''.join([b'&#%d;' % ord(c) for c in text])
377
377
378
378
379 @templatefilter(b'permissions', intype=bytes)
379 @templatefilter(b'permissions', intype=bytes)
380 def permissions(flags):
380 def permissions(flags):
381 if b"l" in flags:
381 if b"l" in flags:
382 return b"lrwxrwxrwx"
382 return b"lrwxrwxrwx"
383 if b"x" in flags:
383 if b"x" in flags:
384 return b"-rwxr-xr-x"
384 return b"-rwxr-xr-x"
385 return b"-rw-r--r--"
385 return b"-rw-r--r--"
386
386
387
387
388 @templatefilter(b'person', intype=bytes)
388 @templatefilter(b'person', intype=bytes)
389 def person(author):
389 def person(author):
390 """Any text. Returns the name before an email address,
390 """Any text. Returns the name before an email address,
391 interpreting it as per RFC 5322.
391 interpreting it as per RFC 5322.
392 """
392 """
393 return stringutil.person(author)
393 return stringutil.person(author)
394
394
395
395
396 @templatefilter(b'revescape', intype=bytes)
396 @templatefilter(b'revescape', intype=bytes)
397 def revescape(text):
397 def revescape(text):
398 """Any text. Escapes all "special" characters, except @.
398 """Any text. Escapes all "special" characters, except @.
399 Forward slashes are escaped twice to prevent web servers from prematurely
399 Forward slashes are escaped twice to prevent web servers from prematurely
400 unescaping them. For example, "@foo bar/baz" becomes "@foo%20bar%252Fbaz".
400 unescaping them. For example, "@foo bar/baz" becomes "@foo%20bar%252Fbaz".
401 """
401 """
402 return urlreq.quote(text, safe=b'/@').replace(b'/', b'%252F')
402 return urlreq.quote(text, safe=b'/@').replace(b'/', b'%252F')
403
403
404
404
405 @templatefilter(b'rfc3339date', intype=templateutil.date)
405 @templatefilter(b'rfc3339date', intype=templateutil.date)
406 def rfc3339date(text):
406 def rfc3339date(text):
407 """Date. Returns a date using the Internet date format
407 """Date. Returns a date using the Internet date format
408 specified in RFC 3339: "2009-08-18T13:00:13+02:00".
408 specified in RFC 3339: "2009-08-18T13:00:13+02:00".
409 """
409 """
410 return dateutil.datestr(text, b"%Y-%m-%dT%H:%M:%S%1:%2")
410 return dateutil.datestr(text, b"%Y-%m-%dT%H:%M:%S%1:%2")
411
411
412
412
413 @templatefilter(b'rfc822date', intype=templateutil.date)
413 @templatefilter(b'rfc822date', intype=templateutil.date)
414 def rfc822date(text):
414 def rfc822date(text):
415 """Date. Returns a date using the same format used in email
415 """Date. Returns a date using the same format used in email
416 headers: "Tue, 18 Aug 2009 13:00:13 +0200".
416 headers: "Tue, 18 Aug 2009 13:00:13 +0200".
417 """
417 """
418 return dateutil.datestr(text, b"%a, %d %b %Y %H:%M:%S %1%2")
418 return dateutil.datestr(text, b"%a, %d %b %Y %H:%M:%S %1%2")
419
419
420
420
421 @templatefilter(b'short', intype=bytes)
421 @templatefilter(b'short', intype=bytes)
422 def short(text):
422 def short(text):
423 """Changeset hash. Returns the short form of a changeset hash,
423 """Changeset hash. Returns the short form of a changeset hash,
424 i.e. a 12 hexadecimal digit string.
424 i.e. a 12 hexadecimal digit string.
425 """
425 """
426 return text[:12]
426 return text[:12]
427
427
428
428
429 @templatefilter(b'shortbisect', intype=bytes)
429 @templatefilter(b'shortbisect', intype=bytes)
430 def shortbisect(label):
430 def shortbisect(label):
431 """Any text. Treats `label` as a bisection status, and
431 """Any text. Treats `label` as a bisection status, and
432 returns a single-character representing the status (G: good, B: bad,
432 returns a single-character representing the status (G: good, B: bad,
433 S: skipped, U: untested, I: ignored). Returns single space if `text`
433 S: skipped, U: untested, I: ignored). Returns single space if `text`
434 is not a valid bisection status.
434 is not a valid bisection status.
435 """
435 """
436 if label:
436 if label:
437 return label[0:1].upper()
437 return label[0:1].upper()
438 return b' '
438 return b' '
439
439
440
440
441 @templatefilter(b'shortdate', intype=templateutil.date)
441 @templatefilter(b'shortdate', intype=templateutil.date)
442 def shortdate(text):
442 def shortdate(text):
443 """Date. Returns a date like "2006-09-18"."""
443 """Date. Returns a date like "2006-09-18"."""
444 return dateutil.shortdate(text)
444 return dateutil.shortdate(text)
445
445
446
446
447 @templatefilter(b'slashpath', intype=bytes)
447 @templatefilter(b'slashpath', intype=bytes)
448 def slashpath(path):
448 def slashpath(path):
449 """Any text. Replaces the native path separator with slash."""
449 """Any text. Replaces the native path separator with slash."""
450 return util.pconvert(path)
450 return util.pconvert(path)
451
451
452
452
453 @templatefilter(b'splitlines', intype=bytes)
453 @templatefilter(b'splitlines', intype=bytes)
454 def splitlines(text):
454 def splitlines(text):
455 """Any text. Split text into a list of lines."""
455 """Any text. Split text into a list of lines."""
456 return templateutil.hybridlist(text.splitlines(), name=b'line')
456 return templateutil.hybridlist(text.splitlines(), name=b'line')
457
457
458
458
459 @templatefilter(b'stringescape', intype=bytes)
459 @templatefilter(b'stringescape', intype=bytes)
460 def stringescape(text):
460 def stringescape(text):
461 return stringutil.escapestr(text)
461 return stringutil.escapestr(text)
462
462
463
463
464 @templatefilter(b'stringify', intype=bytes)
464 @templatefilter(b'stringify', intype=bytes)
465 def stringify(thing):
465 def stringify(thing):
466 """Any type. Turns the value into text by converting values into
466 """Any type. Turns the value into text by converting values into
467 text and concatenating them.
467 text and concatenating them.
468 """
468 """
469 return thing # coerced by the intype
469 return thing # coerced by the intype
470
470
471
471
472 @templatefilter(b'stripdir', intype=bytes)
472 @templatefilter(b'stripdir', intype=bytes)
473 def stripdir(text):
473 def stripdir(text):
474 """Treat the text as path and strip a directory level, if
474 """Treat the text as path and strip a directory level, if
475 possible. For example, "foo" and "foo/bar" becomes "foo".
475 possible. For example, "foo" and "foo/bar" becomes "foo".
476 """
476 """
477 dir = os.path.dirname(text)
477 dir = os.path.dirname(text)
478 if dir == b"":
478 if dir == b"":
479 return os.path.basename(text)
479 return os.path.basename(text)
480 else:
480 else:
481 return dir
481 return dir
482
482
483
483
484 @templatefilter(b'tabindent', intype=bytes)
484 @templatefilter(b'tabindent', intype=bytes)
485 def tabindent(text):
485 def tabindent(text):
486 """Any text. Returns the text, with every non-empty line
486 """Any text. Returns the text, with every non-empty line
487 except the first starting with a tab character.
487 except the first starting with a tab character.
488 """
488 """
489 return indent(text, b'\t')
489 return indent(text, b'\t')
490
490
491
491
492 @templatefilter(b'upper', intype=bytes)
492 @templatefilter(b'upper', intype=bytes)
493 def upper(text):
493 def upper(text):
494 """Any text. Converts the text to uppercase."""
494 """Any text. Converts the text to uppercase."""
495 return encoding.upper(text)
495 return encoding.upper(text)
496
496
497
497
498 @templatefilter(b'urlescape', intype=bytes)
498 @templatefilter(b'urlescape', intype=bytes)
499 def urlescape(text):
499 def urlescape(text):
500 """Any text. Escapes all "special" characters. For example,
500 """Any text. Escapes all "special" characters. For example,
501 "foo bar" becomes "foo%20bar".
501 "foo bar" becomes "foo%20bar".
502 """
502 """
503 return urlreq.quote(text)
503 return urlreq.quote(text)
504
504
505
505
506 @templatefilter(b'user', intype=bytes)
506 @templatefilter(b'user', intype=bytes)
507 def userfilter(text):
507 def userfilter(text):
508 """Any text. Returns a short representation of a user name or email
508 """Any text. Returns a short representation of a user name or email
509 address."""
509 address."""
510 return stringutil.shortuser(text)
510 return stringutil.shortuser(text)
511
511
512
512
513 @templatefilter(b'emailuser', intype=bytes)
513 @templatefilter(b'emailuser', intype=bytes)
514 def emailuser(text):
514 def emailuser(text):
515 """Any text. Returns the user portion of an email address."""
515 """Any text. Returns the user portion of an email address."""
516 return stringutil.emailuser(text)
516 return stringutil.emailuser(text)
517
517
518
518
519 @templatefilter(b'utf8', intype=bytes)
519 @templatefilter(b'utf8', intype=bytes)
520 def utf8(text):
520 def utf8(text):
521 """Any text. Converts from the local character encoding to UTF-8."""
521 """Any text. Converts from the local character encoding to UTF-8."""
522 return encoding.fromlocal(text)
522 return encoding.fromlocal(text)
523
523
524
524
525 @templatefilter(b'xmlescape', intype=bytes)
525 @templatefilter(b'xmlescape', intype=bytes)
526 def xmlescape(text):
526 def xmlescape(text):
527 text = (
527 text = (
528 text.replace(b'&', b'&amp;')
528 text.replace(b'&', b'&amp;')
529 .replace(b'<', b'&lt;')
529 .replace(b'<', b'&lt;')
530 .replace(b'>', b'&gt;')
530 .replace(b'>', b'&gt;')
531 .replace(b'"', b'&quot;')
531 .replace(b'"', b'&quot;')
532 .replace(b"'", b'&#39;')
532 .replace(b"'", b'&#39;')
533 ) # &apos; invalid in HTML
533 ) # &apos; invalid in HTML
534 return re.sub(b'[\x00-\x08\x0B\x0C\x0E-\x1F]', b' ', text)
534 return re.sub(b'[\x00-\x08\x0B\x0C\x0E-\x1F]', b' ', text)
535
535
536
536
537 def websub(text, websubtable):
537 def websub(text, websubtable):
538 """:websub: Any text. Only applies to hgweb. Applies the regular
538 """:websub: Any text. Only applies to hgweb. Applies the regular
539 expression replacements defined in the websub section.
539 expression replacements defined in the websub section.
540 """
540 """
541 if websubtable:
541 if websubtable:
542 for regexp, format in websubtable:
542 for regexp, format in websubtable:
543 text = regexp.sub(format, text)
543 text = regexp.sub(format, text)
544 return text
544 return text
545
545
546
546
547 def loadfilter(ui, extname, registrarobj):
547 def loadfilter(ui, extname, registrarobj):
548 """Load template filter from specified registrarobj"""
548 """Load template filter from specified registrarobj"""
549 for name, func in registrarobj._table.items():
549 for name, func in registrarobj._table.items():
550 filters[name] = func
550 filters[name] = func
551
551
552
552
553 # tell hggettext to extract docstrings from these functions:
553 # tell hggettext to extract docstrings from these functions:
554 i18nfunctions = filters.values()
554 i18nfunctions = filters.values()
General Comments 0
You need to be logged in to leave comments. Login now