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