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