##// END OF EJS Templates
safehasattr: pass attribute name as string instead of bytes...
marmoute -
r51502:90945014 default
parent child Browse files
Show More
@@ -1,559 +1,559 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 range(len(a)):
143 for i in range(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 return stringutil.firstline(text)
271 return stringutil.firstline(text)
272
272
273
273
274 @templatefilter(b'hex', intype=bytes)
274 @templatefilter(b'hex', intype=bytes)
275 def hexfilter(text):
275 def hexfilter(text):
276 """Any text. Convert a binary Mercurial node identifier into
276 """Any text. Convert a binary Mercurial node identifier into
277 its long hexadecimal representation.
277 its long hexadecimal representation.
278 """
278 """
279 return hex(text)
279 return hex(text)
280
280
281
281
282 @templatefilter(b'hgdate', intype=templateutil.date)
282 @templatefilter(b'hgdate', intype=templateutil.date)
283 def hgdate(text):
283 def hgdate(text):
284 """Date. Returns the date as a pair of numbers: "1157407993
284 """Date. Returns the date as a pair of numbers: "1157407993
285 25200" (Unix timestamp, timezone offset).
285 25200" (Unix timestamp, timezone offset).
286 """
286 """
287 return b"%d %d" % text
287 return b"%d %d" % text
288
288
289
289
290 @templatefilter(b'isodate', intype=templateutil.date)
290 @templatefilter(b'isodate', intype=templateutil.date)
291 def isodate(text):
291 def isodate(text):
292 """Date. Returns the date in ISO 8601 format: "2009-08-18 13:00
292 """Date. Returns the date in ISO 8601 format: "2009-08-18 13:00
293 +0200".
293 +0200".
294 """
294 """
295 return dateutil.datestr(text, b'%Y-%m-%d %H:%M %1%2')
295 return dateutil.datestr(text, b'%Y-%m-%d %H:%M %1%2')
296
296
297
297
298 @templatefilter(b'isodatesec', intype=templateutil.date)
298 @templatefilter(b'isodatesec', intype=templateutil.date)
299 def isodatesec(text):
299 def isodatesec(text):
300 """Date. Returns the date in ISO 8601 format, including
300 """Date. Returns the date in ISO 8601 format, including
301 seconds: "2009-08-18 13:00:13 +0200". See also the rfc3339date
301 seconds: "2009-08-18 13:00:13 +0200". See also the rfc3339date
302 filter.
302 filter.
303 """
303 """
304 return dateutil.datestr(text, b'%Y-%m-%d %H:%M:%S %1%2')
304 return dateutil.datestr(text, b'%Y-%m-%d %H:%M:%S %1%2')
305
305
306
306
307 def indent(text, prefix, firstline=b''):
307 def indent(text, prefix, firstline=b''):
308 '''indent each non-empty line of text after first with prefix.'''
308 '''indent each non-empty line of text after first with prefix.'''
309 lines = text.splitlines()
309 lines = text.splitlines()
310 num_lines = len(lines)
310 num_lines = len(lines)
311 endswithnewline = text[-1:] == b'\n'
311 endswithnewline = text[-1:] == b'\n'
312
312
313 def indenter():
313 def indenter():
314 for i in range(num_lines):
314 for i in range(num_lines):
315 l = lines[i]
315 l = lines[i]
316 if l.strip():
316 if l.strip():
317 yield prefix if i else firstline
317 yield prefix if i else firstline
318 yield l
318 yield l
319 if i < num_lines - 1 or endswithnewline:
319 if i < num_lines - 1 or endswithnewline:
320 yield b'\n'
320 yield b'\n'
321
321
322 return b"".join(indenter())
322 return b"".join(indenter())
323
323
324
324
325 @templatefilter(b'json')
325 @templatefilter(b'json')
326 def json(obj, paranoid=True):
326 def json(obj, paranoid=True):
327 """Any object. Serializes the object to a JSON formatted text."""
327 """Any object. Serializes the object to a JSON formatted text."""
328 if obj is None:
328 if obj is None:
329 return b'null'
329 return b'null'
330 elif obj is False:
330 elif obj is False:
331 return b'false'
331 return b'false'
332 elif obj is True:
332 elif obj is True:
333 return b'true'
333 return b'true'
334 elif isinstance(obj, (int, int, float)):
334 elif isinstance(obj, (int, int, float)):
335 return pycompat.bytestr(obj)
335 return pycompat.bytestr(obj)
336 elif isinstance(obj, bytes):
336 elif isinstance(obj, bytes):
337 return b'"%s"' % encoding.jsonescape(obj, paranoid=paranoid)
337 return b'"%s"' % encoding.jsonescape(obj, paranoid=paranoid)
338 elif isinstance(obj, type(u'')):
338 elif isinstance(obj, type(u'')):
339 raise error.ProgrammingError(
339 raise error.ProgrammingError(
340 b'Mercurial only does output with bytes: %r' % obj
340 b'Mercurial only does output with bytes: %r' % obj
341 )
341 )
342 elif util.safehasattr(obj, 'keys'):
342 elif util.safehasattr(obj, 'keys'):
343 out = [
343 out = [
344 b'"%s": %s'
344 b'"%s": %s'
345 % (encoding.jsonescape(k, paranoid=paranoid), json(v, paranoid))
345 % (encoding.jsonescape(k, paranoid=paranoid), json(v, paranoid))
346 for k, v in sorted(obj.items())
346 for k, v in sorted(obj.items())
347 ]
347 ]
348 return b'{' + b', '.join(out) + b'}'
348 return b'{' + b', '.join(out) + b'}'
349 elif util.safehasattr(obj, b'__iter__'):
349 elif util.safehasattr(obj, '__iter__'):
350 out = [json(i, paranoid) for i in obj]
350 out = [json(i, paranoid) for i in obj]
351 return b'[' + b', '.join(out) + b']'
351 return b'[' + b', '.join(out) + b']'
352 raise error.ProgrammingError(b'cannot encode %r' % obj)
352 raise error.ProgrammingError(b'cannot encode %r' % obj)
353
353
354
354
355 @templatefilter(b'lower', intype=bytes)
355 @templatefilter(b'lower', intype=bytes)
356 def lower(text):
356 def lower(text):
357 """Any text. Converts the text to lowercase."""
357 """Any text. Converts the text to lowercase."""
358 return encoding.lower(text)
358 return encoding.lower(text)
359
359
360
360
361 @templatefilter(b'nonempty', intype=bytes)
361 @templatefilter(b'nonempty', intype=bytes)
362 def nonempty(text):
362 def nonempty(text):
363 """Any text. Returns '(none)' if the string is empty."""
363 """Any text. Returns '(none)' if the string is empty."""
364 return text or b"(none)"
364 return text or b"(none)"
365
365
366
366
367 @templatefilter(b'obfuscate', intype=bytes)
367 @templatefilter(b'obfuscate', intype=bytes)
368 def obfuscate(text):
368 def obfuscate(text):
369 """Any text. Returns the input text rendered as a sequence of
369 """Any text. Returns the input text rendered as a sequence of
370 XML entities.
370 XML entities.
371 """
371 """
372 text = str(text, pycompat.sysstr(encoding.encoding), r'replace')
372 text = str(text, pycompat.sysstr(encoding.encoding), r'replace')
373 return b''.join([b'&#%d;' % ord(c) for c in text])
373 return b''.join([b'&#%d;' % ord(c) for c in text])
374
374
375
375
376 @templatefilter(b'permissions', intype=bytes)
376 @templatefilter(b'permissions', intype=bytes)
377 def permissions(flags):
377 def permissions(flags):
378 if b"l" in flags:
378 if b"l" in flags:
379 return b"lrwxrwxrwx"
379 return b"lrwxrwxrwx"
380 if b"x" in flags:
380 if b"x" in flags:
381 return b"-rwxr-xr-x"
381 return b"-rwxr-xr-x"
382 return b"-rw-r--r--"
382 return b"-rw-r--r--"
383
383
384
384
385 @templatefilter(b'person', intype=bytes)
385 @templatefilter(b'person', intype=bytes)
386 def person(author):
386 def person(author):
387 """Any text. Returns the name before an email address,
387 """Any text. Returns the name before an email address,
388 interpreting it as per RFC 5322.
388 interpreting it as per RFC 5322.
389 """
389 """
390 return stringutil.person(author)
390 return stringutil.person(author)
391
391
392
392
393 @templatefilter(b'reverse')
393 @templatefilter(b'reverse')
394 def reverse(list_):
394 def reverse(list_):
395 """List. Reverses the order of list items."""
395 """List. Reverses the order of list items."""
396 if isinstance(list_, list):
396 if isinstance(list_, list):
397 return templateutil.hybridlist(list_[::-1], name=b'item')
397 return templateutil.hybridlist(list_[::-1], name=b'item')
398 raise error.ParseError(_(b'not reversible'))
398 raise error.ParseError(_(b'not reversible'))
399
399
400
400
401 @templatefilter(b'revescape', intype=bytes)
401 @templatefilter(b'revescape', intype=bytes)
402 def revescape(text):
402 def revescape(text):
403 """Any text. Escapes all "special" characters, except @.
403 """Any text. Escapes all "special" characters, except @.
404 Forward slashes are escaped twice to prevent web servers from prematurely
404 Forward slashes are escaped twice to prevent web servers from prematurely
405 unescaping them. For example, "@foo bar/baz" becomes "@foo%20bar%252Fbaz".
405 unescaping them. For example, "@foo bar/baz" becomes "@foo%20bar%252Fbaz".
406 """
406 """
407 return urlreq.quote(text, safe=b'/@').replace(b'/', b'%252F')
407 return urlreq.quote(text, safe=b'/@').replace(b'/', b'%252F')
408
408
409
409
410 @templatefilter(b'rfc3339date', intype=templateutil.date)
410 @templatefilter(b'rfc3339date', intype=templateutil.date)
411 def rfc3339date(text):
411 def rfc3339date(text):
412 """Date. Returns a date using the Internet date format
412 """Date. Returns a date using the Internet date format
413 specified in RFC 3339: "2009-08-18T13:00:13+02:00".
413 specified in RFC 3339: "2009-08-18T13:00:13+02:00".
414 """
414 """
415 return dateutil.datestr(text, b"%Y-%m-%dT%H:%M:%S%1:%2")
415 return dateutil.datestr(text, b"%Y-%m-%dT%H:%M:%S%1:%2")
416
416
417
417
418 @templatefilter(b'rfc822date', intype=templateutil.date)
418 @templatefilter(b'rfc822date', intype=templateutil.date)
419 def rfc822date(text):
419 def rfc822date(text):
420 """Date. Returns a date using the same format used in email
420 """Date. Returns a date using the same format used in email
421 headers: "Tue, 18 Aug 2009 13:00:13 +0200".
421 headers: "Tue, 18 Aug 2009 13:00:13 +0200".
422 """
422 """
423 return dateutil.datestr(text, b"%a, %d %b %Y %H:%M:%S %1%2")
423 return dateutil.datestr(text, b"%a, %d %b %Y %H:%M:%S %1%2")
424
424
425
425
426 @templatefilter(b'short', intype=bytes)
426 @templatefilter(b'short', intype=bytes)
427 def short(text):
427 def short(text):
428 """Changeset hash. Returns the short form of a changeset hash,
428 """Changeset hash. Returns the short form of a changeset hash,
429 i.e. a 12 hexadecimal digit string.
429 i.e. a 12 hexadecimal digit string.
430 """
430 """
431 return text[:12]
431 return text[:12]
432
432
433
433
434 @templatefilter(b'shortbisect', intype=bytes)
434 @templatefilter(b'shortbisect', intype=bytes)
435 def shortbisect(label):
435 def shortbisect(label):
436 """Any text. Treats `label` as a bisection status, and
436 """Any text. Treats `label` as a bisection status, and
437 returns a single-character representing the status (G: good, B: bad,
437 returns a single-character representing the status (G: good, B: bad,
438 S: skipped, U: untested, I: ignored). Returns single space if `text`
438 S: skipped, U: untested, I: ignored). Returns single space if `text`
439 is not a valid bisection status.
439 is not a valid bisection status.
440 """
440 """
441 if label:
441 if label:
442 return label[0:1].upper()
442 return label[0:1].upper()
443 return b' '
443 return b' '
444
444
445
445
446 @templatefilter(b'shortdate', intype=templateutil.date)
446 @templatefilter(b'shortdate', intype=templateutil.date)
447 def shortdate(text):
447 def shortdate(text):
448 """Date. Returns a date like "2006-09-18"."""
448 """Date. Returns a date like "2006-09-18"."""
449 return dateutil.shortdate(text)
449 return dateutil.shortdate(text)
450
450
451
451
452 @templatefilter(b'slashpath', intype=bytes)
452 @templatefilter(b'slashpath', intype=bytes)
453 def slashpath(path):
453 def slashpath(path):
454 """Any text. Replaces the native path separator with slash."""
454 """Any text. Replaces the native path separator with slash."""
455 return util.pconvert(path)
455 return util.pconvert(path)
456
456
457
457
458 @templatefilter(b'splitlines', intype=bytes)
458 @templatefilter(b'splitlines', intype=bytes)
459 def splitlines(text):
459 def splitlines(text):
460 """Any text. Split text into a list of lines."""
460 """Any text. Split text into a list of lines."""
461 return templateutil.hybridlist(text.splitlines(), name=b'line')
461 return templateutil.hybridlist(text.splitlines(), name=b'line')
462
462
463
463
464 @templatefilter(b'stringescape', intype=bytes)
464 @templatefilter(b'stringescape', intype=bytes)
465 def stringescape(text):
465 def stringescape(text):
466 return stringutil.escapestr(text)
466 return stringutil.escapestr(text)
467
467
468
468
469 @templatefilter(b'stringify', intype=bytes)
469 @templatefilter(b'stringify', intype=bytes)
470 def stringify(thing):
470 def stringify(thing):
471 """Any type. Turns the value into text by converting values into
471 """Any type. Turns the value into text by converting values into
472 text and concatenating them.
472 text and concatenating them.
473 """
473 """
474 return thing # coerced by the intype
474 return thing # coerced by the intype
475
475
476
476
477 @templatefilter(b'stripdir', intype=bytes)
477 @templatefilter(b'stripdir', intype=bytes)
478 def stripdir(text):
478 def stripdir(text):
479 """Treat the text as path and strip a directory level, if
479 """Treat the text as path and strip a directory level, if
480 possible. For example, "foo" and "foo/bar" becomes "foo".
480 possible. For example, "foo" and "foo/bar" becomes "foo".
481 """
481 """
482 dir = os.path.dirname(text)
482 dir = os.path.dirname(text)
483 if dir == b"":
483 if dir == b"":
484 return os.path.basename(text)
484 return os.path.basename(text)
485 else:
485 else:
486 return dir
486 return dir
487
487
488
488
489 @templatefilter(b'tabindent', intype=bytes)
489 @templatefilter(b'tabindent', intype=bytes)
490 def tabindent(text):
490 def tabindent(text):
491 """Any text. Returns the text, with every non-empty line
491 """Any text. Returns the text, with every non-empty line
492 except the first starting with a tab character.
492 except the first starting with a tab character.
493 """
493 """
494 return indent(text, b'\t')
494 return indent(text, b'\t')
495
495
496
496
497 @templatefilter(b'upper', intype=bytes)
497 @templatefilter(b'upper', intype=bytes)
498 def upper(text):
498 def upper(text):
499 """Any text. Converts the text to uppercase."""
499 """Any text. Converts the text to uppercase."""
500 return encoding.upper(text)
500 return encoding.upper(text)
501
501
502
502
503 @templatefilter(b'urlescape', intype=bytes)
503 @templatefilter(b'urlescape', intype=bytes)
504 def urlescape(text):
504 def urlescape(text):
505 """Any text. Escapes all "special" characters. For example,
505 """Any text. Escapes all "special" characters. For example,
506 "foo bar" becomes "foo%20bar".
506 "foo bar" becomes "foo%20bar".
507 """
507 """
508 return urlreq.quote(text)
508 return urlreq.quote(text)
509
509
510
510
511 @templatefilter(b'user', intype=bytes)
511 @templatefilter(b'user', intype=bytes)
512 def userfilter(text):
512 def userfilter(text):
513 """Any text. Returns a short representation of a user name or email
513 """Any text. Returns a short representation of a user name or email
514 address."""
514 address."""
515 return stringutil.shortuser(text)
515 return stringutil.shortuser(text)
516
516
517
517
518 @templatefilter(b'emailuser', intype=bytes)
518 @templatefilter(b'emailuser', intype=bytes)
519 def emailuser(text):
519 def emailuser(text):
520 """Any text. Returns the user portion of an email address."""
520 """Any text. Returns the user portion of an email address."""
521 return stringutil.emailuser(text)
521 return stringutil.emailuser(text)
522
522
523
523
524 @templatefilter(b'utf8', intype=bytes)
524 @templatefilter(b'utf8', intype=bytes)
525 def utf8(text):
525 def utf8(text):
526 """Any text. Converts from the local character encoding to UTF-8."""
526 """Any text. Converts from the local character encoding to UTF-8."""
527 return encoding.fromlocal(text)
527 return encoding.fromlocal(text)
528
528
529
529
530 @templatefilter(b'xmlescape', intype=bytes)
530 @templatefilter(b'xmlescape', intype=bytes)
531 def xmlescape(text):
531 def xmlescape(text):
532 text = (
532 text = (
533 text.replace(b'&', b'&amp;')
533 text.replace(b'&', b'&amp;')
534 .replace(b'<', b'&lt;')
534 .replace(b'<', b'&lt;')
535 .replace(b'>', b'&gt;')
535 .replace(b'>', b'&gt;')
536 .replace(b'"', b'&quot;')
536 .replace(b'"', b'&quot;')
537 .replace(b"'", b'&#39;')
537 .replace(b"'", b'&#39;')
538 ) # &apos; invalid in HTML
538 ) # &apos; invalid in HTML
539 return re.sub(b'[\x00-\x08\x0B\x0C\x0E-\x1F]', b' ', text)
539 return re.sub(b'[\x00-\x08\x0B\x0C\x0E-\x1F]', b' ', text)
540
540
541
541
542 def websub(text, websubtable):
542 def websub(text, websubtable):
543 """:websub: Any text. Only applies to hgweb. Applies the regular
543 """:websub: Any text. Only applies to hgweb. Applies the regular
544 expression replacements defined in the websub section.
544 expression replacements defined in the websub section.
545 """
545 """
546 if websubtable:
546 if websubtable:
547 for regexp, format in websubtable:
547 for regexp, format in websubtable:
548 text = regexp.sub(format, text)
548 text = regexp.sub(format, text)
549 return text
549 return text
550
550
551
551
552 def loadfilter(ui, extname, registrarobj):
552 def loadfilter(ui, extname, registrarobj):
553 """Load template filter from specified registrarobj"""
553 """Load template filter from specified registrarobj"""
554 for name, func in registrarobj._table.items():
554 for name, func in registrarobj._table.items():
555 filters[name] = func
555 filters[name] = func
556
556
557
557
558 # tell hggettext to extract docstrings from these functions:
558 # tell hggettext to extract docstrings from these functions:
559 i18nfunctions = filters.values()
559 i18nfunctions = filters.values()
General Comments 0
You need to be logged in to leave comments. Login now