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