##// END OF EJS Templates
templater: fix cbor() filter to accept smartset...
Yuya Nishihara -
r45081:e3e44e6e default
parent child Browse files
Show More
@@ -1,549 +1,553 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 .i18n import _
14 from .i18n import _
15 from . import (
15 from . import (
16 encoding,
16 encoding,
17 error,
17 error,
18 node,
18 node,
19 pycompat,
19 pycompat,
20 registrar,
20 registrar,
21 smartset,
21 templateutil,
22 templateutil,
22 url,
23 url,
23 util,
24 util,
24 )
25 )
25 from .utils import (
26 from .utils import (
26 cborutil,
27 cborutil,
27 dateutil,
28 dateutil,
28 stringutil,
29 stringutil,
29 )
30 )
30
31
31 urlerr = util.urlerr
32 urlerr = util.urlerr
32 urlreq = util.urlreq
33 urlreq = util.urlreq
33
34
34 # filters are callables like:
35 # filters are callables like:
35 # fn(obj)
36 # fn(obj)
36 # with:
37 # with:
37 # obj - object to be filtered (text, date, list and so on)
38 # obj - object to be filtered (text, date, list and so on)
38 filters = {}
39 filters = {}
39
40
40 templatefilter = registrar.templatefilter(filters)
41 templatefilter = registrar.templatefilter(filters)
41
42
42
43
43 @templatefilter(b'addbreaks', intype=bytes)
44 @templatefilter(b'addbreaks', intype=bytes)
44 def addbreaks(text):
45 def addbreaks(text):
45 """Any text. Add an XHTML "<br />" tag before the end of
46 """Any text. Add an XHTML "<br />" tag before the end of
46 every line except the last.
47 every line except the last.
47 """
48 """
48 return text.replace(b'\n', b'<br/>\n')
49 return text.replace(b'\n', b'<br/>\n')
49
50
50
51
51 agescales = [
52 agescales = [
52 (b"year", 3600 * 24 * 365, b'Y'),
53 (b"year", 3600 * 24 * 365, b'Y'),
53 (b"month", 3600 * 24 * 30, b'M'),
54 (b"month", 3600 * 24 * 30, b'M'),
54 (b"week", 3600 * 24 * 7, b'W'),
55 (b"week", 3600 * 24 * 7, b'W'),
55 (b"day", 3600 * 24, b'd'),
56 (b"day", 3600 * 24, b'd'),
56 (b"hour", 3600, b'h'),
57 (b"hour", 3600, b'h'),
57 (b"minute", 60, b'm'),
58 (b"minute", 60, b'm'),
58 (b"second", 1, b's'),
59 (b"second", 1, b's'),
59 ]
60 ]
60
61
61
62
62 @templatefilter(b'age', intype=templateutil.date)
63 @templatefilter(b'age', intype=templateutil.date)
63 def age(date, abbrev=False):
64 def age(date, abbrev=False):
64 """Date. Returns a human-readable date/time difference between the
65 """Date. Returns a human-readable date/time difference between the
65 given date/time and the current date/time.
66 given date/time and the current date/time.
66 """
67 """
67
68
68 def plural(t, c):
69 def plural(t, c):
69 if c == 1:
70 if c == 1:
70 return t
71 return t
71 return t + b"s"
72 return t + b"s"
72
73
73 def fmt(t, c, a):
74 def fmt(t, c, a):
74 if abbrev:
75 if abbrev:
75 return b"%d%s" % (c, a)
76 return b"%d%s" % (c, a)
76 return b"%d %s" % (c, plural(t, c))
77 return b"%d %s" % (c, plural(t, c))
77
78
78 now = time.time()
79 now = time.time()
79 then = date[0]
80 then = date[0]
80 future = False
81 future = False
81 if then > now:
82 if then > now:
82 future = True
83 future = True
83 delta = max(1, int(then - now))
84 delta = max(1, int(then - now))
84 if delta > agescales[0][1] * 30:
85 if delta > agescales[0][1] * 30:
85 return b'in the distant future'
86 return b'in the distant future'
86 else:
87 else:
87 delta = max(1, int(now - then))
88 delta = max(1, int(now - then))
88 if delta > agescales[0][1] * 2:
89 if delta > agescales[0][1] * 2:
89 return dateutil.shortdate(date)
90 return dateutil.shortdate(date)
90
91
91 for t, s, a in agescales:
92 for t, s, a in agescales:
92 n = delta // s
93 n = delta // s
93 if n >= 2 or s == 1:
94 if n >= 2 or s == 1:
94 if future:
95 if future:
95 return b'%s from now' % fmt(t, n, a)
96 return b'%s from now' % fmt(t, n, a)
96 return b'%s ago' % fmt(t, n, a)
97 return b'%s ago' % fmt(t, n, a)
97
98
98
99
99 @templatefilter(b'basename', intype=bytes)
100 @templatefilter(b'basename', intype=bytes)
100 def basename(path):
101 def basename(path):
101 """Any text. Treats the text as a path, and returns the last
102 """Any text. Treats the text as a path, and returns the last
102 component of the path after splitting by the path separator.
103 component of the path after splitting by the path separator.
103 For example, "foo/bar/baz" becomes "baz" and "foo/bar//" becomes "".
104 For example, "foo/bar/baz" becomes "baz" and "foo/bar//" becomes "".
104 """
105 """
105 return os.path.basename(path)
106 return os.path.basename(path)
106
107
107
108
108 @templatefilter(b'cbor')
109 @templatefilter(b'cbor')
109 def cbor(obj):
110 def cbor(obj):
110 """Any object. Serializes the object to CBOR bytes."""
111 """Any object. Serializes the object to CBOR bytes."""
112 if isinstance(obj, smartset.abstractsmartset):
113 # cborutil is stricter about type than json() filter
114 obj = list(obj)
111 return b''.join(cborutil.streamencode(obj))
115 return b''.join(cborutil.streamencode(obj))
112
116
113
117
114 @templatefilter(b'commondir')
118 @templatefilter(b'commondir')
115 def commondir(filelist):
119 def commondir(filelist):
116 """List of text. Treats each list item as file name with /
120 """List of text. Treats each list item as file name with /
117 as path separator and returns the longest common directory
121 as path separator and returns the longest common directory
118 prefix shared by all list items.
122 prefix shared by all list items.
119 Returns the empty string if no common prefix exists.
123 Returns the empty string if no common prefix exists.
120
124
121 The list items are not normalized, i.e. "foo/../bar" is handled as
125 The list items are not normalized, i.e. "foo/../bar" is handled as
122 file "bar" in the directory "foo/..". Leading slashes are ignored.
126 file "bar" in the directory "foo/..". Leading slashes are ignored.
123
127
124 For example, ["foo/bar/baz", "foo/baz/bar"] becomes "foo" and
128 For example, ["foo/bar/baz", "foo/baz/bar"] becomes "foo" and
125 ["foo/bar", "baz"] becomes "".
129 ["foo/bar", "baz"] becomes "".
126 """
130 """
127
131
128 def common(a, b):
132 def common(a, b):
129 if len(a) > len(b):
133 if len(a) > len(b):
130 a = b[: len(a)]
134 a = b[: len(a)]
131 elif len(b) > len(a):
135 elif len(b) > len(a):
132 b = b[: len(a)]
136 b = b[: len(a)]
133 if a == b:
137 if a == b:
134 return a
138 return a
135 for i in pycompat.xrange(len(a)):
139 for i in pycompat.xrange(len(a)):
136 if a[i] != b[i]:
140 if a[i] != b[i]:
137 return a[:i]
141 return a[:i]
138 return a
142 return a
139
143
140 try:
144 try:
141 if not filelist:
145 if not filelist:
142 return b""
146 return b""
143 dirlist = [f.lstrip(b'/').split(b'/')[:-1] for f in filelist]
147 dirlist = [f.lstrip(b'/').split(b'/')[:-1] for f in filelist]
144 if len(dirlist) == 1:
148 if len(dirlist) == 1:
145 return b'/'.join(dirlist[0])
149 return b'/'.join(dirlist[0])
146 a = min(dirlist)
150 a = min(dirlist)
147 b = max(dirlist)
151 b = max(dirlist)
148 # The common prefix of a and b is shared with all
152 # The common prefix of a and b is shared with all
149 # elements of the list since Python sorts lexicographical
153 # elements of the list since Python sorts lexicographical
150 # and [1, x] after [1].
154 # and [1, x] after [1].
151 return b'/'.join(common(a, b))
155 return b'/'.join(common(a, b))
152 except TypeError:
156 except TypeError:
153 raise error.ParseError(_(b'argument is not a list of text'))
157 raise error.ParseError(_(b'argument is not a list of text'))
154
158
155
159
156 @templatefilter(b'count')
160 @templatefilter(b'count')
157 def count(i):
161 def count(i):
158 """List or text. Returns the length as an integer."""
162 """List or text. Returns the length as an integer."""
159 try:
163 try:
160 return len(i)
164 return len(i)
161 except TypeError:
165 except TypeError:
162 raise error.ParseError(_(b'not countable'))
166 raise error.ParseError(_(b'not countable'))
163
167
164
168
165 @templatefilter(b'dirname', intype=bytes)
169 @templatefilter(b'dirname', intype=bytes)
166 def dirname(path):
170 def dirname(path):
167 """Any text. Treats the text as a path, and strips the last
171 """Any text. Treats the text as a path, and strips the last
168 component of the path after splitting by the path separator.
172 component of the path after splitting by the path separator.
169 """
173 """
170 return os.path.dirname(path)
174 return os.path.dirname(path)
171
175
172
176
173 @templatefilter(b'domain', intype=bytes)
177 @templatefilter(b'domain', intype=bytes)
174 def domain(author):
178 def domain(author):
175 """Any text. Finds the first string that looks like an email
179 """Any text. Finds the first string that looks like an email
176 address, and extracts just the domain component. Example: ``User
180 address, and extracts just the domain component. Example: ``User
177 <user@example.com>`` becomes ``example.com``.
181 <user@example.com>`` becomes ``example.com``.
178 """
182 """
179 f = author.find(b'@')
183 f = author.find(b'@')
180 if f == -1:
184 if f == -1:
181 return b''
185 return b''
182 author = author[f + 1 :]
186 author = author[f + 1 :]
183 f = author.find(b'>')
187 f = author.find(b'>')
184 if f >= 0:
188 if f >= 0:
185 author = author[:f]
189 author = author[:f]
186 return author
190 return author
187
191
188
192
189 @templatefilter(b'email', intype=bytes)
193 @templatefilter(b'email', intype=bytes)
190 def email(text):
194 def email(text):
191 """Any text. Extracts the first string that looks like an email
195 """Any text. Extracts the first string that looks like an email
192 address. Example: ``User <user@example.com>`` becomes
196 address. Example: ``User <user@example.com>`` becomes
193 ``user@example.com``.
197 ``user@example.com``.
194 """
198 """
195 return stringutil.email(text)
199 return stringutil.email(text)
196
200
197
201
198 @templatefilter(b'escape', intype=bytes)
202 @templatefilter(b'escape', intype=bytes)
199 def escape(text):
203 def escape(text):
200 """Any text. Replaces the special XML/XHTML characters "&", "<"
204 """Any text. Replaces the special XML/XHTML characters "&", "<"
201 and ">" with XML entities, and filters out NUL characters.
205 and ">" with XML entities, and filters out NUL characters.
202 """
206 """
203 return url.escape(text.replace(b'\0', b''), True)
207 return url.escape(text.replace(b'\0', b''), True)
204
208
205
209
206 para_re = None
210 para_re = None
207 space_re = None
211 space_re = None
208
212
209
213
210 def fill(text, width, initindent=b'', hangindent=b''):
214 def fill(text, width, initindent=b'', hangindent=b''):
211 '''fill many paragraphs with optional indentation.'''
215 '''fill many paragraphs with optional indentation.'''
212 global para_re, space_re
216 global para_re, space_re
213 if para_re is None:
217 if para_re is None:
214 para_re = re.compile(b'(\n\n|\n\\s*[-*]\\s*)', re.M)
218 para_re = re.compile(b'(\n\n|\n\\s*[-*]\\s*)', re.M)
215 space_re = re.compile(br' +')
219 space_re = re.compile(br' +')
216
220
217 def findparas():
221 def findparas():
218 start = 0
222 start = 0
219 while True:
223 while True:
220 m = para_re.search(text, start)
224 m = para_re.search(text, start)
221 if not m:
225 if not m:
222 uctext = encoding.unifromlocal(text[start:])
226 uctext = encoding.unifromlocal(text[start:])
223 w = len(uctext)
227 w = len(uctext)
224 while w > 0 and uctext[w - 1].isspace():
228 while w > 0 and uctext[w - 1].isspace():
225 w -= 1
229 w -= 1
226 yield (
230 yield (
227 encoding.unitolocal(uctext[:w]),
231 encoding.unitolocal(uctext[:w]),
228 encoding.unitolocal(uctext[w:]),
232 encoding.unitolocal(uctext[w:]),
229 )
233 )
230 break
234 break
231 yield text[start : m.start(0)], m.group(1)
235 yield text[start : m.start(0)], m.group(1)
232 start = m.end(1)
236 start = m.end(1)
233
237
234 return b"".join(
238 return b"".join(
235 [
239 [
236 stringutil.wrap(
240 stringutil.wrap(
237 space_re.sub(b' ', stringutil.wrap(para, width)),
241 space_re.sub(b' ', stringutil.wrap(para, width)),
238 width,
242 width,
239 initindent,
243 initindent,
240 hangindent,
244 hangindent,
241 )
245 )
242 + rest
246 + rest
243 for para, rest in findparas()
247 for para, rest in findparas()
244 ]
248 ]
245 )
249 )
246
250
247
251
248 @templatefilter(b'fill68', intype=bytes)
252 @templatefilter(b'fill68', intype=bytes)
249 def fill68(text):
253 def fill68(text):
250 """Any text. Wraps the text to fit in 68 columns."""
254 """Any text. Wraps the text to fit in 68 columns."""
251 return fill(text, 68)
255 return fill(text, 68)
252
256
253
257
254 @templatefilter(b'fill76', intype=bytes)
258 @templatefilter(b'fill76', intype=bytes)
255 def fill76(text):
259 def fill76(text):
256 """Any text. Wraps the text to fit in 76 columns."""
260 """Any text. Wraps the text to fit in 76 columns."""
257 return fill(text, 76)
261 return fill(text, 76)
258
262
259
263
260 @templatefilter(b'firstline', intype=bytes)
264 @templatefilter(b'firstline', intype=bytes)
261 def firstline(text):
265 def firstline(text):
262 """Any text. Returns the first line of text."""
266 """Any text. Returns the first line of text."""
263 try:
267 try:
264 return text.splitlines(True)[0].rstrip(b'\r\n')
268 return text.splitlines(True)[0].rstrip(b'\r\n')
265 except IndexError:
269 except IndexError:
266 return b''
270 return b''
267
271
268
272
269 @templatefilter(b'hex', intype=bytes)
273 @templatefilter(b'hex', intype=bytes)
270 def hexfilter(text):
274 def hexfilter(text):
271 """Any text. Convert a binary Mercurial node identifier into
275 """Any text. Convert a binary Mercurial node identifier into
272 its long hexadecimal representation.
276 its long hexadecimal representation.
273 """
277 """
274 return node.hex(text)
278 return node.hex(text)
275
279
276
280
277 @templatefilter(b'hgdate', intype=templateutil.date)
281 @templatefilter(b'hgdate', intype=templateutil.date)
278 def hgdate(text):
282 def hgdate(text):
279 """Date. Returns the date as a pair of numbers: "1157407993
283 """Date. Returns the date as a pair of numbers: "1157407993
280 25200" (Unix timestamp, timezone offset).
284 25200" (Unix timestamp, timezone offset).
281 """
285 """
282 return b"%d %d" % text
286 return b"%d %d" % text
283
287
284
288
285 @templatefilter(b'isodate', intype=templateutil.date)
289 @templatefilter(b'isodate', intype=templateutil.date)
286 def isodate(text):
290 def isodate(text):
287 """Date. Returns the date in ISO 8601 format: "2009-08-18 13:00
291 """Date. Returns the date in ISO 8601 format: "2009-08-18 13:00
288 +0200".
292 +0200".
289 """
293 """
290 return dateutil.datestr(text, b'%Y-%m-%d %H:%M %1%2')
294 return dateutil.datestr(text, b'%Y-%m-%d %H:%M %1%2')
291
295
292
296
293 @templatefilter(b'isodatesec', intype=templateutil.date)
297 @templatefilter(b'isodatesec', intype=templateutil.date)
294 def isodatesec(text):
298 def isodatesec(text):
295 """Date. Returns the date in ISO 8601 format, including
299 """Date. Returns the date in ISO 8601 format, including
296 seconds: "2009-08-18 13:00:13 +0200". See also the rfc3339date
300 seconds: "2009-08-18 13:00:13 +0200". See also the rfc3339date
297 filter.
301 filter.
298 """
302 """
299 return dateutil.datestr(text, b'%Y-%m-%d %H:%M:%S %1%2')
303 return dateutil.datestr(text, b'%Y-%m-%d %H:%M:%S %1%2')
300
304
301
305
302 def indent(text, prefix, firstline=b''):
306 def indent(text, prefix, firstline=b''):
303 '''indent each non-empty line of text after first with prefix.'''
307 '''indent each non-empty line of text after first with prefix.'''
304 lines = text.splitlines()
308 lines = text.splitlines()
305 num_lines = len(lines)
309 num_lines = len(lines)
306 endswithnewline = text[-1:] == b'\n'
310 endswithnewline = text[-1:] == b'\n'
307
311
308 def indenter():
312 def indenter():
309 for i in pycompat.xrange(num_lines):
313 for i in pycompat.xrange(num_lines):
310 l = lines[i]
314 l = lines[i]
311 if l.strip():
315 if l.strip():
312 yield prefix if i else firstline
316 yield prefix if i else firstline
313 yield l
317 yield l
314 if i < num_lines - 1 or endswithnewline:
318 if i < num_lines - 1 or endswithnewline:
315 yield b'\n'
319 yield b'\n'
316
320
317 return b"".join(indenter())
321 return b"".join(indenter())
318
322
319
323
320 @templatefilter(b'json')
324 @templatefilter(b'json')
321 def json(obj, paranoid=True):
325 def json(obj, paranoid=True):
322 """Any object. Serializes the object to a JSON formatted text."""
326 """Any object. Serializes the object to a JSON formatted text."""
323 if obj is None:
327 if obj is None:
324 return b'null'
328 return b'null'
325 elif obj is False:
329 elif obj is False:
326 return b'false'
330 return b'false'
327 elif obj is True:
331 elif obj is True:
328 return b'true'
332 return b'true'
329 elif isinstance(obj, (int, pycompat.long, float)):
333 elif isinstance(obj, (int, pycompat.long, float)):
330 return pycompat.bytestr(obj)
334 return pycompat.bytestr(obj)
331 elif isinstance(obj, bytes):
335 elif isinstance(obj, bytes):
332 return b'"%s"' % encoding.jsonescape(obj, paranoid=paranoid)
336 return b'"%s"' % encoding.jsonescape(obj, paranoid=paranoid)
333 elif isinstance(obj, type(u'')):
337 elif isinstance(obj, type(u'')):
334 raise error.ProgrammingError(
338 raise error.ProgrammingError(
335 b'Mercurial only does output with bytes: %r' % obj
339 b'Mercurial only does output with bytes: %r' % obj
336 )
340 )
337 elif util.safehasattr(obj, b'keys'):
341 elif util.safehasattr(obj, b'keys'):
338 out = [
342 out = [
339 b'"%s": %s'
343 b'"%s": %s'
340 % (encoding.jsonescape(k, paranoid=paranoid), json(v, paranoid))
344 % (encoding.jsonescape(k, paranoid=paranoid), json(v, paranoid))
341 for k, v in sorted(pycompat.iteritems(obj))
345 for k, v in sorted(pycompat.iteritems(obj))
342 ]
346 ]
343 return b'{' + b', '.join(out) + b'}'
347 return b'{' + b', '.join(out) + b'}'
344 elif util.safehasattr(obj, b'__iter__'):
348 elif util.safehasattr(obj, b'__iter__'):
345 out = [json(i, paranoid) for i in obj]
349 out = [json(i, paranoid) for i in obj]
346 return b'[' + b', '.join(out) + b']'
350 return b'[' + b', '.join(out) + b']'
347 raise error.ProgrammingError(b'cannot encode %r' % obj)
351 raise error.ProgrammingError(b'cannot encode %r' % obj)
348
352
349
353
350 @templatefilter(b'lower', intype=bytes)
354 @templatefilter(b'lower', intype=bytes)
351 def lower(text):
355 def lower(text):
352 """Any text. Converts the text to lowercase."""
356 """Any text. Converts the text to lowercase."""
353 return encoding.lower(text)
357 return encoding.lower(text)
354
358
355
359
356 @templatefilter(b'nonempty', intype=bytes)
360 @templatefilter(b'nonempty', intype=bytes)
357 def nonempty(text):
361 def nonempty(text):
358 """Any text. Returns '(none)' if the string is empty."""
362 """Any text. Returns '(none)' if the string is empty."""
359 return text or b"(none)"
363 return text or b"(none)"
360
364
361
365
362 @templatefilter(b'obfuscate', intype=bytes)
366 @templatefilter(b'obfuscate', intype=bytes)
363 def obfuscate(text):
367 def obfuscate(text):
364 """Any text. Returns the input text rendered as a sequence of
368 """Any text. Returns the input text rendered as a sequence of
365 XML entities.
369 XML entities.
366 """
370 """
367 text = pycompat.unicode(
371 text = pycompat.unicode(
368 text, pycompat.sysstr(encoding.encoding), r'replace'
372 text, pycompat.sysstr(encoding.encoding), r'replace'
369 )
373 )
370 return b''.join([b'&#%d;' % ord(c) for c in text])
374 return b''.join([b'&#%d;' % ord(c) for c in text])
371
375
372
376
373 @templatefilter(b'permissions', intype=bytes)
377 @templatefilter(b'permissions', intype=bytes)
374 def permissions(flags):
378 def permissions(flags):
375 if b"l" in flags:
379 if b"l" in flags:
376 return b"lrwxrwxrwx"
380 return b"lrwxrwxrwx"
377 if b"x" in flags:
381 if b"x" in flags:
378 return b"-rwxr-xr-x"
382 return b"-rwxr-xr-x"
379 return b"-rw-r--r--"
383 return b"-rw-r--r--"
380
384
381
385
382 @templatefilter(b'person', intype=bytes)
386 @templatefilter(b'person', intype=bytes)
383 def person(author):
387 def person(author):
384 """Any text. Returns the name before an email address,
388 """Any text. Returns the name before an email address,
385 interpreting it as per RFC 5322.
389 interpreting it as per RFC 5322.
386 """
390 """
387 return stringutil.person(author)
391 return stringutil.person(author)
388
392
389
393
390 @templatefilter(b'revescape', intype=bytes)
394 @templatefilter(b'revescape', intype=bytes)
391 def revescape(text):
395 def revescape(text):
392 """Any text. Escapes all "special" characters, except @.
396 """Any text. Escapes all "special" characters, except @.
393 Forward slashes are escaped twice to prevent web servers from prematurely
397 Forward slashes are escaped twice to prevent web servers from prematurely
394 unescaping them. For example, "@foo bar/baz" becomes "@foo%20bar%252Fbaz".
398 unescaping them. For example, "@foo bar/baz" becomes "@foo%20bar%252Fbaz".
395 """
399 """
396 return urlreq.quote(text, safe=b'/@').replace(b'/', b'%252F')
400 return urlreq.quote(text, safe=b'/@').replace(b'/', b'%252F')
397
401
398
402
399 @templatefilter(b'rfc3339date', intype=templateutil.date)
403 @templatefilter(b'rfc3339date', intype=templateutil.date)
400 def rfc3339date(text):
404 def rfc3339date(text):
401 """Date. Returns a date using the Internet date format
405 """Date. Returns a date using the Internet date format
402 specified in RFC 3339: "2009-08-18T13:00:13+02:00".
406 specified in RFC 3339: "2009-08-18T13:00:13+02:00".
403 """
407 """
404 return dateutil.datestr(text, b"%Y-%m-%dT%H:%M:%S%1:%2")
408 return dateutil.datestr(text, b"%Y-%m-%dT%H:%M:%S%1:%2")
405
409
406
410
407 @templatefilter(b'rfc822date', intype=templateutil.date)
411 @templatefilter(b'rfc822date', intype=templateutil.date)
408 def rfc822date(text):
412 def rfc822date(text):
409 """Date. Returns a date using the same format used in email
413 """Date. Returns a date using the same format used in email
410 headers: "Tue, 18 Aug 2009 13:00:13 +0200".
414 headers: "Tue, 18 Aug 2009 13:00:13 +0200".
411 """
415 """
412 return dateutil.datestr(text, b"%a, %d %b %Y %H:%M:%S %1%2")
416 return dateutil.datestr(text, b"%a, %d %b %Y %H:%M:%S %1%2")
413
417
414
418
415 @templatefilter(b'short', intype=bytes)
419 @templatefilter(b'short', intype=bytes)
416 def short(text):
420 def short(text):
417 """Changeset hash. Returns the short form of a changeset hash,
421 """Changeset hash. Returns the short form of a changeset hash,
418 i.e. a 12 hexadecimal digit string.
422 i.e. a 12 hexadecimal digit string.
419 """
423 """
420 return text[:12]
424 return text[:12]
421
425
422
426
423 @templatefilter(b'shortbisect', intype=bytes)
427 @templatefilter(b'shortbisect', intype=bytes)
424 def shortbisect(label):
428 def shortbisect(label):
425 """Any text. Treats `label` as a bisection status, and
429 """Any text. Treats `label` as a bisection status, and
426 returns a single-character representing the status (G: good, B: bad,
430 returns a single-character representing the status (G: good, B: bad,
427 S: skipped, U: untested, I: ignored). Returns single space if `text`
431 S: skipped, U: untested, I: ignored). Returns single space if `text`
428 is not a valid bisection status.
432 is not a valid bisection status.
429 """
433 """
430 if label:
434 if label:
431 return label[0:1].upper()
435 return label[0:1].upper()
432 return b' '
436 return b' '
433
437
434
438
435 @templatefilter(b'shortdate', intype=templateutil.date)
439 @templatefilter(b'shortdate', intype=templateutil.date)
436 def shortdate(text):
440 def shortdate(text):
437 """Date. Returns a date like "2006-09-18"."""
441 """Date. Returns a date like "2006-09-18"."""
438 return dateutil.shortdate(text)
442 return dateutil.shortdate(text)
439
443
440
444
441 @templatefilter(b'slashpath', intype=bytes)
445 @templatefilter(b'slashpath', intype=bytes)
442 def slashpath(path):
446 def slashpath(path):
443 """Any text. Replaces the native path separator with slash."""
447 """Any text. Replaces the native path separator with slash."""
444 return util.pconvert(path)
448 return util.pconvert(path)
445
449
446
450
447 @templatefilter(b'splitlines', intype=bytes)
451 @templatefilter(b'splitlines', intype=bytes)
448 def splitlines(text):
452 def splitlines(text):
449 """Any text. Split text into a list of lines."""
453 """Any text. Split text into a list of lines."""
450 return templateutil.hybridlist(text.splitlines(), name=b'line')
454 return templateutil.hybridlist(text.splitlines(), name=b'line')
451
455
452
456
453 @templatefilter(b'stringescape', intype=bytes)
457 @templatefilter(b'stringescape', intype=bytes)
454 def stringescape(text):
458 def stringescape(text):
455 return stringutil.escapestr(text)
459 return stringutil.escapestr(text)
456
460
457
461
458 @templatefilter(b'stringify', intype=bytes)
462 @templatefilter(b'stringify', intype=bytes)
459 def stringify(thing):
463 def stringify(thing):
460 """Any type. Turns the value into text by converting values into
464 """Any type. Turns the value into text by converting values into
461 text and concatenating them.
465 text and concatenating them.
462 """
466 """
463 return thing # coerced by the intype
467 return thing # coerced by the intype
464
468
465
469
466 @templatefilter(b'stripdir', intype=bytes)
470 @templatefilter(b'stripdir', intype=bytes)
467 def stripdir(text):
471 def stripdir(text):
468 """Treat the text as path and strip a directory level, if
472 """Treat the text as path and strip a directory level, if
469 possible. For example, "foo" and "foo/bar" becomes "foo".
473 possible. For example, "foo" and "foo/bar" becomes "foo".
470 """
474 """
471 dir = os.path.dirname(text)
475 dir = os.path.dirname(text)
472 if dir == b"":
476 if dir == b"":
473 return os.path.basename(text)
477 return os.path.basename(text)
474 else:
478 else:
475 return dir
479 return dir
476
480
477
481
478 @templatefilter(b'tabindent', intype=bytes)
482 @templatefilter(b'tabindent', intype=bytes)
479 def tabindent(text):
483 def tabindent(text):
480 """Any text. Returns the text, with every non-empty line
484 """Any text. Returns the text, with every non-empty line
481 except the first starting with a tab character.
485 except the first starting with a tab character.
482 """
486 """
483 return indent(text, b'\t')
487 return indent(text, b'\t')
484
488
485
489
486 @templatefilter(b'upper', intype=bytes)
490 @templatefilter(b'upper', intype=bytes)
487 def upper(text):
491 def upper(text):
488 """Any text. Converts the text to uppercase."""
492 """Any text. Converts the text to uppercase."""
489 return encoding.upper(text)
493 return encoding.upper(text)
490
494
491
495
492 @templatefilter(b'urlescape', intype=bytes)
496 @templatefilter(b'urlescape', intype=bytes)
493 def urlescape(text):
497 def urlescape(text):
494 """Any text. Escapes all "special" characters. For example,
498 """Any text. Escapes all "special" characters. For example,
495 "foo bar" becomes "foo%20bar".
499 "foo bar" becomes "foo%20bar".
496 """
500 """
497 return urlreq.quote(text)
501 return urlreq.quote(text)
498
502
499
503
500 @templatefilter(b'user', intype=bytes)
504 @templatefilter(b'user', intype=bytes)
501 def userfilter(text):
505 def userfilter(text):
502 """Any text. Returns a short representation of a user name or email
506 """Any text. Returns a short representation of a user name or email
503 address."""
507 address."""
504 return stringutil.shortuser(text)
508 return stringutil.shortuser(text)
505
509
506
510
507 @templatefilter(b'emailuser', intype=bytes)
511 @templatefilter(b'emailuser', intype=bytes)
508 def emailuser(text):
512 def emailuser(text):
509 """Any text. Returns the user portion of an email address."""
513 """Any text. Returns the user portion of an email address."""
510 return stringutil.emailuser(text)
514 return stringutil.emailuser(text)
511
515
512
516
513 @templatefilter(b'utf8', intype=bytes)
517 @templatefilter(b'utf8', intype=bytes)
514 def utf8(text):
518 def utf8(text):
515 """Any text. Converts from the local character encoding to UTF-8."""
519 """Any text. Converts from the local character encoding to UTF-8."""
516 return encoding.fromlocal(text)
520 return encoding.fromlocal(text)
517
521
518
522
519 @templatefilter(b'xmlescape', intype=bytes)
523 @templatefilter(b'xmlescape', intype=bytes)
520 def xmlescape(text):
524 def xmlescape(text):
521 text = (
525 text = (
522 text.replace(b'&', b'&amp;')
526 text.replace(b'&', b'&amp;')
523 .replace(b'<', b'&lt;')
527 .replace(b'<', b'&lt;')
524 .replace(b'>', b'&gt;')
528 .replace(b'>', b'&gt;')
525 .replace(b'"', b'&quot;')
529 .replace(b'"', b'&quot;')
526 .replace(b"'", b'&#39;')
530 .replace(b"'", b'&#39;')
527 ) # &apos; invalid in HTML
531 ) # &apos; invalid in HTML
528 return re.sub(b'[\x00-\x08\x0B\x0C\x0E-\x1F]', b' ', text)
532 return re.sub(b'[\x00-\x08\x0B\x0C\x0E-\x1F]', b' ', text)
529
533
530
534
531 def websub(text, websubtable):
535 def websub(text, websubtable):
532 """:websub: Any text. Only applies to hgweb. Applies the regular
536 """:websub: Any text. Only applies to hgweb. Applies the regular
533 expression replacements defined in the websub section.
537 expression replacements defined in the websub section.
534 """
538 """
535 if websubtable:
539 if websubtable:
536 for regexp, format in websubtable:
540 for regexp, format in websubtable:
537 text = regexp.sub(format, text)
541 text = regexp.sub(format, text)
538 return text
542 return text
539
543
540
544
541 def loadfilter(ui, extname, registrarobj):
545 def loadfilter(ui, extname, registrarobj):
542 """Load template filter from specified registrarobj
546 """Load template filter from specified registrarobj
543 """
547 """
544 for name, func in pycompat.iteritems(registrarobj._table):
548 for name, func in pycompat.iteritems(registrarobj._table):
545 filters[name] = func
549 filters[name] = func
546
550
547
551
548 # tell hggettext to extract docstrings from these functions:
552 # tell hggettext to extract docstrings from these functions:
549 i18nfunctions = filters.values()
553 i18nfunctions = filters.values()
@@ -1,1165 +1,1165 b''
1 # templateutil.py - utility for template evaluation
1 # templateutil.py - utility for template evaluation
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 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 abc
10 import abc
11 import types
11 import types
12
12
13 from .i18n import _
13 from .i18n import _
14 from .pycompat import getattr
14 from .pycompat import getattr
15 from . import (
15 from . import (
16 error,
16 error,
17 pycompat,
17 pycompat,
18 smartset,
18 smartset,
19 util,
19 util,
20 )
20 )
21 from .utils import (
21 from .utils import (
22 dateutil,
22 dateutil,
23 stringutil,
23 stringutil,
24 )
24 )
25
25
26
26
27 class ResourceUnavailable(error.Abort):
27 class ResourceUnavailable(error.Abort):
28 pass
28 pass
29
29
30
30
31 class TemplateNotFound(error.Abort):
31 class TemplateNotFound(error.Abort):
32 pass
32 pass
33
33
34
34
35 class wrapped(object): # pytype: disable=ignored-metaclass
35 class wrapped(object): # pytype: disable=ignored-metaclass
36 """Object requiring extra conversion prior to displaying or processing
36 """Object requiring extra conversion prior to displaying or processing
37 as value
37 as value
38
38
39 Use unwrapvalue() or unwrapastype() to obtain the inner object.
39 Use unwrapvalue() or unwrapastype() to obtain the inner object.
40 """
40 """
41
41
42 __metaclass__ = abc.ABCMeta
42 __metaclass__ = abc.ABCMeta
43
43
44 @abc.abstractmethod
44 @abc.abstractmethod
45 def contains(self, context, mapping, item):
45 def contains(self, context, mapping, item):
46 """Test if the specified item is in self
46 """Test if the specified item is in self
47
47
48 The item argument may be a wrapped object.
48 The item argument may be a wrapped object.
49 """
49 """
50
50
51 @abc.abstractmethod
51 @abc.abstractmethod
52 def getmember(self, context, mapping, key):
52 def getmember(self, context, mapping, key):
53 """Return a member item for the specified key
53 """Return a member item for the specified key
54
54
55 The key argument may be a wrapped object.
55 The key argument may be a wrapped object.
56 A returned object may be either a wrapped object or a pure value
56 A returned object may be either a wrapped object or a pure value
57 depending on the self type.
57 depending on the self type.
58 """
58 """
59
59
60 @abc.abstractmethod
60 @abc.abstractmethod
61 def getmin(self, context, mapping):
61 def getmin(self, context, mapping):
62 """Return the smallest item, which may be either a wrapped or a pure
62 """Return the smallest item, which may be either a wrapped or a pure
63 value depending on the self type"""
63 value depending on the self type"""
64
64
65 @abc.abstractmethod
65 @abc.abstractmethod
66 def getmax(self, context, mapping):
66 def getmax(self, context, mapping):
67 """Return the largest item, which may be either a wrapped or a pure
67 """Return the largest item, which may be either a wrapped or a pure
68 value depending on the self type"""
68 value depending on the self type"""
69
69
70 @abc.abstractmethod
70 @abc.abstractmethod
71 def filter(self, context, mapping, select):
71 def filter(self, context, mapping, select):
72 """Return new container of the same type which includes only the
72 """Return new container of the same type which includes only the
73 selected elements
73 selected elements
74
74
75 select() takes each item as a wrapped object and returns True/False.
75 select() takes each item as a wrapped object and returns True/False.
76 """
76 """
77
77
78 @abc.abstractmethod
78 @abc.abstractmethod
79 def itermaps(self, context):
79 def itermaps(self, context):
80 """Yield each template mapping"""
80 """Yield each template mapping"""
81
81
82 @abc.abstractmethod
82 @abc.abstractmethod
83 def join(self, context, mapping, sep):
83 def join(self, context, mapping, sep):
84 """Join items with the separator; Returns a bytes or (possibly nested)
84 """Join items with the separator; Returns a bytes or (possibly nested)
85 generator of bytes
85 generator of bytes
86
86
87 A pre-configured template may be rendered per item if this container
87 A pre-configured template may be rendered per item if this container
88 holds unprintable items.
88 holds unprintable items.
89 """
89 """
90
90
91 @abc.abstractmethod
91 @abc.abstractmethod
92 def show(self, context, mapping):
92 def show(self, context, mapping):
93 """Return a bytes or (possibly nested) generator of bytes representing
93 """Return a bytes or (possibly nested) generator of bytes representing
94 the underlying object
94 the underlying object
95
95
96 A pre-configured template may be rendered if the underlying object is
96 A pre-configured template may be rendered if the underlying object is
97 not printable.
97 not printable.
98 """
98 """
99
99
100 @abc.abstractmethod
100 @abc.abstractmethod
101 def tobool(self, context, mapping):
101 def tobool(self, context, mapping):
102 """Return a boolean representation of the inner value"""
102 """Return a boolean representation of the inner value"""
103
103
104 @abc.abstractmethod
104 @abc.abstractmethod
105 def tovalue(self, context, mapping):
105 def tovalue(self, context, mapping):
106 """Move the inner value object out or create a value representation
106 """Move the inner value object out or create a value representation
107
107
108 A returned value must be serializable by templaterfilters.json().
108 A returned value must be serializable by templaterfilters.json().
109 """
109 """
110
110
111
111
112 class mappable(object): # pytype: disable=ignored-metaclass
112 class mappable(object): # pytype: disable=ignored-metaclass
113 """Object which can be converted to a single template mapping"""
113 """Object which can be converted to a single template mapping"""
114
114
115 __metaclass__ = abc.ABCMeta
115 __metaclass__ = abc.ABCMeta
116
116
117 def itermaps(self, context):
117 def itermaps(self, context):
118 yield self.tomap(context)
118 yield self.tomap(context)
119
119
120 @abc.abstractmethod
120 @abc.abstractmethod
121 def tomap(self, context):
121 def tomap(self, context):
122 """Create a single template mapping representing this"""
122 """Create a single template mapping representing this"""
123
123
124
124
125 class wrappedbytes(wrapped):
125 class wrappedbytes(wrapped):
126 """Wrapper for byte string"""
126 """Wrapper for byte string"""
127
127
128 def __init__(self, value):
128 def __init__(self, value):
129 self._value = value
129 self._value = value
130
130
131 def contains(self, context, mapping, item):
131 def contains(self, context, mapping, item):
132 item = stringify(context, mapping, item)
132 item = stringify(context, mapping, item)
133 return item in self._value
133 return item in self._value
134
134
135 def getmember(self, context, mapping, key):
135 def getmember(self, context, mapping, key):
136 raise error.ParseError(
136 raise error.ParseError(
137 _(b'%r is not a dictionary') % pycompat.bytestr(self._value)
137 _(b'%r is not a dictionary') % pycompat.bytestr(self._value)
138 )
138 )
139
139
140 def getmin(self, context, mapping):
140 def getmin(self, context, mapping):
141 return self._getby(context, mapping, min)
141 return self._getby(context, mapping, min)
142
142
143 def getmax(self, context, mapping):
143 def getmax(self, context, mapping):
144 return self._getby(context, mapping, max)
144 return self._getby(context, mapping, max)
145
145
146 def _getby(self, context, mapping, func):
146 def _getby(self, context, mapping, func):
147 if not self._value:
147 if not self._value:
148 raise error.ParseError(_(b'empty string'))
148 raise error.ParseError(_(b'empty string'))
149 return func(pycompat.iterbytestr(self._value))
149 return func(pycompat.iterbytestr(self._value))
150
150
151 def filter(self, context, mapping, select):
151 def filter(self, context, mapping, select):
152 raise error.ParseError(
152 raise error.ParseError(
153 _(b'%r is not filterable') % pycompat.bytestr(self._value)
153 _(b'%r is not filterable') % pycompat.bytestr(self._value)
154 )
154 )
155
155
156 def itermaps(self, context):
156 def itermaps(self, context):
157 raise error.ParseError(
157 raise error.ParseError(
158 _(b'%r is not iterable of mappings') % pycompat.bytestr(self._value)
158 _(b'%r is not iterable of mappings') % pycompat.bytestr(self._value)
159 )
159 )
160
160
161 def join(self, context, mapping, sep):
161 def join(self, context, mapping, sep):
162 return joinitems(pycompat.iterbytestr(self._value), sep)
162 return joinitems(pycompat.iterbytestr(self._value), sep)
163
163
164 def show(self, context, mapping):
164 def show(self, context, mapping):
165 return self._value
165 return self._value
166
166
167 def tobool(self, context, mapping):
167 def tobool(self, context, mapping):
168 return bool(self._value)
168 return bool(self._value)
169
169
170 def tovalue(self, context, mapping):
170 def tovalue(self, context, mapping):
171 return self._value
171 return self._value
172
172
173
173
174 class wrappedvalue(wrapped):
174 class wrappedvalue(wrapped):
175 """Generic wrapper for pure non-list/dict/bytes value"""
175 """Generic wrapper for pure non-list/dict/bytes value"""
176
176
177 def __init__(self, value):
177 def __init__(self, value):
178 self._value = value
178 self._value = value
179
179
180 def contains(self, context, mapping, item):
180 def contains(self, context, mapping, item):
181 raise error.ParseError(_(b"%r is not iterable") % self._value)
181 raise error.ParseError(_(b"%r is not iterable") % self._value)
182
182
183 def getmember(self, context, mapping, key):
183 def getmember(self, context, mapping, key):
184 raise error.ParseError(_(b'%r is not a dictionary') % self._value)
184 raise error.ParseError(_(b'%r is not a dictionary') % self._value)
185
185
186 def getmin(self, context, mapping):
186 def getmin(self, context, mapping):
187 raise error.ParseError(_(b"%r is not iterable") % self._value)
187 raise error.ParseError(_(b"%r is not iterable") % self._value)
188
188
189 def getmax(self, context, mapping):
189 def getmax(self, context, mapping):
190 raise error.ParseError(_(b"%r is not iterable") % self._value)
190 raise error.ParseError(_(b"%r is not iterable") % self._value)
191
191
192 def filter(self, context, mapping, select):
192 def filter(self, context, mapping, select):
193 raise error.ParseError(_(b"%r is not iterable") % self._value)
193 raise error.ParseError(_(b"%r is not iterable") % self._value)
194
194
195 def itermaps(self, context):
195 def itermaps(self, context):
196 raise error.ParseError(
196 raise error.ParseError(
197 _(b'%r is not iterable of mappings') % self._value
197 _(b'%r is not iterable of mappings') % self._value
198 )
198 )
199
199
200 def join(self, context, mapping, sep):
200 def join(self, context, mapping, sep):
201 raise error.ParseError(_(b'%r is not iterable') % self._value)
201 raise error.ParseError(_(b'%r is not iterable') % self._value)
202
202
203 def show(self, context, mapping):
203 def show(self, context, mapping):
204 if self._value is None:
204 if self._value is None:
205 return b''
205 return b''
206 return pycompat.bytestr(self._value)
206 return pycompat.bytestr(self._value)
207
207
208 def tobool(self, context, mapping):
208 def tobool(self, context, mapping):
209 if self._value is None:
209 if self._value is None:
210 return False
210 return False
211 if isinstance(self._value, bool):
211 if isinstance(self._value, bool):
212 return self._value
212 return self._value
213 # otherwise evaluate as string, which means 0 is True
213 # otherwise evaluate as string, which means 0 is True
214 return bool(pycompat.bytestr(self._value))
214 return bool(pycompat.bytestr(self._value))
215
215
216 def tovalue(self, context, mapping):
216 def tovalue(self, context, mapping):
217 return self._value
217 return self._value
218
218
219
219
220 class date(mappable, wrapped):
220 class date(mappable, wrapped):
221 """Wrapper for date tuple"""
221 """Wrapper for date tuple"""
222
222
223 def __init__(self, value, showfmt=b'%d %d'):
223 def __init__(self, value, showfmt=b'%d %d'):
224 # value may be (float, int), but public interface shouldn't support
224 # value may be (float, int), but public interface shouldn't support
225 # floating-point timestamp
225 # floating-point timestamp
226 self._unixtime, self._tzoffset = map(int, value)
226 self._unixtime, self._tzoffset = map(int, value)
227 self._showfmt = showfmt
227 self._showfmt = showfmt
228
228
229 def contains(self, context, mapping, item):
229 def contains(self, context, mapping, item):
230 raise error.ParseError(_(b'date is not iterable'))
230 raise error.ParseError(_(b'date is not iterable'))
231
231
232 def getmember(self, context, mapping, key):
232 def getmember(self, context, mapping, key):
233 raise error.ParseError(_(b'date is not a dictionary'))
233 raise error.ParseError(_(b'date is not a dictionary'))
234
234
235 def getmin(self, context, mapping):
235 def getmin(self, context, mapping):
236 raise error.ParseError(_(b'date is not iterable'))
236 raise error.ParseError(_(b'date is not iterable'))
237
237
238 def getmax(self, context, mapping):
238 def getmax(self, context, mapping):
239 raise error.ParseError(_(b'date is not iterable'))
239 raise error.ParseError(_(b'date is not iterable'))
240
240
241 def filter(self, context, mapping, select):
241 def filter(self, context, mapping, select):
242 raise error.ParseError(_(b'date is not iterable'))
242 raise error.ParseError(_(b'date is not iterable'))
243
243
244 def join(self, context, mapping, sep):
244 def join(self, context, mapping, sep):
245 raise error.ParseError(_(b"date is not iterable"))
245 raise error.ParseError(_(b"date is not iterable"))
246
246
247 def show(self, context, mapping):
247 def show(self, context, mapping):
248 return self._showfmt % (self._unixtime, self._tzoffset)
248 return self._showfmt % (self._unixtime, self._tzoffset)
249
249
250 def tomap(self, context):
250 def tomap(self, context):
251 return {b'unixtime': self._unixtime, b'tzoffset': self._tzoffset}
251 return {b'unixtime': self._unixtime, b'tzoffset': self._tzoffset}
252
252
253 def tobool(self, context, mapping):
253 def tobool(self, context, mapping):
254 return True
254 return True
255
255
256 def tovalue(self, context, mapping):
256 def tovalue(self, context, mapping):
257 return (self._unixtime, self._tzoffset)
257 return (self._unixtime, self._tzoffset)
258
258
259
259
260 class hybrid(wrapped):
260 class hybrid(wrapped):
261 """Wrapper for list or dict to support legacy template
261 """Wrapper for list or dict to support legacy template
262
262
263 This class allows us to handle both:
263 This class allows us to handle both:
264 - "{files}" (legacy command-line-specific list hack) and
264 - "{files}" (legacy command-line-specific list hack) and
265 - "{files % '{file}\n'}" (hgweb-style with inlining and function support)
265 - "{files % '{file}\n'}" (hgweb-style with inlining and function support)
266 and to access raw values:
266 and to access raw values:
267 - "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
267 - "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
268 - "{get(extras, key)}"
268 - "{get(extras, key)}"
269 - "{files|json}"
269 - "{files|json}"
270 """
270 """
271
271
272 def __init__(self, gen, values, makemap, joinfmt, keytype=None):
272 def __init__(self, gen, values, makemap, joinfmt, keytype=None):
273 self._gen = gen # generator or function returning generator
273 self._gen = gen # generator or function returning generator
274 self._values = values
274 self._values = values
275 self._makemap = makemap
275 self._makemap = makemap
276 self._joinfmt = joinfmt
276 self._joinfmt = joinfmt
277 self._keytype = keytype # hint for 'x in y' where type(x) is unresolved
277 self._keytype = keytype # hint for 'x in y' where type(x) is unresolved
278
278
279 def contains(self, context, mapping, item):
279 def contains(self, context, mapping, item):
280 item = unwrapastype(context, mapping, item, self._keytype)
280 item = unwrapastype(context, mapping, item, self._keytype)
281 return item in self._values
281 return item in self._values
282
282
283 def getmember(self, context, mapping, key):
283 def getmember(self, context, mapping, key):
284 # TODO: maybe split hybrid list/dict types?
284 # TODO: maybe split hybrid list/dict types?
285 if not util.safehasattr(self._values, b'get'):
285 if not util.safehasattr(self._values, b'get'):
286 raise error.ParseError(_(b'not a dictionary'))
286 raise error.ParseError(_(b'not a dictionary'))
287 key = unwrapastype(context, mapping, key, self._keytype)
287 key = unwrapastype(context, mapping, key, self._keytype)
288 return self._wrapvalue(key, self._values.get(key))
288 return self._wrapvalue(key, self._values.get(key))
289
289
290 def getmin(self, context, mapping):
290 def getmin(self, context, mapping):
291 return self._getby(context, mapping, min)
291 return self._getby(context, mapping, min)
292
292
293 def getmax(self, context, mapping):
293 def getmax(self, context, mapping):
294 return self._getby(context, mapping, max)
294 return self._getby(context, mapping, max)
295
295
296 def _getby(self, context, mapping, func):
296 def _getby(self, context, mapping, func):
297 if not self._values:
297 if not self._values:
298 raise error.ParseError(_(b'empty sequence'))
298 raise error.ParseError(_(b'empty sequence'))
299 val = func(self._values)
299 val = func(self._values)
300 return self._wrapvalue(val, val)
300 return self._wrapvalue(val, val)
301
301
302 def _wrapvalue(self, key, val):
302 def _wrapvalue(self, key, val):
303 if val is None:
303 if val is None:
304 return
304 return
305 if util.safehasattr(val, b'_makemap'):
305 if util.safehasattr(val, b'_makemap'):
306 # a nested hybrid list/dict, which has its own way of map operation
306 # a nested hybrid list/dict, which has its own way of map operation
307 return val
307 return val
308 return hybriditem(None, key, val, self._makemap)
308 return hybriditem(None, key, val, self._makemap)
309
309
310 def filter(self, context, mapping, select):
310 def filter(self, context, mapping, select):
311 if util.safehasattr(self._values, b'get'):
311 if util.safehasattr(self._values, b'get'):
312 values = {
312 values = {
313 k: v
313 k: v
314 for k, v in pycompat.iteritems(self._values)
314 for k, v in pycompat.iteritems(self._values)
315 if select(self._wrapvalue(k, v))
315 if select(self._wrapvalue(k, v))
316 }
316 }
317 else:
317 else:
318 values = [v for v in self._values if select(self._wrapvalue(v, v))]
318 values = [v for v in self._values if select(self._wrapvalue(v, v))]
319 return hybrid(None, values, self._makemap, self._joinfmt, self._keytype)
319 return hybrid(None, values, self._makemap, self._joinfmt, self._keytype)
320
320
321 def itermaps(self, context):
321 def itermaps(self, context):
322 makemap = self._makemap
322 makemap = self._makemap
323 for x in self._values:
323 for x in self._values:
324 yield makemap(x)
324 yield makemap(x)
325
325
326 def join(self, context, mapping, sep):
326 def join(self, context, mapping, sep):
327 # TODO: switch gen to (context, mapping) API?
327 # TODO: switch gen to (context, mapping) API?
328 return joinitems((self._joinfmt(x) for x in self._values), sep)
328 return joinitems((self._joinfmt(x) for x in self._values), sep)
329
329
330 def show(self, context, mapping):
330 def show(self, context, mapping):
331 # TODO: switch gen to (context, mapping) API?
331 # TODO: switch gen to (context, mapping) API?
332 gen = self._gen
332 gen = self._gen
333 if gen is None:
333 if gen is None:
334 return self.join(context, mapping, b' ')
334 return self.join(context, mapping, b' ')
335 if callable(gen):
335 if callable(gen):
336 return gen()
336 return gen()
337 return gen
337 return gen
338
338
339 def tobool(self, context, mapping):
339 def tobool(self, context, mapping):
340 return bool(self._values)
340 return bool(self._values)
341
341
342 def tovalue(self, context, mapping):
342 def tovalue(self, context, mapping):
343 # TODO: make it non-recursive for trivial lists/dicts
343 # TODO: make it non-recursive for trivial lists/dicts
344 xs = self._values
344 xs = self._values
345 if util.safehasattr(xs, b'get'):
345 if util.safehasattr(xs, b'get'):
346 return {
346 return {
347 k: unwrapvalue(context, mapping, v)
347 k: unwrapvalue(context, mapping, v)
348 for k, v in pycompat.iteritems(xs)
348 for k, v in pycompat.iteritems(xs)
349 }
349 }
350 return [unwrapvalue(context, mapping, x) for x in xs]
350 return [unwrapvalue(context, mapping, x) for x in xs]
351
351
352
352
353 class hybriditem(mappable, wrapped):
353 class hybriditem(mappable, wrapped):
354 """Wrapper for non-list/dict object to support map operation
354 """Wrapper for non-list/dict object to support map operation
355
355
356 This class allows us to handle both:
356 This class allows us to handle both:
357 - "{manifest}"
357 - "{manifest}"
358 - "{manifest % '{rev}:{node}'}"
358 - "{manifest % '{rev}:{node}'}"
359 - "{manifest.rev}"
359 - "{manifest.rev}"
360 """
360 """
361
361
362 def __init__(self, gen, key, value, makemap):
362 def __init__(self, gen, key, value, makemap):
363 self._gen = gen # generator or function returning generator
363 self._gen = gen # generator or function returning generator
364 self._key = key
364 self._key = key
365 self._value = value # may be generator of strings
365 self._value = value # may be generator of strings
366 self._makemap = makemap
366 self._makemap = makemap
367
367
368 def tomap(self, context):
368 def tomap(self, context):
369 return self._makemap(self._key)
369 return self._makemap(self._key)
370
370
371 def contains(self, context, mapping, item):
371 def contains(self, context, mapping, item):
372 w = makewrapped(context, mapping, self._value)
372 w = makewrapped(context, mapping, self._value)
373 return w.contains(context, mapping, item)
373 return w.contains(context, mapping, item)
374
374
375 def getmember(self, context, mapping, key):
375 def getmember(self, context, mapping, key):
376 w = makewrapped(context, mapping, self._value)
376 w = makewrapped(context, mapping, self._value)
377 return w.getmember(context, mapping, key)
377 return w.getmember(context, mapping, key)
378
378
379 def getmin(self, context, mapping):
379 def getmin(self, context, mapping):
380 w = makewrapped(context, mapping, self._value)
380 w = makewrapped(context, mapping, self._value)
381 return w.getmin(context, mapping)
381 return w.getmin(context, mapping)
382
382
383 def getmax(self, context, mapping):
383 def getmax(self, context, mapping):
384 w = makewrapped(context, mapping, self._value)
384 w = makewrapped(context, mapping, self._value)
385 return w.getmax(context, mapping)
385 return w.getmax(context, mapping)
386
386
387 def filter(self, context, mapping, select):
387 def filter(self, context, mapping, select):
388 w = makewrapped(context, mapping, self._value)
388 w = makewrapped(context, mapping, self._value)
389 return w.filter(context, mapping, select)
389 return w.filter(context, mapping, select)
390
390
391 def join(self, context, mapping, sep):
391 def join(self, context, mapping, sep):
392 w = makewrapped(context, mapping, self._value)
392 w = makewrapped(context, mapping, self._value)
393 return w.join(context, mapping, sep)
393 return w.join(context, mapping, sep)
394
394
395 def show(self, context, mapping):
395 def show(self, context, mapping):
396 # TODO: switch gen to (context, mapping) API?
396 # TODO: switch gen to (context, mapping) API?
397 gen = self._gen
397 gen = self._gen
398 if gen is None:
398 if gen is None:
399 return pycompat.bytestr(self._value)
399 return pycompat.bytestr(self._value)
400 if callable(gen):
400 if callable(gen):
401 return gen()
401 return gen()
402 return gen
402 return gen
403
403
404 def tobool(self, context, mapping):
404 def tobool(self, context, mapping):
405 w = makewrapped(context, mapping, self._value)
405 w = makewrapped(context, mapping, self._value)
406 return w.tobool(context, mapping)
406 return w.tobool(context, mapping)
407
407
408 def tovalue(self, context, mapping):
408 def tovalue(self, context, mapping):
409 return _unthunk(context, mapping, self._value)
409 return _unthunk(context, mapping, self._value)
410
410
411
411
412 class revslist(wrapped):
412 class revslist(wrapped):
413 """Wrapper for a smartset (a list/set of revision numbers)
413 """Wrapper for a smartset (a list/set of revision numbers)
414
414
415 If name specified, the revs will be rendered with the old-style list
415 If name specified, the revs will be rendered with the old-style list
416 template of the given name by default.
416 template of the given name by default.
417 """
417 """
418
418
419 def __init__(self, repo, revs, name=None):
419 def __init__(self, repo, revs, name=None):
420 assert isinstance(revs, smartset.abstractsmartset)
420 assert isinstance(revs, smartset.abstractsmartset)
421 self._repo = repo
421 self._repo = repo
422 self._revs = revs
422 self._revs = revs
423 self._name = name
423 self._name = name
424
424
425 def contains(self, context, mapping, item):
425 def contains(self, context, mapping, item):
426 rev = unwrapinteger(context, mapping, item)
426 rev = unwrapinteger(context, mapping, item)
427 return rev in self._revs
427 return rev in self._revs
428
428
429 def getmember(self, context, mapping, key):
429 def getmember(self, context, mapping, key):
430 raise error.ParseError(_(b'not a dictionary'))
430 raise error.ParseError(_(b'not a dictionary'))
431
431
432 def getmin(self, context, mapping):
432 def getmin(self, context, mapping):
433 makehybriditem = self._makehybriditemfunc()
433 makehybriditem = self._makehybriditemfunc()
434 return makehybriditem(self._revs.min())
434 return makehybriditem(self._revs.min())
435
435
436 def getmax(self, context, mapping):
436 def getmax(self, context, mapping):
437 makehybriditem = self._makehybriditemfunc()
437 makehybriditem = self._makehybriditemfunc()
438 return makehybriditem(self._revs.max())
438 return makehybriditem(self._revs.max())
439
439
440 def filter(self, context, mapping, select):
440 def filter(self, context, mapping, select):
441 makehybriditem = self._makehybriditemfunc()
441 makehybriditem = self._makehybriditemfunc()
442 frevs = self._revs.filter(lambda r: select(makehybriditem(r)))
442 frevs = self._revs.filter(lambda r: select(makehybriditem(r)))
443 # once filtered, no need to support old-style list template
443 # once filtered, no need to support old-style list template
444 return revslist(self._repo, frevs, name=None)
444 return revslist(self._repo, frevs, name=None)
445
445
446 def itermaps(self, context):
446 def itermaps(self, context):
447 makemap = self._makemapfunc()
447 makemap = self._makemapfunc()
448 for r in self._revs:
448 for r in self._revs:
449 yield makemap(r)
449 yield makemap(r)
450
450
451 def _makehybriditemfunc(self):
451 def _makehybriditemfunc(self):
452 makemap = self._makemapfunc()
452 makemap = self._makemapfunc()
453 return lambda r: hybriditem(None, r, r, makemap)
453 return lambda r: hybriditem(None, r, r, makemap)
454
454
455 def _makemapfunc(self):
455 def _makemapfunc(self):
456 repo = self._repo
456 repo = self._repo
457 name = self._name
457 name = self._name
458 if name:
458 if name:
459 return lambda r: {name: r, b'ctx': repo[r]}
459 return lambda r: {name: r, b'ctx': repo[r]}
460 else:
460 else:
461 return lambda r: {b'ctx': repo[r]}
461 return lambda r: {b'ctx': repo[r]}
462
462
463 def join(self, context, mapping, sep):
463 def join(self, context, mapping, sep):
464 return joinitems(self._revs, sep)
464 return joinitems(self._revs, sep)
465
465
466 def show(self, context, mapping):
466 def show(self, context, mapping):
467 if self._name:
467 if self._name:
468 srevs = [b'%d' % r for r in self._revs]
468 srevs = [b'%d' % r for r in self._revs]
469 return _showcompatlist(context, mapping, self._name, srevs)
469 return _showcompatlist(context, mapping, self._name, srevs)
470 else:
470 else:
471 return self.join(context, mapping, b' ')
471 return self.join(context, mapping, b' ')
472
472
473 def tobool(self, context, mapping):
473 def tobool(self, context, mapping):
474 return bool(self._revs)
474 return bool(self._revs)
475
475
476 def tovalue(self, context, mapping):
476 def tovalue(self, context, mapping):
477 return list(self._revs)
477 return self._revs
478
478
479
479
480 class _mappingsequence(wrapped):
480 class _mappingsequence(wrapped):
481 """Wrapper for sequence of template mappings
481 """Wrapper for sequence of template mappings
482
482
483 This represents an inner template structure (i.e. a list of dicts),
483 This represents an inner template structure (i.e. a list of dicts),
484 which can also be rendered by the specified named/literal template.
484 which can also be rendered by the specified named/literal template.
485
485
486 Template mappings may be nested.
486 Template mappings may be nested.
487 """
487 """
488
488
489 def __init__(self, name=None, tmpl=None, sep=b''):
489 def __init__(self, name=None, tmpl=None, sep=b''):
490 if name is not None and tmpl is not None:
490 if name is not None and tmpl is not None:
491 raise error.ProgrammingError(
491 raise error.ProgrammingError(
492 b'name and tmpl are mutually exclusive'
492 b'name and tmpl are mutually exclusive'
493 )
493 )
494 self._name = name
494 self._name = name
495 self._tmpl = tmpl
495 self._tmpl = tmpl
496 self._defaultsep = sep
496 self._defaultsep = sep
497
497
498 def contains(self, context, mapping, item):
498 def contains(self, context, mapping, item):
499 raise error.ParseError(_(b'not comparable'))
499 raise error.ParseError(_(b'not comparable'))
500
500
501 def getmember(self, context, mapping, key):
501 def getmember(self, context, mapping, key):
502 raise error.ParseError(_(b'not a dictionary'))
502 raise error.ParseError(_(b'not a dictionary'))
503
503
504 def getmin(self, context, mapping):
504 def getmin(self, context, mapping):
505 raise error.ParseError(_(b'not comparable'))
505 raise error.ParseError(_(b'not comparable'))
506
506
507 def getmax(self, context, mapping):
507 def getmax(self, context, mapping):
508 raise error.ParseError(_(b'not comparable'))
508 raise error.ParseError(_(b'not comparable'))
509
509
510 def filter(self, context, mapping, select):
510 def filter(self, context, mapping, select):
511 # implement if necessary; we'll need a wrapped type for a mapping dict
511 # implement if necessary; we'll need a wrapped type for a mapping dict
512 raise error.ParseError(_(b'not filterable without template'))
512 raise error.ParseError(_(b'not filterable without template'))
513
513
514 def join(self, context, mapping, sep):
514 def join(self, context, mapping, sep):
515 mapsiter = _iteroverlaymaps(context, mapping, self.itermaps(context))
515 mapsiter = _iteroverlaymaps(context, mapping, self.itermaps(context))
516 if self._name:
516 if self._name:
517 itemiter = (context.process(self._name, m) for m in mapsiter)
517 itemiter = (context.process(self._name, m) for m in mapsiter)
518 elif self._tmpl:
518 elif self._tmpl:
519 itemiter = (context.expand(self._tmpl, m) for m in mapsiter)
519 itemiter = (context.expand(self._tmpl, m) for m in mapsiter)
520 else:
520 else:
521 raise error.ParseError(_(b'not displayable without template'))
521 raise error.ParseError(_(b'not displayable without template'))
522 return joinitems(itemiter, sep)
522 return joinitems(itemiter, sep)
523
523
524 def show(self, context, mapping):
524 def show(self, context, mapping):
525 return self.join(context, mapping, self._defaultsep)
525 return self.join(context, mapping, self._defaultsep)
526
526
527 def tovalue(self, context, mapping):
527 def tovalue(self, context, mapping):
528 knownres = context.knownresourcekeys()
528 knownres = context.knownresourcekeys()
529 items = []
529 items = []
530 for nm in self.itermaps(context):
530 for nm in self.itermaps(context):
531 # drop internal resources (recursively) which shouldn't be displayed
531 # drop internal resources (recursively) which shouldn't be displayed
532 lm = context.overlaymap(mapping, nm)
532 lm = context.overlaymap(mapping, nm)
533 items.append(
533 items.append(
534 {
534 {
535 k: unwrapvalue(context, lm, v)
535 k: unwrapvalue(context, lm, v)
536 for k, v in pycompat.iteritems(nm)
536 for k, v in pycompat.iteritems(nm)
537 if k not in knownres
537 if k not in knownres
538 }
538 }
539 )
539 )
540 return items
540 return items
541
541
542
542
543 class mappinggenerator(_mappingsequence):
543 class mappinggenerator(_mappingsequence):
544 """Wrapper for generator of template mappings
544 """Wrapper for generator of template mappings
545
545
546 The function ``make(context, *args)`` should return a generator of
546 The function ``make(context, *args)`` should return a generator of
547 mapping dicts.
547 mapping dicts.
548 """
548 """
549
549
550 def __init__(self, make, args=(), name=None, tmpl=None, sep=b''):
550 def __init__(self, make, args=(), name=None, tmpl=None, sep=b''):
551 super(mappinggenerator, self).__init__(name, tmpl, sep)
551 super(mappinggenerator, self).__init__(name, tmpl, sep)
552 self._make = make
552 self._make = make
553 self._args = args
553 self._args = args
554
554
555 def itermaps(self, context):
555 def itermaps(self, context):
556 return self._make(context, *self._args)
556 return self._make(context, *self._args)
557
557
558 def tobool(self, context, mapping):
558 def tobool(self, context, mapping):
559 return _nonempty(self.itermaps(context))
559 return _nonempty(self.itermaps(context))
560
560
561
561
562 class mappinglist(_mappingsequence):
562 class mappinglist(_mappingsequence):
563 """Wrapper for list of template mappings"""
563 """Wrapper for list of template mappings"""
564
564
565 def __init__(self, mappings, name=None, tmpl=None, sep=b''):
565 def __init__(self, mappings, name=None, tmpl=None, sep=b''):
566 super(mappinglist, self).__init__(name, tmpl, sep)
566 super(mappinglist, self).__init__(name, tmpl, sep)
567 self._mappings = mappings
567 self._mappings = mappings
568
568
569 def itermaps(self, context):
569 def itermaps(self, context):
570 return iter(self._mappings)
570 return iter(self._mappings)
571
571
572 def tobool(self, context, mapping):
572 def tobool(self, context, mapping):
573 return bool(self._mappings)
573 return bool(self._mappings)
574
574
575
575
576 class mappingdict(mappable, _mappingsequence):
576 class mappingdict(mappable, _mappingsequence):
577 """Wrapper for a single template mapping
577 """Wrapper for a single template mapping
578
578
579 This isn't a sequence in a way that the underlying dict won't be iterated
579 This isn't a sequence in a way that the underlying dict won't be iterated
580 as a dict, but shares most of the _mappingsequence functions.
580 as a dict, but shares most of the _mappingsequence functions.
581 """
581 """
582
582
583 def __init__(self, mapping, name=None, tmpl=None):
583 def __init__(self, mapping, name=None, tmpl=None):
584 super(mappingdict, self).__init__(name, tmpl)
584 super(mappingdict, self).__init__(name, tmpl)
585 self._mapping = mapping
585 self._mapping = mapping
586
586
587 def tomap(self, context):
587 def tomap(self, context):
588 return self._mapping
588 return self._mapping
589
589
590 def tobool(self, context, mapping):
590 def tobool(self, context, mapping):
591 # no idea when a template mapping should be considered an empty, but
591 # no idea when a template mapping should be considered an empty, but
592 # a mapping dict should have at least one item in practice, so always
592 # a mapping dict should have at least one item in practice, so always
593 # mark this as non-empty.
593 # mark this as non-empty.
594 return True
594 return True
595
595
596 def tovalue(self, context, mapping):
596 def tovalue(self, context, mapping):
597 return super(mappingdict, self).tovalue(context, mapping)[0]
597 return super(mappingdict, self).tovalue(context, mapping)[0]
598
598
599
599
600 class mappingnone(wrappedvalue):
600 class mappingnone(wrappedvalue):
601 """Wrapper for None, but supports map operation
601 """Wrapper for None, but supports map operation
602
602
603 This represents None of Optional[mappable]. It's similar to
603 This represents None of Optional[mappable]. It's similar to
604 mapplinglist([]), but the underlying value is not [], but None.
604 mapplinglist([]), but the underlying value is not [], but None.
605 """
605 """
606
606
607 def __init__(self):
607 def __init__(self):
608 super(mappingnone, self).__init__(None)
608 super(mappingnone, self).__init__(None)
609
609
610 def itermaps(self, context):
610 def itermaps(self, context):
611 return iter([])
611 return iter([])
612
612
613
613
614 class mappedgenerator(wrapped):
614 class mappedgenerator(wrapped):
615 """Wrapper for generator of strings which acts as a list
615 """Wrapper for generator of strings which acts as a list
616
616
617 The function ``make(context, *args)`` should return a generator of
617 The function ``make(context, *args)`` should return a generator of
618 byte strings, or a generator of (possibly nested) generators of byte
618 byte strings, or a generator of (possibly nested) generators of byte
619 strings (i.e. a generator for a list of byte strings.)
619 strings (i.e. a generator for a list of byte strings.)
620 """
620 """
621
621
622 def __init__(self, make, args=()):
622 def __init__(self, make, args=()):
623 self._make = make
623 self._make = make
624 self._args = args
624 self._args = args
625
625
626 def contains(self, context, mapping, item):
626 def contains(self, context, mapping, item):
627 item = stringify(context, mapping, item)
627 item = stringify(context, mapping, item)
628 return item in self.tovalue(context, mapping)
628 return item in self.tovalue(context, mapping)
629
629
630 def _gen(self, context):
630 def _gen(self, context):
631 return self._make(context, *self._args)
631 return self._make(context, *self._args)
632
632
633 def getmember(self, context, mapping, key):
633 def getmember(self, context, mapping, key):
634 raise error.ParseError(_(b'not a dictionary'))
634 raise error.ParseError(_(b'not a dictionary'))
635
635
636 def getmin(self, context, mapping):
636 def getmin(self, context, mapping):
637 return self._getby(context, mapping, min)
637 return self._getby(context, mapping, min)
638
638
639 def getmax(self, context, mapping):
639 def getmax(self, context, mapping):
640 return self._getby(context, mapping, max)
640 return self._getby(context, mapping, max)
641
641
642 def _getby(self, context, mapping, func):
642 def _getby(self, context, mapping, func):
643 xs = self.tovalue(context, mapping)
643 xs = self.tovalue(context, mapping)
644 if not xs:
644 if not xs:
645 raise error.ParseError(_(b'empty sequence'))
645 raise error.ParseError(_(b'empty sequence'))
646 return func(xs)
646 return func(xs)
647
647
648 @staticmethod
648 @staticmethod
649 def _filteredgen(context, mapping, make, args, select):
649 def _filteredgen(context, mapping, make, args, select):
650 for x in make(context, *args):
650 for x in make(context, *args):
651 s = stringify(context, mapping, x)
651 s = stringify(context, mapping, x)
652 if select(wrappedbytes(s)):
652 if select(wrappedbytes(s)):
653 yield s
653 yield s
654
654
655 def filter(self, context, mapping, select):
655 def filter(self, context, mapping, select):
656 args = (mapping, self._make, self._args, select)
656 args = (mapping, self._make, self._args, select)
657 return mappedgenerator(self._filteredgen, args)
657 return mappedgenerator(self._filteredgen, args)
658
658
659 def itermaps(self, context):
659 def itermaps(self, context):
660 raise error.ParseError(_(b'list of strings is not mappable'))
660 raise error.ParseError(_(b'list of strings is not mappable'))
661
661
662 def join(self, context, mapping, sep):
662 def join(self, context, mapping, sep):
663 return joinitems(self._gen(context), sep)
663 return joinitems(self._gen(context), sep)
664
664
665 def show(self, context, mapping):
665 def show(self, context, mapping):
666 return self.join(context, mapping, b'')
666 return self.join(context, mapping, b'')
667
667
668 def tobool(self, context, mapping):
668 def tobool(self, context, mapping):
669 return _nonempty(self._gen(context))
669 return _nonempty(self._gen(context))
670
670
671 def tovalue(self, context, mapping):
671 def tovalue(self, context, mapping):
672 return [stringify(context, mapping, x) for x in self._gen(context)]
672 return [stringify(context, mapping, x) for x in self._gen(context)]
673
673
674
674
675 def hybriddict(data, key=b'key', value=b'value', fmt=None, gen=None):
675 def hybriddict(data, key=b'key', value=b'value', fmt=None, gen=None):
676 """Wrap data to support both dict-like and string-like operations"""
676 """Wrap data to support both dict-like and string-like operations"""
677 prefmt = pycompat.identity
677 prefmt = pycompat.identity
678 if fmt is None:
678 if fmt is None:
679 fmt = b'%s=%s'
679 fmt = b'%s=%s'
680 prefmt = pycompat.bytestr
680 prefmt = pycompat.bytestr
681 return hybrid(
681 return hybrid(
682 gen,
682 gen,
683 data,
683 data,
684 lambda k: {key: k, value: data[k]},
684 lambda k: {key: k, value: data[k]},
685 lambda k: fmt % (prefmt(k), prefmt(data[k])),
685 lambda k: fmt % (prefmt(k), prefmt(data[k])),
686 )
686 )
687
687
688
688
689 def hybridlist(data, name, fmt=None, gen=None):
689 def hybridlist(data, name, fmt=None, gen=None):
690 """Wrap data to support both list-like and string-like operations"""
690 """Wrap data to support both list-like and string-like operations"""
691 prefmt = pycompat.identity
691 prefmt = pycompat.identity
692 if fmt is None:
692 if fmt is None:
693 fmt = b'%s'
693 fmt = b'%s'
694 prefmt = pycompat.bytestr
694 prefmt = pycompat.bytestr
695 return hybrid(gen, data, lambda x: {name: x}, lambda x: fmt % prefmt(x))
695 return hybrid(gen, data, lambda x: {name: x}, lambda x: fmt % prefmt(x))
696
696
697
697
698 def compatdict(
698 def compatdict(
699 context,
699 context,
700 mapping,
700 mapping,
701 name,
701 name,
702 data,
702 data,
703 key=b'key',
703 key=b'key',
704 value=b'value',
704 value=b'value',
705 fmt=None,
705 fmt=None,
706 plural=None,
706 plural=None,
707 separator=b' ',
707 separator=b' ',
708 ):
708 ):
709 """Wrap data like hybriddict(), but also supports old-style list template
709 """Wrap data like hybriddict(), but also supports old-style list template
710
710
711 This exists for backward compatibility with the old-style template. Use
711 This exists for backward compatibility with the old-style template. Use
712 hybriddict() for new template keywords.
712 hybriddict() for new template keywords.
713 """
713 """
714 c = [{key: k, value: v} for k, v in pycompat.iteritems(data)]
714 c = [{key: k, value: v} for k, v in pycompat.iteritems(data)]
715 f = _showcompatlist(context, mapping, name, c, plural, separator)
715 f = _showcompatlist(context, mapping, name, c, plural, separator)
716 return hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
716 return hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
717
717
718
718
719 def compatlist(
719 def compatlist(
720 context,
720 context,
721 mapping,
721 mapping,
722 name,
722 name,
723 data,
723 data,
724 element=None,
724 element=None,
725 fmt=None,
725 fmt=None,
726 plural=None,
726 plural=None,
727 separator=b' ',
727 separator=b' ',
728 ):
728 ):
729 """Wrap data like hybridlist(), but also supports old-style list template
729 """Wrap data like hybridlist(), but also supports old-style list template
730
730
731 This exists for backward compatibility with the old-style template. Use
731 This exists for backward compatibility with the old-style template. Use
732 hybridlist() for new template keywords.
732 hybridlist() for new template keywords.
733 """
733 """
734 f = _showcompatlist(context, mapping, name, data, plural, separator)
734 f = _showcompatlist(context, mapping, name, data, plural, separator)
735 return hybridlist(data, name=element or name, fmt=fmt, gen=f)
735 return hybridlist(data, name=element or name, fmt=fmt, gen=f)
736
736
737
737
738 def compatfilecopiesdict(context, mapping, name, copies):
738 def compatfilecopiesdict(context, mapping, name, copies):
739 """Wrap list of (dest, source) file names to support old-style list
739 """Wrap list of (dest, source) file names to support old-style list
740 template and field names
740 template and field names
741
741
742 This exists for backward compatibility. Use hybriddict for new template
742 This exists for backward compatibility. Use hybriddict for new template
743 keywords.
743 keywords.
744 """
744 """
745 # no need to provide {path} to old-style list template
745 # no need to provide {path} to old-style list template
746 c = [{b'name': k, b'source': v} for k, v in copies]
746 c = [{b'name': k, b'source': v} for k, v in copies]
747 f = _showcompatlist(context, mapping, name, c, plural=b'file_copies')
747 f = _showcompatlist(context, mapping, name, c, plural=b'file_copies')
748 copies = util.sortdict(copies)
748 copies = util.sortdict(copies)
749 return hybrid(
749 return hybrid(
750 f,
750 f,
751 copies,
751 copies,
752 lambda k: {b'name': k, b'path': k, b'source': copies[k]},
752 lambda k: {b'name': k, b'path': k, b'source': copies[k]},
753 lambda k: b'%s (%s)' % (k, copies[k]),
753 lambda k: b'%s (%s)' % (k, copies[k]),
754 )
754 )
755
755
756
756
757 def compatfileslist(context, mapping, name, files):
757 def compatfileslist(context, mapping, name, files):
758 """Wrap list of file names to support old-style list template and field
758 """Wrap list of file names to support old-style list template and field
759 names
759 names
760
760
761 This exists for backward compatibility. Use hybridlist for new template
761 This exists for backward compatibility. Use hybridlist for new template
762 keywords.
762 keywords.
763 """
763 """
764 f = _showcompatlist(context, mapping, name, files)
764 f = _showcompatlist(context, mapping, name, files)
765 return hybrid(
765 return hybrid(
766 f, files, lambda x: {b'file': x, b'path': x}, pycompat.identity
766 f, files, lambda x: {b'file': x, b'path': x}, pycompat.identity
767 )
767 )
768
768
769
769
770 def _showcompatlist(
770 def _showcompatlist(
771 context, mapping, name, values, plural=None, separator=b' '
771 context, mapping, name, values, plural=None, separator=b' '
772 ):
772 ):
773 """Return a generator that renders old-style list template
773 """Return a generator that renders old-style list template
774
774
775 name is name of key in template map.
775 name is name of key in template map.
776 values is list of strings or dicts.
776 values is list of strings or dicts.
777 plural is plural of name, if not simply name + 's'.
777 plural is plural of name, if not simply name + 's'.
778 separator is used to join values as a string
778 separator is used to join values as a string
779
779
780 expansion works like this, given name 'foo'.
780 expansion works like this, given name 'foo'.
781
781
782 if values is empty, expand 'no_foos'.
782 if values is empty, expand 'no_foos'.
783
783
784 if 'foo' not in template map, return values as a string,
784 if 'foo' not in template map, return values as a string,
785 joined by 'separator'.
785 joined by 'separator'.
786
786
787 expand 'start_foos'.
787 expand 'start_foos'.
788
788
789 for each value, expand 'foo'. if 'last_foo' in template
789 for each value, expand 'foo'. if 'last_foo' in template
790 map, expand it instead of 'foo' for last key.
790 map, expand it instead of 'foo' for last key.
791
791
792 expand 'end_foos'.
792 expand 'end_foos'.
793 """
793 """
794 if not plural:
794 if not plural:
795 plural = name + b's'
795 plural = name + b's'
796 if not values:
796 if not values:
797 noname = b'no_' + plural
797 noname = b'no_' + plural
798 if context.preload(noname):
798 if context.preload(noname):
799 yield context.process(noname, mapping)
799 yield context.process(noname, mapping)
800 return
800 return
801 if not context.preload(name):
801 if not context.preload(name):
802 if isinstance(values[0], bytes):
802 if isinstance(values[0], bytes):
803 yield separator.join(values)
803 yield separator.join(values)
804 else:
804 else:
805 for v in values:
805 for v in values:
806 r = dict(v)
806 r = dict(v)
807 r.update(mapping)
807 r.update(mapping)
808 yield r
808 yield r
809 return
809 return
810 startname = b'start_' + plural
810 startname = b'start_' + plural
811 if context.preload(startname):
811 if context.preload(startname):
812 yield context.process(startname, mapping)
812 yield context.process(startname, mapping)
813
813
814 def one(v, tag=name):
814 def one(v, tag=name):
815 vmapping = {}
815 vmapping = {}
816 try:
816 try:
817 vmapping.update(v)
817 vmapping.update(v)
818 # Python 2 raises ValueError if the type of v is wrong. Python
818 # Python 2 raises ValueError if the type of v is wrong. Python
819 # 3 raises TypeError.
819 # 3 raises TypeError.
820 except (AttributeError, TypeError, ValueError):
820 except (AttributeError, TypeError, ValueError):
821 try:
821 try:
822 # Python 2 raises ValueError trying to destructure an e.g.
822 # Python 2 raises ValueError trying to destructure an e.g.
823 # bytes. Python 3 raises TypeError.
823 # bytes. Python 3 raises TypeError.
824 for a, b in v:
824 for a, b in v:
825 vmapping[a] = b
825 vmapping[a] = b
826 except (TypeError, ValueError):
826 except (TypeError, ValueError):
827 vmapping[name] = v
827 vmapping[name] = v
828 vmapping = context.overlaymap(mapping, vmapping)
828 vmapping = context.overlaymap(mapping, vmapping)
829 return context.process(tag, vmapping)
829 return context.process(tag, vmapping)
830
830
831 lastname = b'last_' + name
831 lastname = b'last_' + name
832 if context.preload(lastname):
832 if context.preload(lastname):
833 last = values.pop()
833 last = values.pop()
834 else:
834 else:
835 last = None
835 last = None
836 for v in values:
836 for v in values:
837 yield one(v)
837 yield one(v)
838 if last is not None:
838 if last is not None:
839 yield one(last, tag=lastname)
839 yield one(last, tag=lastname)
840 endname = b'end_' + plural
840 endname = b'end_' + plural
841 if context.preload(endname):
841 if context.preload(endname):
842 yield context.process(endname, mapping)
842 yield context.process(endname, mapping)
843
843
844
844
845 def flatten(context, mapping, thing):
845 def flatten(context, mapping, thing):
846 """Yield a single stream from a possibly nested set of iterators"""
846 """Yield a single stream from a possibly nested set of iterators"""
847 if isinstance(thing, wrapped):
847 if isinstance(thing, wrapped):
848 thing = thing.show(context, mapping)
848 thing = thing.show(context, mapping)
849 if isinstance(thing, bytes):
849 if isinstance(thing, bytes):
850 yield thing
850 yield thing
851 elif isinstance(thing, str):
851 elif isinstance(thing, str):
852 # We can only hit this on Python 3, and it's here to guard
852 # We can only hit this on Python 3, and it's here to guard
853 # against infinite recursion.
853 # against infinite recursion.
854 raise error.ProgrammingError(
854 raise error.ProgrammingError(
855 b'Mercurial IO including templates is done'
855 b'Mercurial IO including templates is done'
856 b' with bytes, not strings, got %r' % thing
856 b' with bytes, not strings, got %r' % thing
857 )
857 )
858 elif thing is None:
858 elif thing is None:
859 pass
859 pass
860 elif not util.safehasattr(thing, b'__iter__'):
860 elif not util.safehasattr(thing, b'__iter__'):
861 yield pycompat.bytestr(thing)
861 yield pycompat.bytestr(thing)
862 else:
862 else:
863 for i in thing:
863 for i in thing:
864 if isinstance(i, wrapped):
864 if isinstance(i, wrapped):
865 i = i.show(context, mapping)
865 i = i.show(context, mapping)
866 if isinstance(i, bytes):
866 if isinstance(i, bytes):
867 yield i
867 yield i
868 elif i is None:
868 elif i is None:
869 pass
869 pass
870 elif not util.safehasattr(i, b'__iter__'):
870 elif not util.safehasattr(i, b'__iter__'):
871 yield pycompat.bytestr(i)
871 yield pycompat.bytestr(i)
872 else:
872 else:
873 for j in flatten(context, mapping, i):
873 for j in flatten(context, mapping, i):
874 yield j
874 yield j
875
875
876
876
877 def stringify(context, mapping, thing):
877 def stringify(context, mapping, thing):
878 """Turn values into bytes by converting into text and concatenating them"""
878 """Turn values into bytes by converting into text and concatenating them"""
879 if isinstance(thing, bytes):
879 if isinstance(thing, bytes):
880 return thing # retain localstr to be round-tripped
880 return thing # retain localstr to be round-tripped
881 return b''.join(flatten(context, mapping, thing))
881 return b''.join(flatten(context, mapping, thing))
882
882
883
883
884 def findsymbolicname(arg):
884 def findsymbolicname(arg):
885 """Find symbolic name for the given compiled expression; returns None
885 """Find symbolic name for the given compiled expression; returns None
886 if nothing found reliably"""
886 if nothing found reliably"""
887 while True:
887 while True:
888 func, data = arg
888 func, data = arg
889 if func is runsymbol:
889 if func is runsymbol:
890 return data
890 return data
891 elif func is runfilter:
891 elif func is runfilter:
892 arg = data[0]
892 arg = data[0]
893 else:
893 else:
894 return None
894 return None
895
895
896
896
897 def _nonempty(xiter):
897 def _nonempty(xiter):
898 try:
898 try:
899 next(xiter)
899 next(xiter)
900 return True
900 return True
901 except StopIteration:
901 except StopIteration:
902 return False
902 return False
903
903
904
904
905 def _unthunk(context, mapping, thing):
905 def _unthunk(context, mapping, thing):
906 """Evaluate a lazy byte string into value"""
906 """Evaluate a lazy byte string into value"""
907 if not isinstance(thing, types.GeneratorType):
907 if not isinstance(thing, types.GeneratorType):
908 return thing
908 return thing
909 return stringify(context, mapping, thing)
909 return stringify(context, mapping, thing)
910
910
911
911
912 def evalrawexp(context, mapping, arg):
912 def evalrawexp(context, mapping, arg):
913 """Evaluate given argument as a bare template object which may require
913 """Evaluate given argument as a bare template object which may require
914 further processing (such as folding generator of strings)"""
914 further processing (such as folding generator of strings)"""
915 func, data = arg
915 func, data = arg
916 return func(context, mapping, data)
916 return func(context, mapping, data)
917
917
918
918
919 def evalwrapped(context, mapping, arg):
919 def evalwrapped(context, mapping, arg):
920 """Evaluate given argument to wrapped object"""
920 """Evaluate given argument to wrapped object"""
921 thing = evalrawexp(context, mapping, arg)
921 thing = evalrawexp(context, mapping, arg)
922 return makewrapped(context, mapping, thing)
922 return makewrapped(context, mapping, thing)
923
923
924
924
925 def makewrapped(context, mapping, thing):
925 def makewrapped(context, mapping, thing):
926 """Lift object to a wrapped type"""
926 """Lift object to a wrapped type"""
927 if isinstance(thing, wrapped):
927 if isinstance(thing, wrapped):
928 return thing
928 return thing
929 thing = _unthunk(context, mapping, thing)
929 thing = _unthunk(context, mapping, thing)
930 if isinstance(thing, bytes):
930 if isinstance(thing, bytes):
931 return wrappedbytes(thing)
931 return wrappedbytes(thing)
932 return wrappedvalue(thing)
932 return wrappedvalue(thing)
933
933
934
934
935 def evalfuncarg(context, mapping, arg):
935 def evalfuncarg(context, mapping, arg):
936 """Evaluate given argument as value type"""
936 """Evaluate given argument as value type"""
937 return unwrapvalue(context, mapping, evalrawexp(context, mapping, arg))
937 return unwrapvalue(context, mapping, evalrawexp(context, mapping, arg))
938
938
939
939
940 def unwrapvalue(context, mapping, thing):
940 def unwrapvalue(context, mapping, thing):
941 """Move the inner value object out of the wrapper"""
941 """Move the inner value object out of the wrapper"""
942 if isinstance(thing, wrapped):
942 if isinstance(thing, wrapped):
943 return thing.tovalue(context, mapping)
943 return thing.tovalue(context, mapping)
944 # evalrawexp() may return string, generator of strings or arbitrary object
944 # evalrawexp() may return string, generator of strings or arbitrary object
945 # such as date tuple, but filter does not want generator.
945 # such as date tuple, but filter does not want generator.
946 return _unthunk(context, mapping, thing)
946 return _unthunk(context, mapping, thing)
947
947
948
948
949 def evalboolean(context, mapping, arg):
949 def evalboolean(context, mapping, arg):
950 """Evaluate given argument as boolean, but also takes boolean literals"""
950 """Evaluate given argument as boolean, but also takes boolean literals"""
951 func, data = arg
951 func, data = arg
952 if func is runsymbol:
952 if func is runsymbol:
953 thing = func(context, mapping, data, default=None)
953 thing = func(context, mapping, data, default=None)
954 if thing is None:
954 if thing is None:
955 # not a template keyword, takes as a boolean literal
955 # not a template keyword, takes as a boolean literal
956 thing = stringutil.parsebool(data)
956 thing = stringutil.parsebool(data)
957 else:
957 else:
958 thing = func(context, mapping, data)
958 thing = func(context, mapping, data)
959 return makewrapped(context, mapping, thing).tobool(context, mapping)
959 return makewrapped(context, mapping, thing).tobool(context, mapping)
960
960
961
961
962 def evaldate(context, mapping, arg, err=None):
962 def evaldate(context, mapping, arg, err=None):
963 """Evaluate given argument as a date tuple or a date string; returns
963 """Evaluate given argument as a date tuple or a date string; returns
964 a (unixtime, offset) tuple"""
964 a (unixtime, offset) tuple"""
965 thing = evalrawexp(context, mapping, arg)
965 thing = evalrawexp(context, mapping, arg)
966 return unwrapdate(context, mapping, thing, err)
966 return unwrapdate(context, mapping, thing, err)
967
967
968
968
969 def unwrapdate(context, mapping, thing, err=None):
969 def unwrapdate(context, mapping, thing, err=None):
970 if isinstance(thing, date):
970 if isinstance(thing, date):
971 return thing.tovalue(context, mapping)
971 return thing.tovalue(context, mapping)
972 # TODO: update hgweb to not return bare tuple; then just stringify 'thing'
972 # TODO: update hgweb to not return bare tuple; then just stringify 'thing'
973 thing = unwrapvalue(context, mapping, thing)
973 thing = unwrapvalue(context, mapping, thing)
974 try:
974 try:
975 return dateutil.parsedate(thing)
975 return dateutil.parsedate(thing)
976 except AttributeError:
976 except AttributeError:
977 raise error.ParseError(err or _(b'not a date tuple nor a string'))
977 raise error.ParseError(err or _(b'not a date tuple nor a string'))
978 except error.ParseError:
978 except error.ParseError:
979 if not err:
979 if not err:
980 raise
980 raise
981 raise error.ParseError(err)
981 raise error.ParseError(err)
982
982
983
983
984 def evalinteger(context, mapping, arg, err=None):
984 def evalinteger(context, mapping, arg, err=None):
985 thing = evalrawexp(context, mapping, arg)
985 thing = evalrawexp(context, mapping, arg)
986 return unwrapinteger(context, mapping, thing, err)
986 return unwrapinteger(context, mapping, thing, err)
987
987
988
988
989 def unwrapinteger(context, mapping, thing, err=None):
989 def unwrapinteger(context, mapping, thing, err=None):
990 thing = unwrapvalue(context, mapping, thing)
990 thing = unwrapvalue(context, mapping, thing)
991 try:
991 try:
992 return int(thing)
992 return int(thing)
993 except (TypeError, ValueError):
993 except (TypeError, ValueError):
994 raise error.ParseError(err or _(b'not an integer'))
994 raise error.ParseError(err or _(b'not an integer'))
995
995
996
996
997 def evalstring(context, mapping, arg):
997 def evalstring(context, mapping, arg):
998 return stringify(context, mapping, evalrawexp(context, mapping, arg))
998 return stringify(context, mapping, evalrawexp(context, mapping, arg))
999
999
1000
1000
1001 def evalstringliteral(context, mapping, arg):
1001 def evalstringliteral(context, mapping, arg):
1002 """Evaluate given argument as string template, but returns symbol name
1002 """Evaluate given argument as string template, but returns symbol name
1003 if it is unknown"""
1003 if it is unknown"""
1004 func, data = arg
1004 func, data = arg
1005 if func is runsymbol:
1005 if func is runsymbol:
1006 thing = func(context, mapping, data, default=data)
1006 thing = func(context, mapping, data, default=data)
1007 else:
1007 else:
1008 thing = func(context, mapping, data)
1008 thing = func(context, mapping, data)
1009 return stringify(context, mapping, thing)
1009 return stringify(context, mapping, thing)
1010
1010
1011
1011
1012 _unwrapfuncbytype = {
1012 _unwrapfuncbytype = {
1013 None: unwrapvalue,
1013 None: unwrapvalue,
1014 bytes: stringify,
1014 bytes: stringify,
1015 date: unwrapdate,
1015 date: unwrapdate,
1016 int: unwrapinteger,
1016 int: unwrapinteger,
1017 }
1017 }
1018
1018
1019
1019
1020 def unwrapastype(context, mapping, thing, typ):
1020 def unwrapastype(context, mapping, thing, typ):
1021 """Move the inner value object out of the wrapper and coerce its type"""
1021 """Move the inner value object out of the wrapper and coerce its type"""
1022 try:
1022 try:
1023 f = _unwrapfuncbytype[typ]
1023 f = _unwrapfuncbytype[typ]
1024 except KeyError:
1024 except KeyError:
1025 raise error.ProgrammingError(b'invalid type specified: %r' % typ)
1025 raise error.ProgrammingError(b'invalid type specified: %r' % typ)
1026 return f(context, mapping, thing)
1026 return f(context, mapping, thing)
1027
1027
1028
1028
1029 def runinteger(context, mapping, data):
1029 def runinteger(context, mapping, data):
1030 return int(data)
1030 return int(data)
1031
1031
1032
1032
1033 def runstring(context, mapping, data):
1033 def runstring(context, mapping, data):
1034 return data
1034 return data
1035
1035
1036
1036
1037 def _recursivesymbolblocker(key):
1037 def _recursivesymbolblocker(key):
1038 def showrecursion(context, mapping):
1038 def showrecursion(context, mapping):
1039 raise error.Abort(_(b"recursive reference '%s' in template") % key)
1039 raise error.Abort(_(b"recursive reference '%s' in template") % key)
1040
1040
1041 return showrecursion
1041 return showrecursion
1042
1042
1043
1043
1044 def runsymbol(context, mapping, key, default=b''):
1044 def runsymbol(context, mapping, key, default=b''):
1045 v = context.symbol(mapping, key)
1045 v = context.symbol(mapping, key)
1046 if v is None:
1046 if v is None:
1047 # put poison to cut recursion. we can't move this to parsing phase
1047 # put poison to cut recursion. we can't move this to parsing phase
1048 # because "x = {x}" is allowed if "x" is a keyword. (issue4758)
1048 # because "x = {x}" is allowed if "x" is a keyword. (issue4758)
1049 safemapping = mapping.copy()
1049 safemapping = mapping.copy()
1050 safemapping[key] = _recursivesymbolblocker(key)
1050 safemapping[key] = _recursivesymbolblocker(key)
1051 try:
1051 try:
1052 v = context.process(key, safemapping)
1052 v = context.process(key, safemapping)
1053 except TemplateNotFound:
1053 except TemplateNotFound:
1054 v = default
1054 v = default
1055 if callable(v):
1055 if callable(v):
1056 # new templatekw
1056 # new templatekw
1057 try:
1057 try:
1058 return v(context, mapping)
1058 return v(context, mapping)
1059 except ResourceUnavailable:
1059 except ResourceUnavailable:
1060 # unsupported keyword is mapped to empty just like unknown keyword
1060 # unsupported keyword is mapped to empty just like unknown keyword
1061 return None
1061 return None
1062 return v
1062 return v
1063
1063
1064
1064
1065 def runtemplate(context, mapping, template):
1065 def runtemplate(context, mapping, template):
1066 for arg in template:
1066 for arg in template:
1067 yield evalrawexp(context, mapping, arg)
1067 yield evalrawexp(context, mapping, arg)
1068
1068
1069
1069
1070 def runfilter(context, mapping, data):
1070 def runfilter(context, mapping, data):
1071 arg, filt = data
1071 arg, filt = data
1072 thing = evalrawexp(context, mapping, arg)
1072 thing = evalrawexp(context, mapping, arg)
1073 intype = getattr(filt, '_intype', None)
1073 intype = getattr(filt, '_intype', None)
1074 try:
1074 try:
1075 thing = unwrapastype(context, mapping, thing, intype)
1075 thing = unwrapastype(context, mapping, thing, intype)
1076 return filt(thing)
1076 return filt(thing)
1077 except error.ParseError as e:
1077 except error.ParseError as e:
1078 raise error.ParseError(bytes(e), hint=_formatfiltererror(arg, filt))
1078 raise error.ParseError(bytes(e), hint=_formatfiltererror(arg, filt))
1079
1079
1080
1080
1081 def _formatfiltererror(arg, filt):
1081 def _formatfiltererror(arg, filt):
1082 fn = pycompat.sysbytes(filt.__name__)
1082 fn = pycompat.sysbytes(filt.__name__)
1083 sym = findsymbolicname(arg)
1083 sym = findsymbolicname(arg)
1084 if not sym:
1084 if not sym:
1085 return _(b"incompatible use of template filter '%s'") % fn
1085 return _(b"incompatible use of template filter '%s'") % fn
1086 return _(b"template filter '%s' is not compatible with keyword '%s'") % (
1086 return _(b"template filter '%s' is not compatible with keyword '%s'") % (
1087 fn,
1087 fn,
1088 sym,
1088 sym,
1089 )
1089 )
1090
1090
1091
1091
1092 def _iteroverlaymaps(context, origmapping, newmappings):
1092 def _iteroverlaymaps(context, origmapping, newmappings):
1093 """Generate combined mappings from the original mapping and an iterable
1093 """Generate combined mappings from the original mapping and an iterable
1094 of partial mappings to override the original"""
1094 of partial mappings to override the original"""
1095 for i, nm in enumerate(newmappings):
1095 for i, nm in enumerate(newmappings):
1096 lm = context.overlaymap(origmapping, nm)
1096 lm = context.overlaymap(origmapping, nm)
1097 lm[b'index'] = i
1097 lm[b'index'] = i
1098 yield lm
1098 yield lm
1099
1099
1100
1100
1101 def _applymap(context, mapping, d, darg, targ):
1101 def _applymap(context, mapping, d, darg, targ):
1102 try:
1102 try:
1103 diter = d.itermaps(context)
1103 diter = d.itermaps(context)
1104 except error.ParseError as err:
1104 except error.ParseError as err:
1105 sym = findsymbolicname(darg)
1105 sym = findsymbolicname(darg)
1106 if not sym:
1106 if not sym:
1107 raise
1107 raise
1108 hint = _(b"keyword '%s' does not support map operation") % sym
1108 hint = _(b"keyword '%s' does not support map operation") % sym
1109 raise error.ParseError(bytes(err), hint=hint)
1109 raise error.ParseError(bytes(err), hint=hint)
1110 for lm in _iteroverlaymaps(context, mapping, diter):
1110 for lm in _iteroverlaymaps(context, mapping, diter):
1111 yield evalrawexp(context, lm, targ)
1111 yield evalrawexp(context, lm, targ)
1112
1112
1113
1113
1114 def runmap(context, mapping, data):
1114 def runmap(context, mapping, data):
1115 darg, targ = data
1115 darg, targ = data
1116 d = evalwrapped(context, mapping, darg)
1116 d = evalwrapped(context, mapping, darg)
1117 return mappedgenerator(_applymap, args=(mapping, d, darg, targ))
1117 return mappedgenerator(_applymap, args=(mapping, d, darg, targ))
1118
1118
1119
1119
1120 def runmember(context, mapping, data):
1120 def runmember(context, mapping, data):
1121 darg, memb = data
1121 darg, memb = data
1122 d = evalwrapped(context, mapping, darg)
1122 d = evalwrapped(context, mapping, darg)
1123 if isinstance(d, mappable):
1123 if isinstance(d, mappable):
1124 lm = context.overlaymap(mapping, d.tomap(context))
1124 lm = context.overlaymap(mapping, d.tomap(context))
1125 return runsymbol(context, lm, memb)
1125 return runsymbol(context, lm, memb)
1126 try:
1126 try:
1127 return d.getmember(context, mapping, memb)
1127 return d.getmember(context, mapping, memb)
1128 except error.ParseError as err:
1128 except error.ParseError as err:
1129 sym = findsymbolicname(darg)
1129 sym = findsymbolicname(darg)
1130 if not sym:
1130 if not sym:
1131 raise
1131 raise
1132 hint = _(b"keyword '%s' does not support member operation") % sym
1132 hint = _(b"keyword '%s' does not support member operation") % sym
1133 raise error.ParseError(bytes(err), hint=hint)
1133 raise error.ParseError(bytes(err), hint=hint)
1134
1134
1135
1135
1136 def runnegate(context, mapping, data):
1136 def runnegate(context, mapping, data):
1137 data = evalinteger(
1137 data = evalinteger(
1138 context, mapping, data, _(b'negation needs an integer argument')
1138 context, mapping, data, _(b'negation needs an integer argument')
1139 )
1139 )
1140 return -data
1140 return -data
1141
1141
1142
1142
1143 def runarithmetic(context, mapping, data):
1143 def runarithmetic(context, mapping, data):
1144 func, left, right = data
1144 func, left, right = data
1145 left = evalinteger(
1145 left = evalinteger(
1146 context, mapping, left, _(b'arithmetic only defined on integers')
1146 context, mapping, left, _(b'arithmetic only defined on integers')
1147 )
1147 )
1148 right = evalinteger(
1148 right = evalinteger(
1149 context, mapping, right, _(b'arithmetic only defined on integers')
1149 context, mapping, right, _(b'arithmetic only defined on integers')
1150 )
1150 )
1151 try:
1151 try:
1152 return func(left, right)
1152 return func(left, right)
1153 except ZeroDivisionError:
1153 except ZeroDivisionError:
1154 raise error.Abort(_(b'division by zero is not defined'))
1154 raise error.Abort(_(b'division by zero is not defined'))
1155
1155
1156
1156
1157 def joinitems(itemiter, sep):
1157 def joinitems(itemiter, sep):
1158 """Join items with the separator; Returns generator of bytes"""
1158 """Join items with the separator; Returns generator of bytes"""
1159 first = True
1159 first = True
1160 for x in itemiter:
1160 for x in itemiter:
1161 if first:
1161 if first:
1162 first = False
1162 first = False
1163 elif sep:
1163 elif sep:
1164 yield sep
1164 yield sep
1165 yield x
1165 yield x
General Comments 0
You need to be logged in to leave comments. Login now