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