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