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