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