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