##// END OF EJS Templates
safehasattr: pass attribute name as string instead of bytes...
marmoute -
r51503:a6a17f79 default
parent child Browse files
Show More
@@ -1,1166 +1,1166 b''
1 # templateutil.py - utility for template evaluation
1 # templateutil.py - utility for template evaluation
2 #
2 #
3 # Copyright 2005, 2006 Olivia Mackall <olivia@selenic.com>
3 # Copyright 2005, 2006 Olivia Mackall <olivia@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
8
9 import abc
9 import abc
10 import types
10 import types
11
11
12 from .i18n import _
12 from .i18n import _
13 from .pycompat import getattr
13 from .pycompat import getattr
14 from . import (
14 from . import (
15 error,
15 error,
16 pycompat,
16 pycompat,
17 smartset,
17 smartset,
18 util,
18 util,
19 )
19 )
20 from .utils import (
20 from .utils import (
21 dateutil,
21 dateutil,
22 stringutil,
22 stringutil,
23 )
23 )
24
24
25
25
26 class ResourceUnavailable(error.Abort):
26 class ResourceUnavailable(error.Abort):
27 pass
27 pass
28
28
29
29
30 class TemplateNotFound(error.Abort):
30 class TemplateNotFound(error.Abort):
31 pass
31 pass
32
32
33
33
34 class wrapped: # pytype: disable=ignored-metaclass
34 class wrapped: # pytype: disable=ignored-metaclass
35 """Object requiring extra conversion prior to displaying or processing
35 """Object requiring extra conversion prior to displaying or processing
36 as value
36 as value
37
37
38 Use unwrapvalue() or unwrapastype() to obtain the inner object.
38 Use unwrapvalue() or unwrapastype() to obtain the inner object.
39 """
39 """
40
40
41 __metaclass__ = abc.ABCMeta
41 __metaclass__ = abc.ABCMeta
42
42
43 @abc.abstractmethod
43 @abc.abstractmethod
44 def contains(self, context, mapping, item):
44 def contains(self, context, mapping, item):
45 """Test if the specified item is in self
45 """Test if the specified item is in self
46
46
47 The item argument may be a wrapped object.
47 The item argument may be a wrapped object.
48 """
48 """
49
49
50 @abc.abstractmethod
50 @abc.abstractmethod
51 def getmember(self, context, mapping, key):
51 def getmember(self, context, mapping, key):
52 """Return a member item for the specified key
52 """Return a member item for the specified key
53
53
54 The key argument may be a wrapped object.
54 The key argument may be a wrapped object.
55 A returned object may be either a wrapped object or a pure value
55 A returned object may be either a wrapped object or a pure value
56 depending on the self type.
56 depending on the self type.
57 """
57 """
58
58
59 @abc.abstractmethod
59 @abc.abstractmethod
60 def getmin(self, context, mapping):
60 def getmin(self, context, mapping):
61 """Return the smallest item, which may be either a wrapped or a pure
61 """Return the smallest item, which may be either a wrapped or a pure
62 value depending on the self type"""
62 value depending on the self type"""
63
63
64 @abc.abstractmethod
64 @abc.abstractmethod
65 def getmax(self, context, mapping):
65 def getmax(self, context, mapping):
66 """Return the largest item, which may be either a wrapped or a pure
66 """Return the largest item, which may be either a wrapped or a pure
67 value depending on the self type"""
67 value depending on the self type"""
68
68
69 @abc.abstractmethod
69 @abc.abstractmethod
70 def filter(self, context, mapping, select):
70 def filter(self, context, mapping, select):
71 """Return new container of the same type which includes only the
71 """Return new container of the same type which includes only the
72 selected elements
72 selected elements
73
73
74 select() takes each item as a wrapped object and returns True/False.
74 select() takes each item as a wrapped object and returns True/False.
75 """
75 """
76
76
77 @abc.abstractmethod
77 @abc.abstractmethod
78 def itermaps(self, context):
78 def itermaps(self, context):
79 """Yield each template mapping"""
79 """Yield each template mapping"""
80
80
81 @abc.abstractmethod
81 @abc.abstractmethod
82 def join(self, context, mapping, sep):
82 def join(self, context, mapping, sep):
83 """Join items with the separator; Returns a bytes or (possibly nested)
83 """Join items with the separator; Returns a bytes or (possibly nested)
84 generator of bytes
84 generator of bytes
85
85
86 A pre-configured template may be rendered per item if this container
86 A pre-configured template may be rendered per item if this container
87 holds unprintable items.
87 holds unprintable items.
88 """
88 """
89
89
90 @abc.abstractmethod
90 @abc.abstractmethod
91 def show(self, context, mapping):
91 def show(self, context, mapping):
92 """Return a bytes or (possibly nested) generator of bytes representing
92 """Return a bytes or (possibly nested) generator of bytes representing
93 the underlying object
93 the underlying object
94
94
95 A pre-configured template may be rendered if the underlying object is
95 A pre-configured template may be rendered if the underlying object is
96 not printable.
96 not printable.
97 """
97 """
98
98
99 @abc.abstractmethod
99 @abc.abstractmethod
100 def tobool(self, context, mapping):
100 def tobool(self, context, mapping):
101 """Return a boolean representation of the inner value"""
101 """Return a boolean representation of the inner value"""
102
102
103 @abc.abstractmethod
103 @abc.abstractmethod
104 def tovalue(self, context, mapping):
104 def tovalue(self, context, mapping):
105 """Move the inner value object out or create a value representation
105 """Move the inner value object out or create a value representation
106
106
107 A returned value must be serializable by templaterfilters.json().
107 A returned value must be serializable by templaterfilters.json().
108 """
108 """
109
109
110
110
111 class mappable: # pytype: disable=ignored-metaclass
111 class mappable: # pytype: disable=ignored-metaclass
112 """Object which can be converted to a single template mapping"""
112 """Object which can be converted to a single template mapping"""
113
113
114 __metaclass__ = abc.ABCMeta
114 __metaclass__ = abc.ABCMeta
115
115
116 def itermaps(self, context):
116 def itermaps(self, context):
117 yield self.tomap(context)
117 yield self.tomap(context)
118
118
119 @abc.abstractmethod
119 @abc.abstractmethod
120 def tomap(self, context):
120 def tomap(self, context):
121 """Create a single template mapping representing this"""
121 """Create a single template mapping representing this"""
122
122
123
123
124 class wrappedbytes(wrapped):
124 class wrappedbytes(wrapped):
125 """Wrapper for byte string"""
125 """Wrapper for byte string"""
126
126
127 def __init__(self, value):
127 def __init__(self, value):
128 self._value = value
128 self._value = value
129
129
130 def contains(self, context, mapping, item):
130 def contains(self, context, mapping, item):
131 item = stringify(context, mapping, item)
131 item = stringify(context, mapping, item)
132 return item in self._value
132 return item in self._value
133
133
134 def getmember(self, context, mapping, key):
134 def getmember(self, context, mapping, key):
135 raise error.ParseError(
135 raise error.ParseError(
136 _(b'%r is not a dictionary') % pycompat.bytestr(self._value)
136 _(b'%r is not a dictionary') % pycompat.bytestr(self._value)
137 )
137 )
138
138
139 def getmin(self, context, mapping):
139 def getmin(self, context, mapping):
140 return self._getby(context, mapping, min)
140 return self._getby(context, mapping, min)
141
141
142 def getmax(self, context, mapping):
142 def getmax(self, context, mapping):
143 return self._getby(context, mapping, max)
143 return self._getby(context, mapping, max)
144
144
145 def _getby(self, context, mapping, func):
145 def _getby(self, context, mapping, func):
146 if not self._value:
146 if not self._value:
147 raise error.ParseError(_(b'empty string'))
147 raise error.ParseError(_(b'empty string'))
148 return func(pycompat.iterbytestr(self._value))
148 return func(pycompat.iterbytestr(self._value))
149
149
150 def filter(self, context, mapping, select):
150 def filter(self, context, mapping, select):
151 raise error.ParseError(
151 raise error.ParseError(
152 _(b'%r is not filterable') % pycompat.bytestr(self._value)
152 _(b'%r is not filterable') % pycompat.bytestr(self._value)
153 )
153 )
154
154
155 def itermaps(self, context):
155 def itermaps(self, context):
156 raise error.ParseError(
156 raise error.ParseError(
157 _(b'%r is not iterable of mappings') % pycompat.bytestr(self._value)
157 _(b'%r is not iterable of mappings') % pycompat.bytestr(self._value)
158 )
158 )
159
159
160 def join(self, context, mapping, sep):
160 def join(self, context, mapping, sep):
161 return joinitems(pycompat.iterbytestr(self._value), sep)
161 return joinitems(pycompat.iterbytestr(self._value), sep)
162
162
163 def show(self, context, mapping):
163 def show(self, context, mapping):
164 return self._value
164 return self._value
165
165
166 def tobool(self, context, mapping):
166 def tobool(self, context, mapping):
167 return bool(self._value)
167 return bool(self._value)
168
168
169 def tovalue(self, context, mapping):
169 def tovalue(self, context, mapping):
170 return self._value
170 return self._value
171
171
172
172
173 class wrappedvalue(wrapped):
173 class wrappedvalue(wrapped):
174 """Generic wrapper for pure non-list/dict/bytes value"""
174 """Generic wrapper for pure non-list/dict/bytes value"""
175
175
176 def __init__(self, value):
176 def __init__(self, value):
177 self._value = value
177 self._value = value
178
178
179 def contains(self, context, mapping, item):
179 def contains(self, context, mapping, item):
180 raise error.ParseError(_(b"%r is not iterable") % self._value)
180 raise error.ParseError(_(b"%r is not iterable") % self._value)
181
181
182 def getmember(self, context, mapping, key):
182 def getmember(self, context, mapping, key):
183 raise error.ParseError(_(b'%r is not a dictionary') % self._value)
183 raise error.ParseError(_(b'%r is not a dictionary') % self._value)
184
184
185 def getmin(self, context, mapping):
185 def getmin(self, context, mapping):
186 raise error.ParseError(_(b"%r is not iterable") % self._value)
186 raise error.ParseError(_(b"%r is not iterable") % self._value)
187
187
188 def getmax(self, context, mapping):
188 def getmax(self, context, mapping):
189 raise error.ParseError(_(b"%r is not iterable") % self._value)
189 raise error.ParseError(_(b"%r is not iterable") % self._value)
190
190
191 def filter(self, context, mapping, select):
191 def filter(self, context, mapping, select):
192 raise error.ParseError(_(b"%r is not iterable") % self._value)
192 raise error.ParseError(_(b"%r is not iterable") % self._value)
193
193
194 def itermaps(self, context):
194 def itermaps(self, context):
195 raise error.ParseError(
195 raise error.ParseError(
196 _(b'%r is not iterable of mappings') % self._value
196 _(b'%r is not iterable of mappings') % self._value
197 )
197 )
198
198
199 def join(self, context, mapping, sep):
199 def join(self, context, mapping, sep):
200 raise error.ParseError(_(b'%r is not iterable') % self._value)
200 raise error.ParseError(_(b'%r is not iterable') % self._value)
201
201
202 def show(self, context, mapping):
202 def show(self, context, mapping):
203 if self._value is None:
203 if self._value is None:
204 return b''
204 return b''
205 return pycompat.bytestr(self._value)
205 return pycompat.bytestr(self._value)
206
206
207 def tobool(self, context, mapping):
207 def tobool(self, context, mapping):
208 if self._value is None:
208 if self._value is None:
209 return False
209 return False
210 if isinstance(self._value, bool):
210 if isinstance(self._value, bool):
211 return self._value
211 return self._value
212 # otherwise evaluate as string, which means 0 is True
212 # otherwise evaluate as string, which means 0 is True
213 return bool(pycompat.bytestr(self._value))
213 return bool(pycompat.bytestr(self._value))
214
214
215 def tovalue(self, context, mapping):
215 def tovalue(self, context, mapping):
216 return self._value
216 return self._value
217
217
218
218
219 class date(mappable, wrapped):
219 class date(mappable, wrapped):
220 """Wrapper for date tuple"""
220 """Wrapper for date tuple"""
221
221
222 def __init__(self, value, showfmt=b'%d %d'):
222 def __init__(self, value, showfmt=b'%d %d'):
223 # value may be (float, int), but public interface shouldn't support
223 # value may be (float, int), but public interface shouldn't support
224 # floating-point timestamp
224 # floating-point timestamp
225 self._unixtime, self._tzoffset = map(int, value)
225 self._unixtime, self._tzoffset = map(int, value)
226 self._showfmt = showfmt
226 self._showfmt = showfmt
227
227
228 def contains(self, context, mapping, item):
228 def contains(self, context, mapping, item):
229 raise error.ParseError(_(b'date is not iterable'))
229 raise error.ParseError(_(b'date is not iterable'))
230
230
231 def getmember(self, context, mapping, key):
231 def getmember(self, context, mapping, key):
232 raise error.ParseError(_(b'date is not a dictionary'))
232 raise error.ParseError(_(b'date is not a dictionary'))
233
233
234 def getmin(self, context, mapping):
234 def getmin(self, context, mapping):
235 raise error.ParseError(_(b'date is not iterable'))
235 raise error.ParseError(_(b'date is not iterable'))
236
236
237 def getmax(self, context, mapping):
237 def getmax(self, context, mapping):
238 raise error.ParseError(_(b'date is not iterable'))
238 raise error.ParseError(_(b'date is not iterable'))
239
239
240 def filter(self, context, mapping, select):
240 def filter(self, context, mapping, select):
241 raise error.ParseError(_(b'date is not iterable'))
241 raise error.ParseError(_(b'date is not iterable'))
242
242
243 def join(self, context, mapping, sep):
243 def join(self, context, mapping, sep):
244 raise error.ParseError(_(b"date is not iterable"))
244 raise error.ParseError(_(b"date is not iterable"))
245
245
246 def show(self, context, mapping):
246 def show(self, context, mapping):
247 return self._showfmt % (self._unixtime, self._tzoffset)
247 return self._showfmt % (self._unixtime, self._tzoffset)
248
248
249 def tomap(self, context):
249 def tomap(self, context):
250 return {b'unixtime': self._unixtime, b'tzoffset': self._tzoffset}
250 return {b'unixtime': self._unixtime, b'tzoffset': self._tzoffset}
251
251
252 def tobool(self, context, mapping):
252 def tobool(self, context, mapping):
253 return True
253 return True
254
254
255 def tovalue(self, context, mapping):
255 def tovalue(self, context, mapping):
256 return (self._unixtime, self._tzoffset)
256 return (self._unixtime, self._tzoffset)
257
257
258
258
259 class hybrid(wrapped):
259 class hybrid(wrapped):
260 """Wrapper for list or dict to support legacy template
260 """Wrapper for list or dict to support legacy template
261
261
262 This class allows us to handle both:
262 This class allows us to handle both:
263 - "{files}" (legacy command-line-specific list hack) and
263 - "{files}" (legacy command-line-specific list hack) and
264 - "{files % '{file}\n'}" (hgweb-style with inlining and function support)
264 - "{files % '{file}\n'}" (hgweb-style with inlining and function support)
265 and to access raw values:
265 and to access raw values:
266 - "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
266 - "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
267 - "{get(extras, key)}"
267 - "{get(extras, key)}"
268 - "{files|json}"
268 - "{files|json}"
269 """
269 """
270
270
271 def __init__(self, gen, values, makemap, joinfmt, keytype=None):
271 def __init__(self, gen, values, makemap, joinfmt, keytype=None):
272 self._gen = gen # generator or function returning generator
272 self._gen = gen # generator or function returning generator
273 self._values = values
273 self._values = values
274 self._makemap = makemap
274 self._makemap = makemap
275 self._joinfmt = joinfmt
275 self._joinfmt = joinfmt
276 self._keytype = keytype # hint for 'x in y' where type(x) is unresolved
276 self._keytype = keytype # hint for 'x in y' where type(x) is unresolved
277
277
278 def contains(self, context, mapping, item):
278 def contains(self, context, mapping, item):
279 item = unwrapastype(context, mapping, item, self._keytype)
279 item = unwrapastype(context, mapping, item, self._keytype)
280 return item in self._values
280 return item in self._values
281
281
282 def getmember(self, context, mapping, key):
282 def getmember(self, context, mapping, key):
283 # TODO: maybe split hybrid list/dict types?
283 # TODO: maybe split hybrid list/dict types?
284 if not util.safehasattr(self._values, b'get'):
284 if not util.safehasattr(self._values, 'get'):
285 raise error.ParseError(_(b'not a dictionary'))
285 raise error.ParseError(_(b'not a dictionary'))
286 key = unwrapastype(context, mapping, key, self._keytype)
286 key = unwrapastype(context, mapping, key, self._keytype)
287 return self._wrapvalue(key, self._values.get(key))
287 return self._wrapvalue(key, self._values.get(key))
288
288
289 def getmin(self, context, mapping):
289 def getmin(self, context, mapping):
290 return self._getby(context, mapping, min)
290 return self._getby(context, mapping, min)
291
291
292 def getmax(self, context, mapping):
292 def getmax(self, context, mapping):
293 return self._getby(context, mapping, max)
293 return self._getby(context, mapping, max)
294
294
295 def _getby(self, context, mapping, func):
295 def _getby(self, context, mapping, func):
296 if not self._values:
296 if not self._values:
297 raise error.ParseError(_(b'empty sequence'))
297 raise error.ParseError(_(b'empty sequence'))
298 val = func(self._values)
298 val = func(self._values)
299 return self._wrapvalue(val, val)
299 return self._wrapvalue(val, val)
300
300
301 def _wrapvalue(self, key, val):
301 def _wrapvalue(self, key, val):
302 if val is None:
302 if val is None:
303 return
303 return
304 if util.safehasattr(val, b'_makemap'):
304 if util.safehasattr(val, b'_makemap'):
305 # a nested hybrid list/dict, which has its own way of map operation
305 # a nested hybrid list/dict, which has its own way of map operation
306 return val
306 return val
307 return hybriditem(None, key, val, self._makemap)
307 return hybriditem(None, key, val, self._makemap)
308
308
309 def filter(self, context, mapping, select):
309 def filter(self, context, mapping, select):
310 if util.safehasattr(self._values, b'get'):
310 if util.safehasattr(self._values, b'get'):
311 values = {
311 values = {
312 k: v
312 k: v
313 for k, v in self._values.items()
313 for k, v in self._values.items()
314 if select(self._wrapvalue(k, v))
314 if select(self._wrapvalue(k, v))
315 }
315 }
316 else:
316 else:
317 values = [v for v in self._values if select(self._wrapvalue(v, v))]
317 values = [v for v in self._values if select(self._wrapvalue(v, v))]
318 return hybrid(None, values, self._makemap, self._joinfmt, self._keytype)
318 return hybrid(None, values, self._makemap, self._joinfmt, self._keytype)
319
319
320 def itermaps(self, context):
320 def itermaps(self, context):
321 makemap = self._makemap
321 makemap = self._makemap
322 for x in self._values:
322 for x in self._values:
323 yield makemap(x)
323 yield makemap(x)
324
324
325 def join(self, context, mapping, sep):
325 def join(self, context, mapping, sep):
326 # TODO: switch gen to (context, mapping) API?
326 # TODO: switch gen to (context, mapping) API?
327 return joinitems((self._joinfmt(x) for x in self._values), sep)
327 return joinitems((self._joinfmt(x) for x in self._values), sep)
328
328
329 def show(self, context, mapping):
329 def show(self, context, mapping):
330 # TODO: switch gen to (context, mapping) API?
330 # TODO: switch gen to (context, mapping) API?
331 gen = self._gen
331 gen = self._gen
332 if gen is None:
332 if gen is None:
333 return self.join(context, mapping, b' ')
333 return self.join(context, mapping, b' ')
334 if callable(gen):
334 if callable(gen):
335 return gen()
335 return gen()
336 return gen
336 return gen
337
337
338 def tobool(self, context, mapping):
338 def tobool(self, context, mapping):
339 return bool(self._values)
339 return bool(self._values)
340
340
341 def tovalue(self, context, mapping):
341 def tovalue(self, context, mapping):
342 # TODO: make it non-recursive for trivial lists/dicts
342 # TODO: make it non-recursive for trivial lists/dicts
343 xs = self._values
343 xs = self._values
344 if util.safehasattr(xs, b'get'):
344 if util.safehasattr(xs, b'get'):
345 return {k: unwrapvalue(context, mapping, v) for k, v in xs.items()}
345 return {k: unwrapvalue(context, mapping, v) for k, v in xs.items()}
346 return [unwrapvalue(context, mapping, x) for x in xs]
346 return [unwrapvalue(context, mapping, x) for x in xs]
347
347
348
348
349 class hybriditem(mappable, wrapped):
349 class hybriditem(mappable, wrapped):
350 """Wrapper for non-list/dict object to support map operation
350 """Wrapper for non-list/dict object to support map operation
351
351
352 This class allows us to handle both:
352 This class allows us to handle both:
353 - "{manifest}"
353 - "{manifest}"
354 - "{manifest % '{rev}:{node}'}"
354 - "{manifest % '{rev}:{node}'}"
355 - "{manifest.rev}"
355 - "{manifest.rev}"
356 """
356 """
357
357
358 def __init__(self, gen, key, value, makemap):
358 def __init__(self, gen, key, value, makemap):
359 self._gen = gen # generator or function returning generator
359 self._gen = gen # generator or function returning generator
360 self._key = key
360 self._key = key
361 self._value = value # may be generator of strings
361 self._value = value # may be generator of strings
362 self._makemap = makemap
362 self._makemap = makemap
363
363
364 def tomap(self, context):
364 def tomap(self, context):
365 return self._makemap(self._key)
365 return self._makemap(self._key)
366
366
367 def contains(self, context, mapping, item):
367 def contains(self, context, mapping, item):
368 w = makewrapped(context, mapping, self._value)
368 w = makewrapped(context, mapping, self._value)
369 return w.contains(context, mapping, item)
369 return w.contains(context, mapping, item)
370
370
371 def getmember(self, context, mapping, key):
371 def getmember(self, context, mapping, key):
372 w = makewrapped(context, mapping, self._value)
372 w = makewrapped(context, mapping, self._value)
373 return w.getmember(context, mapping, key)
373 return w.getmember(context, mapping, key)
374
374
375 def getmin(self, context, mapping):
375 def getmin(self, context, mapping):
376 w = makewrapped(context, mapping, self._value)
376 w = makewrapped(context, mapping, self._value)
377 return w.getmin(context, mapping)
377 return w.getmin(context, mapping)
378
378
379 def getmax(self, context, mapping):
379 def getmax(self, context, mapping):
380 w = makewrapped(context, mapping, self._value)
380 w = makewrapped(context, mapping, self._value)
381 return w.getmax(context, mapping)
381 return w.getmax(context, mapping)
382
382
383 def filter(self, context, mapping, select):
383 def filter(self, context, mapping, select):
384 w = makewrapped(context, mapping, self._value)
384 w = makewrapped(context, mapping, self._value)
385 return w.filter(context, mapping, select)
385 return w.filter(context, mapping, select)
386
386
387 def join(self, context, mapping, sep):
387 def join(self, context, mapping, sep):
388 w = makewrapped(context, mapping, self._value)
388 w = makewrapped(context, mapping, self._value)
389 return w.join(context, mapping, sep)
389 return w.join(context, mapping, sep)
390
390
391 def show(self, context, mapping):
391 def show(self, context, mapping):
392 # TODO: switch gen to (context, mapping) API?
392 # TODO: switch gen to (context, mapping) API?
393 gen = self._gen
393 gen = self._gen
394 if gen is None:
394 if gen is None:
395 return pycompat.bytestr(self._value)
395 return pycompat.bytestr(self._value)
396 if callable(gen):
396 if callable(gen):
397 return gen()
397 return gen()
398 return gen
398 return gen
399
399
400 def tobool(self, context, mapping):
400 def tobool(self, context, mapping):
401 w = makewrapped(context, mapping, self._value)
401 w = makewrapped(context, mapping, self._value)
402 return w.tobool(context, mapping)
402 return w.tobool(context, mapping)
403
403
404 def tovalue(self, context, mapping):
404 def tovalue(self, context, mapping):
405 return _unthunk(context, mapping, self._value)
405 return _unthunk(context, mapping, self._value)
406
406
407
407
408 class revslist(wrapped):
408 class revslist(wrapped):
409 """Wrapper for a smartset (a list/set of revision numbers)
409 """Wrapper for a smartset (a list/set of revision numbers)
410
410
411 If name specified, the revs will be rendered with the old-style list
411 If name specified, the revs will be rendered with the old-style list
412 template of the given name by default.
412 template of the given name by default.
413
413
414 The cachekey provides a hint to cache further computation on this
414 The cachekey provides a hint to cache further computation on this
415 smartset. If the underlying smartset is dynamically created, the cachekey
415 smartset. If the underlying smartset is dynamically created, the cachekey
416 should be None.
416 should be None.
417 """
417 """
418
418
419 def __init__(self, repo, revs, name=None, cachekey=None):
419 def __init__(self, repo, revs, name=None, cachekey=None):
420 assert isinstance(revs, smartset.abstractsmartset)
420 assert isinstance(revs, smartset.abstractsmartset)
421 self._repo = repo
421 self._repo = repo
422 self._revs = revs
422 self._revs = revs
423 self._name = name
423 self._name = name
424 self.cachekey = cachekey
424 self.cachekey = cachekey
425
425
426 def contains(self, context, mapping, item):
426 def contains(self, context, mapping, item):
427 rev = unwrapinteger(context, mapping, item)
427 rev = unwrapinteger(context, mapping, item)
428 return rev in self._revs
428 return rev in self._revs
429
429
430 def getmember(self, context, mapping, key):
430 def getmember(self, context, mapping, key):
431 raise error.ParseError(_(b'not a dictionary'))
431 raise error.ParseError(_(b'not a dictionary'))
432
432
433 def getmin(self, context, mapping):
433 def getmin(self, context, mapping):
434 makehybriditem = self._makehybriditemfunc()
434 makehybriditem = self._makehybriditemfunc()
435 return makehybriditem(self._revs.min())
435 return makehybriditem(self._revs.min())
436
436
437 def getmax(self, context, mapping):
437 def getmax(self, context, mapping):
438 makehybriditem = self._makehybriditemfunc()
438 makehybriditem = self._makehybriditemfunc()
439 return makehybriditem(self._revs.max())
439 return makehybriditem(self._revs.max())
440
440
441 def filter(self, context, mapping, select):
441 def filter(self, context, mapping, select):
442 makehybriditem = self._makehybriditemfunc()
442 makehybriditem = self._makehybriditemfunc()
443 frevs = self._revs.filter(lambda r: select(makehybriditem(r)))
443 frevs = self._revs.filter(lambda r: select(makehybriditem(r)))
444 # once filtered, no need to support old-style list template
444 # once filtered, no need to support old-style list template
445 return revslist(self._repo, frevs, name=None)
445 return revslist(self._repo, frevs, name=None)
446
446
447 def itermaps(self, context):
447 def itermaps(self, context):
448 makemap = self._makemapfunc()
448 makemap = self._makemapfunc()
449 for r in self._revs:
449 for r in self._revs:
450 yield makemap(r)
450 yield makemap(r)
451
451
452 def _makehybriditemfunc(self):
452 def _makehybriditemfunc(self):
453 makemap = self._makemapfunc()
453 makemap = self._makemapfunc()
454 return lambda r: hybriditem(None, r, r, makemap)
454 return lambda r: hybriditem(None, r, r, makemap)
455
455
456 def _makemapfunc(self):
456 def _makemapfunc(self):
457 repo = self._repo
457 repo = self._repo
458 name = self._name
458 name = self._name
459 if name:
459 if name:
460 return lambda r: {name: r, b'ctx': repo[r]}
460 return lambda r: {name: r, b'ctx': repo[r]}
461 else:
461 else:
462 return lambda r: {b'ctx': repo[r]}
462 return lambda r: {b'ctx': repo[r]}
463
463
464 def join(self, context, mapping, sep):
464 def join(self, context, mapping, sep):
465 return joinitems(self._revs, sep)
465 return joinitems(self._revs, sep)
466
466
467 def show(self, context, mapping):
467 def show(self, context, mapping):
468 if self._name:
468 if self._name:
469 srevs = [b'%d' % r for r in self._revs]
469 srevs = [b'%d' % r for r in self._revs]
470 return _showcompatlist(context, mapping, self._name, srevs)
470 return _showcompatlist(context, mapping, self._name, srevs)
471 else:
471 else:
472 return self.join(context, mapping, b' ')
472 return self.join(context, mapping, b' ')
473
473
474 def tobool(self, context, mapping):
474 def tobool(self, context, mapping):
475 return bool(self._revs)
475 return bool(self._revs)
476
476
477 def tovalue(self, context, mapping):
477 def tovalue(self, context, mapping):
478 return self._revs
478 return self._revs
479
479
480
480
481 class _mappingsequence(wrapped):
481 class _mappingsequence(wrapped):
482 """Wrapper for sequence of template mappings
482 """Wrapper for sequence of template mappings
483
483
484 This represents an inner template structure (i.e. a list of dicts),
484 This represents an inner template structure (i.e. a list of dicts),
485 which can also be rendered by the specified named/literal template.
485 which can also be rendered by the specified named/literal template.
486
486
487 Template mappings may be nested.
487 Template mappings may be nested.
488 """
488 """
489
489
490 def __init__(self, name=None, tmpl=None, sep=b''):
490 def __init__(self, name=None, tmpl=None, sep=b''):
491 if name is not None and tmpl is not None:
491 if name is not None and tmpl is not None:
492 raise error.ProgrammingError(
492 raise error.ProgrammingError(
493 b'name and tmpl are mutually exclusive'
493 b'name and tmpl are mutually exclusive'
494 )
494 )
495 self._name = name
495 self._name = name
496 self._tmpl = tmpl
496 self._tmpl = tmpl
497 self._defaultsep = sep
497 self._defaultsep = sep
498
498
499 def contains(self, context, mapping, item):
499 def contains(self, context, mapping, item):
500 raise error.ParseError(_(b'not comparable'))
500 raise error.ParseError(_(b'not comparable'))
501
501
502 def getmember(self, context, mapping, key):
502 def getmember(self, context, mapping, key):
503 raise error.ParseError(_(b'not a dictionary'))
503 raise error.ParseError(_(b'not a dictionary'))
504
504
505 def getmin(self, context, mapping):
505 def getmin(self, context, mapping):
506 raise error.ParseError(_(b'not comparable'))
506 raise error.ParseError(_(b'not comparable'))
507
507
508 def getmax(self, context, mapping):
508 def getmax(self, context, mapping):
509 raise error.ParseError(_(b'not comparable'))
509 raise error.ParseError(_(b'not comparable'))
510
510
511 def filter(self, context, mapping, select):
511 def filter(self, context, mapping, select):
512 # implement if necessary; we'll need a wrapped type for a mapping dict
512 # implement if necessary; we'll need a wrapped type for a mapping dict
513 raise error.ParseError(_(b'not filterable without template'))
513 raise error.ParseError(_(b'not filterable without template'))
514
514
515 def join(self, context, mapping, sep):
515 def join(self, context, mapping, sep):
516 mapsiter = _iteroverlaymaps(context, mapping, self.itermaps(context))
516 mapsiter = _iteroverlaymaps(context, mapping, self.itermaps(context))
517 if self._name:
517 if self._name:
518 itemiter = (context.process(self._name, m) for m in mapsiter)
518 itemiter = (context.process(self._name, m) for m in mapsiter)
519 elif self._tmpl:
519 elif self._tmpl:
520 itemiter = (context.expand(self._tmpl, m) for m in mapsiter)
520 itemiter = (context.expand(self._tmpl, m) for m in mapsiter)
521 else:
521 else:
522 raise error.ParseError(_(b'not displayable without template'))
522 raise error.ParseError(_(b'not displayable without template'))
523 return joinitems(itemiter, sep)
523 return joinitems(itemiter, sep)
524
524
525 def show(self, context, mapping):
525 def show(self, context, mapping):
526 return self.join(context, mapping, self._defaultsep)
526 return self.join(context, mapping, self._defaultsep)
527
527
528 def tovalue(self, context, mapping):
528 def tovalue(self, context, mapping):
529 knownres = context.knownresourcekeys()
529 knownres = context.knownresourcekeys()
530 items = []
530 items = []
531 for nm in self.itermaps(context):
531 for nm in self.itermaps(context):
532 # drop internal resources (recursively) which shouldn't be displayed
532 # drop internal resources (recursively) which shouldn't be displayed
533 lm = context.overlaymap(mapping, nm)
533 lm = context.overlaymap(mapping, nm)
534 items.append(
534 items.append(
535 {
535 {
536 k: unwrapvalue(context, lm, v)
536 k: unwrapvalue(context, lm, v)
537 for k, v in nm.items()
537 for k, v in nm.items()
538 if k not in knownres
538 if k not in knownres
539 }
539 }
540 )
540 )
541 return items
541 return items
542
542
543
543
544 class mappinggenerator(_mappingsequence):
544 class mappinggenerator(_mappingsequence):
545 """Wrapper for generator of template mappings
545 """Wrapper for generator of template mappings
546
546
547 The function ``make(context, *args)`` should return a generator of
547 The function ``make(context, *args)`` should return a generator of
548 mapping dicts.
548 mapping dicts.
549 """
549 """
550
550
551 def __init__(self, make, args=(), name=None, tmpl=None, sep=b''):
551 def __init__(self, make, args=(), name=None, tmpl=None, sep=b''):
552 super(mappinggenerator, self).__init__(name, tmpl, sep)
552 super(mappinggenerator, self).__init__(name, tmpl, sep)
553 self._make = make
553 self._make = make
554 self._args = args
554 self._args = args
555
555
556 def itermaps(self, context):
556 def itermaps(self, context):
557 return self._make(context, *self._args)
557 return self._make(context, *self._args)
558
558
559 def tobool(self, context, mapping):
559 def tobool(self, context, mapping):
560 return _nonempty(self.itermaps(context))
560 return _nonempty(self.itermaps(context))
561
561
562
562
563 class mappinglist(_mappingsequence):
563 class mappinglist(_mappingsequence):
564 """Wrapper for list of template mappings"""
564 """Wrapper for list of template mappings"""
565
565
566 def __init__(self, mappings, name=None, tmpl=None, sep=b''):
566 def __init__(self, mappings, name=None, tmpl=None, sep=b''):
567 super(mappinglist, self).__init__(name, tmpl, sep)
567 super(mappinglist, self).__init__(name, tmpl, sep)
568 self._mappings = mappings
568 self._mappings = mappings
569
569
570 def itermaps(self, context):
570 def itermaps(self, context):
571 return iter(self._mappings)
571 return iter(self._mappings)
572
572
573 def tobool(self, context, mapping):
573 def tobool(self, context, mapping):
574 return bool(self._mappings)
574 return bool(self._mappings)
575
575
576
576
577 class mappingdict(mappable, _mappingsequence):
577 class mappingdict(mappable, _mappingsequence):
578 """Wrapper for a single template mapping
578 """Wrapper for a single template mapping
579
579
580 This isn't a sequence in a way that the underlying dict won't be iterated
580 This isn't a sequence in a way that the underlying dict won't be iterated
581 as a dict, but shares most of the _mappingsequence functions.
581 as a dict, but shares most of the _mappingsequence functions.
582 """
582 """
583
583
584 def __init__(self, mapping, name=None, tmpl=None):
584 def __init__(self, mapping, name=None, tmpl=None):
585 super(mappingdict, self).__init__(name, tmpl)
585 super(mappingdict, self).__init__(name, tmpl)
586 self._mapping = mapping
586 self._mapping = mapping
587
587
588 def tomap(self, context):
588 def tomap(self, context):
589 return self._mapping
589 return self._mapping
590
590
591 def tobool(self, context, mapping):
591 def tobool(self, context, mapping):
592 # no idea when a template mapping should be considered an empty, but
592 # no idea when a template mapping should be considered an empty, but
593 # a mapping dict should have at least one item in practice, so always
593 # a mapping dict should have at least one item in practice, so always
594 # mark this as non-empty.
594 # mark this as non-empty.
595 return True
595 return True
596
596
597 def tovalue(self, context, mapping):
597 def tovalue(self, context, mapping):
598 return super(mappingdict, self).tovalue(context, mapping)[0]
598 return super(mappingdict, self).tovalue(context, mapping)[0]
599
599
600
600
601 class mappingnone(wrappedvalue):
601 class mappingnone(wrappedvalue):
602 """Wrapper for None, but supports map operation
602 """Wrapper for None, but supports map operation
603
603
604 This represents None of Optional[mappable]. It's similar to
604 This represents None of Optional[mappable]. It's similar to
605 mapplinglist([]), but the underlying value is not [], but None.
605 mapplinglist([]), but the underlying value is not [], but None.
606 """
606 """
607
607
608 def __init__(self):
608 def __init__(self):
609 super(mappingnone, self).__init__(None)
609 super(mappingnone, self).__init__(None)
610
610
611 def itermaps(self, context):
611 def itermaps(self, context):
612 return iter([])
612 return iter([])
613
613
614
614
615 class mappedgenerator(wrapped):
615 class mappedgenerator(wrapped):
616 """Wrapper for generator of strings which acts as a list
616 """Wrapper for generator of strings which acts as a list
617
617
618 The function ``make(context, *args)`` should return a generator of
618 The function ``make(context, *args)`` should return a generator of
619 byte strings, or a generator of (possibly nested) generators of byte
619 byte strings, or a generator of (possibly nested) generators of byte
620 strings (i.e. a generator for a list of byte strings.)
620 strings (i.e. a generator for a list of byte strings.)
621 """
621 """
622
622
623 def __init__(self, make, args=()):
623 def __init__(self, make, args=()):
624 self._make = make
624 self._make = make
625 self._args = args
625 self._args = args
626
626
627 def contains(self, context, mapping, item):
627 def contains(self, context, mapping, item):
628 item = stringify(context, mapping, item)
628 item = stringify(context, mapping, item)
629 return item in self.tovalue(context, mapping)
629 return item in self.tovalue(context, mapping)
630
630
631 def _gen(self, context):
631 def _gen(self, context):
632 return self._make(context, *self._args)
632 return self._make(context, *self._args)
633
633
634 def getmember(self, context, mapping, key):
634 def getmember(self, context, mapping, key):
635 raise error.ParseError(_(b'not a dictionary'))
635 raise error.ParseError(_(b'not a dictionary'))
636
636
637 def getmin(self, context, mapping):
637 def getmin(self, context, mapping):
638 return self._getby(context, mapping, min)
638 return self._getby(context, mapping, min)
639
639
640 def getmax(self, context, mapping):
640 def getmax(self, context, mapping):
641 return self._getby(context, mapping, max)
641 return self._getby(context, mapping, max)
642
642
643 def _getby(self, context, mapping, func):
643 def _getby(self, context, mapping, func):
644 xs = self.tovalue(context, mapping)
644 xs = self.tovalue(context, mapping)
645 if not xs:
645 if not xs:
646 raise error.ParseError(_(b'empty sequence'))
646 raise error.ParseError(_(b'empty sequence'))
647 return func(xs)
647 return func(xs)
648
648
649 @staticmethod
649 @staticmethod
650 def _filteredgen(context, mapping, make, args, select):
650 def _filteredgen(context, mapping, make, args, select):
651 for x in make(context, *args):
651 for x in make(context, *args):
652 s = stringify(context, mapping, x)
652 s = stringify(context, mapping, x)
653 if select(wrappedbytes(s)):
653 if select(wrappedbytes(s)):
654 yield s
654 yield s
655
655
656 def filter(self, context, mapping, select):
656 def filter(self, context, mapping, select):
657 args = (mapping, self._make, self._args, select)
657 args = (mapping, self._make, self._args, select)
658 return mappedgenerator(self._filteredgen, args)
658 return mappedgenerator(self._filteredgen, args)
659
659
660 def itermaps(self, context):
660 def itermaps(self, context):
661 raise error.ParseError(_(b'list of strings is not mappable'))
661 raise error.ParseError(_(b'list of strings is not mappable'))
662
662
663 def join(self, context, mapping, sep):
663 def join(self, context, mapping, sep):
664 return joinitems(self._gen(context), sep)
664 return joinitems(self._gen(context), sep)
665
665
666 def show(self, context, mapping):
666 def show(self, context, mapping):
667 return self.join(context, mapping, b'')
667 return self.join(context, mapping, b'')
668
668
669 def tobool(self, context, mapping):
669 def tobool(self, context, mapping):
670 return _nonempty(self._gen(context))
670 return _nonempty(self._gen(context))
671
671
672 def tovalue(self, context, mapping):
672 def tovalue(self, context, mapping):
673 return [stringify(context, mapping, x) for x in self._gen(context)]
673 return [stringify(context, mapping, x) for x in self._gen(context)]
674
674
675
675
676 def hybriddict(data, key=b'key', value=b'value', fmt=None, gen=None):
676 def hybriddict(data, key=b'key', value=b'value', fmt=None, gen=None):
677 """Wrap data to support both dict-like and string-like operations"""
677 """Wrap data to support both dict-like and string-like operations"""
678 prefmt = pycompat.identity
678 prefmt = pycompat.identity
679 if fmt is None:
679 if fmt is None:
680 fmt = b'%s=%s'
680 fmt = b'%s=%s'
681 prefmt = pycompat.bytestr
681 prefmt = pycompat.bytestr
682 return hybrid(
682 return hybrid(
683 gen,
683 gen,
684 data,
684 data,
685 lambda k: {key: k, value: data[k]},
685 lambda k: {key: k, value: data[k]},
686 lambda k: fmt % (prefmt(k), prefmt(data[k])),
686 lambda k: fmt % (prefmt(k), prefmt(data[k])),
687 )
687 )
688
688
689
689
690 def hybridlist(data, name, fmt=None, gen=None):
690 def hybridlist(data, name, fmt=None, gen=None):
691 """Wrap data to support both list-like and string-like operations"""
691 """Wrap data to support both list-like and string-like operations"""
692 prefmt = pycompat.identity
692 prefmt = pycompat.identity
693 if fmt is None:
693 if fmt is None:
694 fmt = b'%s'
694 fmt = b'%s'
695 prefmt = pycompat.bytestr
695 prefmt = pycompat.bytestr
696 return hybrid(gen, data, lambda x: {name: x}, lambda x: fmt % prefmt(x))
696 return hybrid(gen, data, lambda x: {name: x}, lambda x: fmt % prefmt(x))
697
697
698
698
699 def compatdict(
699 def compatdict(
700 context,
700 context,
701 mapping,
701 mapping,
702 name,
702 name,
703 data,
703 data,
704 key=b'key',
704 key=b'key',
705 value=b'value',
705 value=b'value',
706 fmt=None,
706 fmt=None,
707 plural=None,
707 plural=None,
708 separator=b' ',
708 separator=b' ',
709 ):
709 ):
710 """Wrap data like hybriddict(), but also supports old-style list template
710 """Wrap data like hybriddict(), but also supports old-style list template
711
711
712 This exists for backward compatibility with the old-style template. Use
712 This exists for backward compatibility with the old-style template. Use
713 hybriddict() for new template keywords.
713 hybriddict() for new template keywords.
714 """
714 """
715 c = [{key: k, value: v} for k, v in data.items()]
715 c = [{key: k, value: v} for k, v in data.items()]
716 f = _showcompatlist(context, mapping, name, c, plural, separator)
716 f = _showcompatlist(context, mapping, name, c, plural, separator)
717 return hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
717 return hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
718
718
719
719
720 def compatlist(
720 def compatlist(
721 context,
721 context,
722 mapping,
722 mapping,
723 name,
723 name,
724 data,
724 data,
725 element=None,
725 element=None,
726 fmt=None,
726 fmt=None,
727 plural=None,
727 plural=None,
728 separator=b' ',
728 separator=b' ',
729 ):
729 ):
730 """Wrap data like hybridlist(), but also supports old-style list template
730 """Wrap data like hybridlist(), but also supports old-style list template
731
731
732 This exists for backward compatibility with the old-style template. Use
732 This exists for backward compatibility with the old-style template. Use
733 hybridlist() for new template keywords.
733 hybridlist() for new template keywords.
734 """
734 """
735 f = _showcompatlist(context, mapping, name, data, plural, separator)
735 f = _showcompatlist(context, mapping, name, data, plural, separator)
736 return hybridlist(data, name=element or name, fmt=fmt, gen=f)
736 return hybridlist(data, name=element or name, fmt=fmt, gen=f)
737
737
738
738
739 def compatfilecopiesdict(context, mapping, name, copies):
739 def compatfilecopiesdict(context, mapping, name, copies):
740 """Wrap list of (dest, source) file names to support old-style list
740 """Wrap list of (dest, source) file names to support old-style list
741 template and field names
741 template and field names
742
742
743 This exists for backward compatibility. Use hybriddict for new template
743 This exists for backward compatibility. Use hybriddict for new template
744 keywords.
744 keywords.
745 """
745 """
746 # no need to provide {path} to old-style list template
746 # no need to provide {path} to old-style list template
747 c = [{b'name': k, b'source': v} for k, v in copies]
747 c = [{b'name': k, b'source': v} for k, v in copies]
748 f = _showcompatlist(context, mapping, name, c, plural=b'file_copies')
748 f = _showcompatlist(context, mapping, name, c, plural=b'file_copies')
749 copies = util.sortdict(copies)
749 copies = util.sortdict(copies)
750 return hybrid(
750 return hybrid(
751 f,
751 f,
752 copies,
752 copies,
753 lambda k: {b'name': k, b'path': k, b'source': copies[k]},
753 lambda k: {b'name': k, b'path': k, b'source': copies[k]},
754 lambda k: b'%s (%s)' % (k, copies[k]),
754 lambda k: b'%s (%s)' % (k, copies[k]),
755 )
755 )
756
756
757
757
758 def compatfileslist(context, mapping, name, files):
758 def compatfileslist(context, mapping, name, files):
759 """Wrap list of file names to support old-style list template and field
759 """Wrap list of file names to support old-style list template and field
760 names
760 names
761
761
762 This exists for backward compatibility. Use hybridlist for new template
762 This exists for backward compatibility. Use hybridlist for new template
763 keywords.
763 keywords.
764 """
764 """
765 f = _showcompatlist(context, mapping, name, files)
765 f = _showcompatlist(context, mapping, name, files)
766 return hybrid(
766 return hybrid(
767 f, files, lambda x: {b'file': x, b'path': x}, pycompat.identity
767 f, files, lambda x: {b'file': x, b'path': x}, pycompat.identity
768 )
768 )
769
769
770
770
771 def _showcompatlist(
771 def _showcompatlist(
772 context, mapping, name, values, plural=None, separator=b' '
772 context, mapping, name, values, plural=None, separator=b' '
773 ):
773 ):
774 """Return a generator that renders old-style list template
774 """Return a generator that renders old-style list template
775
775
776 name is name of key in template map.
776 name is name of key in template map.
777 values is list of strings or dicts.
777 values is list of strings or dicts.
778 plural is plural of name, if not simply name + 's'.
778 plural is plural of name, if not simply name + 's'.
779 separator is used to join values as a string
779 separator is used to join values as a string
780
780
781 expansion works like this, given name 'foo'.
781 expansion works like this, given name 'foo'.
782
782
783 if values is empty, expand 'no_foos'.
783 if values is empty, expand 'no_foos'.
784
784
785 if 'foo' not in template map, return values as a string,
785 if 'foo' not in template map, return values as a string,
786 joined by 'separator'.
786 joined by 'separator'.
787
787
788 expand 'start_foos'.
788 expand 'start_foos'.
789
789
790 for each value, expand 'foo'. if 'last_foo' in template
790 for each value, expand 'foo'. if 'last_foo' in template
791 map, expand it instead of 'foo' for last key.
791 map, expand it instead of 'foo' for last key.
792
792
793 expand 'end_foos'.
793 expand 'end_foos'.
794 """
794 """
795 if not plural:
795 if not plural:
796 plural = name + b's'
796 plural = name + b's'
797 if not values:
797 if not values:
798 noname = b'no_' + plural
798 noname = b'no_' + plural
799 if context.preload(noname):
799 if context.preload(noname):
800 yield context.process(noname, mapping)
800 yield context.process(noname, mapping)
801 return
801 return
802 if not context.preload(name):
802 if not context.preload(name):
803 if isinstance(values[0], bytes):
803 if isinstance(values[0], bytes):
804 yield separator.join(values)
804 yield separator.join(values)
805 else:
805 else:
806 for v in values:
806 for v in values:
807 r = dict(v)
807 r = dict(v)
808 r.update(mapping)
808 r.update(mapping)
809 yield r
809 yield r
810 return
810 return
811 startname = b'start_' + plural
811 startname = b'start_' + plural
812 if context.preload(startname):
812 if context.preload(startname):
813 yield context.process(startname, mapping)
813 yield context.process(startname, mapping)
814
814
815 def one(v, tag=name):
815 def one(v, tag=name):
816 vmapping = {}
816 vmapping = {}
817 try:
817 try:
818 vmapping.update(v)
818 vmapping.update(v)
819 # Python 2 raises ValueError if the type of v is wrong. Python
819 # Python 2 raises ValueError if the type of v is wrong. Python
820 # 3 raises TypeError.
820 # 3 raises TypeError.
821 except (AttributeError, TypeError, ValueError):
821 except (AttributeError, TypeError, ValueError):
822 try:
822 try:
823 # Python 2 raises ValueError trying to destructure an e.g.
823 # Python 2 raises ValueError trying to destructure an e.g.
824 # bytes. Python 3 raises TypeError.
824 # bytes. Python 3 raises TypeError.
825 for a, b in v:
825 for a, b in v:
826 vmapping[a] = b
826 vmapping[a] = b
827 except (TypeError, ValueError):
827 except (TypeError, ValueError):
828 vmapping[name] = v
828 vmapping[name] = v
829 vmapping = context.overlaymap(mapping, vmapping)
829 vmapping = context.overlaymap(mapping, vmapping)
830 return context.process(tag, vmapping)
830 return context.process(tag, vmapping)
831
831
832 lastname = b'last_' + name
832 lastname = b'last_' + name
833 if context.preload(lastname):
833 if context.preload(lastname):
834 last = values.pop()
834 last = values.pop()
835 else:
835 else:
836 last = None
836 last = None
837 for v in values:
837 for v in values:
838 yield one(v)
838 yield one(v)
839 if last is not None:
839 if last is not None:
840 yield one(last, tag=lastname)
840 yield one(last, tag=lastname)
841 endname = b'end_' + plural
841 endname = b'end_' + plural
842 if context.preload(endname):
842 if context.preload(endname):
843 yield context.process(endname, mapping)
843 yield context.process(endname, mapping)
844
844
845
845
846 def flatten(context, mapping, thing):
846 def flatten(context, mapping, thing):
847 """Yield a single stream from a possibly nested set of iterators"""
847 """Yield a single stream from a possibly nested set of iterators"""
848 if isinstance(thing, wrapped):
848 if isinstance(thing, wrapped):
849 thing = thing.show(context, mapping)
849 thing = thing.show(context, mapping)
850 if isinstance(thing, bytes):
850 if isinstance(thing, bytes):
851 yield thing
851 yield thing
852 elif isinstance(thing, str):
852 elif isinstance(thing, str):
853 # We can only hit this on Python 3, and it's here to guard
853 # We can only hit this on Python 3, and it's here to guard
854 # against infinite recursion.
854 # against infinite recursion.
855 raise error.ProgrammingError(
855 raise error.ProgrammingError(
856 b'Mercurial IO including templates is done'
856 b'Mercurial IO including templates is done'
857 b' with bytes, not strings, got %r' % thing
857 b' with bytes, not strings, got %r' % thing
858 )
858 )
859 elif thing is None:
859 elif thing is None:
860 pass
860 pass
861 elif not util.safehasattr(thing, b'__iter__'):
861 elif not util.safehasattr(thing, b'__iter__'):
862 yield pycompat.bytestr(thing)
862 yield pycompat.bytestr(thing)
863 else:
863 else:
864 for i in thing:
864 for i in thing:
865 if isinstance(i, wrapped):
865 if isinstance(i, wrapped):
866 i = i.show(context, mapping)
866 i = i.show(context, mapping)
867 if isinstance(i, bytes):
867 if isinstance(i, bytes):
868 yield i
868 yield i
869 elif i is None:
869 elif i is None:
870 pass
870 pass
871 elif not util.safehasattr(i, '__iter__'):
871 elif not util.safehasattr(i, '__iter__'):
872 yield pycompat.bytestr(i)
872 yield pycompat.bytestr(i)
873 else:
873 else:
874 for j in flatten(context, mapping, i):
874 for j in flatten(context, mapping, i):
875 yield j
875 yield j
876
876
877
877
878 def stringify(context, mapping, thing):
878 def stringify(context, mapping, thing):
879 """Turn values into bytes by converting into text and concatenating them"""
879 """Turn values into bytes by converting into text and concatenating them"""
880 if isinstance(thing, bytes):
880 if isinstance(thing, bytes):
881 return thing # retain localstr to be round-tripped
881 return thing # retain localstr to be round-tripped
882 return b''.join(flatten(context, mapping, thing))
882 return b''.join(flatten(context, mapping, thing))
883
883
884
884
885 def findsymbolicname(arg):
885 def findsymbolicname(arg):
886 """Find symbolic name for the given compiled expression; returns None
886 """Find symbolic name for the given compiled expression; returns None
887 if nothing found reliably"""
887 if nothing found reliably"""
888 while True:
888 while True:
889 func, data = arg
889 func, data = arg
890 if func is runsymbol:
890 if func is runsymbol:
891 return data
891 return data
892 elif func is runfilter:
892 elif func is runfilter:
893 arg = data[0]
893 arg = data[0]
894 else:
894 else:
895 return None
895 return None
896
896
897
897
898 def _nonempty(xiter):
898 def _nonempty(xiter):
899 try:
899 try:
900 next(xiter)
900 next(xiter)
901 return True
901 return True
902 except StopIteration:
902 except StopIteration:
903 return False
903 return False
904
904
905
905
906 def _unthunk(context, mapping, thing):
906 def _unthunk(context, mapping, thing):
907 """Evaluate a lazy byte string into value"""
907 """Evaluate a lazy byte string into value"""
908 if not isinstance(thing, types.GeneratorType):
908 if not isinstance(thing, types.GeneratorType):
909 return thing
909 return thing
910 return stringify(context, mapping, thing)
910 return stringify(context, mapping, thing)
911
911
912
912
913 def evalrawexp(context, mapping, arg):
913 def evalrawexp(context, mapping, arg):
914 """Evaluate given argument as a bare template object which may require
914 """Evaluate given argument as a bare template object which may require
915 further processing (such as folding generator of strings)"""
915 further processing (such as folding generator of strings)"""
916 func, data = arg
916 func, data = arg
917 return func(context, mapping, data)
917 return func(context, mapping, data)
918
918
919
919
920 def evalwrapped(context, mapping, arg):
920 def evalwrapped(context, mapping, arg):
921 """Evaluate given argument to wrapped object"""
921 """Evaluate given argument to wrapped object"""
922 thing = evalrawexp(context, mapping, arg)
922 thing = evalrawexp(context, mapping, arg)
923 return makewrapped(context, mapping, thing)
923 return makewrapped(context, mapping, thing)
924
924
925
925
926 def makewrapped(context, mapping, thing):
926 def makewrapped(context, mapping, thing):
927 """Lift object to a wrapped type"""
927 """Lift object to a wrapped type"""
928 if isinstance(thing, wrapped):
928 if isinstance(thing, wrapped):
929 return thing
929 return thing
930 thing = _unthunk(context, mapping, thing)
930 thing = _unthunk(context, mapping, thing)
931 if isinstance(thing, bytes):
931 if isinstance(thing, bytes):
932 return wrappedbytes(thing)
932 return wrappedbytes(thing)
933 return wrappedvalue(thing)
933 return wrappedvalue(thing)
934
934
935
935
936 def evalfuncarg(context, mapping, arg):
936 def evalfuncarg(context, mapping, arg):
937 """Evaluate given argument as value type"""
937 """Evaluate given argument as value type"""
938 return unwrapvalue(context, mapping, evalrawexp(context, mapping, arg))
938 return unwrapvalue(context, mapping, evalrawexp(context, mapping, arg))
939
939
940
940
941 def unwrapvalue(context, mapping, thing):
941 def unwrapvalue(context, mapping, thing):
942 """Move the inner value object out of the wrapper"""
942 """Move the inner value object out of the wrapper"""
943 if isinstance(thing, wrapped):
943 if isinstance(thing, wrapped):
944 return thing.tovalue(context, mapping)
944 return thing.tovalue(context, mapping)
945 # evalrawexp() may return string, generator of strings or arbitrary object
945 # evalrawexp() may return string, generator of strings or arbitrary object
946 # such as date tuple, but filter does not want generator.
946 # such as date tuple, but filter does not want generator.
947 return _unthunk(context, mapping, thing)
947 return _unthunk(context, mapping, thing)
948
948
949
949
950 def evalboolean(context, mapping, arg):
950 def evalboolean(context, mapping, arg):
951 """Evaluate given argument as boolean, but also takes boolean literals"""
951 """Evaluate given argument as boolean, but also takes boolean literals"""
952 func, data = arg
952 func, data = arg
953 if func is runsymbol:
953 if func is runsymbol:
954 thing = func(context, mapping, data, default=None)
954 thing = func(context, mapping, data, default=None)
955 if thing is None:
955 if thing is None:
956 # not a template keyword, takes as a boolean literal
956 # not a template keyword, takes as a boolean literal
957 thing = stringutil.parsebool(data)
957 thing = stringutil.parsebool(data)
958 else:
958 else:
959 thing = func(context, mapping, data)
959 thing = func(context, mapping, data)
960 return makewrapped(context, mapping, thing).tobool(context, mapping)
960 return makewrapped(context, mapping, thing).tobool(context, mapping)
961
961
962
962
963 def evaldate(context, mapping, arg, err=None):
963 def evaldate(context, mapping, arg, err=None):
964 """Evaluate given argument as a date tuple or a date string; returns
964 """Evaluate given argument as a date tuple or a date string; returns
965 a (unixtime, offset) tuple"""
965 a (unixtime, offset) tuple"""
966 thing = evalrawexp(context, mapping, arg)
966 thing = evalrawexp(context, mapping, arg)
967 return unwrapdate(context, mapping, thing, err)
967 return unwrapdate(context, mapping, thing, err)
968
968
969
969
970 def unwrapdate(context, mapping, thing, err=None):
970 def unwrapdate(context, mapping, thing, err=None):
971 if isinstance(thing, date):
971 if isinstance(thing, date):
972 return thing.tovalue(context, mapping)
972 return thing.tovalue(context, mapping)
973 # TODO: update hgweb to not return bare tuple; then just stringify 'thing'
973 # TODO: update hgweb to not return bare tuple; then just stringify 'thing'
974 thing = unwrapvalue(context, mapping, thing)
974 thing = unwrapvalue(context, mapping, thing)
975 try:
975 try:
976 return dateutil.parsedate(thing)
976 return dateutil.parsedate(thing)
977 except AttributeError:
977 except AttributeError:
978 raise error.ParseError(err or _(b'not a date tuple nor a string'))
978 raise error.ParseError(err or _(b'not a date tuple nor a string'))
979 except error.ParseError:
979 except error.ParseError:
980 if not err:
980 if not err:
981 raise
981 raise
982 raise error.ParseError(err)
982 raise error.ParseError(err)
983
983
984
984
985 def evalinteger(context, mapping, arg, err=None):
985 def evalinteger(context, mapping, arg, err=None):
986 thing = evalrawexp(context, mapping, arg)
986 thing = evalrawexp(context, mapping, arg)
987 return unwrapinteger(context, mapping, thing, err)
987 return unwrapinteger(context, mapping, thing, err)
988
988
989
989
990 def unwrapinteger(context, mapping, thing, err=None):
990 def unwrapinteger(context, mapping, thing, err=None):
991 thing = unwrapvalue(context, mapping, thing)
991 thing = unwrapvalue(context, mapping, thing)
992 try:
992 try:
993 return int(thing)
993 return int(thing)
994 except (TypeError, ValueError):
994 except (TypeError, ValueError):
995 raise error.ParseError(err or _(b'not an integer'))
995 raise error.ParseError(err or _(b'not an integer'))
996
996
997
997
998 def evalstring(context, mapping, arg):
998 def evalstring(context, mapping, arg):
999 return stringify(context, mapping, evalrawexp(context, mapping, arg))
999 return stringify(context, mapping, evalrawexp(context, mapping, arg))
1000
1000
1001
1001
1002 def evalstringliteral(context, mapping, arg):
1002 def evalstringliteral(context, mapping, arg):
1003 """Evaluate given argument as string template, but returns symbol name
1003 """Evaluate given argument as string template, but returns symbol name
1004 if it is unknown"""
1004 if it is unknown"""
1005 func, data = arg
1005 func, data = arg
1006 if func is runsymbol:
1006 if func is runsymbol:
1007 thing = func(context, mapping, data, default=data)
1007 thing = func(context, mapping, data, default=data)
1008 else:
1008 else:
1009 thing = func(context, mapping, data)
1009 thing = func(context, mapping, data)
1010 return stringify(context, mapping, thing)
1010 return stringify(context, mapping, thing)
1011
1011
1012
1012
1013 _unwrapfuncbytype = {
1013 _unwrapfuncbytype = {
1014 None: unwrapvalue,
1014 None: unwrapvalue,
1015 bytes: stringify,
1015 bytes: stringify,
1016 date: unwrapdate,
1016 date: unwrapdate,
1017 int: unwrapinteger,
1017 int: unwrapinteger,
1018 }
1018 }
1019
1019
1020
1020
1021 def unwrapastype(context, mapping, thing, typ):
1021 def unwrapastype(context, mapping, thing, typ):
1022 """Move the inner value object out of the wrapper and coerce its type"""
1022 """Move the inner value object out of the wrapper and coerce its type"""
1023 try:
1023 try:
1024 f = _unwrapfuncbytype[typ]
1024 f = _unwrapfuncbytype[typ]
1025 except KeyError:
1025 except KeyError:
1026 raise error.ProgrammingError(b'invalid type specified: %r' % typ)
1026 raise error.ProgrammingError(b'invalid type specified: %r' % typ)
1027 return f(context, mapping, thing)
1027 return f(context, mapping, thing)
1028
1028
1029
1029
1030 def runinteger(context, mapping, data):
1030 def runinteger(context, mapping, data):
1031 return int(data)
1031 return int(data)
1032
1032
1033
1033
1034 def runstring(context, mapping, data):
1034 def runstring(context, mapping, data):
1035 return data
1035 return data
1036
1036
1037
1037
1038 def _recursivesymbolblocker(key):
1038 def _recursivesymbolblocker(key):
1039 def showrecursion(context, mapping):
1039 def showrecursion(context, mapping):
1040 raise error.Abort(_(b"recursive reference '%s' in template") % key)
1040 raise error.Abort(_(b"recursive reference '%s' in template") % key)
1041
1041
1042 return showrecursion
1042 return showrecursion
1043
1043
1044
1044
1045 def runsymbol(context, mapping, key, default=b''):
1045 def runsymbol(context, mapping, key, default=b''):
1046 v = context.symbol(mapping, key)
1046 v = context.symbol(mapping, key)
1047 if v is None:
1047 if v is None:
1048 # put poison to cut recursion. we can't move this to parsing phase
1048 # put poison to cut recursion. we can't move this to parsing phase
1049 # because "x = {x}" is allowed if "x" is a keyword. (issue4758)
1049 # because "x = {x}" is allowed if "x" is a keyword. (issue4758)
1050 safemapping = mapping.copy()
1050 safemapping = mapping.copy()
1051 safemapping[key] = _recursivesymbolblocker(key)
1051 safemapping[key] = _recursivesymbolblocker(key)
1052 try:
1052 try:
1053 v = context.process(key, safemapping)
1053 v = context.process(key, safemapping)
1054 except TemplateNotFound:
1054 except TemplateNotFound:
1055 v = default
1055 v = default
1056 if callable(v):
1056 if callable(v):
1057 # new templatekw
1057 # new templatekw
1058 try:
1058 try:
1059 return v(context, mapping)
1059 return v(context, mapping)
1060 except ResourceUnavailable:
1060 except ResourceUnavailable:
1061 # unsupported keyword is mapped to empty just like unknown keyword
1061 # unsupported keyword is mapped to empty just like unknown keyword
1062 return None
1062 return None
1063 return v
1063 return v
1064
1064
1065
1065
1066 def runtemplate(context, mapping, template):
1066 def runtemplate(context, mapping, template):
1067 for arg in template:
1067 for arg in template:
1068 yield evalrawexp(context, mapping, arg)
1068 yield evalrawexp(context, mapping, arg)
1069
1069
1070
1070
1071 def runfilter(context, mapping, data):
1071 def runfilter(context, mapping, data):
1072 arg, filt = data
1072 arg, filt = data
1073 thing = evalrawexp(context, mapping, arg)
1073 thing = evalrawexp(context, mapping, arg)
1074 intype = getattr(filt, '_intype', None)
1074 intype = getattr(filt, '_intype', None)
1075 try:
1075 try:
1076 thing = unwrapastype(context, mapping, thing, intype)
1076 thing = unwrapastype(context, mapping, thing, intype)
1077 return filt(thing)
1077 return filt(thing)
1078 except error.ParseError as e:
1078 except error.ParseError as e:
1079 raise error.ParseError(bytes(e), hint=_formatfiltererror(arg, filt))
1079 raise error.ParseError(bytes(e), hint=_formatfiltererror(arg, filt))
1080
1080
1081
1081
1082 def _formatfiltererror(arg, filt):
1082 def _formatfiltererror(arg, filt):
1083 fn = pycompat.sysbytes(filt.__name__)
1083 fn = pycompat.sysbytes(filt.__name__)
1084 sym = findsymbolicname(arg)
1084 sym = findsymbolicname(arg)
1085 if not sym:
1085 if not sym:
1086 return _(b"incompatible use of template filter '%s'") % fn
1086 return _(b"incompatible use of template filter '%s'") % fn
1087 return _(b"template filter '%s' is not compatible with keyword '%s'") % (
1087 return _(b"template filter '%s' is not compatible with keyword '%s'") % (
1088 fn,
1088 fn,
1089 sym,
1089 sym,
1090 )
1090 )
1091
1091
1092
1092
1093 def _iteroverlaymaps(context, origmapping, newmappings):
1093 def _iteroverlaymaps(context, origmapping, newmappings):
1094 """Generate combined mappings from the original mapping and an iterable
1094 """Generate combined mappings from the original mapping and an iterable
1095 of partial mappings to override the original"""
1095 of partial mappings to override the original"""
1096 for i, nm in enumerate(newmappings):
1096 for i, nm in enumerate(newmappings):
1097 lm = context.overlaymap(origmapping, nm)
1097 lm = context.overlaymap(origmapping, nm)
1098 lm[b'index'] = i
1098 lm[b'index'] = i
1099 yield lm
1099 yield lm
1100
1100
1101
1101
1102 def _applymap(context, mapping, d, darg, targ):
1102 def _applymap(context, mapping, d, darg, targ):
1103 try:
1103 try:
1104 diter = d.itermaps(context)
1104 diter = d.itermaps(context)
1105 except error.ParseError as err:
1105 except error.ParseError as err:
1106 sym = findsymbolicname(darg)
1106 sym = findsymbolicname(darg)
1107 if not sym:
1107 if not sym:
1108 raise
1108 raise
1109 hint = _(b"keyword '%s' does not support map operation") % sym
1109 hint = _(b"keyword '%s' does not support map operation") % sym
1110 raise error.ParseError(bytes(err), hint=hint)
1110 raise error.ParseError(bytes(err), hint=hint)
1111 for lm in _iteroverlaymaps(context, mapping, diter):
1111 for lm in _iteroverlaymaps(context, mapping, diter):
1112 yield evalrawexp(context, lm, targ)
1112 yield evalrawexp(context, lm, targ)
1113
1113
1114
1114
1115 def runmap(context, mapping, data):
1115 def runmap(context, mapping, data):
1116 darg, targ = data
1116 darg, targ = data
1117 d = evalwrapped(context, mapping, darg)
1117 d = evalwrapped(context, mapping, darg)
1118 return mappedgenerator(_applymap, args=(mapping, d, darg, targ))
1118 return mappedgenerator(_applymap, args=(mapping, d, darg, targ))
1119
1119
1120
1120
1121 def runmember(context, mapping, data):
1121 def runmember(context, mapping, data):
1122 darg, memb = data
1122 darg, memb = data
1123 d = evalwrapped(context, mapping, darg)
1123 d = evalwrapped(context, mapping, darg)
1124 if isinstance(d, mappable):
1124 if isinstance(d, mappable):
1125 lm = context.overlaymap(mapping, d.tomap(context))
1125 lm = context.overlaymap(mapping, d.tomap(context))
1126 return runsymbol(context, lm, memb)
1126 return runsymbol(context, lm, memb)
1127 try:
1127 try:
1128 return d.getmember(context, mapping, memb)
1128 return d.getmember(context, mapping, memb)
1129 except error.ParseError as err:
1129 except error.ParseError as err:
1130 sym = findsymbolicname(darg)
1130 sym = findsymbolicname(darg)
1131 if not sym:
1131 if not sym:
1132 raise
1132 raise
1133 hint = _(b"keyword '%s' does not support member operation") % sym
1133 hint = _(b"keyword '%s' does not support member operation") % sym
1134 raise error.ParseError(bytes(err), hint=hint)
1134 raise error.ParseError(bytes(err), hint=hint)
1135
1135
1136
1136
1137 def runnegate(context, mapping, data):
1137 def runnegate(context, mapping, data):
1138 data = evalinteger(
1138 data = evalinteger(
1139 context, mapping, data, _(b'negation needs an integer argument')
1139 context, mapping, data, _(b'negation needs an integer argument')
1140 )
1140 )
1141 return -data
1141 return -data
1142
1142
1143
1143
1144 def runarithmetic(context, mapping, data):
1144 def runarithmetic(context, mapping, data):
1145 func, left, right = data
1145 func, left, right = data
1146 left = evalinteger(
1146 left = evalinteger(
1147 context, mapping, left, _(b'arithmetic only defined on integers')
1147 context, mapping, left, _(b'arithmetic only defined on integers')
1148 )
1148 )
1149 right = evalinteger(
1149 right = evalinteger(
1150 context, mapping, right, _(b'arithmetic only defined on integers')
1150 context, mapping, right, _(b'arithmetic only defined on integers')
1151 )
1151 )
1152 try:
1152 try:
1153 return func(left, right)
1153 return func(left, right)
1154 except ZeroDivisionError:
1154 except ZeroDivisionError:
1155 raise error.Abort(_(b'division by zero is not defined'))
1155 raise error.Abort(_(b'division by zero is not defined'))
1156
1156
1157
1157
1158 def joinitems(itemiter, sep):
1158 def joinitems(itemiter, sep):
1159 """Join items with the separator; Returns generator of bytes"""
1159 """Join items with the separator; Returns generator of bytes"""
1160 first = True
1160 first = True
1161 for x in itemiter:
1161 for x in itemiter:
1162 if first:
1162 if first:
1163 first = False
1163 first = False
1164 elif sep:
1164 elif sep:
1165 yield sep
1165 yield sep
1166 yield x
1166 yield x
General Comments 0
You need to be logged in to leave comments. Login now