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