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