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