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