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