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