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