##// END OF EJS Templates
templatefilters: raise ProgrammingError if unencodable type passed to json()...
Yuya Nishihara -
r37247:63144f33 default
parent child Browse files
Show More
@@ -1,440 +1,439
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 if obj is None:
241 if obj is None:
242 return 'null'
242 return 'null'
243 elif obj is False:
243 elif obj is False:
244 return 'false'
244 return 'false'
245 elif obj is True:
245 elif obj is True:
246 return 'true'
246 return 'true'
247 elif isinstance(obj, (int, long, float)):
247 elif isinstance(obj, (int, long, float)):
248 return pycompat.bytestr(obj)
248 return pycompat.bytestr(obj)
249 elif isinstance(obj, bytes):
249 elif isinstance(obj, bytes):
250 return '"%s"' % encoding.jsonescape(obj, paranoid=paranoid)
250 return '"%s"' % encoding.jsonescape(obj, paranoid=paranoid)
251 elif isinstance(obj, str):
251 elif isinstance(obj, str):
252 # This branch is unreachable on Python 2, because bytes == str
252 # This branch is unreachable on Python 2, because bytes == str
253 # and we'll return in the next-earlier block in the elif
253 # and we'll return in the next-earlier block in the elif
254 # ladder. On Python 3, this helps us catch bugs before they
254 # ladder. On Python 3, this helps us catch bugs before they
255 # hurt someone.
255 # hurt someone.
256 raise error.ProgrammingError(
256 raise error.ProgrammingError(
257 'Mercurial only does output with bytes on Python 3: %r' % obj)
257 'Mercurial only does output with bytes on Python 3: %r' % obj)
258 elif util.safehasattr(obj, 'keys'):
258 elif util.safehasattr(obj, 'keys'):
259 out = ['"%s": %s' % (encoding.jsonescape(k, paranoid=paranoid),
259 out = ['"%s": %s' % (encoding.jsonescape(k, paranoid=paranoid),
260 json(v, paranoid))
260 json(v, paranoid))
261 for k, v in sorted(obj.iteritems())]
261 for k, v in sorted(obj.iteritems())]
262 return '{' + ', '.join(out) + '}'
262 return '{' + ', '.join(out) + '}'
263 elif util.safehasattr(obj, '__iter__'):
263 elif util.safehasattr(obj, '__iter__'):
264 out = [json(i, paranoid) for i in obj]
264 out = [json(i, paranoid) for i in obj]
265 return '[' + ', '.join(out) + ']'
265 return '[' + ', '.join(out) + ']'
266 else:
266 raise error.ProgrammingError('cannot encode %r' % obj)
267 raise TypeError('cannot encode type %s' % obj.__class__.__name__)
268
267
269 @templatefilter('lower', intype=bytes)
268 @templatefilter('lower', intype=bytes)
270 def lower(text):
269 def lower(text):
271 """Any text. Converts the text to lowercase."""
270 """Any text. Converts the text to lowercase."""
272 return encoding.lower(text)
271 return encoding.lower(text)
273
272
274 @templatefilter('nonempty', intype=bytes)
273 @templatefilter('nonempty', intype=bytes)
275 def nonempty(text):
274 def nonempty(text):
276 """Any text. Returns '(none)' if the string is empty."""
275 """Any text. Returns '(none)' if the string is empty."""
277 return text or "(none)"
276 return text or "(none)"
278
277
279 @templatefilter('obfuscate', intype=bytes)
278 @templatefilter('obfuscate', intype=bytes)
280 def obfuscate(text):
279 def obfuscate(text):
281 """Any text. Returns the input text rendered as a sequence of
280 """Any text. Returns the input text rendered as a sequence of
282 XML entities.
281 XML entities.
283 """
282 """
284 text = unicode(text, pycompat.sysstr(encoding.encoding), r'replace')
283 text = unicode(text, pycompat.sysstr(encoding.encoding), r'replace')
285 return ''.join(['&#%d;' % ord(c) for c in text])
284 return ''.join(['&#%d;' % ord(c) for c in text])
286
285
287 @templatefilter('permissions', intype=bytes)
286 @templatefilter('permissions', intype=bytes)
288 def permissions(flags):
287 def permissions(flags):
289 if "l" in flags:
288 if "l" in flags:
290 return "lrwxrwxrwx"
289 return "lrwxrwxrwx"
291 if "x" in flags:
290 if "x" in flags:
292 return "-rwxr-xr-x"
291 return "-rwxr-xr-x"
293 return "-rw-r--r--"
292 return "-rw-r--r--"
294
293
295 @templatefilter('person', intype=bytes)
294 @templatefilter('person', intype=bytes)
296 def person(author):
295 def person(author):
297 """Any text. Returns the name before an email address,
296 """Any text. Returns the name before an email address,
298 interpreting it as per RFC 5322.
297 interpreting it as per RFC 5322.
299 """
298 """
300 return stringutil.person(author)
299 return stringutil.person(author)
301
300
302 @templatefilter('revescape', intype=bytes)
301 @templatefilter('revescape', intype=bytes)
303 def revescape(text):
302 def revescape(text):
304 """Any text. Escapes all "special" characters, except @.
303 """Any text. Escapes all "special" characters, except @.
305 Forward slashes are escaped twice to prevent web servers from prematurely
304 Forward slashes are escaped twice to prevent web servers from prematurely
306 unescaping them. For example, "@foo bar/baz" becomes "@foo%20bar%252Fbaz".
305 unescaping them. For example, "@foo bar/baz" becomes "@foo%20bar%252Fbaz".
307 """
306 """
308 return urlreq.quote(text, safe='/@').replace('/', '%252F')
307 return urlreq.quote(text, safe='/@').replace('/', '%252F')
309
308
310 @templatefilter('rfc3339date', intype=templateutil.date)
309 @templatefilter('rfc3339date', intype=templateutil.date)
311 def rfc3339date(text):
310 def rfc3339date(text):
312 """Date. Returns a date using the Internet date format
311 """Date. Returns a date using the Internet date format
313 specified in RFC 3339: "2009-08-18T13:00:13+02:00".
312 specified in RFC 3339: "2009-08-18T13:00:13+02:00".
314 """
313 """
315 return dateutil.datestr(text, "%Y-%m-%dT%H:%M:%S%1:%2")
314 return dateutil.datestr(text, "%Y-%m-%dT%H:%M:%S%1:%2")
316
315
317 @templatefilter('rfc822date', intype=templateutil.date)
316 @templatefilter('rfc822date', intype=templateutil.date)
318 def rfc822date(text):
317 def rfc822date(text):
319 """Date. Returns a date using the same format used in email
318 """Date. Returns a date using the same format used in email
320 headers: "Tue, 18 Aug 2009 13:00:13 +0200".
319 headers: "Tue, 18 Aug 2009 13:00:13 +0200".
321 """
320 """
322 return dateutil.datestr(text, "%a, %d %b %Y %H:%M:%S %1%2")
321 return dateutil.datestr(text, "%a, %d %b %Y %H:%M:%S %1%2")
323
322
324 @templatefilter('short', intype=bytes)
323 @templatefilter('short', intype=bytes)
325 def short(text):
324 def short(text):
326 """Changeset hash. Returns the short form of a changeset hash,
325 """Changeset hash. Returns the short form of a changeset hash,
327 i.e. a 12 hexadecimal digit string.
326 i.e. a 12 hexadecimal digit string.
328 """
327 """
329 return text[:12]
328 return text[:12]
330
329
331 @templatefilter('shortbisect', intype=bytes)
330 @templatefilter('shortbisect', intype=bytes)
332 def shortbisect(label):
331 def shortbisect(label):
333 """Any text. Treats `label` as a bisection status, and
332 """Any text. Treats `label` as a bisection status, and
334 returns a single-character representing the status (G: good, B: bad,
333 returns a single-character representing the status (G: good, B: bad,
335 S: skipped, U: untested, I: ignored). Returns single space if `text`
334 S: skipped, U: untested, I: ignored). Returns single space if `text`
336 is not a valid bisection status.
335 is not a valid bisection status.
337 """
336 """
338 if label:
337 if label:
339 return label[0:1].upper()
338 return label[0:1].upper()
340 return ' '
339 return ' '
341
340
342 @templatefilter('shortdate', intype=templateutil.date)
341 @templatefilter('shortdate', intype=templateutil.date)
343 def shortdate(text):
342 def shortdate(text):
344 """Date. Returns a date like "2006-09-18"."""
343 """Date. Returns a date like "2006-09-18"."""
345 return dateutil.shortdate(text)
344 return dateutil.shortdate(text)
346
345
347 @templatefilter('slashpath', intype=bytes)
346 @templatefilter('slashpath', intype=bytes)
348 def slashpath(path):
347 def slashpath(path):
349 """Any text. Replaces the native path separator with slash."""
348 """Any text. Replaces the native path separator with slash."""
350 return util.pconvert(path)
349 return util.pconvert(path)
351
350
352 @templatefilter('splitlines', intype=bytes)
351 @templatefilter('splitlines', intype=bytes)
353 def splitlines(text):
352 def splitlines(text):
354 """Any text. Split text into a list of lines."""
353 """Any text. Split text into a list of lines."""
355 return templateutil.hybridlist(text.splitlines(), name='line')
354 return templateutil.hybridlist(text.splitlines(), name='line')
356
355
357 @templatefilter('stringescape', intype=bytes)
356 @templatefilter('stringescape', intype=bytes)
358 def stringescape(text):
357 def stringescape(text):
359 return stringutil.escapestr(text)
358 return stringutil.escapestr(text)
360
359
361 @templatefilter('stringify', intype=bytes)
360 @templatefilter('stringify', intype=bytes)
362 def stringify(thing):
361 def stringify(thing):
363 """Any type. Turns the value into text by converting values into
362 """Any type. Turns the value into text by converting values into
364 text and concatenating them.
363 text and concatenating them.
365 """
364 """
366 return thing # coerced by the intype
365 return thing # coerced by the intype
367
366
368 @templatefilter('stripdir', intype=bytes)
367 @templatefilter('stripdir', intype=bytes)
369 def stripdir(text):
368 def stripdir(text):
370 """Treat the text as path and strip a directory level, if
369 """Treat the text as path and strip a directory level, if
371 possible. For example, "foo" and "foo/bar" becomes "foo".
370 possible. For example, "foo" and "foo/bar" becomes "foo".
372 """
371 """
373 dir = os.path.dirname(text)
372 dir = os.path.dirname(text)
374 if dir == "":
373 if dir == "":
375 return os.path.basename(text)
374 return os.path.basename(text)
376 else:
375 else:
377 return dir
376 return dir
378
377
379 @templatefilter('tabindent', intype=bytes)
378 @templatefilter('tabindent', intype=bytes)
380 def tabindent(text):
379 def tabindent(text):
381 """Any text. Returns the text, with every non-empty line
380 """Any text. Returns the text, with every non-empty line
382 except the first starting with a tab character.
381 except the first starting with a tab character.
383 """
382 """
384 return indent(text, '\t')
383 return indent(text, '\t')
385
384
386 @templatefilter('upper', intype=bytes)
385 @templatefilter('upper', intype=bytes)
387 def upper(text):
386 def upper(text):
388 """Any text. Converts the text to uppercase."""
387 """Any text. Converts the text to uppercase."""
389 return encoding.upper(text)
388 return encoding.upper(text)
390
389
391 @templatefilter('urlescape', intype=bytes)
390 @templatefilter('urlescape', intype=bytes)
392 def urlescape(text):
391 def urlescape(text):
393 """Any text. Escapes all "special" characters. For example,
392 """Any text. Escapes all "special" characters. For example,
394 "foo bar" becomes "foo%20bar".
393 "foo bar" becomes "foo%20bar".
395 """
394 """
396 return urlreq.quote(text)
395 return urlreq.quote(text)
397
396
398 @templatefilter('user', intype=bytes)
397 @templatefilter('user', intype=bytes)
399 def userfilter(text):
398 def userfilter(text):
400 """Any text. Returns a short representation of a user name or email
399 """Any text. Returns a short representation of a user name or email
401 address."""
400 address."""
402 return stringutil.shortuser(text)
401 return stringutil.shortuser(text)
403
402
404 @templatefilter('emailuser', intype=bytes)
403 @templatefilter('emailuser', intype=bytes)
405 def emailuser(text):
404 def emailuser(text):
406 """Any text. Returns the user portion of an email address."""
405 """Any text. Returns the user portion of an email address."""
407 return stringutil.emailuser(text)
406 return stringutil.emailuser(text)
408
407
409 @templatefilter('utf8', intype=bytes)
408 @templatefilter('utf8', intype=bytes)
410 def utf8(text):
409 def utf8(text):
411 """Any text. Converts from the local character encoding to UTF-8."""
410 """Any text. Converts from the local character encoding to UTF-8."""
412 return encoding.fromlocal(text)
411 return encoding.fromlocal(text)
413
412
414 @templatefilter('xmlescape', intype=bytes)
413 @templatefilter('xmlescape', intype=bytes)
415 def xmlescape(text):
414 def xmlescape(text):
416 text = (text
415 text = (text
417 .replace('&', '&amp;')
416 .replace('&', '&amp;')
418 .replace('<', '&lt;')
417 .replace('<', '&lt;')
419 .replace('>', '&gt;')
418 .replace('>', '&gt;')
420 .replace('"', '&quot;')
419 .replace('"', '&quot;')
421 .replace("'", '&#39;')) # &apos; invalid in HTML
420 .replace("'", '&#39;')) # &apos; invalid in HTML
422 return re.sub('[\x00-\x08\x0B\x0C\x0E-\x1F]', ' ', text)
421 return re.sub('[\x00-\x08\x0B\x0C\x0E-\x1F]', ' ', text)
423
422
424 def websub(text, websubtable):
423 def websub(text, websubtable):
425 """:websub: Any text. Only applies to hgweb. Applies the regular
424 """:websub: Any text. Only applies to hgweb. Applies the regular
426 expression replacements defined in the websub section.
425 expression replacements defined in the websub section.
427 """
426 """
428 if websubtable:
427 if websubtable:
429 for regexp, format in websubtable:
428 for regexp, format in websubtable:
430 text = regexp.sub(format, text)
429 text = regexp.sub(format, text)
431 return text
430 return text
432
431
433 def loadfilter(ui, extname, registrarobj):
432 def loadfilter(ui, extname, registrarobj):
434 """Load template filter from specified registrarobj
433 """Load template filter from specified registrarobj
435 """
434 """
436 for name, func in registrarobj._table.iteritems():
435 for name, func in registrarobj._table.iteritems():
437 filters[name] = func
436 filters[name] = func
438
437
439 # tell hggettext to extract docstrings from these functions:
438 # tell hggettext to extract docstrings from these functions:
440 i18nfunctions = filters.values()
439 i18nfunctions = filters.values()
General Comments 0
You need to be logged in to leave comments. Login now