##// END OF EJS Templates
templatefilters: drop old jsonescape() function...
Yuya Nishihara -
r28213:93b5c540 default
parent child Browse files
Show More
@@ -1,436 +1,419 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 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import cgi
10 import cgi
11 import os
11 import os
12 import re
12 import re
13 import time
13 import time
14 import urllib
14 import urllib
15
15
16 from . import (
16 from . import (
17 encoding,
17 encoding,
18 hbisect,
18 hbisect,
19 node,
19 node,
20 templatekw,
20 templatekw,
21 util,
21 util,
22 )
22 )
23
23
24 def addbreaks(text):
24 def addbreaks(text):
25 """:addbreaks: Any text. Add an XHTML "<br />" tag before the end of
25 """:addbreaks: Any text. Add an XHTML "<br />" tag before the end of
26 every line except the last.
26 every line except the last.
27 """
27 """
28 return text.replace('\n', '<br/>\n')
28 return text.replace('\n', '<br/>\n')
29
29
30 agescales = [("year", 3600 * 24 * 365, 'Y'),
30 agescales = [("year", 3600 * 24 * 365, 'Y'),
31 ("month", 3600 * 24 * 30, 'M'),
31 ("month", 3600 * 24 * 30, 'M'),
32 ("week", 3600 * 24 * 7, 'W'),
32 ("week", 3600 * 24 * 7, 'W'),
33 ("day", 3600 * 24, 'd'),
33 ("day", 3600 * 24, 'd'),
34 ("hour", 3600, 'h'),
34 ("hour", 3600, 'h'),
35 ("minute", 60, 'm'),
35 ("minute", 60, 'm'),
36 ("second", 1, 's')]
36 ("second", 1, 's')]
37
37
38 def age(date, abbrev=False):
38 def age(date, abbrev=False):
39 """:age: Date. Returns a human-readable date/time difference between the
39 """:age: Date. Returns a human-readable date/time difference between the
40 given date/time and the current date/time.
40 given date/time and the current date/time.
41 """
41 """
42
42
43 def plural(t, c):
43 def plural(t, c):
44 if c == 1:
44 if c == 1:
45 return t
45 return t
46 return t + "s"
46 return t + "s"
47 def fmt(t, c, a):
47 def fmt(t, c, a):
48 if abbrev:
48 if abbrev:
49 return "%d%s" % (c, a)
49 return "%d%s" % (c, a)
50 return "%d %s" % (c, plural(t, c))
50 return "%d %s" % (c, plural(t, c))
51
51
52 now = time.time()
52 now = time.time()
53 then = date[0]
53 then = date[0]
54 future = False
54 future = False
55 if then > now:
55 if then > now:
56 future = True
56 future = True
57 delta = max(1, int(then - now))
57 delta = max(1, int(then - now))
58 if delta > agescales[0][1] * 30:
58 if delta > agescales[0][1] * 30:
59 return 'in the distant future'
59 return 'in the distant future'
60 else:
60 else:
61 delta = max(1, int(now - then))
61 delta = max(1, int(now - then))
62 if delta > agescales[0][1] * 2:
62 if delta > agescales[0][1] * 2:
63 return util.shortdate(date)
63 return util.shortdate(date)
64
64
65 for t, s, a in agescales:
65 for t, s, a in agescales:
66 n = delta // s
66 n = delta // s
67 if n >= 2 or s == 1:
67 if n >= 2 or s == 1:
68 if future:
68 if future:
69 return '%s from now' % fmt(t, n, a)
69 return '%s from now' % fmt(t, n, a)
70 return '%s ago' % fmt(t, n, a)
70 return '%s ago' % fmt(t, n, a)
71
71
72 def basename(path):
72 def basename(path):
73 """:basename: Any text. Treats the text as a path, and returns the last
73 """:basename: Any text. Treats the text as a path, and returns the last
74 component of the path after splitting by the path separator
74 component of the path after splitting by the path separator
75 (ignoring trailing separators). For example, "foo/bar/baz" becomes
75 (ignoring trailing separators). For example, "foo/bar/baz" becomes
76 "baz" and "foo/bar//" becomes "bar".
76 "baz" and "foo/bar//" becomes "bar".
77 """
77 """
78 return os.path.basename(path)
78 return os.path.basename(path)
79
79
80 def count(i):
80 def count(i):
81 """:count: List or text. Returns the length as an integer."""
81 """:count: List or text. Returns the length as an integer."""
82 return len(i)
82 return len(i)
83
83
84 def domain(author):
84 def domain(author):
85 """:domain: Any text. Finds the first string that looks like an email
85 """:domain: Any text. Finds the first string that looks like an email
86 address, and extracts just the domain component. Example: ``User
86 address, and extracts just the domain component. Example: ``User
87 <user@example.com>`` becomes ``example.com``.
87 <user@example.com>`` becomes ``example.com``.
88 """
88 """
89 f = author.find('@')
89 f = author.find('@')
90 if f == -1:
90 if f == -1:
91 return ''
91 return ''
92 author = author[f + 1:]
92 author = author[f + 1:]
93 f = author.find('>')
93 f = author.find('>')
94 if f >= 0:
94 if f >= 0:
95 author = author[:f]
95 author = author[:f]
96 return author
96 return author
97
97
98 def email(text):
98 def email(text):
99 """:email: Any text. Extracts the first string that looks like an email
99 """:email: Any text. Extracts the first string that looks like an email
100 address. Example: ``User <user@example.com>`` becomes
100 address. Example: ``User <user@example.com>`` becomes
101 ``user@example.com``.
101 ``user@example.com``.
102 """
102 """
103 return util.email(text)
103 return util.email(text)
104
104
105 def escape(text):
105 def escape(text):
106 """:escape: Any text. Replaces the special XML/XHTML characters "&", "<"
106 """:escape: Any text. Replaces the special XML/XHTML characters "&", "<"
107 and ">" with XML entities, and filters out NUL characters.
107 and ">" with XML entities, and filters out NUL characters.
108 """
108 """
109 return cgi.escape(text.replace('\0', ''), True)
109 return cgi.escape(text.replace('\0', ''), True)
110
110
111 para_re = None
111 para_re = None
112 space_re = None
112 space_re = None
113
113
114 def fill(text, width, initindent='', hangindent=''):
114 def fill(text, width, initindent='', hangindent=''):
115 '''fill many paragraphs with optional indentation.'''
115 '''fill many paragraphs with optional indentation.'''
116 global para_re, space_re
116 global para_re, space_re
117 if para_re is None:
117 if para_re is None:
118 para_re = re.compile('(\n\n|\n\\s*[-*]\\s*)', re.M)
118 para_re = re.compile('(\n\n|\n\\s*[-*]\\s*)', re.M)
119 space_re = re.compile(r' +')
119 space_re = re.compile(r' +')
120
120
121 def findparas():
121 def findparas():
122 start = 0
122 start = 0
123 while True:
123 while True:
124 m = para_re.search(text, start)
124 m = para_re.search(text, start)
125 if not m:
125 if not m:
126 uctext = unicode(text[start:], encoding.encoding)
126 uctext = unicode(text[start:], encoding.encoding)
127 w = len(uctext)
127 w = len(uctext)
128 while 0 < w and uctext[w - 1].isspace():
128 while 0 < w and uctext[w - 1].isspace():
129 w -= 1
129 w -= 1
130 yield (uctext[:w].encode(encoding.encoding),
130 yield (uctext[:w].encode(encoding.encoding),
131 uctext[w:].encode(encoding.encoding))
131 uctext[w:].encode(encoding.encoding))
132 break
132 break
133 yield text[start:m.start(0)], m.group(1)
133 yield text[start:m.start(0)], m.group(1)
134 start = m.end(1)
134 start = m.end(1)
135
135
136 return "".join([util.wrap(space_re.sub(' ', util.wrap(para, width)),
136 return "".join([util.wrap(space_re.sub(' ', util.wrap(para, width)),
137 width, initindent, hangindent) + rest
137 width, initindent, hangindent) + rest
138 for para, rest in findparas()])
138 for para, rest in findparas()])
139
139
140 def fill68(text):
140 def fill68(text):
141 """:fill68: Any text. Wraps the text to fit in 68 columns."""
141 """:fill68: Any text. Wraps the text to fit in 68 columns."""
142 return fill(text, 68)
142 return fill(text, 68)
143
143
144 def fill76(text):
144 def fill76(text):
145 """:fill76: Any text. Wraps the text to fit in 76 columns."""
145 """:fill76: Any text. Wraps the text to fit in 76 columns."""
146 return fill(text, 76)
146 return fill(text, 76)
147
147
148 def firstline(text):
148 def firstline(text):
149 """:firstline: Any text. Returns the first line of text."""
149 """:firstline: Any text. Returns the first line of text."""
150 try:
150 try:
151 return text.splitlines(True)[0].rstrip('\r\n')
151 return text.splitlines(True)[0].rstrip('\r\n')
152 except IndexError:
152 except IndexError:
153 return ''
153 return ''
154
154
155 def hexfilter(text):
155 def hexfilter(text):
156 """:hex: Any text. Convert a binary Mercurial node identifier into
156 """:hex: Any text. Convert a binary Mercurial node identifier into
157 its long hexadecimal representation.
157 its long hexadecimal representation.
158 """
158 """
159 return node.hex(text)
159 return node.hex(text)
160
160
161 def hgdate(text):
161 def hgdate(text):
162 """:hgdate: Date. Returns the date as a pair of numbers: "1157407993
162 """:hgdate: Date. Returns the date as a pair of numbers: "1157407993
163 25200" (Unix timestamp, timezone offset).
163 25200" (Unix timestamp, timezone offset).
164 """
164 """
165 return "%d %d" % text
165 return "%d %d" % text
166
166
167 def isodate(text):
167 def isodate(text):
168 """:isodate: Date. Returns the date in ISO 8601 format: "2009-08-18 13:00
168 """:isodate: Date. Returns the date in ISO 8601 format: "2009-08-18 13:00
169 +0200".
169 +0200".
170 """
170 """
171 return util.datestr(text, '%Y-%m-%d %H:%M %1%2')
171 return util.datestr(text, '%Y-%m-%d %H:%M %1%2')
172
172
173 def isodatesec(text):
173 def isodatesec(text):
174 """:isodatesec: Date. Returns the date in ISO 8601 format, including
174 """:isodatesec: Date. Returns the date in ISO 8601 format, including
175 seconds: "2009-08-18 13:00:13 +0200". See also the rfc3339date
175 seconds: "2009-08-18 13:00:13 +0200". See also the rfc3339date
176 filter.
176 filter.
177 """
177 """
178 return util.datestr(text, '%Y-%m-%d %H:%M:%S %1%2')
178 return util.datestr(text, '%Y-%m-%d %H:%M:%S %1%2')
179
179
180 def indent(text, prefix):
180 def indent(text, prefix):
181 '''indent each non-empty line of text after first with prefix.'''
181 '''indent each non-empty line of text after first with prefix.'''
182 lines = text.splitlines()
182 lines = text.splitlines()
183 num_lines = len(lines)
183 num_lines = len(lines)
184 endswithnewline = text[-1:] == '\n'
184 endswithnewline = text[-1:] == '\n'
185 def indenter():
185 def indenter():
186 for i in xrange(num_lines):
186 for i in xrange(num_lines):
187 l = lines[i]
187 l = lines[i]
188 if i and l.strip():
188 if i and l.strip():
189 yield prefix
189 yield prefix
190 yield l
190 yield l
191 if i < num_lines - 1 or endswithnewline:
191 if i < num_lines - 1 or endswithnewline:
192 yield '\n'
192 yield '\n'
193 return "".join(indenter())
193 return "".join(indenter())
194
194
195 def json(obj):
195 def json(obj):
196 if obj is None or obj is False or obj is True:
196 if obj is None or obj is False or obj is True:
197 return {None: 'null', False: 'false', True: 'true'}[obj]
197 return {None: 'null', False: 'false', True: 'true'}[obj]
198 elif isinstance(obj, int) or isinstance(obj, float):
198 elif isinstance(obj, int) or isinstance(obj, float):
199 return str(obj)
199 return str(obj)
200 elif isinstance(obj, str):
200 elif isinstance(obj, str):
201 return '"%s"' % encoding.jsonescape(obj, paranoid=True)
201 return '"%s"' % encoding.jsonescape(obj, paranoid=True)
202 elif util.safehasattr(obj, 'keys'):
202 elif util.safehasattr(obj, 'keys'):
203 out = []
203 out = []
204 for k, v in sorted(obj.iteritems()):
204 for k, v in sorted(obj.iteritems()):
205 s = '%s: %s' % (json(k), json(v))
205 s = '%s: %s' % (json(k), json(v))
206 out.append(s)
206 out.append(s)
207 return '{' + ', '.join(out) + '}'
207 return '{' + ', '.join(out) + '}'
208 elif util.safehasattr(obj, '__iter__'):
208 elif util.safehasattr(obj, '__iter__'):
209 out = []
209 out = []
210 for i in obj:
210 for i in obj:
211 out.append(json(i))
211 out.append(json(i))
212 return '[' + ', '.join(out) + ']'
212 return '[' + ', '.join(out) + ']'
213 elif util.safehasattr(obj, '__call__'):
213 elif util.safehasattr(obj, '__call__'):
214 return json(obj())
214 return json(obj())
215 else:
215 else:
216 raise TypeError('cannot encode type %s' % obj.__class__.__name__)
216 raise TypeError('cannot encode type %s' % obj.__class__.__name__)
217
217
218 def _uescape(c):
219 if 0x20 <= ord(c) < 0x80:
220 return c
221 else:
222 return '\\u%04x' % ord(c)
223
224 _escapes = [
225 ('\\', '\\\\'), ('"', '\\"'), ('\t', '\\t'), ('\n', '\\n'),
226 ('\r', '\\r'), ('\f', '\\f'), ('\b', '\\b'),
227 ('<', '\\u003c'), ('>', '\\u003e'), ('\0', '\\u0000')
228 ]
229
230 def jsonescape(s):
231 for k, v in _escapes:
232 s = s.replace(k, v)
233 return ''.join(_uescape(c) for c in s)
234
235 def lower(text):
218 def lower(text):
236 """:lower: Any text. Converts the text to lowercase."""
219 """:lower: Any text. Converts the text to lowercase."""
237 return encoding.lower(text)
220 return encoding.lower(text)
238
221
239 def nonempty(str):
222 def nonempty(str):
240 """:nonempty: Any text. Returns '(none)' if the string is empty."""
223 """:nonempty: Any text. Returns '(none)' if the string is empty."""
241 return str or "(none)"
224 return str or "(none)"
242
225
243 def obfuscate(text):
226 def obfuscate(text):
244 """:obfuscate: Any text. Returns the input text rendered as a sequence of
227 """:obfuscate: Any text. Returns the input text rendered as a sequence of
245 XML entities.
228 XML entities.
246 """
229 """
247 text = unicode(text, encoding.encoding, 'replace')
230 text = unicode(text, encoding.encoding, 'replace')
248 return ''.join(['&#%d;' % ord(c) for c in text])
231 return ''.join(['&#%d;' % ord(c) for c in text])
249
232
250 def permissions(flags):
233 def permissions(flags):
251 if "l" in flags:
234 if "l" in flags:
252 return "lrwxrwxrwx"
235 return "lrwxrwxrwx"
253 if "x" in flags:
236 if "x" in flags:
254 return "-rwxr-xr-x"
237 return "-rwxr-xr-x"
255 return "-rw-r--r--"
238 return "-rw-r--r--"
256
239
257 def person(author):
240 def person(author):
258 """:person: Any text. Returns the name before an email address,
241 """:person: Any text. Returns the name before an email address,
259 interpreting it as per RFC 5322.
242 interpreting it as per RFC 5322.
260
243
261 >>> person('foo@bar')
244 >>> person('foo@bar')
262 'foo'
245 'foo'
263 >>> person('Foo Bar <foo@bar>')
246 >>> person('Foo Bar <foo@bar>')
264 'Foo Bar'
247 'Foo Bar'
265 >>> person('"Foo Bar" <foo@bar>')
248 >>> person('"Foo Bar" <foo@bar>')
266 'Foo Bar'
249 'Foo Bar'
267 >>> person('"Foo \"buz\" Bar" <foo@bar>')
250 >>> person('"Foo \"buz\" Bar" <foo@bar>')
268 'Foo "buz" Bar'
251 'Foo "buz" Bar'
269 >>> # The following are invalid, but do exist in real-life
252 >>> # The following are invalid, but do exist in real-life
270 ...
253 ...
271 >>> person('Foo "buz" Bar <foo@bar>')
254 >>> person('Foo "buz" Bar <foo@bar>')
272 'Foo "buz" Bar'
255 'Foo "buz" Bar'
273 >>> person('"Foo Bar <foo@bar>')
256 >>> person('"Foo Bar <foo@bar>')
274 'Foo Bar'
257 'Foo Bar'
275 """
258 """
276 if '@' not in author:
259 if '@' not in author:
277 return author
260 return author
278 f = author.find('<')
261 f = author.find('<')
279 if f != -1:
262 if f != -1:
280 return author[:f].strip(' "').replace('\\"', '"')
263 return author[:f].strip(' "').replace('\\"', '"')
281 f = author.find('@')
264 f = author.find('@')
282 return author[:f].replace('.', ' ')
265 return author[:f].replace('.', ' ')
283
266
284 def revescape(text):
267 def revescape(text):
285 """:revescape: Any text. Escapes all "special" characters, except @.
268 """:revescape: Any text. Escapes all "special" characters, except @.
286 Forward slashes are escaped twice to prevent web servers from prematurely
269 Forward slashes are escaped twice to prevent web servers from prematurely
287 unescaping them. For example, "@foo bar/baz" becomes "@foo%20bar%252Fbaz".
270 unescaping them. For example, "@foo bar/baz" becomes "@foo%20bar%252Fbaz".
288 """
271 """
289 return urllib.quote(text, safe='/@').replace('/', '%252F')
272 return urllib.quote(text, safe='/@').replace('/', '%252F')
290
273
291 def rfc3339date(text):
274 def rfc3339date(text):
292 """:rfc3339date: Date. Returns a date using the Internet date format
275 """:rfc3339date: Date. Returns a date using the Internet date format
293 specified in RFC 3339: "2009-08-18T13:00:13+02:00".
276 specified in RFC 3339: "2009-08-18T13:00:13+02:00".
294 """
277 """
295 return util.datestr(text, "%Y-%m-%dT%H:%M:%S%1:%2")
278 return util.datestr(text, "%Y-%m-%dT%H:%M:%S%1:%2")
296
279
297 def rfc822date(text):
280 def rfc822date(text):
298 """:rfc822date: Date. Returns a date using the same format used in email
281 """:rfc822date: Date. Returns a date using the same format used in email
299 headers: "Tue, 18 Aug 2009 13:00:13 +0200".
282 headers: "Tue, 18 Aug 2009 13:00:13 +0200".
300 """
283 """
301 return util.datestr(text, "%a, %d %b %Y %H:%M:%S %1%2")
284 return util.datestr(text, "%a, %d %b %Y %H:%M:%S %1%2")
302
285
303 def short(text):
286 def short(text):
304 """:short: Changeset hash. Returns the short form of a changeset hash,
287 """:short: Changeset hash. Returns the short form of a changeset hash,
305 i.e. a 12 hexadecimal digit string.
288 i.e. a 12 hexadecimal digit string.
306 """
289 """
307 return text[:12]
290 return text[:12]
308
291
309 def shortbisect(text):
292 def shortbisect(text):
310 """:shortbisect: Any text. Treats `text` as a bisection status, and
293 """:shortbisect: Any text. Treats `text` as a bisection status, and
311 returns a single-character representing the status (G: good, B: bad,
294 returns a single-character representing the status (G: good, B: bad,
312 S: skipped, U: untested, I: ignored). Returns single space if `text`
295 S: skipped, U: untested, I: ignored). Returns single space if `text`
313 is not a valid bisection status.
296 is not a valid bisection status.
314 """
297 """
315 return hbisect.shortlabel(text) or ' '
298 return hbisect.shortlabel(text) or ' '
316
299
317 def shortdate(text):
300 def shortdate(text):
318 """:shortdate: Date. Returns a date like "2006-09-18"."""
301 """:shortdate: Date. Returns a date like "2006-09-18"."""
319 return util.shortdate(text)
302 return util.shortdate(text)
320
303
321 def splitlines(text):
304 def splitlines(text):
322 """:splitlines: Any text. Split text into a list of lines."""
305 """:splitlines: Any text. Split text into a list of lines."""
323 return templatekw.showlist('line', text.splitlines(), 'lines')
306 return templatekw.showlist('line', text.splitlines(), 'lines')
324
307
325 def stringescape(text):
308 def stringescape(text):
326 return text.encode('string_escape')
309 return text.encode('string_escape')
327
310
328 def stringify(thing):
311 def stringify(thing):
329 """:stringify: Any type. Turns the value into text by converting values into
312 """:stringify: Any type. Turns the value into text by converting values into
330 text and concatenating them.
313 text and concatenating them.
331 """
314 """
332 if util.safehasattr(thing, '__iter__') and not isinstance(thing, str):
315 if util.safehasattr(thing, '__iter__') and not isinstance(thing, str):
333 return "".join([stringify(t) for t in thing if t is not None])
316 return "".join([stringify(t) for t in thing if t is not None])
334 if thing is None:
317 if thing is None:
335 return ""
318 return ""
336 return str(thing)
319 return str(thing)
337
320
338 def stripdir(text):
321 def stripdir(text):
339 """:stripdir: Treat the text as path and strip a directory level, if
322 """:stripdir: Treat the text as path and strip a directory level, if
340 possible. For example, "foo" and "foo/bar" becomes "foo".
323 possible. For example, "foo" and "foo/bar" becomes "foo".
341 """
324 """
342 dir = os.path.dirname(text)
325 dir = os.path.dirname(text)
343 if dir == "":
326 if dir == "":
344 return os.path.basename(text)
327 return os.path.basename(text)
345 else:
328 else:
346 return dir
329 return dir
347
330
348 def tabindent(text):
331 def tabindent(text):
349 """:tabindent: Any text. Returns the text, with every non-empty line
332 """:tabindent: Any text. Returns the text, with every non-empty line
350 except the first starting with a tab character.
333 except the first starting with a tab character.
351 """
334 """
352 return indent(text, '\t')
335 return indent(text, '\t')
353
336
354 def upper(text):
337 def upper(text):
355 """:upper: Any text. Converts the text to uppercase."""
338 """:upper: Any text. Converts the text to uppercase."""
356 return encoding.upper(text)
339 return encoding.upper(text)
357
340
358 def urlescape(text):
341 def urlescape(text):
359 """:urlescape: Any text. Escapes all "special" characters. For example,
342 """:urlescape: Any text. Escapes all "special" characters. For example,
360 "foo bar" becomes "foo%20bar".
343 "foo bar" becomes "foo%20bar".
361 """
344 """
362 return urllib.quote(text)
345 return urllib.quote(text)
363
346
364 def userfilter(text):
347 def userfilter(text):
365 """:user: Any text. Returns a short representation of a user name or email
348 """:user: Any text. Returns a short representation of a user name or email
366 address."""
349 address."""
367 return util.shortuser(text)
350 return util.shortuser(text)
368
351
369 def emailuser(text):
352 def emailuser(text):
370 """:emailuser: Any text. Returns the user portion of an email address."""
353 """:emailuser: Any text. Returns the user portion of an email address."""
371 return util.emailuser(text)
354 return util.emailuser(text)
372
355
373 def utf8(text):
356 def utf8(text):
374 """:utf8: Any text. Converts from the local character encoding to UTF-8."""
357 """:utf8: Any text. Converts from the local character encoding to UTF-8."""
375 return encoding.fromlocal(text)
358 return encoding.fromlocal(text)
376
359
377 def xmlescape(text):
360 def xmlescape(text):
378 text = (text
361 text = (text
379 .replace('&', '&amp;')
362 .replace('&', '&amp;')
380 .replace('<', '&lt;')
363 .replace('<', '&lt;')
381 .replace('>', '&gt;')
364 .replace('>', '&gt;')
382 .replace('"', '&quot;')
365 .replace('"', '&quot;')
383 .replace("'", '&#39;')) # &apos; invalid in HTML
366 .replace("'", '&#39;')) # &apos; invalid in HTML
384 return re.sub('[\x00-\x08\x0B\x0C\x0E-\x1F]', ' ', text)
367 return re.sub('[\x00-\x08\x0B\x0C\x0E-\x1F]', ' ', text)
385
368
386 filters = {
369 filters = {
387 "addbreaks": addbreaks,
370 "addbreaks": addbreaks,
388 "age": age,
371 "age": age,
389 "basename": basename,
372 "basename": basename,
390 "count": count,
373 "count": count,
391 "domain": domain,
374 "domain": domain,
392 "email": email,
375 "email": email,
393 "escape": escape,
376 "escape": escape,
394 "fill68": fill68,
377 "fill68": fill68,
395 "fill76": fill76,
378 "fill76": fill76,
396 "firstline": firstline,
379 "firstline": firstline,
397 "hex": hexfilter,
380 "hex": hexfilter,
398 "hgdate": hgdate,
381 "hgdate": hgdate,
399 "isodate": isodate,
382 "isodate": isodate,
400 "isodatesec": isodatesec,
383 "isodatesec": isodatesec,
401 "json": json,
384 "json": json,
402 "lower": lower,
385 "lower": lower,
403 "nonempty": nonempty,
386 "nonempty": nonempty,
404 "obfuscate": obfuscate,
387 "obfuscate": obfuscate,
405 "permissions": permissions,
388 "permissions": permissions,
406 "person": person,
389 "person": person,
407 "revescape": revescape,
390 "revescape": revescape,
408 "rfc3339date": rfc3339date,
391 "rfc3339date": rfc3339date,
409 "rfc822date": rfc822date,
392 "rfc822date": rfc822date,
410 "short": short,
393 "short": short,
411 "shortbisect": shortbisect,
394 "shortbisect": shortbisect,
412 "shortdate": shortdate,
395 "shortdate": shortdate,
413 "splitlines": splitlines,
396 "splitlines": splitlines,
414 "stringescape": stringescape,
397 "stringescape": stringescape,
415 "stringify": stringify,
398 "stringify": stringify,
416 "stripdir": stripdir,
399 "stripdir": stripdir,
417 "tabindent": tabindent,
400 "tabindent": tabindent,
418 "upper": upper,
401 "upper": upper,
419 "urlescape": urlescape,
402 "urlescape": urlescape,
420 "user": userfilter,
403 "user": userfilter,
421 "emailuser": emailuser,
404 "emailuser": emailuser,
422 "utf8": utf8,
405 "utf8": utf8,
423 "xmlescape": xmlescape,
406 "xmlescape": xmlescape,
424 }
407 }
425
408
426 def websub(text, websubtable):
409 def websub(text, websubtable):
427 """:websub: Any text. Only applies to hgweb. Applies the regular
410 """:websub: Any text. Only applies to hgweb. Applies the regular
428 expression replacements defined in the websub section.
411 expression replacements defined in the websub section.
429 """
412 """
430 if websubtable:
413 if websubtable:
431 for regexp, format in websubtable:
414 for regexp, format in websubtable:
432 text = regexp.sub(format, text)
415 text = regexp.sub(format, text)
433 return text
416 return text
434
417
435 # tell hggettext to extract docstrings from these functions:
418 # tell hggettext to extract docstrings from these functions:
436 i18nfunctions = filters.values()
419 i18nfunctions = filters.values()
@@ -1,60 +1,47 b''
1
1
2 $ cat > engine.py << EOF
2 $ cat > engine.py << EOF
3 >
3 >
4 > from mercurial import templater
4 > from mercurial import templater
5 >
5 >
6 > class mytemplater(object):
6 > class mytemplater(object):
7 > def __init__(self, loader, filters, defaults):
7 > def __init__(self, loader, filters, defaults):
8 > self.loader = loader
8 > self.loader = loader
9 >
9 >
10 > def process(self, t, map):
10 > def process(self, t, map):
11 > tmpl = self.loader(t)
11 > tmpl = self.loader(t)
12 > for k, v in map.iteritems():
12 > for k, v in map.iteritems():
13 > if k in ('templ', 'ctx', 'repo', 'revcache', 'cache'):
13 > if k in ('templ', 'ctx', 'repo', 'revcache', 'cache'):
14 > continue
14 > continue
15 > if hasattr(v, '__call__'):
15 > if hasattr(v, '__call__'):
16 > v = v(**map)
16 > v = v(**map)
17 > v = templater.stringify(v)
17 > v = templater.stringify(v)
18 > tmpl = tmpl.replace('{{%s}}' % k, v)
18 > tmpl = tmpl.replace('{{%s}}' % k, v)
19 > yield tmpl
19 > yield tmpl
20 >
20 >
21 > templater.engines['my'] = mytemplater
21 > templater.engines['my'] = mytemplater
22 > EOF
22 > EOF
23 $ hg init test
23 $ hg init test
24 $ echo '[extensions]' > test/.hg/hgrc
24 $ echo '[extensions]' > test/.hg/hgrc
25 $ echo "engine = `pwd`/engine.py" >> test/.hg/hgrc
25 $ echo "engine = `pwd`/engine.py" >> test/.hg/hgrc
26 $ cd test
26 $ cd test
27 $ cat > mymap << EOF
27 $ cat > mymap << EOF
28 > changeset = my:changeset.txt
28 > changeset = my:changeset.txt
29 > EOF
29 > EOF
30 $ cat > changeset.txt << EOF
30 $ cat > changeset.txt << EOF
31 > {{rev}} {{node}} {{author}}
31 > {{rev}} {{node}} {{author}}
32 > EOF
32 > EOF
33 $ hg ci -Ama
33 $ hg ci -Ama
34 adding changeset.txt
34 adding changeset.txt
35 adding mymap
35 adding mymap
36 $ hg log --style=./mymap
36 $ hg log --style=./mymap
37 0 97e5f848f0936960273bbf75be6388cd0350a32b test
37 0 97e5f848f0936960273bbf75be6388cd0350a32b test
38
38
39 $ cat > changeset.txt << EOF
39 $ cat > changeset.txt << EOF
40 > {{p1rev}} {{p1node}} {{p2rev}} {{p2node}}
40 > {{p1rev}} {{p1node}} {{p2rev}} {{p2node}}
41 > EOF
41 > EOF
42 $ hg ci -Ama
42 $ hg ci -Ama
43 $ hg log --style=./mymap
43 $ hg log --style=./mymap
44 0 97e5f848f0936960273bbf75be6388cd0350a32b -1 0000000000000000000000000000000000000000
44 0 97e5f848f0936960273bbf75be6388cd0350a32b -1 0000000000000000000000000000000000000000
45 -1 0000000000000000000000000000000000000000 -1 0000000000000000000000000000000000000000
45 -1 0000000000000000000000000000000000000000 -1 0000000000000000000000000000000000000000
46
46
47 Fuzzing the unicode escaper to ensure it produces valid data
48
49 #if hypothesis
50
51 >>> from hypothesishelpers import *
52 >>> import mercurial.templatefilters as tf
53 >>> import json
54 >>> @check(st.text().map(lambda s: s.encode('utf-8')))
55 ... def testtfescapeproducesvalidjson(text):
56 ... json.loads('"' + tf.jsonescape(text) + '"')
57
58 #endif
59
60 $ cd ..
47 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now