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