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