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