##// END OF EJS Templates
templatefilters: defend against evil unicode strs in json filter...
Augie Fackler -
r34838:4fdc4adb default
parent child Browse files
Show More
@@ -1,437 +1,445 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 . import (
14 from . import (
15 encoding,
15 encoding,
16 error,
16 hbisect,
17 hbisect,
17 node,
18 node,
18 pycompat,
19 pycompat,
19 registrar,
20 registrar,
20 templatekw,
21 templatekw,
21 url,
22 url,
22 util,
23 util,
23 )
24 )
24
25
25 urlerr = util.urlerr
26 urlerr = util.urlerr
26 urlreq = util.urlreq
27 urlreq = util.urlreq
27
28
28 if pycompat.ispy3:
29 if pycompat.ispy3:
29 long = int
30 long = int
30
31
31 # filters are callables like:
32 # filters are callables like:
32 # fn(obj)
33 # fn(obj)
33 # with:
34 # with:
34 # obj - object to be filtered (text, date, list and so on)
35 # obj - object to be filtered (text, date, list and so on)
35 filters = {}
36 filters = {}
36
37
37 templatefilter = registrar.templatefilter(filters)
38 templatefilter = registrar.templatefilter(filters)
38
39
39 @templatefilter('addbreaks')
40 @templatefilter('addbreaks')
40 def addbreaks(text):
41 def addbreaks(text):
41 """Any text. Add an XHTML "<br />" tag before the end of
42 """Any text. Add an XHTML "<br />" tag before the end of
42 every line except the last.
43 every line except the last.
43 """
44 """
44 return text.replace('\n', '<br/>\n')
45 return text.replace('\n', '<br/>\n')
45
46
46 agescales = [("year", 3600 * 24 * 365, 'Y'),
47 agescales = [("year", 3600 * 24 * 365, 'Y'),
47 ("month", 3600 * 24 * 30, 'M'),
48 ("month", 3600 * 24 * 30, 'M'),
48 ("week", 3600 * 24 * 7, 'W'),
49 ("week", 3600 * 24 * 7, 'W'),
49 ("day", 3600 * 24, 'd'),
50 ("day", 3600 * 24, 'd'),
50 ("hour", 3600, 'h'),
51 ("hour", 3600, 'h'),
51 ("minute", 60, 'm'),
52 ("minute", 60, 'm'),
52 ("second", 1, 's')]
53 ("second", 1, 's')]
53
54
54 @templatefilter('age')
55 @templatefilter('age')
55 def age(date, abbrev=False):
56 def age(date, abbrev=False):
56 """Date. Returns a human-readable date/time difference between the
57 """Date. Returns a human-readable date/time difference between the
57 given date/time and the current date/time.
58 given date/time and the current date/time.
58 """
59 """
59
60
60 def plural(t, c):
61 def plural(t, c):
61 if c == 1:
62 if c == 1:
62 return t
63 return t
63 return t + "s"
64 return t + "s"
64 def fmt(t, c, a):
65 def fmt(t, c, a):
65 if abbrev:
66 if abbrev:
66 return "%d%s" % (c, a)
67 return "%d%s" % (c, a)
67 return "%d %s" % (c, plural(t, c))
68 return "%d %s" % (c, plural(t, c))
68
69
69 now = time.time()
70 now = time.time()
70 then = date[0]
71 then = date[0]
71 future = False
72 future = False
72 if then > now:
73 if then > now:
73 future = True
74 future = True
74 delta = max(1, int(then - now))
75 delta = max(1, int(then - now))
75 if delta > agescales[0][1] * 30:
76 if delta > agescales[0][1] * 30:
76 return 'in the distant future'
77 return 'in the distant future'
77 else:
78 else:
78 delta = max(1, int(now - then))
79 delta = max(1, int(now - then))
79 if delta > agescales[0][1] * 2:
80 if delta > agescales[0][1] * 2:
80 return util.shortdate(date)
81 return util.shortdate(date)
81
82
82 for t, s, a in agescales:
83 for t, s, a in agescales:
83 n = delta // s
84 n = delta // s
84 if n >= 2 or s == 1:
85 if n >= 2 or s == 1:
85 if future:
86 if future:
86 return '%s from now' % fmt(t, n, a)
87 return '%s from now' % fmt(t, n, a)
87 return '%s ago' % fmt(t, n, a)
88 return '%s ago' % fmt(t, n, a)
88
89
89 @templatefilter('basename')
90 @templatefilter('basename')
90 def basename(path):
91 def basename(path):
91 """Any text. Treats the text as a path, and returns the last
92 """Any text. Treats the text as a path, and returns the last
92 component of the path after splitting by the path separator
93 component of the path after splitting by the path separator
93 (ignoring trailing separators). For example, "foo/bar/baz" becomes
94 (ignoring trailing separators). For example, "foo/bar/baz" becomes
94 "baz" and "foo/bar//" becomes "bar".
95 "baz" and "foo/bar//" becomes "bar".
95 """
96 """
96 return os.path.basename(path)
97 return os.path.basename(path)
97
98
98 @templatefilter('count')
99 @templatefilter('count')
99 def count(i):
100 def count(i):
100 """List or text. Returns the length as an integer."""
101 """List or text. Returns the length as an integer."""
101 return len(i)
102 return len(i)
102
103
103 @templatefilter('domain')
104 @templatefilter('domain')
104 def domain(author):
105 def domain(author):
105 """Any text. Finds the first string that looks like an email
106 """Any text. Finds the first string that looks like an email
106 address, and extracts just the domain component. Example: ``User
107 address, and extracts just the domain component. Example: ``User
107 <user@example.com>`` becomes ``example.com``.
108 <user@example.com>`` becomes ``example.com``.
108 """
109 """
109 f = author.find('@')
110 f = author.find('@')
110 if f == -1:
111 if f == -1:
111 return ''
112 return ''
112 author = author[f + 1:]
113 author = author[f + 1:]
113 f = author.find('>')
114 f = author.find('>')
114 if f >= 0:
115 if f >= 0:
115 author = author[:f]
116 author = author[:f]
116 return author
117 return author
117
118
118 @templatefilter('email')
119 @templatefilter('email')
119 def email(text):
120 def email(text):
120 """Any text. Extracts the first string that looks like an email
121 """Any text. Extracts the first string that looks like an email
121 address. Example: ``User <user@example.com>`` becomes
122 address. Example: ``User <user@example.com>`` becomes
122 ``user@example.com``.
123 ``user@example.com``.
123 """
124 """
124 return util.email(text)
125 return util.email(text)
125
126
126 @templatefilter('escape')
127 @templatefilter('escape')
127 def escape(text):
128 def escape(text):
128 """Any text. Replaces the special XML/XHTML characters "&", "<"
129 """Any text. Replaces the special XML/XHTML characters "&", "<"
129 and ">" with XML entities, and filters out NUL characters.
130 and ">" with XML entities, and filters out NUL characters.
130 """
131 """
131 return url.escape(text.replace('\0', ''), True)
132 return url.escape(text.replace('\0', ''), True)
132
133
133 para_re = None
134 para_re = None
134 space_re = None
135 space_re = None
135
136
136 def fill(text, width, initindent='', hangindent=''):
137 def fill(text, width, initindent='', hangindent=''):
137 '''fill many paragraphs with optional indentation.'''
138 '''fill many paragraphs with optional indentation.'''
138 global para_re, space_re
139 global para_re, space_re
139 if para_re is None:
140 if para_re is None:
140 para_re = re.compile('(\n\n|\n\\s*[-*]\\s*)', re.M)
141 para_re = re.compile('(\n\n|\n\\s*[-*]\\s*)', re.M)
141 space_re = re.compile(r' +')
142 space_re = re.compile(r' +')
142
143
143 def findparas():
144 def findparas():
144 start = 0
145 start = 0
145 while True:
146 while True:
146 m = para_re.search(text, start)
147 m = para_re.search(text, start)
147 if not m:
148 if not m:
148 uctext = unicode(text[start:], encoding.encoding)
149 uctext = unicode(text[start:], encoding.encoding)
149 w = len(uctext)
150 w = len(uctext)
150 while 0 < w and uctext[w - 1].isspace():
151 while 0 < w and uctext[w - 1].isspace():
151 w -= 1
152 w -= 1
152 yield (uctext[:w].encode(encoding.encoding),
153 yield (uctext[:w].encode(encoding.encoding),
153 uctext[w:].encode(encoding.encoding))
154 uctext[w:].encode(encoding.encoding))
154 break
155 break
155 yield text[start:m.start(0)], m.group(1)
156 yield text[start:m.start(0)], m.group(1)
156 start = m.end(1)
157 start = m.end(1)
157
158
158 return "".join([util.wrap(space_re.sub(' ', util.wrap(para, width)),
159 return "".join([util.wrap(space_re.sub(' ', util.wrap(para, width)),
159 width, initindent, hangindent) + rest
160 width, initindent, hangindent) + rest
160 for para, rest in findparas()])
161 for para, rest in findparas()])
161
162
162 @templatefilter('fill68')
163 @templatefilter('fill68')
163 def fill68(text):
164 def fill68(text):
164 """Any text. Wraps the text to fit in 68 columns."""
165 """Any text. Wraps the text to fit in 68 columns."""
165 return fill(text, 68)
166 return fill(text, 68)
166
167
167 @templatefilter('fill76')
168 @templatefilter('fill76')
168 def fill76(text):
169 def fill76(text):
169 """Any text. Wraps the text to fit in 76 columns."""
170 """Any text. Wraps the text to fit in 76 columns."""
170 return fill(text, 76)
171 return fill(text, 76)
171
172
172 @templatefilter('firstline')
173 @templatefilter('firstline')
173 def firstline(text):
174 def firstline(text):
174 """Any text. Returns the first line of text."""
175 """Any text. Returns the first line of text."""
175 try:
176 try:
176 return text.splitlines(True)[0].rstrip('\r\n')
177 return text.splitlines(True)[0].rstrip('\r\n')
177 except IndexError:
178 except IndexError:
178 return ''
179 return ''
179
180
180 @templatefilter('hex')
181 @templatefilter('hex')
181 def hexfilter(text):
182 def hexfilter(text):
182 """Any text. Convert a binary Mercurial node identifier into
183 """Any text. Convert a binary Mercurial node identifier into
183 its long hexadecimal representation.
184 its long hexadecimal representation.
184 """
185 """
185 return node.hex(text)
186 return node.hex(text)
186
187
187 @templatefilter('hgdate')
188 @templatefilter('hgdate')
188 def hgdate(text):
189 def hgdate(text):
189 """Date. Returns the date as a pair of numbers: "1157407993
190 """Date. Returns the date as a pair of numbers: "1157407993
190 25200" (Unix timestamp, timezone offset).
191 25200" (Unix timestamp, timezone offset).
191 """
192 """
192 return "%d %d" % text
193 return "%d %d" % text
193
194
194 @templatefilter('isodate')
195 @templatefilter('isodate')
195 def isodate(text):
196 def isodate(text):
196 """Date. Returns the date in ISO 8601 format: "2009-08-18 13:00
197 """Date. Returns the date in ISO 8601 format: "2009-08-18 13:00
197 +0200".
198 +0200".
198 """
199 """
199 return util.datestr(text, '%Y-%m-%d %H:%M %1%2')
200 return util.datestr(text, '%Y-%m-%d %H:%M %1%2')
200
201
201 @templatefilter('isodatesec')
202 @templatefilter('isodatesec')
202 def isodatesec(text):
203 def isodatesec(text):
203 """Date. Returns the date in ISO 8601 format, including
204 """Date. Returns the date in ISO 8601 format, including
204 seconds: "2009-08-18 13:00:13 +0200". See also the rfc3339date
205 seconds: "2009-08-18 13:00:13 +0200". See also the rfc3339date
205 filter.
206 filter.
206 """
207 """
207 return util.datestr(text, '%Y-%m-%d %H:%M:%S %1%2')
208 return util.datestr(text, '%Y-%m-%d %H:%M:%S %1%2')
208
209
209 def indent(text, prefix):
210 def indent(text, prefix):
210 '''indent each non-empty line of text after first with prefix.'''
211 '''indent each non-empty line of text after first with prefix.'''
211 lines = text.splitlines()
212 lines = text.splitlines()
212 num_lines = len(lines)
213 num_lines = len(lines)
213 endswithnewline = text[-1:] == '\n'
214 endswithnewline = text[-1:] == '\n'
214 def indenter():
215 def indenter():
215 for i in xrange(num_lines):
216 for i in xrange(num_lines):
216 l = lines[i]
217 l = lines[i]
217 if i and l.strip():
218 if i and l.strip():
218 yield prefix
219 yield prefix
219 yield l
220 yield l
220 if i < num_lines - 1 or endswithnewline:
221 if i < num_lines - 1 or endswithnewline:
221 yield '\n'
222 yield '\n'
222 return "".join(indenter())
223 return "".join(indenter())
223
224
224 @templatefilter('json')
225 @templatefilter('json')
225 def json(obj, paranoid=True):
226 def json(obj, paranoid=True):
226 if obj is None:
227 if obj is None:
227 return 'null'
228 return 'null'
228 elif obj is False:
229 elif obj is False:
229 return 'false'
230 return 'false'
230 elif obj is True:
231 elif obj is True:
231 return 'true'
232 return 'true'
232 elif isinstance(obj, (int, long, float)):
233 elif isinstance(obj, (int, long, float)):
233 return pycompat.bytestr(obj)
234 return pycompat.bytestr(obj)
234 elif isinstance(obj, bytes):
235 elif isinstance(obj, bytes):
235 return '"%s"' % encoding.jsonescape(obj, paranoid=paranoid)
236 return '"%s"' % encoding.jsonescape(obj, paranoid=paranoid)
237 elif isinstance(obj, str):
238 # This branch is unreachable on Python 2, because bytes == str
239 # and we'll return in the next-earlier block in the elif
240 # ladder. On Python 3, this helps us catch bugs before they
241 # hurt someone.
242 raise error.ProgrammingError(
243 'Mercurial only does output with bytes on Python 3: %r' % obj)
236 elif util.safehasattr(obj, 'keys'):
244 elif util.safehasattr(obj, 'keys'):
237 out = ['"%s": %s' % (encoding.jsonescape(k, paranoid=paranoid),
245 out = ['"%s": %s' % (encoding.jsonescape(k, paranoid=paranoid),
238 json(v, paranoid))
246 json(v, paranoid))
239 for k, v in sorted(obj.iteritems())]
247 for k, v in sorted(obj.iteritems())]
240 return '{' + ', '.join(out) + '}'
248 return '{' + ', '.join(out) + '}'
241 elif util.safehasattr(obj, '__iter__'):
249 elif util.safehasattr(obj, '__iter__'):
242 out = [json(i, paranoid) for i in obj]
250 out = [json(i, paranoid) for i in obj]
243 return '[' + ', '.join(out) + ']'
251 return '[' + ', '.join(out) + ']'
244 else:
252 else:
245 raise TypeError('cannot encode type %s' % obj.__class__.__name__)
253 raise TypeError('cannot encode type %s' % obj.__class__.__name__)
246
254
247 @templatefilter('lower')
255 @templatefilter('lower')
248 def lower(text):
256 def lower(text):
249 """Any text. Converts the text to lowercase."""
257 """Any text. Converts the text to lowercase."""
250 return encoding.lower(text)
258 return encoding.lower(text)
251
259
252 @templatefilter('nonempty')
260 @templatefilter('nonempty')
253 def nonempty(str):
261 def nonempty(str):
254 """Any text. Returns '(none)' if the string is empty."""
262 """Any text. Returns '(none)' if the string is empty."""
255 return str or "(none)"
263 return str or "(none)"
256
264
257 @templatefilter('obfuscate')
265 @templatefilter('obfuscate')
258 def obfuscate(text):
266 def obfuscate(text):
259 """Any text. Returns the input text rendered as a sequence of
267 """Any text. Returns the input text rendered as a sequence of
260 XML entities.
268 XML entities.
261 """
269 """
262 text = unicode(text, encoding.encoding, 'replace')
270 text = unicode(text, encoding.encoding, 'replace')
263 return ''.join(['&#%d;' % ord(c) for c in text])
271 return ''.join(['&#%d;' % ord(c) for c in text])
264
272
265 @templatefilter('permissions')
273 @templatefilter('permissions')
266 def permissions(flags):
274 def permissions(flags):
267 if "l" in flags:
275 if "l" in flags:
268 return "lrwxrwxrwx"
276 return "lrwxrwxrwx"
269 if "x" in flags:
277 if "x" in flags:
270 return "-rwxr-xr-x"
278 return "-rwxr-xr-x"
271 return "-rw-r--r--"
279 return "-rw-r--r--"
272
280
273 @templatefilter('person')
281 @templatefilter('person')
274 def person(author):
282 def person(author):
275 """Any text. Returns the name before an email address,
283 """Any text. Returns the name before an email address,
276 interpreting it as per RFC 5322.
284 interpreting it as per RFC 5322.
277
285
278 >>> person(b'foo@bar')
286 >>> person(b'foo@bar')
279 'foo'
287 'foo'
280 >>> person(b'Foo Bar <foo@bar>')
288 >>> person(b'Foo Bar <foo@bar>')
281 'Foo Bar'
289 'Foo Bar'
282 >>> person(b'"Foo Bar" <foo@bar>')
290 >>> person(b'"Foo Bar" <foo@bar>')
283 'Foo Bar'
291 'Foo Bar'
284 >>> person(b'"Foo \"buz\" Bar" <foo@bar>')
292 >>> person(b'"Foo \"buz\" Bar" <foo@bar>')
285 'Foo "buz" Bar'
293 'Foo "buz" Bar'
286 >>> # The following are invalid, but do exist in real-life
294 >>> # The following are invalid, but do exist in real-life
287 ...
295 ...
288 >>> person(b'Foo "buz" Bar <foo@bar>')
296 >>> person(b'Foo "buz" Bar <foo@bar>')
289 'Foo "buz" Bar'
297 'Foo "buz" Bar'
290 >>> person(b'"Foo Bar <foo@bar>')
298 >>> person(b'"Foo Bar <foo@bar>')
291 'Foo Bar'
299 'Foo Bar'
292 """
300 """
293 if '@' not in author:
301 if '@' not in author:
294 return author
302 return author
295 f = author.find('<')
303 f = author.find('<')
296 if f != -1:
304 if f != -1:
297 return author[:f].strip(' "').replace('\\"', '"')
305 return author[:f].strip(' "').replace('\\"', '"')
298 f = author.find('@')
306 f = author.find('@')
299 return author[:f].replace('.', ' ')
307 return author[:f].replace('.', ' ')
300
308
301 @templatefilter('revescape')
309 @templatefilter('revescape')
302 def revescape(text):
310 def revescape(text):
303 """Any text. Escapes all "special" characters, except @.
311 """Any text. Escapes all "special" characters, except @.
304 Forward slashes are escaped twice to prevent web servers from prematurely
312 Forward slashes are escaped twice to prevent web servers from prematurely
305 unescaping them. For example, "@foo bar/baz" becomes "@foo%20bar%252Fbaz".
313 unescaping them. For example, "@foo bar/baz" becomes "@foo%20bar%252Fbaz".
306 """
314 """
307 return urlreq.quote(text, safe='/@').replace('/', '%252F')
315 return urlreq.quote(text, safe='/@').replace('/', '%252F')
308
316
309 @templatefilter('rfc3339date')
317 @templatefilter('rfc3339date')
310 def rfc3339date(text):
318 def rfc3339date(text):
311 """Date. Returns a date using the Internet date format
319 """Date. Returns a date using the Internet date format
312 specified in RFC 3339: "2009-08-18T13:00:13+02:00".
320 specified in RFC 3339: "2009-08-18T13:00:13+02:00".
313 """
321 """
314 return util.datestr(text, "%Y-%m-%dT%H:%M:%S%1:%2")
322 return util.datestr(text, "%Y-%m-%dT%H:%M:%S%1:%2")
315
323
316 @templatefilter('rfc822date')
324 @templatefilter('rfc822date')
317 def rfc822date(text):
325 def rfc822date(text):
318 """Date. Returns a date using the same format used in email
326 """Date. Returns a date using the same format used in email
319 headers: "Tue, 18 Aug 2009 13:00:13 +0200".
327 headers: "Tue, 18 Aug 2009 13:00:13 +0200".
320 """
328 """
321 return util.datestr(text, "%a, %d %b %Y %H:%M:%S %1%2")
329 return util.datestr(text, "%a, %d %b %Y %H:%M:%S %1%2")
322
330
323 @templatefilter('short')
331 @templatefilter('short')
324 def short(text):
332 def short(text):
325 """Changeset hash. Returns the short form of a changeset hash,
333 """Changeset hash. Returns the short form of a changeset hash,
326 i.e. a 12 hexadecimal digit string.
334 i.e. a 12 hexadecimal digit string.
327 """
335 """
328 return text[:12]
336 return text[:12]
329
337
330 @templatefilter('shortbisect')
338 @templatefilter('shortbisect')
331 def shortbisect(text):
339 def shortbisect(text):
332 """Any text. Treats `text` as a bisection status, and
340 """Any text. Treats `text` as a bisection status, and
333 returns a single-character representing the status (G: good, B: bad,
341 returns a single-character representing the status (G: good, B: bad,
334 S: skipped, U: untested, I: ignored). Returns single space if `text`
342 S: skipped, U: untested, I: ignored). Returns single space if `text`
335 is not a valid bisection status.
343 is not a valid bisection status.
336 """
344 """
337 return hbisect.shortlabel(text) or ' '
345 return hbisect.shortlabel(text) or ' '
338
346
339 @templatefilter('shortdate')
347 @templatefilter('shortdate')
340 def shortdate(text):
348 def shortdate(text):
341 """Date. Returns a date like "2006-09-18"."""
349 """Date. Returns a date like "2006-09-18"."""
342 return util.shortdate(text)
350 return util.shortdate(text)
343
351
344 @templatefilter('splitlines')
352 @templatefilter('splitlines')
345 def splitlines(text):
353 def splitlines(text):
346 """Any text. Split text into a list of lines."""
354 """Any text. Split text into a list of lines."""
347 return templatekw.hybridlist(text.splitlines(), name='line')
355 return templatekw.hybridlist(text.splitlines(), name='line')
348
356
349 @templatefilter('stringescape')
357 @templatefilter('stringescape')
350 def stringescape(text):
358 def stringescape(text):
351 return util.escapestr(text)
359 return util.escapestr(text)
352
360
353 @templatefilter('stringify')
361 @templatefilter('stringify')
354 def stringify(thing):
362 def stringify(thing):
355 """Any type. Turns the value into text by converting values into
363 """Any type. Turns the value into text by converting values into
356 text and concatenating them.
364 text and concatenating them.
357 """
365 """
358 thing = templatekw.unwraphybrid(thing)
366 thing = templatekw.unwraphybrid(thing)
359 if util.safehasattr(thing, '__iter__') and not isinstance(thing, bytes):
367 if util.safehasattr(thing, '__iter__') and not isinstance(thing, bytes):
360 return "".join([stringify(t) for t in thing if t is not None])
368 return "".join([stringify(t) for t in thing if t is not None])
361 if thing is None:
369 if thing is None:
362 return ""
370 return ""
363 return pycompat.bytestr(thing)
371 return pycompat.bytestr(thing)
364
372
365 @templatefilter('stripdir')
373 @templatefilter('stripdir')
366 def stripdir(text):
374 def stripdir(text):
367 """Treat the text as path and strip a directory level, if
375 """Treat the text as path and strip a directory level, if
368 possible. For example, "foo" and "foo/bar" becomes "foo".
376 possible. For example, "foo" and "foo/bar" becomes "foo".
369 """
377 """
370 dir = os.path.dirname(text)
378 dir = os.path.dirname(text)
371 if dir == "":
379 if dir == "":
372 return os.path.basename(text)
380 return os.path.basename(text)
373 else:
381 else:
374 return dir
382 return dir
375
383
376 @templatefilter('tabindent')
384 @templatefilter('tabindent')
377 def tabindent(text):
385 def tabindent(text):
378 """Any text. Returns the text, with every non-empty line
386 """Any text. Returns the text, with every non-empty line
379 except the first starting with a tab character.
387 except the first starting with a tab character.
380 """
388 """
381 return indent(text, '\t')
389 return indent(text, '\t')
382
390
383 @templatefilter('upper')
391 @templatefilter('upper')
384 def upper(text):
392 def upper(text):
385 """Any text. Converts the text to uppercase."""
393 """Any text. Converts the text to uppercase."""
386 return encoding.upper(text)
394 return encoding.upper(text)
387
395
388 @templatefilter('urlescape')
396 @templatefilter('urlescape')
389 def urlescape(text):
397 def urlescape(text):
390 """Any text. Escapes all "special" characters. For example,
398 """Any text. Escapes all "special" characters. For example,
391 "foo bar" becomes "foo%20bar".
399 "foo bar" becomes "foo%20bar".
392 """
400 """
393 return urlreq.quote(text)
401 return urlreq.quote(text)
394
402
395 @templatefilter('user')
403 @templatefilter('user')
396 def userfilter(text):
404 def userfilter(text):
397 """Any text. Returns a short representation of a user name or email
405 """Any text. Returns a short representation of a user name or email
398 address."""
406 address."""
399 return util.shortuser(text)
407 return util.shortuser(text)
400
408
401 @templatefilter('emailuser')
409 @templatefilter('emailuser')
402 def emailuser(text):
410 def emailuser(text):
403 """Any text. Returns the user portion of an email address."""
411 """Any text. Returns the user portion of an email address."""
404 return util.emailuser(text)
412 return util.emailuser(text)
405
413
406 @templatefilter('utf8')
414 @templatefilter('utf8')
407 def utf8(text):
415 def utf8(text):
408 """Any text. Converts from the local character encoding to UTF-8."""
416 """Any text. Converts from the local character encoding to UTF-8."""
409 return encoding.fromlocal(text)
417 return encoding.fromlocal(text)
410
418
411 @templatefilter('xmlescape')
419 @templatefilter('xmlescape')
412 def xmlescape(text):
420 def xmlescape(text):
413 text = (text
421 text = (text
414 .replace('&', '&amp;')
422 .replace('&', '&amp;')
415 .replace('<', '&lt;')
423 .replace('<', '&lt;')
416 .replace('>', '&gt;')
424 .replace('>', '&gt;')
417 .replace('"', '&quot;')
425 .replace('"', '&quot;')
418 .replace("'", '&#39;')) # &apos; invalid in HTML
426 .replace("'", '&#39;')) # &apos; invalid in HTML
419 return re.sub('[\x00-\x08\x0B\x0C\x0E-\x1F]', ' ', text)
427 return re.sub('[\x00-\x08\x0B\x0C\x0E-\x1F]', ' ', text)
420
428
421 def websub(text, websubtable):
429 def websub(text, websubtable):
422 """:websub: Any text. Only applies to hgweb. Applies the regular
430 """:websub: Any text. Only applies to hgweb. Applies the regular
423 expression replacements defined in the websub section.
431 expression replacements defined in the websub section.
424 """
432 """
425 if websubtable:
433 if websubtable:
426 for regexp, format in websubtable:
434 for regexp, format in websubtable:
427 text = regexp.sub(format, text)
435 text = regexp.sub(format, text)
428 return text
436 return text
429
437
430 def loadfilter(ui, extname, registrarobj):
438 def loadfilter(ui, extname, registrarobj):
431 """Load template filter from specified registrarobj
439 """Load template filter from specified registrarobj
432 """
440 """
433 for name, func in registrarobj._table.iteritems():
441 for name, func in registrarobj._table.iteritems():
434 filters[name] = func
442 filters[name] = func
435
443
436 # tell hggettext to extract docstrings from these functions:
444 # tell hggettext to extract docstrings from these functions:
437 i18nfunctions = filters.values()
445 i18nfunctions = filters.values()
General Comments 0
You need to be logged in to leave comments. Login now