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