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