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