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