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