##// END OF EJS Templates
templater: factor out unwrapastype() from evalastype()...
Yuya Nishihara -
r37180:0fb28899 default
parent child Browse files
Show More
@@ -1,664 +1,664 b''
1 # templatefuncs.py - common template functions
1 # templatefuncs.py - common template functions
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 re
10 import re
11
11
12 from .i18n import _
12 from .i18n import _
13 from . import (
13 from . import (
14 color,
14 color,
15 encoding,
15 encoding,
16 error,
16 error,
17 minirst,
17 minirst,
18 obsutil,
18 obsutil,
19 pycompat,
19 pycompat,
20 registrar,
20 registrar,
21 revset as revsetmod,
21 revset as revsetmod,
22 revsetlang,
22 revsetlang,
23 scmutil,
23 scmutil,
24 templatefilters,
24 templatefilters,
25 templatekw,
25 templatekw,
26 templateutil,
26 templateutil,
27 util,
27 util,
28 )
28 )
29 from .utils import dateutil
29 from .utils import dateutil
30
30
31 evalrawexp = templateutil.evalrawexp
31 evalrawexp = templateutil.evalrawexp
32 evalfuncarg = templateutil.evalfuncarg
32 evalfuncarg = templateutil.evalfuncarg
33 evalboolean = templateutil.evalboolean
33 evalboolean = templateutil.evalboolean
34 evalinteger = templateutil.evalinteger
34 evalinteger = templateutil.evalinteger
35 evalstring = templateutil.evalstring
35 evalstring = templateutil.evalstring
36 evalstringliteral = templateutil.evalstringliteral
36 evalstringliteral = templateutil.evalstringliteral
37 evalastype = templateutil.evalastype
38
37
39 # dict of template built-in functions
38 # dict of template built-in functions
40 funcs = {}
39 funcs = {}
41 templatefunc = registrar.templatefunc(funcs)
40 templatefunc = registrar.templatefunc(funcs)
42
41
43 @templatefunc('date(date[, fmt])')
42 @templatefunc('date(date[, fmt])')
44 def date(context, mapping, args):
43 def date(context, mapping, args):
45 """Format a date. See :hg:`help dates` for formatting
44 """Format a date. See :hg:`help dates` for formatting
46 strings. The default is a Unix date format, including the timezone:
45 strings. The default is a Unix date format, including the timezone:
47 "Mon Sep 04 15:13:13 2006 0700"."""
46 "Mon Sep 04 15:13:13 2006 0700"."""
48 if not (1 <= len(args) <= 2):
47 if not (1 <= len(args) <= 2):
49 # i18n: "date" is a keyword
48 # i18n: "date" is a keyword
50 raise error.ParseError(_("date expects one or two arguments"))
49 raise error.ParseError(_("date expects one or two arguments"))
51
50
52 date = evalfuncarg(context, mapping, args[0])
51 date = evalfuncarg(context, mapping, args[0])
53 fmt = None
52 fmt = None
54 if len(args) == 2:
53 if len(args) == 2:
55 fmt = evalstring(context, mapping, args[1])
54 fmt = evalstring(context, mapping, args[1])
56 try:
55 try:
57 if fmt is None:
56 if fmt is None:
58 return dateutil.datestr(date)
57 return dateutil.datestr(date)
59 else:
58 else:
60 return dateutil.datestr(date, fmt)
59 return dateutil.datestr(date, fmt)
61 except (TypeError, ValueError):
60 except (TypeError, ValueError):
62 # i18n: "date" is a keyword
61 # i18n: "date" is a keyword
63 raise error.ParseError(_("date expects a date information"))
62 raise error.ParseError(_("date expects a date information"))
64
63
65 @templatefunc('dict([[key=]value...])', argspec='*args **kwargs')
64 @templatefunc('dict([[key=]value...])', argspec='*args **kwargs')
66 def dict_(context, mapping, args):
65 def dict_(context, mapping, args):
67 """Construct a dict from key-value pairs. A key may be omitted if
66 """Construct a dict from key-value pairs. A key may be omitted if
68 a value expression can provide an unambiguous name."""
67 a value expression can provide an unambiguous name."""
69 data = util.sortdict()
68 data = util.sortdict()
70
69
71 for v in args['args']:
70 for v in args['args']:
72 k = templateutil.findsymbolicname(v)
71 k = templateutil.findsymbolicname(v)
73 if not k:
72 if not k:
74 raise error.ParseError(_('dict key cannot be inferred'))
73 raise error.ParseError(_('dict key cannot be inferred'))
75 if k in data or k in args['kwargs']:
74 if k in data or k in args['kwargs']:
76 raise error.ParseError(_("duplicated dict key '%s' inferred") % k)
75 raise error.ParseError(_("duplicated dict key '%s' inferred") % k)
77 data[k] = evalfuncarg(context, mapping, v)
76 data[k] = evalfuncarg(context, mapping, v)
78
77
79 data.update((k, evalfuncarg(context, mapping, v))
78 data.update((k, evalfuncarg(context, mapping, v))
80 for k, v in args['kwargs'].iteritems())
79 for k, v in args['kwargs'].iteritems())
81 return templateutil.hybriddict(data)
80 return templateutil.hybriddict(data)
82
81
83 @templatefunc('diff([includepattern [, excludepattern]])')
82 @templatefunc('diff([includepattern [, excludepattern]])')
84 def diff(context, mapping, args):
83 def diff(context, mapping, args):
85 """Show a diff, optionally
84 """Show a diff, optionally
86 specifying files to include or exclude."""
85 specifying files to include or exclude."""
87 if len(args) > 2:
86 if len(args) > 2:
88 # i18n: "diff" is a keyword
87 # i18n: "diff" is a keyword
89 raise error.ParseError(_("diff expects zero, one, or two arguments"))
88 raise error.ParseError(_("diff expects zero, one, or two arguments"))
90
89
91 def getpatterns(i):
90 def getpatterns(i):
92 if i < len(args):
91 if i < len(args):
93 s = evalstring(context, mapping, args[i]).strip()
92 s = evalstring(context, mapping, args[i]).strip()
94 if s:
93 if s:
95 return [s]
94 return [s]
96 return []
95 return []
97
96
98 ctx = context.resource(mapping, 'ctx')
97 ctx = context.resource(mapping, 'ctx')
99 chunks = ctx.diff(match=ctx.match([], getpatterns(0), getpatterns(1)))
98 chunks = ctx.diff(match=ctx.match([], getpatterns(0), getpatterns(1)))
100
99
101 return ''.join(chunks)
100 return ''.join(chunks)
102
101
103 @templatefunc('extdata(source)', argspec='source')
102 @templatefunc('extdata(source)', argspec='source')
104 def extdata(context, mapping, args):
103 def extdata(context, mapping, args):
105 """Show a text read from the specified extdata source. (EXPERIMENTAL)"""
104 """Show a text read from the specified extdata source. (EXPERIMENTAL)"""
106 if 'source' not in args:
105 if 'source' not in args:
107 # i18n: "extdata" is a keyword
106 # i18n: "extdata" is a keyword
108 raise error.ParseError(_('extdata expects one argument'))
107 raise error.ParseError(_('extdata expects one argument'))
109
108
110 source = evalstring(context, mapping, args['source'])
109 source = evalstring(context, mapping, args['source'])
111 cache = context.resource(mapping, 'cache').setdefault('extdata', {})
110 cache = context.resource(mapping, 'cache').setdefault('extdata', {})
112 ctx = context.resource(mapping, 'ctx')
111 ctx = context.resource(mapping, 'ctx')
113 if source in cache:
112 if source in cache:
114 data = cache[source]
113 data = cache[source]
115 else:
114 else:
116 data = cache[source] = scmutil.extdatasource(ctx.repo(), source)
115 data = cache[source] = scmutil.extdatasource(ctx.repo(), source)
117 return data.get(ctx.rev(), '')
116 return data.get(ctx.rev(), '')
118
117
119 @templatefunc('files(pattern)')
118 @templatefunc('files(pattern)')
120 def files(context, mapping, args):
119 def files(context, mapping, args):
121 """All files of the current changeset matching the pattern. See
120 """All files of the current changeset matching the pattern. See
122 :hg:`help patterns`."""
121 :hg:`help patterns`."""
123 if not len(args) == 1:
122 if not len(args) == 1:
124 # i18n: "files" is a keyword
123 # i18n: "files" is a keyword
125 raise error.ParseError(_("files expects one argument"))
124 raise error.ParseError(_("files expects one argument"))
126
125
127 raw = evalstring(context, mapping, args[0])
126 raw = evalstring(context, mapping, args[0])
128 ctx = context.resource(mapping, 'ctx')
127 ctx = context.resource(mapping, 'ctx')
129 m = ctx.match([raw])
128 m = ctx.match([raw])
130 files = list(ctx.matches(m))
129 files = list(ctx.matches(m))
131 return templateutil.compatlist(context, mapping, "file", files)
130 return templateutil.compatlist(context, mapping, "file", files)
132
131
133 @templatefunc('fill(text[, width[, initialident[, hangindent]]])')
132 @templatefunc('fill(text[, width[, initialident[, hangindent]]])')
134 def fill(context, mapping, args):
133 def fill(context, mapping, args):
135 """Fill many
134 """Fill many
136 paragraphs with optional indentation. See the "fill" filter."""
135 paragraphs with optional indentation. See the "fill" filter."""
137 if not (1 <= len(args) <= 4):
136 if not (1 <= len(args) <= 4):
138 # i18n: "fill" is a keyword
137 # i18n: "fill" is a keyword
139 raise error.ParseError(_("fill expects one to four arguments"))
138 raise error.ParseError(_("fill expects one to four arguments"))
140
139
141 text = evalstring(context, mapping, args[0])
140 text = evalstring(context, mapping, args[0])
142 width = 76
141 width = 76
143 initindent = ''
142 initindent = ''
144 hangindent = ''
143 hangindent = ''
145 if 2 <= len(args) <= 4:
144 if 2 <= len(args) <= 4:
146 width = evalinteger(context, mapping, args[1],
145 width = evalinteger(context, mapping, args[1],
147 # i18n: "fill" is a keyword
146 # i18n: "fill" is a keyword
148 _("fill expects an integer width"))
147 _("fill expects an integer width"))
149 try:
148 try:
150 initindent = evalstring(context, mapping, args[2])
149 initindent = evalstring(context, mapping, args[2])
151 hangindent = evalstring(context, mapping, args[3])
150 hangindent = evalstring(context, mapping, args[3])
152 except IndexError:
151 except IndexError:
153 pass
152 pass
154
153
155 return templatefilters.fill(text, width, initindent, hangindent)
154 return templatefilters.fill(text, width, initindent, hangindent)
156
155
157 @templatefunc('formatnode(node)')
156 @templatefunc('formatnode(node)')
158 def formatnode(context, mapping, args):
157 def formatnode(context, mapping, args):
159 """Obtain the preferred form of a changeset hash. (DEPRECATED)"""
158 """Obtain the preferred form of a changeset hash. (DEPRECATED)"""
160 if len(args) != 1:
159 if len(args) != 1:
161 # i18n: "formatnode" is a keyword
160 # i18n: "formatnode" is a keyword
162 raise error.ParseError(_("formatnode expects one argument"))
161 raise error.ParseError(_("formatnode expects one argument"))
163
162
164 ui = context.resource(mapping, 'ui')
163 ui = context.resource(mapping, 'ui')
165 node = evalstring(context, mapping, args[0])
164 node = evalstring(context, mapping, args[0])
166 if ui.debugflag:
165 if ui.debugflag:
167 return node
166 return node
168 return templatefilters.short(node)
167 return templatefilters.short(node)
169
168
170 @templatefunc('pad(text, width[, fillchar=\' \'[, left=False]])',
169 @templatefunc('pad(text, width[, fillchar=\' \'[, left=False]])',
171 argspec='text width fillchar left')
170 argspec='text width fillchar left')
172 def pad(context, mapping, args):
171 def pad(context, mapping, args):
173 """Pad text with a
172 """Pad text with a
174 fill character."""
173 fill character."""
175 if 'text' not in args or 'width' not in args:
174 if 'text' not in args or 'width' not in args:
176 # i18n: "pad" is a keyword
175 # i18n: "pad" is a keyword
177 raise error.ParseError(_("pad() expects two to four arguments"))
176 raise error.ParseError(_("pad() expects two to four arguments"))
178
177
179 width = evalinteger(context, mapping, args['width'],
178 width = evalinteger(context, mapping, args['width'],
180 # i18n: "pad" is a keyword
179 # i18n: "pad" is a keyword
181 _("pad() expects an integer width"))
180 _("pad() expects an integer width"))
182
181
183 text = evalstring(context, mapping, args['text'])
182 text = evalstring(context, mapping, args['text'])
184
183
185 left = False
184 left = False
186 fillchar = ' '
185 fillchar = ' '
187 if 'fillchar' in args:
186 if 'fillchar' in args:
188 fillchar = evalstring(context, mapping, args['fillchar'])
187 fillchar = evalstring(context, mapping, args['fillchar'])
189 if len(color.stripeffects(fillchar)) != 1:
188 if len(color.stripeffects(fillchar)) != 1:
190 # i18n: "pad" is a keyword
189 # i18n: "pad" is a keyword
191 raise error.ParseError(_("pad() expects a single fill character"))
190 raise error.ParseError(_("pad() expects a single fill character"))
192 if 'left' in args:
191 if 'left' in args:
193 left = evalboolean(context, mapping, args['left'])
192 left = evalboolean(context, mapping, args['left'])
194
193
195 fillwidth = width - encoding.colwidth(color.stripeffects(text))
194 fillwidth = width - encoding.colwidth(color.stripeffects(text))
196 if fillwidth <= 0:
195 if fillwidth <= 0:
197 return text
196 return text
198 if left:
197 if left:
199 return fillchar * fillwidth + text
198 return fillchar * fillwidth + text
200 else:
199 else:
201 return text + fillchar * fillwidth
200 return text + fillchar * fillwidth
202
201
203 @templatefunc('indent(text, indentchars[, firstline])')
202 @templatefunc('indent(text, indentchars[, firstline])')
204 def indent(context, mapping, args):
203 def indent(context, mapping, args):
205 """Indents all non-empty lines
204 """Indents all non-empty lines
206 with the characters given in the indentchars string. An optional
205 with the characters given in the indentchars string. An optional
207 third parameter will override the indent for the first line only
206 third parameter will override the indent for the first line only
208 if present."""
207 if present."""
209 if not (2 <= len(args) <= 3):
208 if not (2 <= len(args) <= 3):
210 # i18n: "indent" is a keyword
209 # i18n: "indent" is a keyword
211 raise error.ParseError(_("indent() expects two or three arguments"))
210 raise error.ParseError(_("indent() expects two or three arguments"))
212
211
213 text = evalstring(context, mapping, args[0])
212 text = evalstring(context, mapping, args[0])
214 indent = evalstring(context, mapping, args[1])
213 indent = evalstring(context, mapping, args[1])
215
214
216 if len(args) == 3:
215 if len(args) == 3:
217 firstline = evalstring(context, mapping, args[2])
216 firstline = evalstring(context, mapping, args[2])
218 else:
217 else:
219 firstline = indent
218 firstline = indent
220
219
221 # the indent function doesn't indent the first line, so we do it here
220 # the indent function doesn't indent the first line, so we do it here
222 return templatefilters.indent(firstline + text, indent)
221 return templatefilters.indent(firstline + text, indent)
223
222
224 @templatefunc('get(dict, key)')
223 @templatefunc('get(dict, key)')
225 def get(context, mapping, args):
224 def get(context, mapping, args):
226 """Get an attribute/key from an object. Some keywords
225 """Get an attribute/key from an object. Some keywords
227 are complex types. This function allows you to obtain the value of an
226 are complex types. This function allows you to obtain the value of an
228 attribute on these types."""
227 attribute on these types."""
229 if len(args) != 2:
228 if len(args) != 2:
230 # i18n: "get" is a keyword
229 # i18n: "get" is a keyword
231 raise error.ParseError(_("get() expects two arguments"))
230 raise error.ParseError(_("get() expects two arguments"))
232
231
233 dictarg = evalfuncarg(context, mapping, args[0])
232 dictarg = evalfuncarg(context, mapping, args[0])
234 if not util.safehasattr(dictarg, 'get'):
233 if not util.safehasattr(dictarg, 'get'):
235 # i18n: "get" is a keyword
234 # i18n: "get" is a keyword
236 raise error.ParseError(_("get() expects a dict as first argument"))
235 raise error.ParseError(_("get() expects a dict as first argument"))
237
236
238 key = evalfuncarg(context, mapping, args[1])
237 key = evalfuncarg(context, mapping, args[1])
239 return templateutil.getdictitem(dictarg, key)
238 return templateutil.getdictitem(dictarg, key)
240
239
241 @templatefunc('if(expr, then[, else])')
240 @templatefunc('if(expr, then[, else])')
242 def if_(context, mapping, args):
241 def if_(context, mapping, args):
243 """Conditionally execute based on the result of
242 """Conditionally execute based on the result of
244 an expression."""
243 an expression."""
245 if not (2 <= len(args) <= 3):
244 if not (2 <= len(args) <= 3):
246 # i18n: "if" is a keyword
245 # i18n: "if" is a keyword
247 raise error.ParseError(_("if expects two or three arguments"))
246 raise error.ParseError(_("if expects two or three arguments"))
248
247
249 test = evalboolean(context, mapping, args[0])
248 test = evalboolean(context, mapping, args[0])
250 if test:
249 if test:
251 return evalrawexp(context, mapping, args[1])
250 return evalrawexp(context, mapping, args[1])
252 elif len(args) == 3:
251 elif len(args) == 3:
253 return evalrawexp(context, mapping, args[2])
252 return evalrawexp(context, mapping, args[2])
254
253
255 @templatefunc('ifcontains(needle, haystack, then[, else])')
254 @templatefunc('ifcontains(needle, haystack, then[, else])')
256 def ifcontains(context, mapping, args):
255 def ifcontains(context, mapping, args):
257 """Conditionally execute based
256 """Conditionally execute based
258 on whether the item "needle" is in "haystack"."""
257 on whether the item "needle" is in "haystack"."""
259 if not (3 <= len(args) <= 4):
258 if not (3 <= len(args) <= 4):
260 # i18n: "ifcontains" is a keyword
259 # i18n: "ifcontains" is a keyword
261 raise error.ParseError(_("ifcontains expects three or four arguments"))
260 raise error.ParseError(_("ifcontains expects three or four arguments"))
262
261
263 haystack = evalfuncarg(context, mapping, args[1])
262 haystack = evalfuncarg(context, mapping, args[1])
263 keytype = getattr(haystack, 'keytype', None)
264 try:
264 try:
265 needle = evalastype(context, mapping, args[0],
265 needle = evalrawexp(context, mapping, args[0])
266 getattr(haystack, 'keytype', None) or bytes)
266 needle = templateutil.unwrapastype(needle, keytype or bytes)
267 found = (needle in haystack)
267 found = (needle in haystack)
268 except error.ParseError:
268 except error.ParseError:
269 found = False
269 found = False
270
270
271 if found:
271 if found:
272 return evalrawexp(context, mapping, args[2])
272 return evalrawexp(context, mapping, args[2])
273 elif len(args) == 4:
273 elif len(args) == 4:
274 return evalrawexp(context, mapping, args[3])
274 return evalrawexp(context, mapping, args[3])
275
275
276 @templatefunc('ifeq(expr1, expr2, then[, else])')
276 @templatefunc('ifeq(expr1, expr2, then[, else])')
277 def ifeq(context, mapping, args):
277 def ifeq(context, mapping, args):
278 """Conditionally execute based on
278 """Conditionally execute based on
279 whether 2 items are equivalent."""
279 whether 2 items are equivalent."""
280 if not (3 <= len(args) <= 4):
280 if not (3 <= len(args) <= 4):
281 # i18n: "ifeq" is a keyword
281 # i18n: "ifeq" is a keyword
282 raise error.ParseError(_("ifeq expects three or four arguments"))
282 raise error.ParseError(_("ifeq expects three or four arguments"))
283
283
284 test = evalstring(context, mapping, args[0])
284 test = evalstring(context, mapping, args[0])
285 match = evalstring(context, mapping, args[1])
285 match = evalstring(context, mapping, args[1])
286 if test == match:
286 if test == match:
287 return evalrawexp(context, mapping, args[2])
287 return evalrawexp(context, mapping, args[2])
288 elif len(args) == 4:
288 elif len(args) == 4:
289 return evalrawexp(context, mapping, args[3])
289 return evalrawexp(context, mapping, args[3])
290
290
291 @templatefunc('join(list, sep)')
291 @templatefunc('join(list, sep)')
292 def join(context, mapping, args):
292 def join(context, mapping, args):
293 """Join items in a list with a delimiter."""
293 """Join items in a list with a delimiter."""
294 if not (1 <= len(args) <= 2):
294 if not (1 <= len(args) <= 2):
295 # i18n: "join" is a keyword
295 # i18n: "join" is a keyword
296 raise error.ParseError(_("join expects one or two arguments"))
296 raise error.ParseError(_("join expects one or two arguments"))
297
297
298 # TODO: perhaps this should be evalfuncarg(), but it can't because hgweb
298 # TODO: perhaps this should be evalfuncarg(), but it can't because hgweb
299 # abuses generator as a keyword that returns a list of dicts.
299 # abuses generator as a keyword that returns a list of dicts.
300 joinset = evalrawexp(context, mapping, args[0])
300 joinset = evalrawexp(context, mapping, args[0])
301 joinset = templateutil.unwrapvalue(joinset)
301 joinset = templateutil.unwrapvalue(joinset)
302 joinfmt = getattr(joinset, 'joinfmt', pycompat.identity)
302 joinfmt = getattr(joinset, 'joinfmt', pycompat.identity)
303 joiner = " "
303 joiner = " "
304 if len(args) > 1:
304 if len(args) > 1:
305 joiner = evalstring(context, mapping, args[1])
305 joiner = evalstring(context, mapping, args[1])
306
306
307 first = True
307 first = True
308 for x in pycompat.maybebytestr(joinset):
308 for x in pycompat.maybebytestr(joinset):
309 if first:
309 if first:
310 first = False
310 first = False
311 else:
311 else:
312 yield joiner
312 yield joiner
313 yield joinfmt(x)
313 yield joinfmt(x)
314
314
315 @templatefunc('label(label, expr)')
315 @templatefunc('label(label, expr)')
316 def label(context, mapping, args):
316 def label(context, mapping, args):
317 """Apply a label to generated content. Content with
317 """Apply a label to generated content. Content with
318 a label applied can result in additional post-processing, such as
318 a label applied can result in additional post-processing, such as
319 automatic colorization."""
319 automatic colorization."""
320 if len(args) != 2:
320 if len(args) != 2:
321 # i18n: "label" is a keyword
321 # i18n: "label" is a keyword
322 raise error.ParseError(_("label expects two arguments"))
322 raise error.ParseError(_("label expects two arguments"))
323
323
324 ui = context.resource(mapping, 'ui')
324 ui = context.resource(mapping, 'ui')
325 thing = evalstring(context, mapping, args[1])
325 thing = evalstring(context, mapping, args[1])
326 # preserve unknown symbol as literal so effects like 'red', 'bold',
326 # preserve unknown symbol as literal so effects like 'red', 'bold',
327 # etc. don't need to be quoted
327 # etc. don't need to be quoted
328 label = evalstringliteral(context, mapping, args[0])
328 label = evalstringliteral(context, mapping, args[0])
329
329
330 return ui.label(thing, label)
330 return ui.label(thing, label)
331
331
332 @templatefunc('latesttag([pattern])')
332 @templatefunc('latesttag([pattern])')
333 def latesttag(context, mapping, args):
333 def latesttag(context, mapping, args):
334 """The global tags matching the given pattern on the
334 """The global tags matching the given pattern on the
335 most recent globally tagged ancestor of this changeset.
335 most recent globally tagged ancestor of this changeset.
336 If no such tags exist, the "{tag}" template resolves to
336 If no such tags exist, the "{tag}" template resolves to
337 the string "null"."""
337 the string "null"."""
338 if len(args) > 1:
338 if len(args) > 1:
339 # i18n: "latesttag" is a keyword
339 # i18n: "latesttag" is a keyword
340 raise error.ParseError(_("latesttag expects at most one argument"))
340 raise error.ParseError(_("latesttag expects at most one argument"))
341
341
342 pattern = None
342 pattern = None
343 if len(args) == 1:
343 if len(args) == 1:
344 pattern = evalstring(context, mapping, args[0])
344 pattern = evalstring(context, mapping, args[0])
345 return templatekw.showlatesttags(context, mapping, pattern)
345 return templatekw.showlatesttags(context, mapping, pattern)
346
346
347 @templatefunc('localdate(date[, tz])')
347 @templatefunc('localdate(date[, tz])')
348 def localdate(context, mapping, args):
348 def localdate(context, mapping, args):
349 """Converts a date to the specified timezone.
349 """Converts a date to the specified timezone.
350 The default is local date."""
350 The default is local date."""
351 if not (1 <= len(args) <= 2):
351 if not (1 <= len(args) <= 2):
352 # i18n: "localdate" is a keyword
352 # i18n: "localdate" is a keyword
353 raise error.ParseError(_("localdate expects one or two arguments"))
353 raise error.ParseError(_("localdate expects one or two arguments"))
354
354
355 date = evalfuncarg(context, mapping, args[0])
355 date = evalfuncarg(context, mapping, args[0])
356 try:
356 try:
357 date = dateutil.parsedate(date)
357 date = dateutil.parsedate(date)
358 except AttributeError: # not str nor date tuple
358 except AttributeError: # not str nor date tuple
359 # i18n: "localdate" is a keyword
359 # i18n: "localdate" is a keyword
360 raise error.ParseError(_("localdate expects a date information"))
360 raise error.ParseError(_("localdate expects a date information"))
361 if len(args) >= 2:
361 if len(args) >= 2:
362 tzoffset = None
362 tzoffset = None
363 tz = evalfuncarg(context, mapping, args[1])
363 tz = evalfuncarg(context, mapping, args[1])
364 if isinstance(tz, bytes):
364 if isinstance(tz, bytes):
365 tzoffset, remainder = dateutil.parsetimezone(tz)
365 tzoffset, remainder = dateutil.parsetimezone(tz)
366 if remainder:
366 if remainder:
367 tzoffset = None
367 tzoffset = None
368 if tzoffset is None:
368 if tzoffset is None:
369 try:
369 try:
370 tzoffset = int(tz)
370 tzoffset = int(tz)
371 except (TypeError, ValueError):
371 except (TypeError, ValueError):
372 # i18n: "localdate" is a keyword
372 # i18n: "localdate" is a keyword
373 raise error.ParseError(_("localdate expects a timezone"))
373 raise error.ParseError(_("localdate expects a timezone"))
374 else:
374 else:
375 tzoffset = dateutil.makedate()[1]
375 tzoffset = dateutil.makedate()[1]
376 return (date[0], tzoffset)
376 return (date[0], tzoffset)
377
377
378 @templatefunc('max(iterable)')
378 @templatefunc('max(iterable)')
379 def max_(context, mapping, args, **kwargs):
379 def max_(context, mapping, args, **kwargs):
380 """Return the max of an iterable"""
380 """Return the max of an iterable"""
381 if len(args) != 1:
381 if len(args) != 1:
382 # i18n: "max" is a keyword
382 # i18n: "max" is a keyword
383 raise error.ParseError(_("max expects one argument"))
383 raise error.ParseError(_("max expects one argument"))
384
384
385 iterable = evalfuncarg(context, mapping, args[0])
385 iterable = evalfuncarg(context, mapping, args[0])
386 try:
386 try:
387 x = max(pycompat.maybebytestr(iterable))
387 x = max(pycompat.maybebytestr(iterable))
388 except (TypeError, ValueError):
388 except (TypeError, ValueError):
389 # i18n: "max" is a keyword
389 # i18n: "max" is a keyword
390 raise error.ParseError(_("max first argument should be an iterable"))
390 raise error.ParseError(_("max first argument should be an iterable"))
391 return templateutil.wraphybridvalue(iterable, x, x)
391 return templateutil.wraphybridvalue(iterable, x, x)
392
392
393 @templatefunc('min(iterable)')
393 @templatefunc('min(iterable)')
394 def min_(context, mapping, args, **kwargs):
394 def min_(context, mapping, args, **kwargs):
395 """Return the min of an iterable"""
395 """Return the min of an iterable"""
396 if len(args) != 1:
396 if len(args) != 1:
397 # i18n: "min" is a keyword
397 # i18n: "min" is a keyword
398 raise error.ParseError(_("min expects one argument"))
398 raise error.ParseError(_("min expects one argument"))
399
399
400 iterable = evalfuncarg(context, mapping, args[0])
400 iterable = evalfuncarg(context, mapping, args[0])
401 try:
401 try:
402 x = min(pycompat.maybebytestr(iterable))
402 x = min(pycompat.maybebytestr(iterable))
403 except (TypeError, ValueError):
403 except (TypeError, ValueError):
404 # i18n: "min" is a keyword
404 # i18n: "min" is a keyword
405 raise error.ParseError(_("min first argument should be an iterable"))
405 raise error.ParseError(_("min first argument should be an iterable"))
406 return templateutil.wraphybridvalue(iterable, x, x)
406 return templateutil.wraphybridvalue(iterable, x, x)
407
407
408 @templatefunc('mod(a, b)')
408 @templatefunc('mod(a, b)')
409 def mod(context, mapping, args):
409 def mod(context, mapping, args):
410 """Calculate a mod b such that a / b + a mod b == a"""
410 """Calculate a mod b such that a / b + a mod b == a"""
411 if not len(args) == 2:
411 if not len(args) == 2:
412 # i18n: "mod" is a keyword
412 # i18n: "mod" is a keyword
413 raise error.ParseError(_("mod expects two arguments"))
413 raise error.ParseError(_("mod expects two arguments"))
414
414
415 func = lambda a, b: a % b
415 func = lambda a, b: a % b
416 return templateutil.runarithmetic(context, mapping,
416 return templateutil.runarithmetic(context, mapping,
417 (func, args[0], args[1]))
417 (func, args[0], args[1]))
418
418
419 @templatefunc('obsfateoperations(markers)')
419 @templatefunc('obsfateoperations(markers)')
420 def obsfateoperations(context, mapping, args):
420 def obsfateoperations(context, mapping, args):
421 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
421 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
422 if len(args) != 1:
422 if len(args) != 1:
423 # i18n: "obsfateoperations" is a keyword
423 # i18n: "obsfateoperations" is a keyword
424 raise error.ParseError(_("obsfateoperations expects one argument"))
424 raise error.ParseError(_("obsfateoperations expects one argument"))
425
425
426 markers = evalfuncarg(context, mapping, args[0])
426 markers = evalfuncarg(context, mapping, args[0])
427
427
428 try:
428 try:
429 data = obsutil.markersoperations(markers)
429 data = obsutil.markersoperations(markers)
430 return templateutil.hybridlist(data, name='operation')
430 return templateutil.hybridlist(data, name='operation')
431 except (TypeError, KeyError):
431 except (TypeError, KeyError):
432 # i18n: "obsfateoperations" is a keyword
432 # i18n: "obsfateoperations" is a keyword
433 errmsg = _("obsfateoperations first argument should be an iterable")
433 errmsg = _("obsfateoperations first argument should be an iterable")
434 raise error.ParseError(errmsg)
434 raise error.ParseError(errmsg)
435
435
436 @templatefunc('obsfatedate(markers)')
436 @templatefunc('obsfatedate(markers)')
437 def obsfatedate(context, mapping, args):
437 def obsfatedate(context, mapping, args):
438 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
438 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
439 if len(args) != 1:
439 if len(args) != 1:
440 # i18n: "obsfatedate" is a keyword
440 # i18n: "obsfatedate" is a keyword
441 raise error.ParseError(_("obsfatedate expects one argument"))
441 raise error.ParseError(_("obsfatedate expects one argument"))
442
442
443 markers = evalfuncarg(context, mapping, args[0])
443 markers = evalfuncarg(context, mapping, args[0])
444
444
445 try:
445 try:
446 data = obsutil.markersdates(markers)
446 data = obsutil.markersdates(markers)
447 return templateutil.hybridlist(data, name='date', fmt='%d %d')
447 return templateutil.hybridlist(data, name='date', fmt='%d %d')
448 except (TypeError, KeyError):
448 except (TypeError, KeyError):
449 # i18n: "obsfatedate" is a keyword
449 # i18n: "obsfatedate" is a keyword
450 errmsg = _("obsfatedate first argument should be an iterable")
450 errmsg = _("obsfatedate first argument should be an iterable")
451 raise error.ParseError(errmsg)
451 raise error.ParseError(errmsg)
452
452
453 @templatefunc('obsfateusers(markers)')
453 @templatefunc('obsfateusers(markers)')
454 def obsfateusers(context, mapping, args):
454 def obsfateusers(context, mapping, args):
455 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
455 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
456 if len(args) != 1:
456 if len(args) != 1:
457 # i18n: "obsfateusers" is a keyword
457 # i18n: "obsfateusers" is a keyword
458 raise error.ParseError(_("obsfateusers expects one argument"))
458 raise error.ParseError(_("obsfateusers expects one argument"))
459
459
460 markers = evalfuncarg(context, mapping, args[0])
460 markers = evalfuncarg(context, mapping, args[0])
461
461
462 try:
462 try:
463 data = obsutil.markersusers(markers)
463 data = obsutil.markersusers(markers)
464 return templateutil.hybridlist(data, name='user')
464 return templateutil.hybridlist(data, name='user')
465 except (TypeError, KeyError, ValueError):
465 except (TypeError, KeyError, ValueError):
466 # i18n: "obsfateusers" is a keyword
466 # i18n: "obsfateusers" is a keyword
467 msg = _("obsfateusers first argument should be an iterable of "
467 msg = _("obsfateusers first argument should be an iterable of "
468 "obsmakers")
468 "obsmakers")
469 raise error.ParseError(msg)
469 raise error.ParseError(msg)
470
470
471 @templatefunc('obsfateverb(successors, markers)')
471 @templatefunc('obsfateverb(successors, markers)')
472 def obsfateverb(context, mapping, args):
472 def obsfateverb(context, mapping, args):
473 """Compute obsfate related information based on successors (EXPERIMENTAL)"""
473 """Compute obsfate related information based on successors (EXPERIMENTAL)"""
474 if len(args) != 2:
474 if len(args) != 2:
475 # i18n: "obsfateverb" is a keyword
475 # i18n: "obsfateverb" is a keyword
476 raise error.ParseError(_("obsfateverb expects two arguments"))
476 raise error.ParseError(_("obsfateverb expects two arguments"))
477
477
478 successors = evalfuncarg(context, mapping, args[0])
478 successors = evalfuncarg(context, mapping, args[0])
479 markers = evalfuncarg(context, mapping, args[1])
479 markers = evalfuncarg(context, mapping, args[1])
480
480
481 try:
481 try:
482 return obsutil.obsfateverb(successors, markers)
482 return obsutil.obsfateverb(successors, markers)
483 except TypeError:
483 except TypeError:
484 # i18n: "obsfateverb" is a keyword
484 # i18n: "obsfateverb" is a keyword
485 errmsg = _("obsfateverb first argument should be countable")
485 errmsg = _("obsfateverb first argument should be countable")
486 raise error.ParseError(errmsg)
486 raise error.ParseError(errmsg)
487
487
488 @templatefunc('relpath(path)')
488 @templatefunc('relpath(path)')
489 def relpath(context, mapping, args):
489 def relpath(context, mapping, args):
490 """Convert a repository-absolute path into a filesystem path relative to
490 """Convert a repository-absolute path into a filesystem path relative to
491 the current working directory."""
491 the current working directory."""
492 if len(args) != 1:
492 if len(args) != 1:
493 # i18n: "relpath" is a keyword
493 # i18n: "relpath" is a keyword
494 raise error.ParseError(_("relpath expects one argument"))
494 raise error.ParseError(_("relpath expects one argument"))
495
495
496 repo = context.resource(mapping, 'ctx').repo()
496 repo = context.resource(mapping, 'ctx').repo()
497 path = evalstring(context, mapping, args[0])
497 path = evalstring(context, mapping, args[0])
498 return repo.pathto(path)
498 return repo.pathto(path)
499
499
500 @templatefunc('revset(query[, formatargs...])')
500 @templatefunc('revset(query[, formatargs...])')
501 def revset(context, mapping, args):
501 def revset(context, mapping, args):
502 """Execute a revision set query. See
502 """Execute a revision set query. See
503 :hg:`help revset`."""
503 :hg:`help revset`."""
504 if not len(args) > 0:
504 if not len(args) > 0:
505 # i18n: "revset" is a keyword
505 # i18n: "revset" is a keyword
506 raise error.ParseError(_("revset expects one or more arguments"))
506 raise error.ParseError(_("revset expects one or more arguments"))
507
507
508 raw = evalstring(context, mapping, args[0])
508 raw = evalstring(context, mapping, args[0])
509 ctx = context.resource(mapping, 'ctx')
509 ctx = context.resource(mapping, 'ctx')
510 repo = ctx.repo()
510 repo = ctx.repo()
511
511
512 def query(expr):
512 def query(expr):
513 m = revsetmod.match(repo.ui, expr, repo=repo)
513 m = revsetmod.match(repo.ui, expr, repo=repo)
514 return m(repo)
514 return m(repo)
515
515
516 if len(args) > 1:
516 if len(args) > 1:
517 formatargs = [evalfuncarg(context, mapping, a) for a in args[1:]]
517 formatargs = [evalfuncarg(context, mapping, a) for a in args[1:]]
518 revs = query(revsetlang.formatspec(raw, *formatargs))
518 revs = query(revsetlang.formatspec(raw, *formatargs))
519 revs = list(revs)
519 revs = list(revs)
520 else:
520 else:
521 cache = context.resource(mapping, 'cache')
521 cache = context.resource(mapping, 'cache')
522 revsetcache = cache.setdefault("revsetcache", {})
522 revsetcache = cache.setdefault("revsetcache", {})
523 if raw in revsetcache:
523 if raw in revsetcache:
524 revs = revsetcache[raw]
524 revs = revsetcache[raw]
525 else:
525 else:
526 revs = query(raw)
526 revs = query(raw)
527 revs = list(revs)
527 revs = list(revs)
528 revsetcache[raw] = revs
528 revsetcache[raw] = revs
529 return templatekw.showrevslist(context, mapping, "revision", revs)
529 return templatekw.showrevslist(context, mapping, "revision", revs)
530
530
531 @templatefunc('rstdoc(text, style)')
531 @templatefunc('rstdoc(text, style)')
532 def rstdoc(context, mapping, args):
532 def rstdoc(context, mapping, args):
533 """Format reStructuredText."""
533 """Format reStructuredText."""
534 if len(args) != 2:
534 if len(args) != 2:
535 # i18n: "rstdoc" is a keyword
535 # i18n: "rstdoc" is a keyword
536 raise error.ParseError(_("rstdoc expects two arguments"))
536 raise error.ParseError(_("rstdoc expects two arguments"))
537
537
538 text = evalstring(context, mapping, args[0])
538 text = evalstring(context, mapping, args[0])
539 style = evalstring(context, mapping, args[1])
539 style = evalstring(context, mapping, args[1])
540
540
541 return minirst.format(text, style=style, keep=['verbose'])
541 return minirst.format(text, style=style, keep=['verbose'])
542
542
543 @templatefunc('separate(sep, args)', argspec='sep *args')
543 @templatefunc('separate(sep, args)', argspec='sep *args')
544 def separate(context, mapping, args):
544 def separate(context, mapping, args):
545 """Add a separator between non-empty arguments."""
545 """Add a separator between non-empty arguments."""
546 if 'sep' not in args:
546 if 'sep' not in args:
547 # i18n: "separate" is a keyword
547 # i18n: "separate" is a keyword
548 raise error.ParseError(_("separate expects at least one argument"))
548 raise error.ParseError(_("separate expects at least one argument"))
549
549
550 sep = evalstring(context, mapping, args['sep'])
550 sep = evalstring(context, mapping, args['sep'])
551 first = True
551 first = True
552 for arg in args['args']:
552 for arg in args['args']:
553 argstr = evalstring(context, mapping, arg)
553 argstr = evalstring(context, mapping, arg)
554 if not argstr:
554 if not argstr:
555 continue
555 continue
556 if first:
556 if first:
557 first = False
557 first = False
558 else:
558 else:
559 yield sep
559 yield sep
560 yield argstr
560 yield argstr
561
561
562 @templatefunc('shortest(node, minlength=4)')
562 @templatefunc('shortest(node, minlength=4)')
563 def shortest(context, mapping, args):
563 def shortest(context, mapping, args):
564 """Obtain the shortest representation of
564 """Obtain the shortest representation of
565 a node."""
565 a node."""
566 if not (1 <= len(args) <= 2):
566 if not (1 <= len(args) <= 2):
567 # i18n: "shortest" is a keyword
567 # i18n: "shortest" is a keyword
568 raise error.ParseError(_("shortest() expects one or two arguments"))
568 raise error.ParseError(_("shortest() expects one or two arguments"))
569
569
570 node = evalstring(context, mapping, args[0])
570 node = evalstring(context, mapping, args[0])
571
571
572 minlength = 4
572 minlength = 4
573 if len(args) > 1:
573 if len(args) > 1:
574 minlength = evalinteger(context, mapping, args[1],
574 minlength = evalinteger(context, mapping, args[1],
575 # i18n: "shortest" is a keyword
575 # i18n: "shortest" is a keyword
576 _("shortest() expects an integer minlength"))
576 _("shortest() expects an integer minlength"))
577
577
578 # _partialmatch() of filtered changelog could take O(len(repo)) time,
578 # _partialmatch() of filtered changelog could take O(len(repo)) time,
579 # which would be unacceptably slow. so we look for hash collision in
579 # which would be unacceptably slow. so we look for hash collision in
580 # unfiltered space, which means some hashes may be slightly longer.
580 # unfiltered space, which means some hashes may be slightly longer.
581 cl = context.resource(mapping, 'ctx')._repo.unfiltered().changelog
581 cl = context.resource(mapping, 'ctx')._repo.unfiltered().changelog
582 return cl.shortest(node, minlength)
582 return cl.shortest(node, minlength)
583
583
584 @templatefunc('strip(text[, chars])')
584 @templatefunc('strip(text[, chars])')
585 def strip(context, mapping, args):
585 def strip(context, mapping, args):
586 """Strip characters from a string. By default,
586 """Strip characters from a string. By default,
587 strips all leading and trailing whitespace."""
587 strips all leading and trailing whitespace."""
588 if not (1 <= len(args) <= 2):
588 if not (1 <= len(args) <= 2):
589 # i18n: "strip" is a keyword
589 # i18n: "strip" is a keyword
590 raise error.ParseError(_("strip expects one or two arguments"))
590 raise error.ParseError(_("strip expects one or two arguments"))
591
591
592 text = evalstring(context, mapping, args[0])
592 text = evalstring(context, mapping, args[0])
593 if len(args) == 2:
593 if len(args) == 2:
594 chars = evalstring(context, mapping, args[1])
594 chars = evalstring(context, mapping, args[1])
595 return text.strip(chars)
595 return text.strip(chars)
596 return text.strip()
596 return text.strip()
597
597
598 @templatefunc('sub(pattern, replacement, expression)')
598 @templatefunc('sub(pattern, replacement, expression)')
599 def sub(context, mapping, args):
599 def sub(context, mapping, args):
600 """Perform text substitution
600 """Perform text substitution
601 using regular expressions."""
601 using regular expressions."""
602 if len(args) != 3:
602 if len(args) != 3:
603 # i18n: "sub" is a keyword
603 # i18n: "sub" is a keyword
604 raise error.ParseError(_("sub expects three arguments"))
604 raise error.ParseError(_("sub expects three arguments"))
605
605
606 pat = evalstring(context, mapping, args[0])
606 pat = evalstring(context, mapping, args[0])
607 rpl = evalstring(context, mapping, args[1])
607 rpl = evalstring(context, mapping, args[1])
608 src = evalstring(context, mapping, args[2])
608 src = evalstring(context, mapping, args[2])
609 try:
609 try:
610 patre = re.compile(pat)
610 patre = re.compile(pat)
611 except re.error:
611 except re.error:
612 # i18n: "sub" is a keyword
612 # i18n: "sub" is a keyword
613 raise error.ParseError(_("sub got an invalid pattern: %s") % pat)
613 raise error.ParseError(_("sub got an invalid pattern: %s") % pat)
614 try:
614 try:
615 yield patre.sub(rpl, src)
615 yield patre.sub(rpl, src)
616 except re.error:
616 except re.error:
617 # i18n: "sub" is a keyword
617 # i18n: "sub" is a keyword
618 raise error.ParseError(_("sub got an invalid replacement: %s") % rpl)
618 raise error.ParseError(_("sub got an invalid replacement: %s") % rpl)
619
619
620 @templatefunc('startswith(pattern, text)')
620 @templatefunc('startswith(pattern, text)')
621 def startswith(context, mapping, args):
621 def startswith(context, mapping, args):
622 """Returns the value from the "text" argument
622 """Returns the value from the "text" argument
623 if it begins with the content from the "pattern" argument."""
623 if it begins with the content from the "pattern" argument."""
624 if len(args) != 2:
624 if len(args) != 2:
625 # i18n: "startswith" is a keyword
625 # i18n: "startswith" is a keyword
626 raise error.ParseError(_("startswith expects two arguments"))
626 raise error.ParseError(_("startswith expects two arguments"))
627
627
628 patn = evalstring(context, mapping, args[0])
628 patn = evalstring(context, mapping, args[0])
629 text = evalstring(context, mapping, args[1])
629 text = evalstring(context, mapping, args[1])
630 if text.startswith(patn):
630 if text.startswith(patn):
631 return text
631 return text
632 return ''
632 return ''
633
633
634 @templatefunc('word(number, text[, separator])')
634 @templatefunc('word(number, text[, separator])')
635 def word(context, mapping, args):
635 def word(context, mapping, args):
636 """Return the nth word from a string."""
636 """Return the nth word from a string."""
637 if not (2 <= len(args) <= 3):
637 if not (2 <= len(args) <= 3):
638 # i18n: "word" is a keyword
638 # i18n: "word" is a keyword
639 raise error.ParseError(_("word expects two or three arguments, got %d")
639 raise error.ParseError(_("word expects two or three arguments, got %d")
640 % len(args))
640 % len(args))
641
641
642 num = evalinteger(context, mapping, args[0],
642 num = evalinteger(context, mapping, args[0],
643 # i18n: "word" is a keyword
643 # i18n: "word" is a keyword
644 _("word expects an integer index"))
644 _("word expects an integer index"))
645 text = evalstring(context, mapping, args[1])
645 text = evalstring(context, mapping, args[1])
646 if len(args) == 3:
646 if len(args) == 3:
647 splitter = evalstring(context, mapping, args[2])
647 splitter = evalstring(context, mapping, args[2])
648 else:
648 else:
649 splitter = None
649 splitter = None
650
650
651 tokens = text.split(splitter)
651 tokens = text.split(splitter)
652 if num >= len(tokens) or num < -len(tokens):
652 if num >= len(tokens) or num < -len(tokens):
653 return ''
653 return ''
654 else:
654 else:
655 return tokens[num]
655 return tokens[num]
656
656
657 def loadfunction(ui, extname, registrarobj):
657 def loadfunction(ui, extname, registrarobj):
658 """Load template function from specified registrarobj
658 """Load template function from specified registrarobj
659 """
659 """
660 for name, func in registrarobj._table.iteritems():
660 for name, func in registrarobj._table.iteritems():
661 funcs[name] = func
661 funcs[name] = func
662
662
663 # tell hggettext to extract docstrings from these functions:
663 # tell hggettext to extract docstrings from these functions:
664 i18nfunctions = funcs.values()
664 i18nfunctions = funcs.values()
@@ -1,476 +1,477 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 types
10 import types
11
11
12 from .i18n import _
12 from .i18n import _
13 from . import (
13 from . import (
14 error,
14 error,
15 pycompat,
15 pycompat,
16 util,
16 util,
17 )
17 )
18 from .utils import (
18 from .utils import (
19 stringutil,
19 stringutil,
20 )
20 )
21
21
22 class ResourceUnavailable(error.Abort):
22 class ResourceUnavailable(error.Abort):
23 pass
23 pass
24
24
25 class TemplateNotFound(error.Abort):
25 class TemplateNotFound(error.Abort):
26 pass
26 pass
27
27
28 class hybrid(object):
28 class hybrid(object):
29 """Wrapper for list or dict to support legacy template
29 """Wrapper for list or dict to support legacy template
30
30
31 This class allows us to handle both:
31 This class allows us to handle both:
32 - "{files}" (legacy command-line-specific list hack) and
32 - "{files}" (legacy command-line-specific list hack) and
33 - "{files % '{file}\n'}" (hgweb-style with inlining and function support)
33 - "{files % '{file}\n'}" (hgweb-style with inlining and function support)
34 and to access raw values:
34 and to access raw values:
35 - "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
35 - "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
36 - "{get(extras, key)}"
36 - "{get(extras, key)}"
37 - "{files|json}"
37 - "{files|json}"
38 """
38 """
39
39
40 def __init__(self, gen, values, makemap, joinfmt, keytype=None):
40 def __init__(self, gen, values, makemap, joinfmt, keytype=None):
41 if gen is not None:
41 if gen is not None:
42 self.gen = gen # generator or function returning generator
42 self.gen = gen # generator or function returning generator
43 self._values = values
43 self._values = values
44 self._makemap = makemap
44 self._makemap = makemap
45 self.joinfmt = joinfmt
45 self.joinfmt = joinfmt
46 self.keytype = keytype # hint for 'x in y' where type(x) is unresolved
46 self.keytype = keytype # hint for 'x in y' where type(x) is unresolved
47 def gen(self):
47 def gen(self):
48 """Default generator to stringify this as {join(self, ' ')}"""
48 """Default generator to stringify this as {join(self, ' ')}"""
49 for i, x in enumerate(self._values):
49 for i, x in enumerate(self._values):
50 if i > 0:
50 if i > 0:
51 yield ' '
51 yield ' '
52 yield self.joinfmt(x)
52 yield self.joinfmt(x)
53 def itermaps(self):
53 def itermaps(self):
54 makemap = self._makemap
54 makemap = self._makemap
55 for x in self._values:
55 for x in self._values:
56 yield makemap(x)
56 yield makemap(x)
57 def __contains__(self, x):
57 def __contains__(self, x):
58 return x in self._values
58 return x in self._values
59 def __getitem__(self, key):
59 def __getitem__(self, key):
60 return self._values[key]
60 return self._values[key]
61 def __len__(self):
61 def __len__(self):
62 return len(self._values)
62 return len(self._values)
63 def __iter__(self):
63 def __iter__(self):
64 return iter(self._values)
64 return iter(self._values)
65 def __getattr__(self, name):
65 def __getattr__(self, name):
66 if name not in (r'get', r'items', r'iteritems', r'iterkeys',
66 if name not in (r'get', r'items', r'iteritems', r'iterkeys',
67 r'itervalues', r'keys', r'values'):
67 r'itervalues', r'keys', r'values'):
68 raise AttributeError(name)
68 raise AttributeError(name)
69 return getattr(self._values, name)
69 return getattr(self._values, name)
70
70
71 class mappable(object):
71 class mappable(object):
72 """Wrapper for non-list/dict object to support map operation
72 """Wrapper for non-list/dict object to support map operation
73
73
74 This class allows us to handle both:
74 This class allows us to handle both:
75 - "{manifest}"
75 - "{manifest}"
76 - "{manifest % '{rev}:{node}'}"
76 - "{manifest % '{rev}:{node}'}"
77 - "{manifest.rev}"
77 - "{manifest.rev}"
78
78
79 Unlike a hybrid, this does not simulate the behavior of the underling
79 Unlike a hybrid, this does not simulate the behavior of the underling
80 value. Use unwrapvalue() or unwraphybrid() to obtain the inner object.
80 value. Use unwrapvalue(), unwrapastype(), or unwraphybrid() to obtain
81 the inner object.
81 """
82 """
82
83
83 def __init__(self, gen, key, value, makemap):
84 def __init__(self, gen, key, value, makemap):
84 if gen is not None:
85 if gen is not None:
85 self.gen = gen # generator or function returning generator
86 self.gen = gen # generator or function returning generator
86 self._key = key
87 self._key = key
87 self._value = value # may be generator of strings
88 self._value = value # may be generator of strings
88 self._makemap = makemap
89 self._makemap = makemap
89
90
90 def gen(self):
91 def gen(self):
91 yield pycompat.bytestr(self._value)
92 yield pycompat.bytestr(self._value)
92
93
93 def tomap(self):
94 def tomap(self):
94 return self._makemap(self._key)
95 return self._makemap(self._key)
95
96
96 def itermaps(self):
97 def itermaps(self):
97 yield self.tomap()
98 yield self.tomap()
98
99
99 def hybriddict(data, key='key', value='value', fmt=None, gen=None):
100 def hybriddict(data, key='key', value='value', fmt=None, gen=None):
100 """Wrap data to support both dict-like and string-like operations"""
101 """Wrap data to support both dict-like and string-like operations"""
101 prefmt = pycompat.identity
102 prefmt = pycompat.identity
102 if fmt is None:
103 if fmt is None:
103 fmt = '%s=%s'
104 fmt = '%s=%s'
104 prefmt = pycompat.bytestr
105 prefmt = pycompat.bytestr
105 return hybrid(gen, data, lambda k: {key: k, value: data[k]},
106 return hybrid(gen, data, lambda k: {key: k, value: data[k]},
106 lambda k: fmt % (prefmt(k), prefmt(data[k])))
107 lambda k: fmt % (prefmt(k), prefmt(data[k])))
107
108
108 def hybridlist(data, name, fmt=None, gen=None):
109 def hybridlist(data, name, fmt=None, gen=None):
109 """Wrap data to support both list-like and string-like operations"""
110 """Wrap data to support both list-like and string-like operations"""
110 prefmt = pycompat.identity
111 prefmt = pycompat.identity
111 if fmt is None:
112 if fmt is None:
112 fmt = '%s'
113 fmt = '%s'
113 prefmt = pycompat.bytestr
114 prefmt = pycompat.bytestr
114 return hybrid(gen, data, lambda x: {name: x}, lambda x: fmt % prefmt(x))
115 return hybrid(gen, data, lambda x: {name: x}, lambda x: fmt % prefmt(x))
115
116
116 def unwraphybrid(thing):
117 def unwraphybrid(thing):
117 """Return an object which can be stringified possibly by using a legacy
118 """Return an object which can be stringified possibly by using a legacy
118 template"""
119 template"""
119 gen = getattr(thing, 'gen', None)
120 gen = getattr(thing, 'gen', None)
120 if gen is None:
121 if gen is None:
121 return thing
122 return thing
122 if callable(gen):
123 if callable(gen):
123 return gen()
124 return gen()
124 return gen
125 return gen
125
126
126 def unwrapvalue(thing):
127 def unwrapvalue(thing):
127 """Move the inner value object out of the wrapper"""
128 """Move the inner value object out of the wrapper"""
128 if not util.safehasattr(thing, '_value'):
129 if not util.safehasattr(thing, '_value'):
129 return thing
130 return thing
130 return thing._value
131 return thing._value
131
132
132 def wraphybridvalue(container, key, value):
133 def wraphybridvalue(container, key, value):
133 """Wrap an element of hybrid container to be mappable
134 """Wrap an element of hybrid container to be mappable
134
135
135 The key is passed to the makemap function of the given container, which
136 The key is passed to the makemap function of the given container, which
136 should be an item generated by iter(container).
137 should be an item generated by iter(container).
137 """
138 """
138 makemap = getattr(container, '_makemap', None)
139 makemap = getattr(container, '_makemap', None)
139 if makemap is None:
140 if makemap is None:
140 return value
141 return value
141 if util.safehasattr(value, '_makemap'):
142 if util.safehasattr(value, '_makemap'):
142 # a nested hybrid list/dict, which has its own way of map operation
143 # a nested hybrid list/dict, which has its own way of map operation
143 return value
144 return value
144 return mappable(None, key, value, makemap)
145 return mappable(None, key, value, makemap)
145
146
146 def compatdict(context, mapping, name, data, key='key', value='value',
147 def compatdict(context, mapping, name, data, key='key', value='value',
147 fmt=None, plural=None, separator=' '):
148 fmt=None, plural=None, separator=' '):
148 """Wrap data like hybriddict(), but also supports old-style list template
149 """Wrap data like hybriddict(), but also supports old-style list template
149
150
150 This exists for backward compatibility with the old-style template. Use
151 This exists for backward compatibility with the old-style template. Use
151 hybriddict() for new template keywords.
152 hybriddict() for new template keywords.
152 """
153 """
153 c = [{key: k, value: v} for k, v in data.iteritems()]
154 c = [{key: k, value: v} for k, v in data.iteritems()]
154 f = _showcompatlist(context, mapping, name, c, plural, separator)
155 f = _showcompatlist(context, mapping, name, c, plural, separator)
155 return hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
156 return hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
156
157
157 def compatlist(context, mapping, name, data, element=None, fmt=None,
158 def compatlist(context, mapping, name, data, element=None, fmt=None,
158 plural=None, separator=' '):
159 plural=None, separator=' '):
159 """Wrap data like hybridlist(), but also supports old-style list template
160 """Wrap data like hybridlist(), but also supports old-style list template
160
161
161 This exists for backward compatibility with the old-style template. Use
162 This exists for backward compatibility with the old-style template. Use
162 hybridlist() for new template keywords.
163 hybridlist() for new template keywords.
163 """
164 """
164 f = _showcompatlist(context, mapping, name, data, plural, separator)
165 f = _showcompatlist(context, mapping, name, data, plural, separator)
165 return hybridlist(data, name=element or name, fmt=fmt, gen=f)
166 return hybridlist(data, name=element or name, fmt=fmt, gen=f)
166
167
167 def _showcompatlist(context, mapping, name, values, plural=None, separator=' '):
168 def _showcompatlist(context, mapping, name, values, plural=None, separator=' '):
168 """Return a generator that renders old-style list template
169 """Return a generator that renders old-style list template
169
170
170 name is name of key in template map.
171 name is name of key in template map.
171 values is list of strings or dicts.
172 values is list of strings or dicts.
172 plural is plural of name, if not simply name + 's'.
173 plural is plural of name, if not simply name + 's'.
173 separator is used to join values as a string
174 separator is used to join values as a string
174
175
175 expansion works like this, given name 'foo'.
176 expansion works like this, given name 'foo'.
176
177
177 if values is empty, expand 'no_foos'.
178 if values is empty, expand 'no_foos'.
178
179
179 if 'foo' not in template map, return values as a string,
180 if 'foo' not in template map, return values as a string,
180 joined by 'separator'.
181 joined by 'separator'.
181
182
182 expand 'start_foos'.
183 expand 'start_foos'.
183
184
184 for each value, expand 'foo'. if 'last_foo' in template
185 for each value, expand 'foo'. if 'last_foo' in template
185 map, expand it instead of 'foo' for last key.
186 map, expand it instead of 'foo' for last key.
186
187
187 expand 'end_foos'.
188 expand 'end_foos'.
188 """
189 """
189 if not plural:
190 if not plural:
190 plural = name + 's'
191 plural = name + 's'
191 if not values:
192 if not values:
192 noname = 'no_' + plural
193 noname = 'no_' + plural
193 if context.preload(noname):
194 if context.preload(noname):
194 yield context.process(noname, mapping)
195 yield context.process(noname, mapping)
195 return
196 return
196 if not context.preload(name):
197 if not context.preload(name):
197 if isinstance(values[0], bytes):
198 if isinstance(values[0], bytes):
198 yield separator.join(values)
199 yield separator.join(values)
199 else:
200 else:
200 for v in values:
201 for v in values:
201 r = dict(v)
202 r = dict(v)
202 r.update(mapping)
203 r.update(mapping)
203 yield r
204 yield r
204 return
205 return
205 startname = 'start_' + plural
206 startname = 'start_' + plural
206 if context.preload(startname):
207 if context.preload(startname):
207 yield context.process(startname, mapping)
208 yield context.process(startname, mapping)
208 def one(v, tag=name):
209 def one(v, tag=name):
209 vmapping = {}
210 vmapping = {}
210 try:
211 try:
211 vmapping.update(v)
212 vmapping.update(v)
212 # Python 2 raises ValueError if the type of v is wrong. Python
213 # Python 2 raises ValueError if the type of v is wrong. Python
213 # 3 raises TypeError.
214 # 3 raises TypeError.
214 except (AttributeError, TypeError, ValueError):
215 except (AttributeError, TypeError, ValueError):
215 try:
216 try:
216 # Python 2 raises ValueError trying to destructure an e.g.
217 # Python 2 raises ValueError trying to destructure an e.g.
217 # bytes. Python 3 raises TypeError.
218 # bytes. Python 3 raises TypeError.
218 for a, b in v:
219 for a, b in v:
219 vmapping[a] = b
220 vmapping[a] = b
220 except (TypeError, ValueError):
221 except (TypeError, ValueError):
221 vmapping[name] = v
222 vmapping[name] = v
222 vmapping = context.overlaymap(mapping, vmapping)
223 vmapping = context.overlaymap(mapping, vmapping)
223 return context.process(tag, vmapping)
224 return context.process(tag, vmapping)
224 lastname = 'last_' + name
225 lastname = 'last_' + name
225 if context.preload(lastname):
226 if context.preload(lastname):
226 last = values.pop()
227 last = values.pop()
227 else:
228 else:
228 last = None
229 last = None
229 for v in values:
230 for v in values:
230 yield one(v)
231 yield one(v)
231 if last is not None:
232 if last is not None:
232 yield one(last, tag=lastname)
233 yield one(last, tag=lastname)
233 endname = 'end_' + plural
234 endname = 'end_' + plural
234 if context.preload(endname):
235 if context.preload(endname):
235 yield context.process(endname, mapping)
236 yield context.process(endname, mapping)
236
237
237 def flatten(thing):
238 def flatten(thing):
238 """Yield a single stream from a possibly nested set of iterators"""
239 """Yield a single stream from a possibly nested set of iterators"""
239 thing = unwraphybrid(thing)
240 thing = unwraphybrid(thing)
240 if isinstance(thing, bytes):
241 if isinstance(thing, bytes):
241 yield thing
242 yield thing
242 elif isinstance(thing, str):
243 elif isinstance(thing, str):
243 # We can only hit this on Python 3, and it's here to guard
244 # We can only hit this on Python 3, and it's here to guard
244 # against infinite recursion.
245 # against infinite recursion.
245 raise error.ProgrammingError('Mercurial IO including templates is done'
246 raise error.ProgrammingError('Mercurial IO including templates is done'
246 ' with bytes, not strings, got %r' % thing)
247 ' with bytes, not strings, got %r' % thing)
247 elif thing is None:
248 elif thing is None:
248 pass
249 pass
249 elif not util.safehasattr(thing, '__iter__'):
250 elif not util.safehasattr(thing, '__iter__'):
250 yield pycompat.bytestr(thing)
251 yield pycompat.bytestr(thing)
251 else:
252 else:
252 for i in thing:
253 for i in thing:
253 i = unwraphybrid(i)
254 i = unwraphybrid(i)
254 if isinstance(i, bytes):
255 if isinstance(i, bytes):
255 yield i
256 yield i
256 elif i is None:
257 elif i is None:
257 pass
258 pass
258 elif not util.safehasattr(i, '__iter__'):
259 elif not util.safehasattr(i, '__iter__'):
259 yield pycompat.bytestr(i)
260 yield pycompat.bytestr(i)
260 else:
261 else:
261 for j in flatten(i):
262 for j in flatten(i):
262 yield j
263 yield j
263
264
264 def stringify(thing):
265 def stringify(thing):
265 """Turn values into bytes by converting into text and concatenating them"""
266 """Turn values into bytes by converting into text and concatenating them"""
266 if isinstance(thing, bytes):
267 if isinstance(thing, bytes):
267 return thing # retain localstr to be round-tripped
268 return thing # retain localstr to be round-tripped
268 return b''.join(flatten(thing))
269 return b''.join(flatten(thing))
269
270
270 def findsymbolicname(arg):
271 def findsymbolicname(arg):
271 """Find symbolic name for the given compiled expression; returns None
272 """Find symbolic name for the given compiled expression; returns None
272 if nothing found reliably"""
273 if nothing found reliably"""
273 while True:
274 while True:
274 func, data = arg
275 func, data = arg
275 if func is runsymbol:
276 if func is runsymbol:
276 return data
277 return data
277 elif func is runfilter:
278 elif func is runfilter:
278 arg = data[0]
279 arg = data[0]
279 else:
280 else:
280 return None
281 return None
281
282
282 def evalrawexp(context, mapping, arg):
283 def evalrawexp(context, mapping, arg):
283 """Evaluate given argument as a bare template object which may require
284 """Evaluate given argument as a bare template object which may require
284 further processing (such as folding generator of strings)"""
285 further processing (such as folding generator of strings)"""
285 func, data = arg
286 func, data = arg
286 return func(context, mapping, data)
287 return func(context, mapping, data)
287
288
288 def evalfuncarg(context, mapping, arg):
289 def evalfuncarg(context, mapping, arg):
289 """Evaluate given argument as value type"""
290 """Evaluate given argument as value type"""
290 return _unwrapvalue(evalrawexp(context, mapping, arg))
291 return _unwrapvalue(evalrawexp(context, mapping, arg))
291
292
292 # TODO: unify this with unwrapvalue() once the bug of templatefunc.join()
293 # TODO: unify this with unwrapvalue() once the bug of templatefunc.join()
293 # is fixed. we can't do that right now because join() has to take a generator
294 # is fixed. we can't do that right now because join() has to take a generator
294 # of byte strings as it is, not a lazy byte string.
295 # of byte strings as it is, not a lazy byte string.
295 def _unwrapvalue(thing):
296 def _unwrapvalue(thing):
296 thing = unwrapvalue(thing)
297 thing = unwrapvalue(thing)
297 # evalrawexp() may return string, generator of strings or arbitrary object
298 # evalrawexp() may return string, generator of strings or arbitrary object
298 # such as date tuple, but filter does not want generator.
299 # such as date tuple, but filter does not want generator.
299 if isinstance(thing, types.GeneratorType):
300 if isinstance(thing, types.GeneratorType):
300 thing = stringify(thing)
301 thing = stringify(thing)
301 return thing
302 return thing
302
303
303 def evalboolean(context, mapping, arg):
304 def evalboolean(context, mapping, arg):
304 """Evaluate given argument as boolean, but also takes boolean literals"""
305 """Evaluate given argument as boolean, but also takes boolean literals"""
305 func, data = arg
306 func, data = arg
306 if func is runsymbol:
307 if func is runsymbol:
307 thing = func(context, mapping, data, default=None)
308 thing = func(context, mapping, data, default=None)
308 if thing is None:
309 if thing is None:
309 # not a template keyword, takes as a boolean literal
310 # not a template keyword, takes as a boolean literal
310 thing = stringutil.parsebool(data)
311 thing = stringutil.parsebool(data)
311 else:
312 else:
312 thing = func(context, mapping, data)
313 thing = func(context, mapping, data)
313 thing = unwrapvalue(thing)
314 thing = unwrapvalue(thing)
314 if isinstance(thing, bool):
315 if isinstance(thing, bool):
315 return thing
316 return thing
316 # other objects are evaluated as strings, which means 0 is True, but
317 # other objects are evaluated as strings, which means 0 is True, but
317 # empty dict/list should be False as they are expected to be ''
318 # empty dict/list should be False as they are expected to be ''
318 return bool(stringify(thing))
319 return bool(stringify(thing))
319
320
320 def evalinteger(context, mapping, arg, err=None):
321 def evalinteger(context, mapping, arg, err=None):
321 return unwrapinteger(evalrawexp(context, mapping, arg), err)
322 return unwrapinteger(evalrawexp(context, mapping, arg), err)
322
323
323 def unwrapinteger(thing, err=None):
324 def unwrapinteger(thing, err=None):
324 thing = _unwrapvalue(thing)
325 thing = _unwrapvalue(thing)
325 try:
326 try:
326 return int(thing)
327 return int(thing)
327 except (TypeError, ValueError):
328 except (TypeError, ValueError):
328 raise error.ParseError(err or _('not an integer'))
329 raise error.ParseError(err or _('not an integer'))
329
330
330 def evalstring(context, mapping, arg):
331 def evalstring(context, mapping, arg):
331 return stringify(evalrawexp(context, mapping, arg))
332 return stringify(evalrawexp(context, mapping, arg))
332
333
333 def evalstringliteral(context, mapping, arg):
334 def evalstringliteral(context, mapping, arg):
334 """Evaluate given argument as string template, but returns symbol name
335 """Evaluate given argument as string template, but returns symbol name
335 if it is unknown"""
336 if it is unknown"""
336 func, data = arg
337 func, data = arg
337 if func is runsymbol:
338 if func is runsymbol:
338 thing = func(context, mapping, data, default=data)
339 thing = func(context, mapping, data, default=data)
339 else:
340 else:
340 thing = func(context, mapping, data)
341 thing = func(context, mapping, data)
341 return stringify(thing)
342 return stringify(thing)
342
343
343 _evalfuncbytype = {
344 _unwrapfuncbytype = {
344 bytes: evalstring,
345 bytes: stringify,
345 int: evalinteger,
346 int: unwrapinteger,
346 }
347 }
347
348
348 def evalastype(context, mapping, arg, typ):
349 def unwrapastype(thing, typ):
349 """Evaluate given argument and coerce its type"""
350 """Move the inner value object out of the wrapper and coerce its type"""
350 try:
351 try:
351 f = _evalfuncbytype[typ]
352 f = _unwrapfuncbytype[typ]
352 except KeyError:
353 except KeyError:
353 raise error.ProgrammingError('invalid type specified: %r' % typ)
354 raise error.ProgrammingError('invalid type specified: %r' % typ)
354 return f(context, mapping, arg)
355 return f(thing)
355
356
356 def runinteger(context, mapping, data):
357 def runinteger(context, mapping, data):
357 return int(data)
358 return int(data)
358
359
359 def runstring(context, mapping, data):
360 def runstring(context, mapping, data):
360 return data
361 return data
361
362
362 def _recursivesymbolblocker(key):
363 def _recursivesymbolblocker(key):
363 def showrecursion(**args):
364 def showrecursion(**args):
364 raise error.Abort(_("recursive reference '%s' in template") % key)
365 raise error.Abort(_("recursive reference '%s' in template") % key)
365 return showrecursion
366 return showrecursion
366
367
367 def runsymbol(context, mapping, key, default=''):
368 def runsymbol(context, mapping, key, default=''):
368 v = context.symbol(mapping, key)
369 v = context.symbol(mapping, key)
369 if v is None:
370 if v is None:
370 # put poison to cut recursion. we can't move this to parsing phase
371 # put poison to cut recursion. we can't move this to parsing phase
371 # because "x = {x}" is allowed if "x" is a keyword. (issue4758)
372 # because "x = {x}" is allowed if "x" is a keyword. (issue4758)
372 safemapping = mapping.copy()
373 safemapping = mapping.copy()
373 safemapping[key] = _recursivesymbolblocker(key)
374 safemapping[key] = _recursivesymbolblocker(key)
374 try:
375 try:
375 v = context.process(key, safemapping)
376 v = context.process(key, safemapping)
376 except TemplateNotFound:
377 except TemplateNotFound:
377 v = default
378 v = default
378 if callable(v) and getattr(v, '_requires', None) is None:
379 if callable(v) and getattr(v, '_requires', None) is None:
379 # old templatekw: expand all keywords and resources
380 # old templatekw: expand all keywords and resources
380 # (TODO: deprecate this after porting web template keywords to new API)
381 # (TODO: deprecate this after porting web template keywords to new API)
381 props = {k: context._resources.lookup(context, mapping, k)
382 props = {k: context._resources.lookup(context, mapping, k)
382 for k in context._resources.knownkeys()}
383 for k in context._resources.knownkeys()}
383 # pass context to _showcompatlist() through templatekw._showlist()
384 # pass context to _showcompatlist() through templatekw._showlist()
384 props['templ'] = context
385 props['templ'] = context
385 props.update(mapping)
386 props.update(mapping)
386 return v(**pycompat.strkwargs(props))
387 return v(**pycompat.strkwargs(props))
387 if callable(v):
388 if callable(v):
388 # new templatekw
389 # new templatekw
389 try:
390 try:
390 return v(context, mapping)
391 return v(context, mapping)
391 except ResourceUnavailable:
392 except ResourceUnavailable:
392 # unsupported keyword is mapped to empty just like unknown keyword
393 # unsupported keyword is mapped to empty just like unknown keyword
393 return None
394 return None
394 return v
395 return v
395
396
396 def runtemplate(context, mapping, template):
397 def runtemplate(context, mapping, template):
397 for arg in template:
398 for arg in template:
398 yield evalrawexp(context, mapping, arg)
399 yield evalrawexp(context, mapping, arg)
399
400
400 def runfilter(context, mapping, data):
401 def runfilter(context, mapping, data):
401 arg, filt = data
402 arg, filt = data
402 thing = evalfuncarg(context, mapping, arg)
403 thing = evalfuncarg(context, mapping, arg)
403 try:
404 try:
404 return filt(thing)
405 return filt(thing)
405 except (ValueError, AttributeError, TypeError):
406 except (ValueError, AttributeError, TypeError):
406 sym = findsymbolicname(arg)
407 sym = findsymbolicname(arg)
407 if sym:
408 if sym:
408 msg = (_("template filter '%s' is not compatible with keyword '%s'")
409 msg = (_("template filter '%s' is not compatible with keyword '%s'")
409 % (pycompat.sysbytes(filt.__name__), sym))
410 % (pycompat.sysbytes(filt.__name__), sym))
410 else:
411 else:
411 msg = (_("incompatible use of template filter '%s'")
412 msg = (_("incompatible use of template filter '%s'")
412 % pycompat.sysbytes(filt.__name__))
413 % pycompat.sysbytes(filt.__name__))
413 raise error.Abort(msg)
414 raise error.Abort(msg)
414
415
415 def runmap(context, mapping, data):
416 def runmap(context, mapping, data):
416 darg, targ = data
417 darg, targ = data
417 d = evalrawexp(context, mapping, darg)
418 d = evalrawexp(context, mapping, darg)
418 if util.safehasattr(d, 'itermaps'):
419 if util.safehasattr(d, 'itermaps'):
419 diter = d.itermaps()
420 diter = d.itermaps()
420 else:
421 else:
421 try:
422 try:
422 diter = iter(d)
423 diter = iter(d)
423 except TypeError:
424 except TypeError:
424 sym = findsymbolicname(darg)
425 sym = findsymbolicname(darg)
425 if sym:
426 if sym:
426 raise error.ParseError(_("keyword '%s' is not iterable") % sym)
427 raise error.ParseError(_("keyword '%s' is not iterable") % sym)
427 else:
428 else:
428 raise error.ParseError(_("%r is not iterable") % d)
429 raise error.ParseError(_("%r is not iterable") % d)
429
430
430 for i, v in enumerate(diter):
431 for i, v in enumerate(diter):
431 if isinstance(v, dict):
432 if isinstance(v, dict):
432 lm = context.overlaymap(mapping, v)
433 lm = context.overlaymap(mapping, v)
433 lm['index'] = i
434 lm['index'] = i
434 yield evalrawexp(context, lm, targ)
435 yield evalrawexp(context, lm, targ)
435 else:
436 else:
436 # v is not an iterable of dicts, this happen when 'key'
437 # v is not an iterable of dicts, this happen when 'key'
437 # has been fully expanded already and format is useless.
438 # has been fully expanded already and format is useless.
438 # If so, return the expanded value.
439 # If so, return the expanded value.
439 yield v
440 yield v
440
441
441 def runmember(context, mapping, data):
442 def runmember(context, mapping, data):
442 darg, memb = data
443 darg, memb = data
443 d = evalrawexp(context, mapping, darg)
444 d = evalrawexp(context, mapping, darg)
444 if util.safehasattr(d, 'tomap'):
445 if util.safehasattr(d, 'tomap'):
445 lm = context.overlaymap(mapping, d.tomap())
446 lm = context.overlaymap(mapping, d.tomap())
446 return runsymbol(context, lm, memb)
447 return runsymbol(context, lm, memb)
447 if util.safehasattr(d, 'get'):
448 if util.safehasattr(d, 'get'):
448 return getdictitem(d, memb)
449 return getdictitem(d, memb)
449
450
450 sym = findsymbolicname(darg)
451 sym = findsymbolicname(darg)
451 if sym:
452 if sym:
452 raise error.ParseError(_("keyword '%s' has no member") % sym)
453 raise error.ParseError(_("keyword '%s' has no member") % sym)
453 else:
454 else:
454 raise error.ParseError(_("%r has no member") % pycompat.bytestr(d))
455 raise error.ParseError(_("%r has no member") % pycompat.bytestr(d))
455
456
456 def runnegate(context, mapping, data):
457 def runnegate(context, mapping, data):
457 data = evalinteger(context, mapping, data,
458 data = evalinteger(context, mapping, data,
458 _('negation needs an integer argument'))
459 _('negation needs an integer argument'))
459 return -data
460 return -data
460
461
461 def runarithmetic(context, mapping, data):
462 def runarithmetic(context, mapping, data):
462 func, left, right = data
463 func, left, right = data
463 left = evalinteger(context, mapping, left,
464 left = evalinteger(context, mapping, left,
464 _('arithmetic only defined on integers'))
465 _('arithmetic only defined on integers'))
465 right = evalinteger(context, mapping, right,
466 right = evalinteger(context, mapping, right,
466 _('arithmetic only defined on integers'))
467 _('arithmetic only defined on integers'))
467 try:
468 try:
468 return func(left, right)
469 return func(left, right)
469 except ZeroDivisionError:
470 except ZeroDivisionError:
470 raise error.Abort(_('division by zero is not defined'))
471 raise error.Abort(_('division by zero is not defined'))
471
472
472 def getdictitem(dictarg, key):
473 def getdictitem(dictarg, key):
473 val = dictarg.get(key)
474 val = dictarg.get(key)
474 if val is None:
475 if val is None:
475 return
476 return
476 return wraphybridvalue(dictarg, key, val)
477 return wraphybridvalue(dictarg, key, val)
General Comments 0
You need to be logged in to leave comments. Login now