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