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