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