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