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