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