##// END OF EJS Templates
templater: drop hybrid-ness on unwrapvalue()...
Yuya Nishihara -
r38288:cf8d210d default
parent child Browse files
Show More
@@ -1,823 +1,810 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 contains(self, context, mapping, item):
41 def contains(self, context, mapping, item):
42 """Test if the specified item is in self
42 """Test if the specified item is in self
43
43
44 The item argument may be a wrapped object.
44 The item argument may be a wrapped object.
45 """
45 """
46
46
47 @abc.abstractmethod
47 @abc.abstractmethod
48 def getmember(self, context, mapping, key):
48 def getmember(self, context, mapping, key):
49 """Return a member item for the specified key
49 """Return a member item for the specified key
50
50
51 The key argument may be a wrapped object.
51 The key argument may be a wrapped object.
52 A returned object may be either a wrapped object or a pure value
52 A returned object may be either a wrapped object or a pure value
53 depending on the self type.
53 depending on the self type.
54 """
54 """
55
55
56 @abc.abstractmethod
56 @abc.abstractmethod
57 def getmin(self, context, mapping):
57 def getmin(self, context, mapping):
58 """Return the smallest item, which may be either a wrapped or a pure
58 """Return the smallest item, which may be either a wrapped or a pure
59 value depending on the self type"""
59 value depending on the self type"""
60
60
61 @abc.abstractmethod
61 @abc.abstractmethod
62 def getmax(self, context, mapping):
62 def getmax(self, context, mapping):
63 """Return the largest item, which may be either a wrapped or a pure
63 """Return the largest item, which may be either a wrapped or a pure
64 value depending on the self type"""
64 value depending on the self type"""
65
65
66 @abc.abstractmethod
66 @abc.abstractmethod
67 def itermaps(self, context):
67 def itermaps(self, context):
68 """Yield each template mapping"""
68 """Yield each template mapping"""
69
69
70 @abc.abstractmethod
70 @abc.abstractmethod
71 def join(self, context, mapping, sep):
71 def join(self, context, mapping, sep):
72 """Join items with the separator; Returns a bytes or (possibly nested)
72 """Join items with the separator; Returns a bytes or (possibly nested)
73 generator of bytes
73 generator of bytes
74
74
75 A pre-configured template may be rendered per item if this container
75 A pre-configured template may be rendered per item if this container
76 holds unprintable items.
76 holds unprintable items.
77 """
77 """
78
78
79 @abc.abstractmethod
79 @abc.abstractmethod
80 def show(self, context, mapping):
80 def show(self, context, mapping):
81 """Return a bytes or (possibly nested) generator of bytes representing
81 """Return a bytes or (possibly nested) generator of bytes representing
82 the underlying object
82 the underlying object
83
83
84 A pre-configured template may be rendered if the underlying object is
84 A pre-configured template may be rendered if the underlying object is
85 not printable.
85 not printable.
86 """
86 """
87
87
88 @abc.abstractmethod
88 @abc.abstractmethod
89 def tovalue(self, context, mapping):
89 def tovalue(self, context, mapping):
90 """Move the inner value object out or create a value representation
90 """Move the inner value object out or create a value representation
91
91
92 A returned value must be serializable by templaterfilters.json().
92 A returned value must be serializable by templaterfilters.json().
93 """
93 """
94
94
95 class wrappedbytes(wrapped):
95 class wrappedbytes(wrapped):
96 """Wrapper for byte string"""
96 """Wrapper for byte string"""
97
97
98 def __init__(self, value):
98 def __init__(self, value):
99 self._value = value
99 self._value = value
100
100
101 def contains(self, context, mapping, item):
101 def contains(self, context, mapping, item):
102 item = stringify(context, mapping, item)
102 item = stringify(context, mapping, item)
103 return item in self._value
103 return item in self._value
104
104
105 def getmember(self, context, mapping, key):
105 def getmember(self, context, mapping, key):
106 raise error.ParseError(_('%r is not a dictionary')
106 raise error.ParseError(_('%r is not a dictionary')
107 % pycompat.bytestr(self._value))
107 % pycompat.bytestr(self._value))
108
108
109 def getmin(self, context, mapping):
109 def getmin(self, context, mapping):
110 return self._getby(context, mapping, min)
110 return self._getby(context, mapping, min)
111
111
112 def getmax(self, context, mapping):
112 def getmax(self, context, mapping):
113 return self._getby(context, mapping, max)
113 return self._getby(context, mapping, max)
114
114
115 def _getby(self, context, mapping, func):
115 def _getby(self, context, mapping, func):
116 if not self._value:
116 if not self._value:
117 raise error.ParseError(_('empty string'))
117 raise error.ParseError(_('empty string'))
118 return func(pycompat.iterbytestr(self._value))
118 return func(pycompat.iterbytestr(self._value))
119
119
120 def itermaps(self, context):
120 def itermaps(self, context):
121 raise error.ParseError(_('%r is not iterable of mappings')
121 raise error.ParseError(_('%r is not iterable of mappings')
122 % pycompat.bytestr(self._value))
122 % pycompat.bytestr(self._value))
123
123
124 def join(self, context, mapping, sep):
124 def join(self, context, mapping, sep):
125 return joinitems(pycompat.iterbytestr(self._value), sep)
125 return joinitems(pycompat.iterbytestr(self._value), sep)
126
126
127 def show(self, context, mapping):
127 def show(self, context, mapping):
128 return self._value
128 return self._value
129
129
130 def tovalue(self, context, mapping):
130 def tovalue(self, context, mapping):
131 return self._value
131 return self._value
132
132
133 class wrappedvalue(wrapped):
133 class wrappedvalue(wrapped):
134 """Generic wrapper for pure non-list/dict/bytes value"""
134 """Generic wrapper for pure non-list/dict/bytes value"""
135
135
136 def __init__(self, value):
136 def __init__(self, value):
137 self._value = value
137 self._value = value
138
138
139 def contains(self, context, mapping, item):
139 def contains(self, context, mapping, item):
140 raise error.ParseError(_("%r is not iterable") % self._value)
140 raise error.ParseError(_("%r is not iterable") % self._value)
141
141
142 def getmember(self, context, mapping, key):
142 def getmember(self, context, mapping, key):
143 raise error.ParseError(_('%r is not a dictionary') % self._value)
143 raise error.ParseError(_('%r is not a dictionary') % self._value)
144
144
145 def getmin(self, context, mapping):
145 def getmin(self, context, mapping):
146 raise error.ParseError(_("%r is not iterable") % self._value)
146 raise error.ParseError(_("%r is not iterable") % self._value)
147
147
148 def getmax(self, context, mapping):
148 def getmax(self, context, mapping):
149 raise error.ParseError(_("%r is not iterable") % self._value)
149 raise error.ParseError(_("%r is not iterable") % self._value)
150
150
151 def itermaps(self, context):
151 def itermaps(self, context):
152 raise error.ParseError(_('%r is not iterable of mappings')
152 raise error.ParseError(_('%r is not iterable of mappings')
153 % self._value)
153 % self._value)
154
154
155 def join(self, context, mapping, sep):
155 def join(self, context, mapping, sep):
156 raise error.ParseError(_('%r is not iterable') % self._value)
156 raise error.ParseError(_('%r is not iterable') % self._value)
157
157
158 def show(self, context, mapping):
158 def show(self, context, mapping):
159 return pycompat.bytestr(self._value)
159 return pycompat.bytestr(self._value)
160
160
161 def tovalue(self, context, mapping):
161 def tovalue(self, context, mapping):
162 return self._value
162 return self._value
163
163
164 # stub for representing a date type; may be a real date type that can
164 # stub for representing a date type; may be a real date type that can
165 # provide a readable string value
165 # provide a readable string value
166 class date(object):
166 class date(object):
167 pass
167 pass
168
168
169 class hybrid(wrapped):
169 class hybrid(wrapped):
170 """Wrapper for list or dict to support legacy template
170 """Wrapper for list or dict to support legacy template
171
171
172 This class allows us to handle both:
172 This class allows us to handle both:
173 - "{files}" (legacy command-line-specific list hack) and
173 - "{files}" (legacy command-line-specific list hack) and
174 - "{files % '{file}\n'}" (hgweb-style with inlining and function support)
174 - "{files % '{file}\n'}" (hgweb-style with inlining and function support)
175 and to access raw values:
175 and to access raw values:
176 - "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
176 - "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
177 - "{get(extras, key)}"
177 - "{get(extras, key)}"
178 - "{files|json}"
178 - "{files|json}"
179 """
179 """
180
180
181 def __init__(self, gen, values, makemap, joinfmt, keytype=None):
181 def __init__(self, gen, values, makemap, joinfmt, keytype=None):
182 self._gen = gen # generator or function returning generator
182 self._gen = gen # generator or function returning generator
183 self._values = values
183 self._values = values
184 self._makemap = makemap
184 self._makemap = makemap
185 self._joinfmt = joinfmt
185 self._joinfmt = joinfmt
186 self._keytype = keytype # hint for 'x in y' where type(x) is unresolved
186 self._keytype = keytype # hint for 'x in y' where type(x) is unresolved
187
187
188 def contains(self, context, mapping, item):
188 def contains(self, context, mapping, item):
189 item = unwrapastype(context, mapping, item, self._keytype)
189 item = unwrapastype(context, mapping, item, self._keytype)
190 return item in self._values
190 return item in self._values
191
191
192 def getmember(self, context, mapping, key):
192 def getmember(self, context, mapping, key):
193 # TODO: maybe split hybrid list/dict types?
193 # TODO: maybe split hybrid list/dict types?
194 if not util.safehasattr(self._values, 'get'):
194 if not util.safehasattr(self._values, 'get'):
195 raise error.ParseError(_('not a dictionary'))
195 raise error.ParseError(_('not a dictionary'))
196 key = unwrapastype(context, mapping, key, self._keytype)
196 key = unwrapastype(context, mapping, key, self._keytype)
197 return self._wrapvalue(key, self._values.get(key))
197 return self._wrapvalue(key, self._values.get(key))
198
198
199 def getmin(self, context, mapping):
199 def getmin(self, context, mapping):
200 return self._getby(context, mapping, min)
200 return self._getby(context, mapping, min)
201
201
202 def getmax(self, context, mapping):
202 def getmax(self, context, mapping):
203 return self._getby(context, mapping, max)
203 return self._getby(context, mapping, max)
204
204
205 def _getby(self, context, mapping, func):
205 def _getby(self, context, mapping, func):
206 if not self._values:
206 if not self._values:
207 raise error.ParseError(_('empty sequence'))
207 raise error.ParseError(_('empty sequence'))
208 val = func(self._values)
208 val = func(self._values)
209 return self._wrapvalue(val, val)
209 return self._wrapvalue(val, val)
210
210
211 def _wrapvalue(self, key, val):
211 def _wrapvalue(self, key, val):
212 if val is None:
212 if val is None:
213 return
213 return
214 if util.safehasattr(val, '_makemap'):
214 if util.safehasattr(val, '_makemap'):
215 # a nested hybrid list/dict, which has its own way of map operation
215 # a nested hybrid list/dict, which has its own way of map operation
216 return val
216 return val
217 return mappable(None, key, val, self._makemap)
217 return mappable(None, key, val, self._makemap)
218
218
219 def itermaps(self, context):
219 def itermaps(self, context):
220 makemap = self._makemap
220 makemap = self._makemap
221 for x in self._values:
221 for x in self._values:
222 yield makemap(x)
222 yield makemap(x)
223
223
224 def join(self, context, mapping, sep):
224 def join(self, context, mapping, sep):
225 # TODO: switch gen to (context, mapping) API?
225 # TODO: switch gen to (context, mapping) API?
226 return joinitems((self._joinfmt(x) for x in self._values), sep)
226 return joinitems((self._joinfmt(x) for x in self._values), sep)
227
227
228 def show(self, context, mapping):
228 def show(self, context, mapping):
229 # TODO: switch gen to (context, mapping) API?
229 # TODO: switch gen to (context, mapping) API?
230 gen = self._gen
230 gen = self._gen
231 if gen is None:
231 if gen is None:
232 return self.join(context, mapping, ' ')
232 return self.join(context, mapping, ' ')
233 if callable(gen):
233 if callable(gen):
234 return gen()
234 return gen()
235 return gen
235 return gen
236
236
237 def tovalue(self, context, mapping):
237 def tovalue(self, context, mapping):
238 # TODO: return self._values and get rid of proxy methods
238 # TODO: make it non-recursive for trivial lists/dicts
239 return self
239 xs = self._values
240
240 if util.safehasattr(xs, 'get'):
241 def __contains__(self, x):
241 return {k: unwrapvalue(context, mapping, v)
242 return x in self._values
242 for k, v in xs.iteritems()}
243 def __getitem__(self, key):
243 return [unwrapvalue(context, mapping, x) for x in xs]
244 return self._values[key]
245 def __len__(self):
246 return len(self._values)
247 def __iter__(self):
248 return iter(self._values)
249 def __getattr__(self, name):
250 if name not in (r'get', r'items', r'iteritems', r'iterkeys',
251 r'itervalues', r'keys', r'values'):
252 raise AttributeError(name)
253 return getattr(self._values, name)
254
244
255 class mappable(wrapped):
245 class mappable(wrapped):
256 """Wrapper for non-list/dict object to support map operation
246 """Wrapper for non-list/dict object to support map operation
257
247
258 This class allows us to handle both:
248 This class allows us to handle both:
259 - "{manifest}"
249 - "{manifest}"
260 - "{manifest % '{rev}:{node}'}"
250 - "{manifest % '{rev}:{node}'}"
261 - "{manifest.rev}"
251 - "{manifest.rev}"
262
263 Unlike a hybrid, this does not simulate the behavior of the underling
264 value.
265 """
252 """
266
253
267 def __init__(self, gen, key, value, makemap):
254 def __init__(self, gen, key, value, makemap):
268 self._gen = gen # generator or function returning generator
255 self._gen = gen # generator or function returning generator
269 self._key = key
256 self._key = key
270 self._value = value # may be generator of strings
257 self._value = value # may be generator of strings
271 self._makemap = makemap
258 self._makemap = makemap
272
259
273 def tomap(self):
260 def tomap(self):
274 return self._makemap(self._key)
261 return self._makemap(self._key)
275
262
276 def contains(self, context, mapping, item):
263 def contains(self, context, mapping, item):
277 w = makewrapped(context, mapping, self._value)
264 w = makewrapped(context, mapping, self._value)
278 return w.contains(context, mapping, item)
265 return w.contains(context, mapping, item)
279
266
280 def getmember(self, context, mapping, key):
267 def getmember(self, context, mapping, key):
281 w = makewrapped(context, mapping, self._value)
268 w = makewrapped(context, mapping, self._value)
282 return w.getmember(context, mapping, key)
269 return w.getmember(context, mapping, key)
283
270
284 def getmin(self, context, mapping):
271 def getmin(self, context, mapping):
285 w = makewrapped(context, mapping, self._value)
272 w = makewrapped(context, mapping, self._value)
286 return w.getmin(context, mapping)
273 return w.getmin(context, mapping)
287
274
288 def getmax(self, context, mapping):
275 def getmax(self, context, mapping):
289 w = makewrapped(context, mapping, self._value)
276 w = makewrapped(context, mapping, self._value)
290 return w.getmax(context, mapping)
277 return w.getmax(context, mapping)
291
278
292 def itermaps(self, context):
279 def itermaps(self, context):
293 yield self.tomap()
280 yield self.tomap()
294
281
295 def join(self, context, mapping, sep):
282 def join(self, context, mapping, sep):
296 w = makewrapped(context, mapping, self._value)
283 w = makewrapped(context, mapping, self._value)
297 return w.join(context, mapping, sep)
284 return w.join(context, mapping, sep)
298
285
299 def show(self, context, mapping):
286 def show(self, context, mapping):
300 # TODO: switch gen to (context, mapping) API?
287 # TODO: switch gen to (context, mapping) API?
301 gen = self._gen
288 gen = self._gen
302 if gen is None:
289 if gen is None:
303 return pycompat.bytestr(self._value)
290 return pycompat.bytestr(self._value)
304 if callable(gen):
291 if callable(gen):
305 return gen()
292 return gen()
306 return gen
293 return gen
307
294
308 def tovalue(self, context, mapping):
295 def tovalue(self, context, mapping):
309 return _unthunk(context, mapping, self._value)
296 return _unthunk(context, mapping, self._value)
310
297
311 class _mappingsequence(wrapped):
298 class _mappingsequence(wrapped):
312 """Wrapper for sequence of template mappings
299 """Wrapper for sequence of template mappings
313
300
314 This represents an inner template structure (i.e. a list of dicts),
301 This represents an inner template structure (i.e. a list of dicts),
315 which can also be rendered by the specified named/literal template.
302 which can also be rendered by the specified named/literal template.
316
303
317 Template mappings may be nested.
304 Template mappings may be nested.
318 """
305 """
319
306
320 def __init__(self, name=None, tmpl=None, sep=''):
307 def __init__(self, name=None, tmpl=None, sep=''):
321 if name is not None and tmpl is not None:
308 if name is not None and tmpl is not None:
322 raise error.ProgrammingError('name and tmpl are mutually exclusive')
309 raise error.ProgrammingError('name and tmpl are mutually exclusive')
323 self._name = name
310 self._name = name
324 self._tmpl = tmpl
311 self._tmpl = tmpl
325 self._defaultsep = sep
312 self._defaultsep = sep
326
313
327 def contains(self, context, mapping, item):
314 def contains(self, context, mapping, item):
328 raise error.ParseError(_('not comparable'))
315 raise error.ParseError(_('not comparable'))
329
316
330 def getmember(self, context, mapping, key):
317 def getmember(self, context, mapping, key):
331 raise error.ParseError(_('not a dictionary'))
318 raise error.ParseError(_('not a dictionary'))
332
319
333 def getmin(self, context, mapping):
320 def getmin(self, context, mapping):
334 raise error.ParseError(_('not comparable'))
321 raise error.ParseError(_('not comparable'))
335
322
336 def getmax(self, context, mapping):
323 def getmax(self, context, mapping):
337 raise error.ParseError(_('not comparable'))
324 raise error.ParseError(_('not comparable'))
338
325
339 def join(self, context, mapping, sep):
326 def join(self, context, mapping, sep):
340 mapsiter = _iteroverlaymaps(context, mapping, self.itermaps(context))
327 mapsiter = _iteroverlaymaps(context, mapping, self.itermaps(context))
341 if self._name:
328 if self._name:
342 itemiter = (context.process(self._name, m) for m in mapsiter)
329 itemiter = (context.process(self._name, m) for m in mapsiter)
343 elif self._tmpl:
330 elif self._tmpl:
344 itemiter = (context.expand(self._tmpl, m) for m in mapsiter)
331 itemiter = (context.expand(self._tmpl, m) for m in mapsiter)
345 else:
332 else:
346 raise error.ParseError(_('not displayable without template'))
333 raise error.ParseError(_('not displayable without template'))
347 return joinitems(itemiter, sep)
334 return joinitems(itemiter, sep)
348
335
349 def show(self, context, mapping):
336 def show(self, context, mapping):
350 return self.join(context, mapping, self._defaultsep)
337 return self.join(context, mapping, self._defaultsep)
351
338
352 def tovalue(self, context, mapping):
339 def tovalue(self, context, mapping):
353 knownres = context.knownresourcekeys()
340 knownres = context.knownresourcekeys()
354 items = []
341 items = []
355 for nm in self.itermaps(context):
342 for nm in self.itermaps(context):
356 # drop internal resources (recursively) which shouldn't be displayed
343 # drop internal resources (recursively) which shouldn't be displayed
357 lm = context.overlaymap(mapping, nm)
344 lm = context.overlaymap(mapping, nm)
358 items.append({k: unwrapvalue(context, lm, v)
345 items.append({k: unwrapvalue(context, lm, v)
359 for k, v in nm.iteritems() if k not in knownres})
346 for k, v in nm.iteritems() if k not in knownres})
360 return items
347 return items
361
348
362 class mappinggenerator(_mappingsequence):
349 class mappinggenerator(_mappingsequence):
363 """Wrapper for generator of template mappings
350 """Wrapper for generator of template mappings
364
351
365 The function ``make(context, *args)`` should return a generator of
352 The function ``make(context, *args)`` should return a generator of
366 mapping dicts.
353 mapping dicts.
367 """
354 """
368
355
369 def __init__(self, make, args=(), name=None, tmpl=None, sep=''):
356 def __init__(self, make, args=(), name=None, tmpl=None, sep=''):
370 super(mappinggenerator, self).__init__(name, tmpl, sep)
357 super(mappinggenerator, self).__init__(name, tmpl, sep)
371 self._make = make
358 self._make = make
372 self._args = args
359 self._args = args
373
360
374 def itermaps(self, context):
361 def itermaps(self, context):
375 return self._make(context, *self._args)
362 return self._make(context, *self._args)
376
363
377 class mappinglist(_mappingsequence):
364 class mappinglist(_mappingsequence):
378 """Wrapper for list of template mappings"""
365 """Wrapper for list of template mappings"""
379
366
380 def __init__(self, mappings, name=None, tmpl=None, sep=''):
367 def __init__(self, mappings, name=None, tmpl=None, sep=''):
381 super(mappinglist, self).__init__(name, tmpl, sep)
368 super(mappinglist, self).__init__(name, tmpl, sep)
382 self._mappings = mappings
369 self._mappings = mappings
383
370
384 def itermaps(self, context):
371 def itermaps(self, context):
385 return iter(self._mappings)
372 return iter(self._mappings)
386
373
387 class mappedgenerator(wrapped):
374 class mappedgenerator(wrapped):
388 """Wrapper for generator of strings which acts as a list
375 """Wrapper for generator of strings which acts as a list
389
376
390 The function ``make(context, *args)`` should return a generator of
377 The function ``make(context, *args)`` should return a generator of
391 byte strings, or a generator of (possibly nested) generators of byte
378 byte strings, or a generator of (possibly nested) generators of byte
392 strings (i.e. a generator for a list of byte strings.)
379 strings (i.e. a generator for a list of byte strings.)
393 """
380 """
394
381
395 def __init__(self, make, args=()):
382 def __init__(self, make, args=()):
396 self._make = make
383 self._make = make
397 self._args = args
384 self._args = args
398
385
399 def contains(self, context, mapping, item):
386 def contains(self, context, mapping, item):
400 item = stringify(context, mapping, item)
387 item = stringify(context, mapping, item)
401 return item in self.tovalue(context, mapping)
388 return item in self.tovalue(context, mapping)
402
389
403 def _gen(self, context):
390 def _gen(self, context):
404 return self._make(context, *self._args)
391 return self._make(context, *self._args)
405
392
406 def getmember(self, context, mapping, key):
393 def getmember(self, context, mapping, key):
407 raise error.ParseError(_('not a dictionary'))
394 raise error.ParseError(_('not a dictionary'))
408
395
409 def getmin(self, context, mapping):
396 def getmin(self, context, mapping):
410 return self._getby(context, mapping, min)
397 return self._getby(context, mapping, min)
411
398
412 def getmax(self, context, mapping):
399 def getmax(self, context, mapping):
413 return self._getby(context, mapping, max)
400 return self._getby(context, mapping, max)
414
401
415 def _getby(self, context, mapping, func):
402 def _getby(self, context, mapping, func):
416 xs = self.tovalue(context, mapping)
403 xs = self.tovalue(context, mapping)
417 if not xs:
404 if not xs:
418 raise error.ParseError(_('empty sequence'))
405 raise error.ParseError(_('empty sequence'))
419 return func(xs)
406 return func(xs)
420
407
421 def itermaps(self, context):
408 def itermaps(self, context):
422 raise error.ParseError(_('list of strings is not mappable'))
409 raise error.ParseError(_('list of strings is not mappable'))
423
410
424 def join(self, context, mapping, sep):
411 def join(self, context, mapping, sep):
425 return joinitems(self._gen(context), sep)
412 return joinitems(self._gen(context), sep)
426
413
427 def show(self, context, mapping):
414 def show(self, context, mapping):
428 return self.join(context, mapping, '')
415 return self.join(context, mapping, '')
429
416
430 def tovalue(self, context, mapping):
417 def tovalue(self, context, mapping):
431 return [stringify(context, mapping, x) for x in self._gen(context)]
418 return [stringify(context, mapping, x) for x in self._gen(context)]
432
419
433 def hybriddict(data, key='key', value='value', fmt=None, gen=None):
420 def hybriddict(data, key='key', value='value', fmt=None, gen=None):
434 """Wrap data to support both dict-like and string-like operations"""
421 """Wrap data to support both dict-like and string-like operations"""
435 prefmt = pycompat.identity
422 prefmt = pycompat.identity
436 if fmt is None:
423 if fmt is None:
437 fmt = '%s=%s'
424 fmt = '%s=%s'
438 prefmt = pycompat.bytestr
425 prefmt = pycompat.bytestr
439 return hybrid(gen, data, lambda k: {key: k, value: data[k]},
426 return hybrid(gen, data, lambda k: {key: k, value: data[k]},
440 lambda k: fmt % (prefmt(k), prefmt(data[k])))
427 lambda k: fmt % (prefmt(k), prefmt(data[k])))
441
428
442 def hybridlist(data, name, fmt=None, gen=None):
429 def hybridlist(data, name, fmt=None, gen=None):
443 """Wrap data to support both list-like and string-like operations"""
430 """Wrap data to support both list-like and string-like operations"""
444 prefmt = pycompat.identity
431 prefmt = pycompat.identity
445 if fmt is None:
432 if fmt is None:
446 fmt = '%s'
433 fmt = '%s'
447 prefmt = pycompat.bytestr
434 prefmt = pycompat.bytestr
448 return hybrid(gen, data, lambda x: {name: x}, lambda x: fmt % prefmt(x))
435 return hybrid(gen, data, lambda x: {name: x}, lambda x: fmt % prefmt(x))
449
436
450 def unwraphybrid(context, mapping, thing):
437 def unwraphybrid(context, mapping, thing):
451 """Return an object which can be stringified possibly by using a legacy
438 """Return an object which can be stringified possibly by using a legacy
452 template"""
439 template"""
453 if not isinstance(thing, wrapped):
440 if not isinstance(thing, wrapped):
454 return thing
441 return thing
455 return thing.show(context, mapping)
442 return thing.show(context, mapping)
456
443
457 def compatdict(context, mapping, name, data, key='key', value='value',
444 def compatdict(context, mapping, name, data, key='key', value='value',
458 fmt=None, plural=None, separator=' '):
445 fmt=None, plural=None, separator=' '):
459 """Wrap data like hybriddict(), but also supports old-style list template
446 """Wrap data like hybriddict(), but also supports old-style list template
460
447
461 This exists for backward compatibility with the old-style template. Use
448 This exists for backward compatibility with the old-style template. Use
462 hybriddict() for new template keywords.
449 hybriddict() for new template keywords.
463 """
450 """
464 c = [{key: k, value: v} for k, v in data.iteritems()]
451 c = [{key: k, value: v} for k, v in data.iteritems()]
465 f = _showcompatlist(context, mapping, name, c, plural, separator)
452 f = _showcompatlist(context, mapping, name, c, plural, separator)
466 return hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
453 return hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
467
454
468 def compatlist(context, mapping, name, data, element=None, fmt=None,
455 def compatlist(context, mapping, name, data, element=None, fmt=None,
469 plural=None, separator=' '):
456 plural=None, separator=' '):
470 """Wrap data like hybridlist(), but also supports old-style list template
457 """Wrap data like hybridlist(), but also supports old-style list template
471
458
472 This exists for backward compatibility with the old-style template. Use
459 This exists for backward compatibility with the old-style template. Use
473 hybridlist() for new template keywords.
460 hybridlist() for new template keywords.
474 """
461 """
475 f = _showcompatlist(context, mapping, name, data, plural, separator)
462 f = _showcompatlist(context, mapping, name, data, plural, separator)
476 return hybridlist(data, name=element or name, fmt=fmt, gen=f)
463 return hybridlist(data, name=element or name, fmt=fmt, gen=f)
477
464
478 def _showcompatlist(context, mapping, name, values, plural=None, separator=' '):
465 def _showcompatlist(context, mapping, name, values, plural=None, separator=' '):
479 """Return a generator that renders old-style list template
466 """Return a generator that renders old-style list template
480
467
481 name is name of key in template map.
468 name is name of key in template map.
482 values is list of strings or dicts.
469 values is list of strings or dicts.
483 plural is plural of name, if not simply name + 's'.
470 plural is plural of name, if not simply name + 's'.
484 separator is used to join values as a string
471 separator is used to join values as a string
485
472
486 expansion works like this, given name 'foo'.
473 expansion works like this, given name 'foo'.
487
474
488 if values is empty, expand 'no_foos'.
475 if values is empty, expand 'no_foos'.
489
476
490 if 'foo' not in template map, return values as a string,
477 if 'foo' not in template map, return values as a string,
491 joined by 'separator'.
478 joined by 'separator'.
492
479
493 expand 'start_foos'.
480 expand 'start_foos'.
494
481
495 for each value, expand 'foo'. if 'last_foo' in template
482 for each value, expand 'foo'. if 'last_foo' in template
496 map, expand it instead of 'foo' for last key.
483 map, expand it instead of 'foo' for last key.
497
484
498 expand 'end_foos'.
485 expand 'end_foos'.
499 """
486 """
500 if not plural:
487 if not plural:
501 plural = name + 's'
488 plural = name + 's'
502 if not values:
489 if not values:
503 noname = 'no_' + plural
490 noname = 'no_' + plural
504 if context.preload(noname):
491 if context.preload(noname):
505 yield context.process(noname, mapping)
492 yield context.process(noname, mapping)
506 return
493 return
507 if not context.preload(name):
494 if not context.preload(name):
508 if isinstance(values[0], bytes):
495 if isinstance(values[0], bytes):
509 yield separator.join(values)
496 yield separator.join(values)
510 else:
497 else:
511 for v in values:
498 for v in values:
512 r = dict(v)
499 r = dict(v)
513 r.update(mapping)
500 r.update(mapping)
514 yield r
501 yield r
515 return
502 return
516 startname = 'start_' + plural
503 startname = 'start_' + plural
517 if context.preload(startname):
504 if context.preload(startname):
518 yield context.process(startname, mapping)
505 yield context.process(startname, mapping)
519 def one(v, tag=name):
506 def one(v, tag=name):
520 vmapping = {}
507 vmapping = {}
521 try:
508 try:
522 vmapping.update(v)
509 vmapping.update(v)
523 # Python 2 raises ValueError if the type of v is wrong. Python
510 # Python 2 raises ValueError if the type of v is wrong. Python
524 # 3 raises TypeError.
511 # 3 raises TypeError.
525 except (AttributeError, TypeError, ValueError):
512 except (AttributeError, TypeError, ValueError):
526 try:
513 try:
527 # Python 2 raises ValueError trying to destructure an e.g.
514 # Python 2 raises ValueError trying to destructure an e.g.
528 # bytes. Python 3 raises TypeError.
515 # bytes. Python 3 raises TypeError.
529 for a, b in v:
516 for a, b in v:
530 vmapping[a] = b
517 vmapping[a] = b
531 except (TypeError, ValueError):
518 except (TypeError, ValueError):
532 vmapping[name] = v
519 vmapping[name] = v
533 vmapping = context.overlaymap(mapping, vmapping)
520 vmapping = context.overlaymap(mapping, vmapping)
534 return context.process(tag, vmapping)
521 return context.process(tag, vmapping)
535 lastname = 'last_' + name
522 lastname = 'last_' + name
536 if context.preload(lastname):
523 if context.preload(lastname):
537 last = values.pop()
524 last = values.pop()
538 else:
525 else:
539 last = None
526 last = None
540 for v in values:
527 for v in values:
541 yield one(v)
528 yield one(v)
542 if last is not None:
529 if last is not None:
543 yield one(last, tag=lastname)
530 yield one(last, tag=lastname)
544 endname = 'end_' + plural
531 endname = 'end_' + plural
545 if context.preload(endname):
532 if context.preload(endname):
546 yield context.process(endname, mapping)
533 yield context.process(endname, mapping)
547
534
548 def flatten(context, mapping, thing):
535 def flatten(context, mapping, thing):
549 """Yield a single stream from a possibly nested set of iterators"""
536 """Yield a single stream from a possibly nested set of iterators"""
550 thing = unwraphybrid(context, mapping, thing)
537 thing = unwraphybrid(context, mapping, thing)
551 if isinstance(thing, bytes):
538 if isinstance(thing, bytes):
552 yield thing
539 yield thing
553 elif isinstance(thing, str):
540 elif isinstance(thing, str):
554 # We can only hit this on Python 3, and it's here to guard
541 # We can only hit this on Python 3, and it's here to guard
555 # against infinite recursion.
542 # against infinite recursion.
556 raise error.ProgrammingError('Mercurial IO including templates is done'
543 raise error.ProgrammingError('Mercurial IO including templates is done'
557 ' with bytes, not strings, got %r' % thing)
544 ' with bytes, not strings, got %r' % thing)
558 elif thing is None:
545 elif thing is None:
559 pass
546 pass
560 elif not util.safehasattr(thing, '__iter__'):
547 elif not util.safehasattr(thing, '__iter__'):
561 yield pycompat.bytestr(thing)
548 yield pycompat.bytestr(thing)
562 else:
549 else:
563 for i in thing:
550 for i in thing:
564 i = unwraphybrid(context, mapping, i)
551 i = unwraphybrid(context, mapping, i)
565 if isinstance(i, bytes):
552 if isinstance(i, bytes):
566 yield i
553 yield i
567 elif i is None:
554 elif i is None:
568 pass
555 pass
569 elif not util.safehasattr(i, '__iter__'):
556 elif not util.safehasattr(i, '__iter__'):
570 yield pycompat.bytestr(i)
557 yield pycompat.bytestr(i)
571 else:
558 else:
572 for j in flatten(context, mapping, i):
559 for j in flatten(context, mapping, i):
573 yield j
560 yield j
574
561
575 def stringify(context, mapping, thing):
562 def stringify(context, mapping, thing):
576 """Turn values into bytes by converting into text and concatenating them"""
563 """Turn values into bytes by converting into text and concatenating them"""
577 if isinstance(thing, bytes):
564 if isinstance(thing, bytes):
578 return thing # retain localstr to be round-tripped
565 return thing # retain localstr to be round-tripped
579 return b''.join(flatten(context, mapping, thing))
566 return b''.join(flatten(context, mapping, thing))
580
567
581 def findsymbolicname(arg):
568 def findsymbolicname(arg):
582 """Find symbolic name for the given compiled expression; returns None
569 """Find symbolic name for the given compiled expression; returns None
583 if nothing found reliably"""
570 if nothing found reliably"""
584 while True:
571 while True:
585 func, data = arg
572 func, data = arg
586 if func is runsymbol:
573 if func is runsymbol:
587 return data
574 return data
588 elif func is runfilter:
575 elif func is runfilter:
589 arg = data[0]
576 arg = data[0]
590 else:
577 else:
591 return None
578 return None
592
579
593 def _unthunk(context, mapping, thing):
580 def _unthunk(context, mapping, thing):
594 """Evaluate a lazy byte string into value"""
581 """Evaluate a lazy byte string into value"""
595 if not isinstance(thing, types.GeneratorType):
582 if not isinstance(thing, types.GeneratorType):
596 return thing
583 return thing
597 return stringify(context, mapping, thing)
584 return stringify(context, mapping, thing)
598
585
599 def evalrawexp(context, mapping, arg):
586 def evalrawexp(context, mapping, arg):
600 """Evaluate given argument as a bare template object which may require
587 """Evaluate given argument as a bare template object which may require
601 further processing (such as folding generator of strings)"""
588 further processing (such as folding generator of strings)"""
602 func, data = arg
589 func, data = arg
603 return func(context, mapping, data)
590 return func(context, mapping, data)
604
591
605 def evalwrapped(context, mapping, arg):
592 def evalwrapped(context, mapping, arg):
606 """Evaluate given argument to wrapped object"""
593 """Evaluate given argument to wrapped object"""
607 thing = evalrawexp(context, mapping, arg)
594 thing = evalrawexp(context, mapping, arg)
608 return makewrapped(context, mapping, thing)
595 return makewrapped(context, mapping, thing)
609
596
610 def makewrapped(context, mapping, thing):
597 def makewrapped(context, mapping, thing):
611 """Lift object to a wrapped type"""
598 """Lift object to a wrapped type"""
612 if isinstance(thing, wrapped):
599 if isinstance(thing, wrapped):
613 return thing
600 return thing
614 thing = _unthunk(context, mapping, thing)
601 thing = _unthunk(context, mapping, thing)
615 if isinstance(thing, bytes):
602 if isinstance(thing, bytes):
616 return wrappedbytes(thing)
603 return wrappedbytes(thing)
617 return wrappedvalue(thing)
604 return wrappedvalue(thing)
618
605
619 def evalfuncarg(context, mapping, arg):
606 def evalfuncarg(context, mapping, arg):
620 """Evaluate given argument as value type"""
607 """Evaluate given argument as value type"""
621 return unwrapvalue(context, mapping, evalrawexp(context, mapping, arg))
608 return unwrapvalue(context, mapping, evalrawexp(context, mapping, arg))
622
609
623 def unwrapvalue(context, mapping, thing):
610 def unwrapvalue(context, mapping, thing):
624 """Move the inner value object out of the wrapper"""
611 """Move the inner value object out of the wrapper"""
625 if isinstance(thing, wrapped):
612 if isinstance(thing, wrapped):
626 return thing.tovalue(context, mapping)
613 return thing.tovalue(context, mapping)
627 # evalrawexp() may return string, generator of strings or arbitrary object
614 # evalrawexp() may return string, generator of strings or arbitrary object
628 # such as date tuple, but filter does not want generator.
615 # such as date tuple, but filter does not want generator.
629 return _unthunk(context, mapping, thing)
616 return _unthunk(context, mapping, thing)
630
617
631 def evalboolean(context, mapping, arg):
618 def evalboolean(context, mapping, arg):
632 """Evaluate given argument as boolean, but also takes boolean literals"""
619 """Evaluate given argument as boolean, but also takes boolean literals"""
633 func, data = arg
620 func, data = arg
634 if func is runsymbol:
621 if func is runsymbol:
635 thing = func(context, mapping, data, default=None)
622 thing = func(context, mapping, data, default=None)
636 if thing is None:
623 if thing is None:
637 # not a template keyword, takes as a boolean literal
624 # not a template keyword, takes as a boolean literal
638 thing = stringutil.parsebool(data)
625 thing = stringutil.parsebool(data)
639 else:
626 else:
640 thing = func(context, mapping, data)
627 thing = func(context, mapping, data)
641 if isinstance(thing, wrapped):
628 if isinstance(thing, wrapped):
642 thing = thing.tovalue(context, mapping)
629 thing = thing.tovalue(context, mapping)
643 if isinstance(thing, bool):
630 if isinstance(thing, bool):
644 return thing
631 return thing
645 # other objects are evaluated as strings, which means 0 is True, but
632 # other objects are evaluated as strings, which means 0 is True, but
646 # empty dict/list should be False as they are expected to be ''
633 # empty dict/list should be False as they are expected to be ''
647 return bool(stringify(context, mapping, thing))
634 return bool(stringify(context, mapping, thing))
648
635
649 def evaldate(context, mapping, arg, err=None):
636 def evaldate(context, mapping, arg, err=None):
650 """Evaluate given argument as a date tuple or a date string; returns
637 """Evaluate given argument as a date tuple or a date string; returns
651 a (unixtime, offset) tuple"""
638 a (unixtime, offset) tuple"""
652 thing = evalrawexp(context, mapping, arg)
639 thing = evalrawexp(context, mapping, arg)
653 return unwrapdate(context, mapping, thing, err)
640 return unwrapdate(context, mapping, thing, err)
654
641
655 def unwrapdate(context, mapping, thing, err=None):
642 def unwrapdate(context, mapping, thing, err=None):
656 thing = unwrapvalue(context, mapping, thing)
643 thing = unwrapvalue(context, mapping, thing)
657 try:
644 try:
658 return dateutil.parsedate(thing)
645 return dateutil.parsedate(thing)
659 except AttributeError:
646 except AttributeError:
660 raise error.ParseError(err or _('not a date tuple nor a string'))
647 raise error.ParseError(err or _('not a date tuple nor a string'))
661 except error.ParseError:
648 except error.ParseError:
662 if not err:
649 if not err:
663 raise
650 raise
664 raise error.ParseError(err)
651 raise error.ParseError(err)
665
652
666 def evalinteger(context, mapping, arg, err=None):
653 def evalinteger(context, mapping, arg, err=None):
667 thing = evalrawexp(context, mapping, arg)
654 thing = evalrawexp(context, mapping, arg)
668 return unwrapinteger(context, mapping, thing, err)
655 return unwrapinteger(context, mapping, thing, err)
669
656
670 def unwrapinteger(context, mapping, thing, err=None):
657 def unwrapinteger(context, mapping, thing, err=None):
671 thing = unwrapvalue(context, mapping, thing)
658 thing = unwrapvalue(context, mapping, thing)
672 try:
659 try:
673 return int(thing)
660 return int(thing)
674 except (TypeError, ValueError):
661 except (TypeError, ValueError):
675 raise error.ParseError(err or _('not an integer'))
662 raise error.ParseError(err or _('not an integer'))
676
663
677 def evalstring(context, mapping, arg):
664 def evalstring(context, mapping, arg):
678 return stringify(context, mapping, evalrawexp(context, mapping, arg))
665 return stringify(context, mapping, evalrawexp(context, mapping, arg))
679
666
680 def evalstringliteral(context, mapping, arg):
667 def evalstringliteral(context, mapping, arg):
681 """Evaluate given argument as string template, but returns symbol name
668 """Evaluate given argument as string template, but returns symbol name
682 if it is unknown"""
669 if it is unknown"""
683 func, data = arg
670 func, data = arg
684 if func is runsymbol:
671 if func is runsymbol:
685 thing = func(context, mapping, data, default=data)
672 thing = func(context, mapping, data, default=data)
686 else:
673 else:
687 thing = func(context, mapping, data)
674 thing = func(context, mapping, data)
688 return stringify(context, mapping, thing)
675 return stringify(context, mapping, thing)
689
676
690 _unwrapfuncbytype = {
677 _unwrapfuncbytype = {
691 None: unwrapvalue,
678 None: unwrapvalue,
692 bytes: stringify,
679 bytes: stringify,
693 date: unwrapdate,
680 date: unwrapdate,
694 int: unwrapinteger,
681 int: unwrapinteger,
695 }
682 }
696
683
697 def unwrapastype(context, mapping, thing, typ):
684 def unwrapastype(context, mapping, thing, typ):
698 """Move the inner value object out of the wrapper and coerce its type"""
685 """Move the inner value object out of the wrapper and coerce its type"""
699 try:
686 try:
700 f = _unwrapfuncbytype[typ]
687 f = _unwrapfuncbytype[typ]
701 except KeyError:
688 except KeyError:
702 raise error.ProgrammingError('invalid type specified: %r' % typ)
689 raise error.ProgrammingError('invalid type specified: %r' % typ)
703 return f(context, mapping, thing)
690 return f(context, mapping, thing)
704
691
705 def runinteger(context, mapping, data):
692 def runinteger(context, mapping, data):
706 return int(data)
693 return int(data)
707
694
708 def runstring(context, mapping, data):
695 def runstring(context, mapping, data):
709 return data
696 return data
710
697
711 def _recursivesymbolblocker(key):
698 def _recursivesymbolblocker(key):
712 def showrecursion(**args):
699 def showrecursion(**args):
713 raise error.Abort(_("recursive reference '%s' in template") % key)
700 raise error.Abort(_("recursive reference '%s' in template") % key)
714 return showrecursion
701 return showrecursion
715
702
716 def runsymbol(context, mapping, key, default=''):
703 def runsymbol(context, mapping, key, default=''):
717 v = context.symbol(mapping, key)
704 v = context.symbol(mapping, key)
718 if v is None:
705 if v is None:
719 # put poison to cut recursion. we can't move this to parsing phase
706 # put poison to cut recursion. we can't move this to parsing phase
720 # because "x = {x}" is allowed if "x" is a keyword. (issue4758)
707 # because "x = {x}" is allowed if "x" is a keyword. (issue4758)
721 safemapping = mapping.copy()
708 safemapping = mapping.copy()
722 safemapping[key] = _recursivesymbolblocker(key)
709 safemapping[key] = _recursivesymbolblocker(key)
723 try:
710 try:
724 v = context.process(key, safemapping)
711 v = context.process(key, safemapping)
725 except TemplateNotFound:
712 except TemplateNotFound:
726 v = default
713 v = default
727 if callable(v) and getattr(v, '_requires', None) is None:
714 if callable(v) and getattr(v, '_requires', None) is None:
728 # old templatekw: expand all keywords and resources
715 # old templatekw: expand all keywords and resources
729 # (TODO: deprecate this after porting web template keywords to new API)
716 # (TODO: deprecate this after porting web template keywords to new API)
730 props = {k: context._resources.lookup(context, mapping, k)
717 props = {k: context._resources.lookup(context, mapping, k)
731 for k in context._resources.knownkeys()}
718 for k in context._resources.knownkeys()}
732 # pass context to _showcompatlist() through templatekw._showlist()
719 # pass context to _showcompatlist() through templatekw._showlist()
733 props['templ'] = context
720 props['templ'] = context
734 props.update(mapping)
721 props.update(mapping)
735 return v(**pycompat.strkwargs(props))
722 return v(**pycompat.strkwargs(props))
736 if callable(v):
723 if callable(v):
737 # new templatekw
724 # new templatekw
738 try:
725 try:
739 return v(context, mapping)
726 return v(context, mapping)
740 except ResourceUnavailable:
727 except ResourceUnavailable:
741 # unsupported keyword is mapped to empty just like unknown keyword
728 # unsupported keyword is mapped to empty just like unknown keyword
742 return None
729 return None
743 return v
730 return v
744
731
745 def runtemplate(context, mapping, template):
732 def runtemplate(context, mapping, template):
746 for arg in template:
733 for arg in template:
747 yield evalrawexp(context, mapping, arg)
734 yield evalrawexp(context, mapping, arg)
748
735
749 def runfilter(context, mapping, data):
736 def runfilter(context, mapping, data):
750 arg, filt = data
737 arg, filt = data
751 thing = evalrawexp(context, mapping, arg)
738 thing = evalrawexp(context, mapping, arg)
752 intype = getattr(filt, '_intype', None)
739 intype = getattr(filt, '_intype', None)
753 try:
740 try:
754 thing = unwrapastype(context, mapping, thing, intype)
741 thing = unwrapastype(context, mapping, thing, intype)
755 return filt(thing)
742 return filt(thing)
756 except error.ParseError as e:
743 except error.ParseError as e:
757 raise error.ParseError(bytes(e), hint=_formatfiltererror(arg, filt))
744 raise error.ParseError(bytes(e), hint=_formatfiltererror(arg, filt))
758
745
759 def _formatfiltererror(arg, filt):
746 def _formatfiltererror(arg, filt):
760 fn = pycompat.sysbytes(filt.__name__)
747 fn = pycompat.sysbytes(filt.__name__)
761 sym = findsymbolicname(arg)
748 sym = findsymbolicname(arg)
762 if not sym:
749 if not sym:
763 return _("incompatible use of template filter '%s'") % fn
750 return _("incompatible use of template filter '%s'") % fn
764 return (_("template filter '%s' is not compatible with keyword '%s'")
751 return (_("template filter '%s' is not compatible with keyword '%s'")
765 % (fn, sym))
752 % (fn, sym))
766
753
767 def _iteroverlaymaps(context, origmapping, newmappings):
754 def _iteroverlaymaps(context, origmapping, newmappings):
768 """Generate combined mappings from the original mapping and an iterable
755 """Generate combined mappings from the original mapping and an iterable
769 of partial mappings to override the original"""
756 of partial mappings to override the original"""
770 for i, nm in enumerate(newmappings):
757 for i, nm in enumerate(newmappings):
771 lm = context.overlaymap(origmapping, nm)
758 lm = context.overlaymap(origmapping, nm)
772 lm['index'] = i
759 lm['index'] = i
773 yield lm
760 yield lm
774
761
775 def _applymap(context, mapping, d, targ):
762 def _applymap(context, mapping, d, targ):
776 for lm in _iteroverlaymaps(context, mapping, d.itermaps(context)):
763 for lm in _iteroverlaymaps(context, mapping, d.itermaps(context)):
777 yield evalrawexp(context, lm, targ)
764 yield evalrawexp(context, lm, targ)
778
765
779 def runmap(context, mapping, data):
766 def runmap(context, mapping, data):
780 darg, targ = data
767 darg, targ = data
781 d = evalwrapped(context, mapping, darg)
768 d = evalwrapped(context, mapping, darg)
782 return mappedgenerator(_applymap, args=(mapping, d, targ))
769 return mappedgenerator(_applymap, args=(mapping, d, targ))
783
770
784 def runmember(context, mapping, data):
771 def runmember(context, mapping, data):
785 darg, memb = data
772 darg, memb = data
786 d = evalwrapped(context, mapping, darg)
773 d = evalwrapped(context, mapping, darg)
787 if util.safehasattr(d, 'tomap'):
774 if util.safehasattr(d, 'tomap'):
788 lm = context.overlaymap(mapping, d.tomap())
775 lm = context.overlaymap(mapping, d.tomap())
789 return runsymbol(context, lm, memb)
776 return runsymbol(context, lm, memb)
790 try:
777 try:
791 return d.getmember(context, mapping, memb)
778 return d.getmember(context, mapping, memb)
792 except error.ParseError as err:
779 except error.ParseError as err:
793 sym = findsymbolicname(darg)
780 sym = findsymbolicname(darg)
794 if not sym:
781 if not sym:
795 raise
782 raise
796 hint = _("keyword '%s' does not support member operation") % sym
783 hint = _("keyword '%s' does not support member operation") % sym
797 raise error.ParseError(bytes(err), hint=hint)
784 raise error.ParseError(bytes(err), hint=hint)
798
785
799 def runnegate(context, mapping, data):
786 def runnegate(context, mapping, data):
800 data = evalinteger(context, mapping, data,
787 data = evalinteger(context, mapping, data,
801 _('negation needs an integer argument'))
788 _('negation needs an integer argument'))
802 return -data
789 return -data
803
790
804 def runarithmetic(context, mapping, data):
791 def runarithmetic(context, mapping, data):
805 func, left, right = data
792 func, left, right = data
806 left = evalinteger(context, mapping, left,
793 left = evalinteger(context, mapping, left,
807 _('arithmetic only defined on integers'))
794 _('arithmetic only defined on integers'))
808 right = evalinteger(context, mapping, right,
795 right = evalinteger(context, mapping, right,
809 _('arithmetic only defined on integers'))
796 _('arithmetic only defined on integers'))
810 try:
797 try:
811 return func(left, right)
798 return func(left, right)
812 except ZeroDivisionError:
799 except ZeroDivisionError:
813 raise error.Abort(_('division by zero is not defined'))
800 raise error.Abort(_('division by zero is not defined'))
814
801
815 def joinitems(itemiter, sep):
802 def joinitems(itemiter, sep):
816 """Join items with the separator; Returns generator of bytes"""
803 """Join items with the separator; Returns generator of bytes"""
817 first = True
804 first = True
818 for x in itemiter:
805 for x in itemiter:
819 if first:
806 if first:
820 first = False
807 first = False
821 elif sep:
808 elif sep:
822 yield sep
809 yield sep
823 yield x
810 yield x
General Comments 0
You need to be logged in to leave comments. Login now