Mercurial > hg
comparison mercurial/templatefilters.py @ 28693:11f623b5668f
templatefilters: use templatefilter to mark a function as template filter
Using decorator can localize changes for adding (or removing) a
template filter function in source code.
This patch also removes leading ":FILTER:" part in help document of
each filters, because using templatefilter makes it useless.
This patch uses not 'filter' but 'templatefilter' as a decorator name,
because the former name hides Python built-in one, even though the
latter is a little redundant in 'templatefilters.py'.
author | FUJIWARA Katsunori <foozy@lares.dti.ne.jp> |
---|---|
date | Wed, 30 Mar 2016 02:10:44 +0900 |
parents | 6b3b958daf03 |
children | 032c4c2f802a |
comparison
equal
deleted
inserted
replaced
28692:6b3b958daf03 | 28693:11f623b5668f |
---|---|
15 | 15 |
16 from . import ( | 16 from . import ( |
17 encoding, | 17 encoding, |
18 hbisect, | 18 hbisect, |
19 node, | 19 node, |
20 registrar, | |
20 templatekw, | 21 templatekw, |
21 util, | 22 util, |
22 ) | 23 ) |
23 | 24 |
25 # filters are callables like: | |
26 # fn(obj) | |
27 # with: | |
28 # obj - object to be filtered (text, date, list and so on) | |
29 filters = {} | |
30 | |
31 templatefilter = registrar.templatefilter(filters) | |
32 | |
33 @templatefilter('addbreaks') | |
24 def addbreaks(text): | 34 def addbreaks(text): |
25 """:addbreaks: Any text. Add an XHTML "<br />" tag before the end of | 35 """Any text. Add an XHTML "<br />" tag before the end of |
26 every line except the last. | 36 every line except the last. |
27 """ | 37 """ |
28 return text.replace('\n', '<br/>\n') | 38 return text.replace('\n', '<br/>\n') |
29 | 39 |
30 agescales = [("year", 3600 * 24 * 365, 'Y'), | 40 agescales = [("year", 3600 * 24 * 365, 'Y'), |
33 ("day", 3600 * 24, 'd'), | 43 ("day", 3600 * 24, 'd'), |
34 ("hour", 3600, 'h'), | 44 ("hour", 3600, 'h'), |
35 ("minute", 60, 'm'), | 45 ("minute", 60, 'm'), |
36 ("second", 1, 's')] | 46 ("second", 1, 's')] |
37 | 47 |
48 @templatefilter('age') | |
38 def age(date, abbrev=False): | 49 def age(date, abbrev=False): |
39 """:age: Date. Returns a human-readable date/time difference between the | 50 """Date. Returns a human-readable date/time difference between the |
40 given date/time and the current date/time. | 51 given date/time and the current date/time. |
41 """ | 52 """ |
42 | 53 |
43 def plural(t, c): | 54 def plural(t, c): |
44 if c == 1: | 55 if c == 1: |
67 if n >= 2 or s == 1: | 78 if n >= 2 or s == 1: |
68 if future: | 79 if future: |
69 return '%s from now' % fmt(t, n, a) | 80 return '%s from now' % fmt(t, n, a) |
70 return '%s ago' % fmt(t, n, a) | 81 return '%s ago' % fmt(t, n, a) |
71 | 82 |
83 @templatefilter('basename') | |
72 def basename(path): | 84 def basename(path): |
73 """:basename: Any text. Treats the text as a path, and returns the last | 85 """Any text. Treats the text as a path, and returns the last |
74 component of the path after splitting by the path separator | 86 component of the path after splitting by the path separator |
75 (ignoring trailing separators). For example, "foo/bar/baz" becomes | 87 (ignoring trailing separators). For example, "foo/bar/baz" becomes |
76 "baz" and "foo/bar//" becomes "bar". | 88 "baz" and "foo/bar//" becomes "bar". |
77 """ | 89 """ |
78 return os.path.basename(path) | 90 return os.path.basename(path) |
79 | 91 |
92 @templatefilter('count') | |
80 def count(i): | 93 def count(i): |
81 """:count: List or text. Returns the length as an integer.""" | 94 """List or text. Returns the length as an integer.""" |
82 return len(i) | 95 return len(i) |
83 | 96 |
97 @templatefilter('domain') | |
84 def domain(author): | 98 def domain(author): |
85 """:domain: Any text. Finds the first string that looks like an email | 99 """Any text. Finds the first string that looks like an email |
86 address, and extracts just the domain component. Example: ``User | 100 address, and extracts just the domain component. Example: ``User |
87 <user@example.com>`` becomes ``example.com``. | 101 <user@example.com>`` becomes ``example.com``. |
88 """ | 102 """ |
89 f = author.find('@') | 103 f = author.find('@') |
90 if f == -1: | 104 if f == -1: |
93 f = author.find('>') | 107 f = author.find('>') |
94 if f >= 0: | 108 if f >= 0: |
95 author = author[:f] | 109 author = author[:f] |
96 return author | 110 return author |
97 | 111 |
112 @templatefilter('email') | |
98 def email(text): | 113 def email(text): |
99 """:email: Any text. Extracts the first string that looks like an email | 114 """Any text. Extracts the first string that looks like an email |
100 address. Example: ``User <user@example.com>`` becomes | 115 address. Example: ``User <user@example.com>`` becomes |
101 ``user@example.com``. | 116 ``user@example.com``. |
102 """ | 117 """ |
103 return util.email(text) | 118 return util.email(text) |
104 | 119 |
120 @templatefilter('escape') | |
105 def escape(text): | 121 def escape(text): |
106 """:escape: Any text. Replaces the special XML/XHTML characters "&", "<" | 122 """Any text. Replaces the special XML/XHTML characters "&", "<" |
107 and ">" with XML entities, and filters out NUL characters. | 123 and ">" with XML entities, and filters out NUL characters. |
108 """ | 124 """ |
109 return cgi.escape(text.replace('\0', ''), True) | 125 return cgi.escape(text.replace('\0', ''), True) |
110 | 126 |
111 para_re = None | 127 para_re = None |
135 | 151 |
136 return "".join([util.wrap(space_re.sub(' ', util.wrap(para, width)), | 152 return "".join([util.wrap(space_re.sub(' ', util.wrap(para, width)), |
137 width, initindent, hangindent) + rest | 153 width, initindent, hangindent) + rest |
138 for para, rest in findparas()]) | 154 for para, rest in findparas()]) |
139 | 155 |
156 @templatefilter('fill68') | |
140 def fill68(text): | 157 def fill68(text): |
141 """:fill68: Any text. Wraps the text to fit in 68 columns.""" | 158 """Any text. Wraps the text to fit in 68 columns.""" |
142 return fill(text, 68) | 159 return fill(text, 68) |
143 | 160 |
161 @templatefilter('fill76') | |
144 def fill76(text): | 162 def fill76(text): |
145 """:fill76: Any text. Wraps the text to fit in 76 columns.""" | 163 """Any text. Wraps the text to fit in 76 columns.""" |
146 return fill(text, 76) | 164 return fill(text, 76) |
147 | 165 |
166 @templatefilter('firstline') | |
148 def firstline(text): | 167 def firstline(text): |
149 """:firstline: Any text. Returns the first line of text.""" | 168 """Any text. Returns the first line of text.""" |
150 try: | 169 try: |
151 return text.splitlines(True)[0].rstrip('\r\n') | 170 return text.splitlines(True)[0].rstrip('\r\n') |
152 except IndexError: | 171 except IndexError: |
153 return '' | 172 return '' |
154 | 173 |
174 @templatefilter('hex') | |
155 def hexfilter(text): | 175 def hexfilter(text): |
156 """:hex: Any text. Convert a binary Mercurial node identifier into | 176 """Any text. Convert a binary Mercurial node identifier into |
157 its long hexadecimal representation. | 177 its long hexadecimal representation. |
158 """ | 178 """ |
159 return node.hex(text) | 179 return node.hex(text) |
160 | 180 |
181 @templatefilter('hgdate') | |
161 def hgdate(text): | 182 def hgdate(text): |
162 """:hgdate: Date. Returns the date as a pair of numbers: "1157407993 | 183 """Date. Returns the date as a pair of numbers: "1157407993 |
163 25200" (Unix timestamp, timezone offset). | 184 25200" (Unix timestamp, timezone offset). |
164 """ | 185 """ |
165 return "%d %d" % text | 186 return "%d %d" % text |
166 | 187 |
188 @templatefilter('isodate') | |
167 def isodate(text): | 189 def isodate(text): |
168 """:isodate: Date. Returns the date in ISO 8601 format: "2009-08-18 13:00 | 190 """Date. Returns the date in ISO 8601 format: "2009-08-18 13:00 |
169 +0200". | 191 +0200". |
170 """ | 192 """ |
171 return util.datestr(text, '%Y-%m-%d %H:%M %1%2') | 193 return util.datestr(text, '%Y-%m-%d %H:%M %1%2') |
172 | 194 |
195 @templatefilter('isodatesec') | |
173 def isodatesec(text): | 196 def isodatesec(text): |
174 """:isodatesec: Date. Returns the date in ISO 8601 format, including | 197 """Date. Returns the date in ISO 8601 format, including |
175 seconds: "2009-08-18 13:00:13 +0200". See also the rfc3339date | 198 seconds: "2009-08-18 13:00:13 +0200". See also the rfc3339date |
176 filter. | 199 filter. |
177 """ | 200 """ |
178 return util.datestr(text, '%Y-%m-%d %H:%M:%S %1%2') | 201 return util.datestr(text, '%Y-%m-%d %H:%M:%S %1%2') |
179 | 202 |
190 yield l | 213 yield l |
191 if i < num_lines - 1 or endswithnewline: | 214 if i < num_lines - 1 or endswithnewline: |
192 yield '\n' | 215 yield '\n' |
193 return "".join(indenter()) | 216 return "".join(indenter()) |
194 | 217 |
218 @templatefilter('json') | |
195 def json(obj): | 219 def json(obj): |
196 if obj is None or obj is False or obj is True: | 220 if obj is None or obj is False or obj is True: |
197 return {None: 'null', False: 'false', True: 'true'}[obj] | 221 return {None: 'null', False: 'false', True: 'true'}[obj] |
198 elif isinstance(obj, int) or isinstance(obj, float): | 222 elif isinstance(obj, int) or isinstance(obj, float): |
199 return str(obj) | 223 return str(obj) |
213 elif util.safehasattr(obj, '__call__'): | 237 elif util.safehasattr(obj, '__call__'): |
214 return json(obj()) | 238 return json(obj()) |
215 else: | 239 else: |
216 raise TypeError('cannot encode type %s' % obj.__class__.__name__) | 240 raise TypeError('cannot encode type %s' % obj.__class__.__name__) |
217 | 241 |
242 @templatefilter('lower') | |
218 def lower(text): | 243 def lower(text): |
219 """:lower: Any text. Converts the text to lowercase.""" | 244 """Any text. Converts the text to lowercase.""" |
220 return encoding.lower(text) | 245 return encoding.lower(text) |
221 | 246 |
247 @templatefilter('nonempty') | |
222 def nonempty(str): | 248 def nonempty(str): |
223 """:nonempty: Any text. Returns '(none)' if the string is empty.""" | 249 """Any text. Returns '(none)' if the string is empty.""" |
224 return str or "(none)" | 250 return str or "(none)" |
225 | 251 |
252 @templatefilter('obfuscate') | |
226 def obfuscate(text): | 253 def obfuscate(text): |
227 """:obfuscate: Any text. Returns the input text rendered as a sequence of | 254 """Any text. Returns the input text rendered as a sequence of |
228 XML entities. | 255 XML entities. |
229 """ | 256 """ |
230 text = unicode(text, encoding.encoding, 'replace') | 257 text = unicode(text, encoding.encoding, 'replace') |
231 return ''.join(['&#%d;' % ord(c) for c in text]) | 258 return ''.join(['&#%d;' % ord(c) for c in text]) |
232 | 259 |
260 @templatefilter('permissions') | |
233 def permissions(flags): | 261 def permissions(flags): |
234 if "l" in flags: | 262 if "l" in flags: |
235 return "lrwxrwxrwx" | 263 return "lrwxrwxrwx" |
236 if "x" in flags: | 264 if "x" in flags: |
237 return "-rwxr-xr-x" | 265 return "-rwxr-xr-x" |
238 return "-rw-r--r--" | 266 return "-rw-r--r--" |
239 | 267 |
268 @templatefilter('person') | |
240 def person(author): | 269 def person(author): |
241 """:person: Any text. Returns the name before an email address, | 270 """Any text. Returns the name before an email address, |
242 interpreting it as per RFC 5322. | 271 interpreting it as per RFC 5322. |
243 | 272 |
244 >>> person('foo@bar') | 273 >>> person('foo@bar') |
245 'foo' | 274 'foo' |
246 >>> person('Foo Bar <foo@bar>') | 275 >>> person('Foo Bar <foo@bar>') |
262 if f != -1: | 291 if f != -1: |
263 return author[:f].strip(' "').replace('\\"', '"') | 292 return author[:f].strip(' "').replace('\\"', '"') |
264 f = author.find('@') | 293 f = author.find('@') |
265 return author[:f].replace('.', ' ') | 294 return author[:f].replace('.', ' ') |
266 | 295 |
296 @templatefilter('revescape') | |
267 def revescape(text): | 297 def revescape(text): |
268 """:revescape: Any text. Escapes all "special" characters, except @. | 298 """Any text. Escapes all "special" characters, except @. |
269 Forward slashes are escaped twice to prevent web servers from prematurely | 299 Forward slashes are escaped twice to prevent web servers from prematurely |
270 unescaping them. For example, "@foo bar/baz" becomes "@foo%20bar%252Fbaz". | 300 unescaping them. For example, "@foo bar/baz" becomes "@foo%20bar%252Fbaz". |
271 """ | 301 """ |
272 return urllib.quote(text, safe='/@').replace('/', '%252F') | 302 return urllib.quote(text, safe='/@').replace('/', '%252F') |
273 | 303 |
304 @templatefilter('rfc3339date') | |
274 def rfc3339date(text): | 305 def rfc3339date(text): |
275 """:rfc3339date: Date. Returns a date using the Internet date format | 306 """Date. Returns a date using the Internet date format |
276 specified in RFC 3339: "2009-08-18T13:00:13+02:00". | 307 specified in RFC 3339: "2009-08-18T13:00:13+02:00". |
277 """ | 308 """ |
278 return util.datestr(text, "%Y-%m-%dT%H:%M:%S%1:%2") | 309 return util.datestr(text, "%Y-%m-%dT%H:%M:%S%1:%2") |
279 | 310 |
311 @templatefilter('rfc822date') | |
280 def rfc822date(text): | 312 def rfc822date(text): |
281 """:rfc822date: Date. Returns a date using the same format used in email | 313 """Date. Returns a date using the same format used in email |
282 headers: "Tue, 18 Aug 2009 13:00:13 +0200". | 314 headers: "Tue, 18 Aug 2009 13:00:13 +0200". |
283 """ | 315 """ |
284 return util.datestr(text, "%a, %d %b %Y %H:%M:%S %1%2") | 316 return util.datestr(text, "%a, %d %b %Y %H:%M:%S %1%2") |
285 | 317 |
318 @templatefilter('short') | |
286 def short(text): | 319 def short(text): |
287 """:short: Changeset hash. Returns the short form of a changeset hash, | 320 """Changeset hash. Returns the short form of a changeset hash, |
288 i.e. a 12 hexadecimal digit string. | 321 i.e. a 12 hexadecimal digit string. |
289 """ | 322 """ |
290 return text[:12] | 323 return text[:12] |
291 | 324 |
325 @templatefilter('shortbisect') | |
292 def shortbisect(text): | 326 def shortbisect(text): |
293 """:shortbisect: Any text. Treats `text` as a bisection status, and | 327 """Any text. Treats `text` as a bisection status, and |
294 returns a single-character representing the status (G: good, B: bad, | 328 returns a single-character representing the status (G: good, B: bad, |
295 S: skipped, U: untested, I: ignored). Returns single space if `text` | 329 S: skipped, U: untested, I: ignored). Returns single space if `text` |
296 is not a valid bisection status. | 330 is not a valid bisection status. |
297 """ | 331 """ |
298 return hbisect.shortlabel(text) or ' ' | 332 return hbisect.shortlabel(text) or ' ' |
299 | 333 |
334 @templatefilter('shortdate') | |
300 def shortdate(text): | 335 def shortdate(text): |
301 """:shortdate: Date. Returns a date like "2006-09-18".""" | 336 """Date. Returns a date like "2006-09-18".""" |
302 return util.shortdate(text) | 337 return util.shortdate(text) |
303 | 338 |
339 @templatefilter('splitlines') | |
304 def splitlines(text): | 340 def splitlines(text): |
305 """:splitlines: Any text. Split text into a list of lines.""" | 341 """Any text. Split text into a list of lines.""" |
306 return templatekw.showlist('line', text.splitlines(), 'lines') | 342 return templatekw.showlist('line', text.splitlines(), 'lines') |
307 | 343 |
344 @templatefilter('stringescape') | |
308 def stringescape(text): | 345 def stringescape(text): |
309 return text.encode('string_escape') | 346 return text.encode('string_escape') |
310 | 347 |
348 @templatefilter('stringify') | |
311 def stringify(thing): | 349 def stringify(thing): |
312 """:stringify: Any type. Turns the value into text by converting values into | 350 """Any type. Turns the value into text by converting values into |
313 text and concatenating them. | 351 text and concatenating them. |
314 """ | 352 """ |
315 if util.safehasattr(thing, '__iter__') and not isinstance(thing, str): | 353 if util.safehasattr(thing, '__iter__') and not isinstance(thing, str): |
316 return "".join([stringify(t) for t in thing if t is not None]) | 354 return "".join([stringify(t) for t in thing if t is not None]) |
317 if thing is None: | 355 if thing is None: |
318 return "" | 356 return "" |
319 return str(thing) | 357 return str(thing) |
320 | 358 |
359 @templatefilter('stripdir') | |
321 def stripdir(text): | 360 def stripdir(text): |
322 """:stripdir: Treat the text as path and strip a directory level, if | 361 """Treat the text as path and strip a directory level, if |
323 possible. For example, "foo" and "foo/bar" becomes "foo". | 362 possible. For example, "foo" and "foo/bar" becomes "foo". |
324 """ | 363 """ |
325 dir = os.path.dirname(text) | 364 dir = os.path.dirname(text) |
326 if dir == "": | 365 if dir == "": |
327 return os.path.basename(text) | 366 return os.path.basename(text) |
328 else: | 367 else: |
329 return dir | 368 return dir |
330 | 369 |
370 @templatefilter('tabindent') | |
331 def tabindent(text): | 371 def tabindent(text): |
332 """:tabindent: Any text. Returns the text, with every non-empty line | 372 """Any text. Returns the text, with every non-empty line |
333 except the first starting with a tab character. | 373 except the first starting with a tab character. |
334 """ | 374 """ |
335 return indent(text, '\t') | 375 return indent(text, '\t') |
336 | 376 |
377 @templatefilter('upper') | |
337 def upper(text): | 378 def upper(text): |
338 """:upper: Any text. Converts the text to uppercase.""" | 379 """Any text. Converts the text to uppercase.""" |
339 return encoding.upper(text) | 380 return encoding.upper(text) |
340 | 381 |
382 @templatefilter('urlescape') | |
341 def urlescape(text): | 383 def urlescape(text): |
342 """:urlescape: Any text. Escapes all "special" characters. For example, | 384 """Any text. Escapes all "special" characters. For example, |
343 "foo bar" becomes "foo%20bar". | 385 "foo bar" becomes "foo%20bar". |
344 """ | 386 """ |
345 return urllib.quote(text) | 387 return urllib.quote(text) |
346 | 388 |
389 @templatefilter('user') | |
347 def userfilter(text): | 390 def userfilter(text): |
348 """:user: Any text. Returns a short representation of a user name or email | 391 """Any text. Returns a short representation of a user name or email |
349 address.""" | 392 address.""" |
350 return util.shortuser(text) | 393 return util.shortuser(text) |
351 | 394 |
395 @templatefilter('emailuser') | |
352 def emailuser(text): | 396 def emailuser(text): |
353 """:emailuser: Any text. Returns the user portion of an email address.""" | 397 """Any text. Returns the user portion of an email address.""" |
354 return util.emailuser(text) | 398 return util.emailuser(text) |
355 | 399 |
400 @templatefilter('utf8') | |
356 def utf8(text): | 401 def utf8(text): |
357 """:utf8: Any text. Converts from the local character encoding to UTF-8.""" | 402 """Any text. Converts from the local character encoding to UTF-8.""" |
358 return encoding.fromlocal(text) | 403 return encoding.fromlocal(text) |
359 | 404 |
405 @templatefilter('xmlescape') | |
360 def xmlescape(text): | 406 def xmlescape(text): |
361 text = (text | 407 text = (text |
362 .replace('&', '&') | 408 .replace('&', '&') |
363 .replace('<', '<') | 409 .replace('<', '<') |
364 .replace('>', '>') | 410 .replace('>', '>') |
365 .replace('"', '"') | 411 .replace('"', '"') |
366 .replace("'", ''')) # ' invalid in HTML | 412 .replace("'", ''')) # ' invalid in HTML |
367 return re.sub('[\x00-\x08\x0B\x0C\x0E-\x1F]', ' ', text) | 413 return re.sub('[\x00-\x08\x0B\x0C\x0E-\x1F]', ' ', text) |
368 | 414 |
369 filters = { | |
370 "addbreaks": addbreaks, | |
371 "age": age, | |
372 "basename": basename, | |
373 "count": count, | |
374 "domain": domain, | |
375 "email": email, | |
376 "escape": escape, | |
377 "fill68": fill68, | |
378 "fill76": fill76, | |
379 "firstline": firstline, | |
380 "hex": hexfilter, | |
381 "hgdate": hgdate, | |
382 "isodate": isodate, | |
383 "isodatesec": isodatesec, | |
384 "json": json, | |
385 "lower": lower, | |
386 "nonempty": nonempty, | |
387 "obfuscate": obfuscate, | |
388 "permissions": permissions, | |
389 "person": person, | |
390 "revescape": revescape, | |
391 "rfc3339date": rfc3339date, | |
392 "rfc822date": rfc822date, | |
393 "short": short, | |
394 "shortbisect": shortbisect, | |
395 "shortdate": shortdate, | |
396 "splitlines": splitlines, | |
397 "stringescape": stringescape, | |
398 "stringify": stringify, | |
399 "stripdir": stripdir, | |
400 "tabindent": tabindent, | |
401 "upper": upper, | |
402 "urlescape": urlescape, | |
403 "user": userfilter, | |
404 "emailuser": emailuser, | |
405 "utf8": utf8, | |
406 "xmlescape": xmlescape, | |
407 } | |
408 | |
409 def websub(text, websubtable): | 415 def websub(text, websubtable): |
410 """:websub: Any text. Only applies to hgweb. Applies the regular | 416 """:websub: Any text. Only applies to hgweb. Applies the regular |
411 expression replacements defined in the websub section. | 417 expression replacements defined in the websub section. |
412 """ | 418 """ |
413 if websubtable: | 419 if websubtable: |