##// END OF EJS Templates
templater: attach hint to input-type error of runfilter()...
Yuya Nishihara -
r37243:920589f5 default
parent child Browse files
Show More
@@ -1,496 +1,499 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 dateutil,
19 dateutil,
20 stringutil,
20 stringutil,
21 )
21 )
22
22
23 class ResourceUnavailable(error.Abort):
23 class ResourceUnavailable(error.Abort):
24 pass
24 pass
25
25
26 class TemplateNotFound(error.Abort):
26 class TemplateNotFound(error.Abort):
27 pass
27 pass
28
28
29 class hybrid(object):
29 class hybrid(object):
30 """Wrapper for list or dict to support legacy template
30 """Wrapper for list or dict to support legacy template
31
31
32 This class allows us to handle both:
32 This class allows us to handle both:
33 - "{files}" (legacy command-line-specific list hack) and
33 - "{files}" (legacy command-line-specific list hack) and
34 - "{files % '{file}\n'}" (hgweb-style with inlining and function support)
34 - "{files % '{file}\n'}" (hgweb-style with inlining and function support)
35 and to access raw values:
35 and to access raw values:
36 - "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
36 - "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
37 - "{get(extras, key)}"
37 - "{get(extras, key)}"
38 - "{files|json}"
38 - "{files|json}"
39 """
39 """
40
40
41 def __init__(self, gen, values, makemap, joinfmt, keytype=None):
41 def __init__(self, gen, values, makemap, joinfmt, keytype=None):
42 if gen is not None:
42 if gen is not None:
43 self.gen = gen # generator or function returning generator
43 self.gen = gen # generator or function returning generator
44 self._values = values
44 self._values = values
45 self._makemap = makemap
45 self._makemap = makemap
46 self.joinfmt = joinfmt
46 self.joinfmt = joinfmt
47 self.keytype = keytype # hint for 'x in y' where type(x) is unresolved
47 self.keytype = keytype # hint for 'x in y' where type(x) is unresolved
48 def gen(self):
48 def gen(self):
49 """Default generator to stringify this as {join(self, ' ')}"""
49 """Default generator to stringify this as {join(self, ' ')}"""
50 for i, x in enumerate(self._values):
50 for i, x in enumerate(self._values):
51 if i > 0:
51 if i > 0:
52 yield ' '
52 yield ' '
53 yield self.joinfmt(x)
53 yield self.joinfmt(x)
54 def itermaps(self):
54 def itermaps(self):
55 makemap = self._makemap
55 makemap = self._makemap
56 for x in self._values:
56 for x in self._values:
57 yield makemap(x)
57 yield makemap(x)
58 def __contains__(self, x):
58 def __contains__(self, x):
59 return x in self._values
59 return x in self._values
60 def __getitem__(self, key):
60 def __getitem__(self, key):
61 return self._values[key]
61 return self._values[key]
62 def __len__(self):
62 def __len__(self):
63 return len(self._values)
63 return len(self._values)
64 def __iter__(self):
64 def __iter__(self):
65 return iter(self._values)
65 return iter(self._values)
66 def __getattr__(self, name):
66 def __getattr__(self, name):
67 if name not in (r'get', r'items', r'iteritems', r'iterkeys',
67 if name not in (r'get', r'items', r'iteritems', r'iterkeys',
68 r'itervalues', r'keys', r'values'):
68 r'itervalues', r'keys', r'values'):
69 raise AttributeError(name)
69 raise AttributeError(name)
70 return getattr(self._values, name)
70 return getattr(self._values, name)
71
71
72 class mappable(object):
72 class mappable(object):
73 """Wrapper for non-list/dict object to support map operation
73 """Wrapper for non-list/dict object to support map operation
74
74
75 This class allows us to handle both:
75 This class allows us to handle both:
76 - "{manifest}"
76 - "{manifest}"
77 - "{manifest % '{rev}:{node}'}"
77 - "{manifest % '{rev}:{node}'}"
78 - "{manifest.rev}"
78 - "{manifest.rev}"
79
79
80 Unlike a hybrid, this does not simulate the behavior of the underling
80 Unlike a hybrid, this does not simulate the behavior of the underling
81 value. Use unwrapvalue(), unwrapastype(), or unwraphybrid() to obtain
81 value. Use unwrapvalue(), unwrapastype(), or unwraphybrid() to obtain
82 the inner object.
82 the inner object.
83 """
83 """
84
84
85 def __init__(self, gen, key, value, makemap):
85 def __init__(self, gen, key, value, makemap):
86 if gen is not None:
86 if gen is not None:
87 self.gen = gen # generator or function returning generator
87 self.gen = gen # generator or function returning generator
88 self._key = key
88 self._key = key
89 self._value = value # may be generator of strings
89 self._value = value # may be generator of strings
90 self._makemap = makemap
90 self._makemap = makemap
91
91
92 def gen(self):
92 def gen(self):
93 yield pycompat.bytestr(self._value)
93 yield pycompat.bytestr(self._value)
94
94
95 def tomap(self):
95 def tomap(self):
96 return self._makemap(self._key)
96 return self._makemap(self._key)
97
97
98 def itermaps(self):
98 def itermaps(self):
99 yield self.tomap()
99 yield self.tomap()
100
100
101 def hybriddict(data, key='key', value='value', fmt=None, gen=None):
101 def hybriddict(data, key='key', value='value', fmt=None, gen=None):
102 """Wrap data to support both dict-like and string-like operations"""
102 """Wrap data to support both dict-like and string-like operations"""
103 prefmt = pycompat.identity
103 prefmt = pycompat.identity
104 if fmt is None:
104 if fmt is None:
105 fmt = '%s=%s'
105 fmt = '%s=%s'
106 prefmt = pycompat.bytestr
106 prefmt = pycompat.bytestr
107 return hybrid(gen, data, lambda k: {key: k, value: data[k]},
107 return hybrid(gen, data, lambda k: {key: k, value: data[k]},
108 lambda k: fmt % (prefmt(k), prefmt(data[k])))
108 lambda k: fmt % (prefmt(k), prefmt(data[k])))
109
109
110 def hybridlist(data, name, fmt=None, gen=None):
110 def hybridlist(data, name, fmt=None, gen=None):
111 """Wrap data to support both list-like and string-like operations"""
111 """Wrap data to support both list-like and string-like operations"""
112 prefmt = pycompat.identity
112 prefmt = pycompat.identity
113 if fmt is None:
113 if fmt is None:
114 fmt = '%s'
114 fmt = '%s'
115 prefmt = pycompat.bytestr
115 prefmt = pycompat.bytestr
116 return hybrid(gen, data, lambda x: {name: x}, lambda x: fmt % prefmt(x))
116 return hybrid(gen, data, lambda x: {name: x}, lambda x: fmt % prefmt(x))
117
117
118 def unwraphybrid(thing):
118 def unwraphybrid(thing):
119 """Return an object which can be stringified possibly by using a legacy
119 """Return an object which can be stringified possibly by using a legacy
120 template"""
120 template"""
121 gen = getattr(thing, 'gen', None)
121 gen = getattr(thing, 'gen', None)
122 if gen is None:
122 if gen is None:
123 return thing
123 return thing
124 if callable(gen):
124 if callable(gen):
125 return gen()
125 return gen()
126 return gen
126 return gen
127
127
128 def unwrapvalue(thing):
128 def unwrapvalue(thing):
129 """Move the inner value object out of the wrapper"""
129 """Move the inner value object out of the wrapper"""
130 if not util.safehasattr(thing, '_value'):
130 if not util.safehasattr(thing, '_value'):
131 return thing
131 return thing
132 return thing._value
132 return thing._value
133
133
134 def wraphybridvalue(container, key, value):
134 def wraphybridvalue(container, key, value):
135 """Wrap an element of hybrid container to be mappable
135 """Wrap an element of hybrid container to be mappable
136
136
137 The key is passed to the makemap function of the given container, which
137 The key is passed to the makemap function of the given container, which
138 should be an item generated by iter(container).
138 should be an item generated by iter(container).
139 """
139 """
140 makemap = getattr(container, '_makemap', None)
140 makemap = getattr(container, '_makemap', None)
141 if makemap is None:
141 if makemap is None:
142 return value
142 return value
143 if util.safehasattr(value, '_makemap'):
143 if util.safehasattr(value, '_makemap'):
144 # a nested hybrid list/dict, which has its own way of map operation
144 # a nested hybrid list/dict, which has its own way of map operation
145 return value
145 return value
146 return mappable(None, key, value, makemap)
146 return mappable(None, key, value, makemap)
147
147
148 def compatdict(context, mapping, name, data, key='key', value='value',
148 def compatdict(context, mapping, name, data, key='key', value='value',
149 fmt=None, plural=None, separator=' '):
149 fmt=None, plural=None, separator=' '):
150 """Wrap data like hybriddict(), but also supports old-style list template
150 """Wrap data like hybriddict(), but also supports old-style list template
151
151
152 This exists for backward compatibility with the old-style template. Use
152 This exists for backward compatibility with the old-style template. Use
153 hybriddict() for new template keywords.
153 hybriddict() for new template keywords.
154 """
154 """
155 c = [{key: k, value: v} for k, v in data.iteritems()]
155 c = [{key: k, value: v} for k, v in data.iteritems()]
156 f = _showcompatlist(context, mapping, name, c, plural, separator)
156 f = _showcompatlist(context, mapping, name, c, plural, separator)
157 return hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
157 return hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
158
158
159 def compatlist(context, mapping, name, data, element=None, fmt=None,
159 def compatlist(context, mapping, name, data, element=None, fmt=None,
160 plural=None, separator=' '):
160 plural=None, separator=' '):
161 """Wrap data like hybridlist(), but also supports old-style list template
161 """Wrap data like hybridlist(), but also supports old-style list template
162
162
163 This exists for backward compatibility with the old-style template. Use
163 This exists for backward compatibility with the old-style template. Use
164 hybridlist() for new template keywords.
164 hybridlist() for new template keywords.
165 """
165 """
166 f = _showcompatlist(context, mapping, name, data, plural, separator)
166 f = _showcompatlist(context, mapping, name, data, plural, separator)
167 return hybridlist(data, name=element or name, fmt=fmt, gen=f)
167 return hybridlist(data, name=element or name, fmt=fmt, gen=f)
168
168
169 def _showcompatlist(context, mapping, name, values, plural=None, separator=' '):
169 def _showcompatlist(context, mapping, name, values, plural=None, separator=' '):
170 """Return a generator that renders old-style list template
170 """Return a generator that renders old-style list template
171
171
172 name is name of key in template map.
172 name is name of key in template map.
173 values is list of strings or dicts.
173 values is list of strings or dicts.
174 plural is plural of name, if not simply name + 's'.
174 plural is plural of name, if not simply name + 's'.
175 separator is used to join values as a string
175 separator is used to join values as a string
176
176
177 expansion works like this, given name 'foo'.
177 expansion works like this, given name 'foo'.
178
178
179 if values is empty, expand 'no_foos'.
179 if values is empty, expand 'no_foos'.
180
180
181 if 'foo' not in template map, return values as a string,
181 if 'foo' not in template map, return values as a string,
182 joined by 'separator'.
182 joined by 'separator'.
183
183
184 expand 'start_foos'.
184 expand 'start_foos'.
185
185
186 for each value, expand 'foo'. if 'last_foo' in template
186 for each value, expand 'foo'. if 'last_foo' in template
187 map, expand it instead of 'foo' for last key.
187 map, expand it instead of 'foo' for last key.
188
188
189 expand 'end_foos'.
189 expand 'end_foos'.
190 """
190 """
191 if not plural:
191 if not plural:
192 plural = name + 's'
192 plural = name + 's'
193 if not values:
193 if not values:
194 noname = 'no_' + plural
194 noname = 'no_' + plural
195 if context.preload(noname):
195 if context.preload(noname):
196 yield context.process(noname, mapping)
196 yield context.process(noname, mapping)
197 return
197 return
198 if not context.preload(name):
198 if not context.preload(name):
199 if isinstance(values[0], bytes):
199 if isinstance(values[0], bytes):
200 yield separator.join(values)
200 yield separator.join(values)
201 else:
201 else:
202 for v in values:
202 for v in values:
203 r = dict(v)
203 r = dict(v)
204 r.update(mapping)
204 r.update(mapping)
205 yield r
205 yield r
206 return
206 return
207 startname = 'start_' + plural
207 startname = 'start_' + plural
208 if context.preload(startname):
208 if context.preload(startname):
209 yield context.process(startname, mapping)
209 yield context.process(startname, mapping)
210 def one(v, tag=name):
210 def one(v, tag=name):
211 vmapping = {}
211 vmapping = {}
212 try:
212 try:
213 vmapping.update(v)
213 vmapping.update(v)
214 # Python 2 raises ValueError if the type of v is wrong. Python
214 # Python 2 raises ValueError if the type of v is wrong. Python
215 # 3 raises TypeError.
215 # 3 raises TypeError.
216 except (AttributeError, TypeError, ValueError):
216 except (AttributeError, TypeError, ValueError):
217 try:
217 try:
218 # Python 2 raises ValueError trying to destructure an e.g.
218 # Python 2 raises ValueError trying to destructure an e.g.
219 # bytes. Python 3 raises TypeError.
219 # bytes. Python 3 raises TypeError.
220 for a, b in v:
220 for a, b in v:
221 vmapping[a] = b
221 vmapping[a] = b
222 except (TypeError, ValueError):
222 except (TypeError, ValueError):
223 vmapping[name] = v
223 vmapping[name] = v
224 vmapping = context.overlaymap(mapping, vmapping)
224 vmapping = context.overlaymap(mapping, vmapping)
225 return context.process(tag, vmapping)
225 return context.process(tag, vmapping)
226 lastname = 'last_' + name
226 lastname = 'last_' + name
227 if context.preload(lastname):
227 if context.preload(lastname):
228 last = values.pop()
228 last = values.pop()
229 else:
229 else:
230 last = None
230 last = None
231 for v in values:
231 for v in values:
232 yield one(v)
232 yield one(v)
233 if last is not None:
233 if last is not None:
234 yield one(last, tag=lastname)
234 yield one(last, tag=lastname)
235 endname = 'end_' + plural
235 endname = 'end_' + plural
236 if context.preload(endname):
236 if context.preload(endname):
237 yield context.process(endname, mapping)
237 yield context.process(endname, mapping)
238
238
239 def flatten(thing):
239 def flatten(thing):
240 """Yield a single stream from a possibly nested set of iterators"""
240 """Yield a single stream from a possibly nested set of iterators"""
241 thing = unwraphybrid(thing)
241 thing = unwraphybrid(thing)
242 if isinstance(thing, bytes):
242 if isinstance(thing, bytes):
243 yield thing
243 yield thing
244 elif isinstance(thing, str):
244 elif isinstance(thing, str):
245 # We can only hit this on Python 3, and it's here to guard
245 # We can only hit this on Python 3, and it's here to guard
246 # against infinite recursion.
246 # against infinite recursion.
247 raise error.ProgrammingError('Mercurial IO including templates is done'
247 raise error.ProgrammingError('Mercurial IO including templates is done'
248 ' with bytes, not strings, got %r' % thing)
248 ' with bytes, not strings, got %r' % thing)
249 elif thing is None:
249 elif thing is None:
250 pass
250 pass
251 elif not util.safehasattr(thing, '__iter__'):
251 elif not util.safehasattr(thing, '__iter__'):
252 yield pycompat.bytestr(thing)
252 yield pycompat.bytestr(thing)
253 else:
253 else:
254 for i in thing:
254 for i in thing:
255 i = unwraphybrid(i)
255 i = unwraphybrid(i)
256 if isinstance(i, bytes):
256 if isinstance(i, bytes):
257 yield i
257 yield i
258 elif i is None:
258 elif i is None:
259 pass
259 pass
260 elif not util.safehasattr(i, '__iter__'):
260 elif not util.safehasattr(i, '__iter__'):
261 yield pycompat.bytestr(i)
261 yield pycompat.bytestr(i)
262 else:
262 else:
263 for j in flatten(i):
263 for j in flatten(i):
264 yield j
264 yield j
265
265
266 def stringify(thing):
266 def stringify(thing):
267 """Turn values into bytes by converting into text and concatenating them"""
267 """Turn values into bytes by converting into text and concatenating them"""
268 if isinstance(thing, bytes):
268 if isinstance(thing, bytes):
269 return thing # retain localstr to be round-tripped
269 return thing # retain localstr to be round-tripped
270 return b''.join(flatten(thing))
270 return b''.join(flatten(thing))
271
271
272 def findsymbolicname(arg):
272 def findsymbolicname(arg):
273 """Find symbolic name for the given compiled expression; returns None
273 """Find symbolic name for the given compiled expression; returns None
274 if nothing found reliably"""
274 if nothing found reliably"""
275 while True:
275 while True:
276 func, data = arg
276 func, data = arg
277 if func is runsymbol:
277 if func is runsymbol:
278 return data
278 return data
279 elif func is runfilter:
279 elif func is runfilter:
280 arg = data[0]
280 arg = data[0]
281 else:
281 else:
282 return None
282 return None
283
283
284 def evalrawexp(context, mapping, arg):
284 def evalrawexp(context, mapping, arg):
285 """Evaluate given argument as a bare template object which may require
285 """Evaluate given argument as a bare template object which may require
286 further processing (such as folding generator of strings)"""
286 further processing (such as folding generator of strings)"""
287 func, data = arg
287 func, data = arg
288 return func(context, mapping, data)
288 return func(context, mapping, data)
289
289
290 def evalfuncarg(context, mapping, arg):
290 def evalfuncarg(context, mapping, arg):
291 """Evaluate given argument as value type"""
291 """Evaluate given argument as value type"""
292 return _unwrapvalue(evalrawexp(context, mapping, arg))
292 return _unwrapvalue(evalrawexp(context, mapping, arg))
293
293
294 # TODO: unify this with unwrapvalue() once the bug of templatefunc.join()
294 # TODO: unify this with unwrapvalue() once the bug of templatefunc.join()
295 # is fixed. we can't do that right now because join() has to take a generator
295 # is fixed. we can't do that right now because join() has to take a generator
296 # of byte strings as it is, not a lazy byte string.
296 # of byte strings as it is, not a lazy byte string.
297 def _unwrapvalue(thing):
297 def _unwrapvalue(thing):
298 thing = unwrapvalue(thing)
298 thing = unwrapvalue(thing)
299 # evalrawexp() may return string, generator of strings or arbitrary object
299 # evalrawexp() may return string, generator of strings or arbitrary object
300 # such as date tuple, but filter does not want generator.
300 # such as date tuple, but filter does not want generator.
301 if isinstance(thing, types.GeneratorType):
301 if isinstance(thing, types.GeneratorType):
302 thing = stringify(thing)
302 thing = stringify(thing)
303 return thing
303 return thing
304
304
305 def evalboolean(context, mapping, arg):
305 def evalboolean(context, mapping, arg):
306 """Evaluate given argument as boolean, but also takes boolean literals"""
306 """Evaluate given argument as boolean, but also takes boolean literals"""
307 func, data = arg
307 func, data = arg
308 if func is runsymbol:
308 if func is runsymbol:
309 thing = func(context, mapping, data, default=None)
309 thing = func(context, mapping, data, default=None)
310 if thing is None:
310 if thing is None:
311 # not a template keyword, takes as a boolean literal
311 # not a template keyword, takes as a boolean literal
312 thing = stringutil.parsebool(data)
312 thing = stringutil.parsebool(data)
313 else:
313 else:
314 thing = func(context, mapping, data)
314 thing = func(context, mapping, data)
315 thing = unwrapvalue(thing)
315 thing = unwrapvalue(thing)
316 if isinstance(thing, bool):
316 if isinstance(thing, bool):
317 return thing
317 return thing
318 # other objects are evaluated as strings, which means 0 is True, but
318 # other objects are evaluated as strings, which means 0 is True, but
319 # empty dict/list should be False as they are expected to be ''
319 # empty dict/list should be False as they are expected to be ''
320 return bool(stringify(thing))
320 return bool(stringify(thing))
321
321
322 def evaldate(context, mapping, arg, err=None):
322 def evaldate(context, mapping, arg, err=None):
323 """Evaluate given argument as a date tuple or a date string; returns
323 """Evaluate given argument as a date tuple or a date string; returns
324 a (unixtime, offset) tuple"""
324 a (unixtime, offset) tuple"""
325 return unwrapdate(evalrawexp(context, mapping, arg), err)
325 return unwrapdate(evalrawexp(context, mapping, arg), err)
326
326
327 def unwrapdate(thing, err=None):
327 def unwrapdate(thing, err=None):
328 thing = _unwrapvalue(thing)
328 thing = _unwrapvalue(thing)
329 try:
329 try:
330 return dateutil.parsedate(thing)
330 return dateutil.parsedate(thing)
331 except AttributeError:
331 except AttributeError:
332 raise error.ParseError(err or _('not a date tuple nor a string'))
332 raise error.ParseError(err or _('not a date tuple nor a string'))
333 except error.ParseError:
333 except error.ParseError:
334 if not err:
334 if not err:
335 raise
335 raise
336 raise error.ParseError(err)
336 raise error.ParseError(err)
337
337
338 def evalinteger(context, mapping, arg, err=None):
338 def evalinteger(context, mapping, arg, err=None):
339 return unwrapinteger(evalrawexp(context, mapping, arg), err)
339 return unwrapinteger(evalrawexp(context, mapping, arg), err)
340
340
341 def unwrapinteger(thing, err=None):
341 def unwrapinteger(thing, err=None):
342 thing = _unwrapvalue(thing)
342 thing = _unwrapvalue(thing)
343 try:
343 try:
344 return int(thing)
344 return int(thing)
345 except (TypeError, ValueError):
345 except (TypeError, ValueError):
346 raise error.ParseError(err or _('not an integer'))
346 raise error.ParseError(err or _('not an integer'))
347
347
348 def evalstring(context, mapping, arg):
348 def evalstring(context, mapping, arg):
349 return stringify(evalrawexp(context, mapping, arg))
349 return stringify(evalrawexp(context, mapping, arg))
350
350
351 def evalstringliteral(context, mapping, arg):
351 def evalstringliteral(context, mapping, arg):
352 """Evaluate given argument as string template, but returns symbol name
352 """Evaluate given argument as string template, but returns symbol name
353 if it is unknown"""
353 if it is unknown"""
354 func, data = arg
354 func, data = arg
355 if func is runsymbol:
355 if func is runsymbol:
356 thing = func(context, mapping, data, default=data)
356 thing = func(context, mapping, data, default=data)
357 else:
357 else:
358 thing = func(context, mapping, data)
358 thing = func(context, mapping, data)
359 return stringify(thing)
359 return stringify(thing)
360
360
361 _unwrapfuncbytype = {
361 _unwrapfuncbytype = {
362 None: _unwrapvalue,
362 None: _unwrapvalue,
363 bytes: stringify,
363 bytes: stringify,
364 int: unwrapinteger,
364 int: unwrapinteger,
365 }
365 }
366
366
367 def unwrapastype(thing, typ):
367 def unwrapastype(thing, typ):
368 """Move the inner value object out of the wrapper and coerce its type"""
368 """Move the inner value object out of the wrapper and coerce its type"""
369 try:
369 try:
370 f = _unwrapfuncbytype[typ]
370 f = _unwrapfuncbytype[typ]
371 except KeyError:
371 except KeyError:
372 raise error.ProgrammingError('invalid type specified: %r' % typ)
372 raise error.ProgrammingError('invalid type specified: %r' % typ)
373 return f(thing)
373 return f(thing)
374
374
375 def runinteger(context, mapping, data):
375 def runinteger(context, mapping, data):
376 return int(data)
376 return int(data)
377
377
378 def runstring(context, mapping, data):
378 def runstring(context, mapping, data):
379 return data
379 return data
380
380
381 def _recursivesymbolblocker(key):
381 def _recursivesymbolblocker(key):
382 def showrecursion(**args):
382 def showrecursion(**args):
383 raise error.Abort(_("recursive reference '%s' in template") % key)
383 raise error.Abort(_("recursive reference '%s' in template") % key)
384 return showrecursion
384 return showrecursion
385
385
386 def runsymbol(context, mapping, key, default=''):
386 def runsymbol(context, mapping, key, default=''):
387 v = context.symbol(mapping, key)
387 v = context.symbol(mapping, key)
388 if v is None:
388 if v is None:
389 # put poison to cut recursion. we can't move this to parsing phase
389 # put poison to cut recursion. we can't move this to parsing phase
390 # because "x = {x}" is allowed if "x" is a keyword. (issue4758)
390 # because "x = {x}" is allowed if "x" is a keyword. (issue4758)
391 safemapping = mapping.copy()
391 safemapping = mapping.copy()
392 safemapping[key] = _recursivesymbolblocker(key)
392 safemapping[key] = _recursivesymbolblocker(key)
393 try:
393 try:
394 v = context.process(key, safemapping)
394 v = context.process(key, safemapping)
395 except TemplateNotFound:
395 except TemplateNotFound:
396 v = default
396 v = default
397 if callable(v) and getattr(v, '_requires', None) is None:
397 if callable(v) and getattr(v, '_requires', None) is None:
398 # old templatekw: expand all keywords and resources
398 # old templatekw: expand all keywords and resources
399 # (TODO: deprecate this after porting web template keywords to new API)
399 # (TODO: deprecate this after porting web template keywords to new API)
400 props = {k: context._resources.lookup(context, mapping, k)
400 props = {k: context._resources.lookup(context, mapping, k)
401 for k in context._resources.knownkeys()}
401 for k in context._resources.knownkeys()}
402 # pass context to _showcompatlist() through templatekw._showlist()
402 # pass context to _showcompatlist() through templatekw._showlist()
403 props['templ'] = context
403 props['templ'] = context
404 props.update(mapping)
404 props.update(mapping)
405 return v(**pycompat.strkwargs(props))
405 return v(**pycompat.strkwargs(props))
406 if callable(v):
406 if callable(v):
407 # new templatekw
407 # new templatekw
408 try:
408 try:
409 return v(context, mapping)
409 return v(context, mapping)
410 except ResourceUnavailable:
410 except ResourceUnavailable:
411 # unsupported keyword is mapped to empty just like unknown keyword
411 # unsupported keyword is mapped to empty just like unknown keyword
412 return None
412 return None
413 return v
413 return v
414
414
415 def runtemplate(context, mapping, template):
415 def runtemplate(context, mapping, template):
416 for arg in template:
416 for arg in template:
417 yield evalrawexp(context, mapping, arg)
417 yield evalrawexp(context, mapping, arg)
418
418
419 def runfilter(context, mapping, data):
419 def runfilter(context, mapping, data):
420 arg, filt = data
420 arg, filt = data
421 thing = evalrawexp(context, mapping, arg)
421 thing = evalrawexp(context, mapping, arg)
422 try:
422 try:
423 thing = unwrapastype(thing, getattr(filt, '_intype', None))
423 thing = unwrapastype(thing, getattr(filt, '_intype', None))
424 return filt(thing)
424 return filt(thing)
425 except (ValueError, AttributeError, TypeError):
425 except (ValueError, AttributeError, TypeError):
426 sym = findsymbolicname(arg)
426 raise error.Abort(_formatfiltererror(arg, filt))
427 if sym:
427 except error.ParseError as e:
428 msg = (_("template filter '%s' is not compatible with keyword '%s'")
428 raise error.ParseError(bytes(e), hint=_formatfiltererror(arg, filt))
429 % (pycompat.sysbytes(filt.__name__), sym))
429
430 else:
430 def _formatfiltererror(arg, filt):
431 msg = (_("incompatible use of template filter '%s'")
431 fn = pycompat.sysbytes(filt.__name__)
432 % pycompat.sysbytes(filt.__name__))
432 sym = findsymbolicname(arg)
433 raise error.Abort(msg)
433 if not sym:
434 return _("incompatible use of template filter '%s'") % fn
435 return (_("template filter '%s' is not compatible with keyword '%s'")
436 % (fn, sym))
434
437
435 def runmap(context, mapping, data):
438 def runmap(context, mapping, data):
436 darg, targ = data
439 darg, targ = data
437 d = evalrawexp(context, mapping, darg)
440 d = evalrawexp(context, mapping, darg)
438 if util.safehasattr(d, 'itermaps'):
441 if util.safehasattr(d, 'itermaps'):
439 diter = d.itermaps()
442 diter = d.itermaps()
440 else:
443 else:
441 try:
444 try:
442 diter = iter(d)
445 diter = iter(d)
443 except TypeError:
446 except TypeError:
444 sym = findsymbolicname(darg)
447 sym = findsymbolicname(darg)
445 if sym:
448 if sym:
446 raise error.ParseError(_("keyword '%s' is not iterable") % sym)
449 raise error.ParseError(_("keyword '%s' is not iterable") % sym)
447 else:
450 else:
448 raise error.ParseError(_("%r is not iterable") % d)
451 raise error.ParseError(_("%r is not iterable") % d)
449
452
450 for i, v in enumerate(diter):
453 for i, v in enumerate(diter):
451 if isinstance(v, dict):
454 if isinstance(v, dict):
452 lm = context.overlaymap(mapping, v)
455 lm = context.overlaymap(mapping, v)
453 lm['index'] = i
456 lm['index'] = i
454 yield evalrawexp(context, lm, targ)
457 yield evalrawexp(context, lm, targ)
455 else:
458 else:
456 # v is not an iterable of dicts, this happen when 'key'
459 # v is not an iterable of dicts, this happen when 'key'
457 # has been fully expanded already and format is useless.
460 # has been fully expanded already and format is useless.
458 # If so, return the expanded value.
461 # If so, return the expanded value.
459 yield v
462 yield v
460
463
461 def runmember(context, mapping, data):
464 def runmember(context, mapping, data):
462 darg, memb = data
465 darg, memb = data
463 d = evalrawexp(context, mapping, darg)
466 d = evalrawexp(context, mapping, darg)
464 if util.safehasattr(d, 'tomap'):
467 if util.safehasattr(d, 'tomap'):
465 lm = context.overlaymap(mapping, d.tomap())
468 lm = context.overlaymap(mapping, d.tomap())
466 return runsymbol(context, lm, memb)
469 return runsymbol(context, lm, memb)
467 if util.safehasattr(d, 'get'):
470 if util.safehasattr(d, 'get'):
468 return getdictitem(d, memb)
471 return getdictitem(d, memb)
469
472
470 sym = findsymbolicname(darg)
473 sym = findsymbolicname(darg)
471 if sym:
474 if sym:
472 raise error.ParseError(_("keyword '%s' has no member") % sym)
475 raise error.ParseError(_("keyword '%s' has no member") % sym)
473 else:
476 else:
474 raise error.ParseError(_("%r has no member") % pycompat.bytestr(d))
477 raise error.ParseError(_("%r has no member") % pycompat.bytestr(d))
475
478
476 def runnegate(context, mapping, data):
479 def runnegate(context, mapping, data):
477 data = evalinteger(context, mapping, data,
480 data = evalinteger(context, mapping, data,
478 _('negation needs an integer argument'))
481 _('negation needs an integer argument'))
479 return -data
482 return -data
480
483
481 def runarithmetic(context, mapping, data):
484 def runarithmetic(context, mapping, data):
482 func, left, right = data
485 func, left, right = data
483 left = evalinteger(context, mapping, left,
486 left = evalinteger(context, mapping, left,
484 _('arithmetic only defined on integers'))
487 _('arithmetic only defined on integers'))
485 right = evalinteger(context, mapping, right,
488 right = evalinteger(context, mapping, right,
486 _('arithmetic only defined on integers'))
489 _('arithmetic only defined on integers'))
487 try:
490 try:
488 return func(left, right)
491 return func(left, right)
489 except ZeroDivisionError:
492 except ZeroDivisionError:
490 raise error.Abort(_('division by zero is not defined'))
493 raise error.Abort(_('division by zero is not defined'))
491
494
492 def getdictitem(dictarg, key):
495 def getdictitem(dictarg, key):
493 val = dictarg.get(key)
496 val = dictarg.get(key)
494 if val is None:
497 if val is None:
495 return
498 return
496 return wraphybridvalue(dictarg, key, val)
499 return wraphybridvalue(dictarg, key, val)
General Comments 0
You need to be logged in to leave comments. Login now