359 # even if it exists in mapping. this allows us to override mapping |
361 # even if it exists in mapping. this allows us to override mapping |
360 # by web templates, e.g. 'changelogtag' is redefined in map file. |
362 # by web templates, e.g. 'changelogtag' is redefined in map file. |
361 return context._load(exp[1]) |
363 return context._load(exp[1]) |
362 raise error.ParseError(_("expected template specifier")) |
364 raise error.ParseError(_("expected template specifier")) |
363 |
365 |
364 def findsymbolicname(arg): |
|
365 """Find symbolic name for the given compiled expression; returns None |
|
366 if nothing found reliably""" |
|
367 while True: |
|
368 func, data = arg |
|
369 if func is runsymbol: |
|
370 return data |
|
371 elif func is runfilter: |
|
372 arg = data[0] |
|
373 else: |
|
374 return None |
|
375 |
|
376 def evalrawexp(context, mapping, arg): |
|
377 """Evaluate given argument as a bare template object which may require |
|
378 further processing (such as folding generator of strings)""" |
|
379 func, data = arg |
|
380 return func(context, mapping, data) |
|
381 |
|
382 def evalfuncarg(context, mapping, arg): |
|
383 """Evaluate given argument as value type""" |
|
384 thing = evalrawexp(context, mapping, arg) |
|
385 thing = templatekw.unwrapvalue(thing) |
|
386 # evalrawexp() may return string, generator of strings or arbitrary object |
|
387 # such as date tuple, but filter does not want generator. |
|
388 if isinstance(thing, types.GeneratorType): |
|
389 thing = stringify(thing) |
|
390 return thing |
|
391 |
|
392 def evalboolean(context, mapping, arg): |
|
393 """Evaluate given argument as boolean, but also takes boolean literals""" |
|
394 func, data = arg |
|
395 if func is runsymbol: |
|
396 thing = func(context, mapping, data, default=None) |
|
397 if thing is None: |
|
398 # not a template keyword, takes as a boolean literal |
|
399 thing = util.parsebool(data) |
|
400 else: |
|
401 thing = func(context, mapping, data) |
|
402 thing = templatekw.unwrapvalue(thing) |
|
403 if isinstance(thing, bool): |
|
404 return thing |
|
405 # other objects are evaluated as strings, which means 0 is True, but |
|
406 # empty dict/list should be False as they are expected to be '' |
|
407 return bool(stringify(thing)) |
|
408 |
|
409 def evalinteger(context, mapping, arg, err=None): |
|
410 v = evalfuncarg(context, mapping, arg) |
|
411 try: |
|
412 return int(v) |
|
413 except (TypeError, ValueError): |
|
414 raise error.ParseError(err or _('not an integer')) |
|
415 |
|
416 def evalstring(context, mapping, arg): |
|
417 return stringify(evalrawexp(context, mapping, arg)) |
|
418 |
|
419 def evalstringliteral(context, mapping, arg): |
|
420 """Evaluate given argument as string template, but returns symbol name |
|
421 if it is unknown""" |
|
422 func, data = arg |
|
423 if func is runsymbol: |
|
424 thing = func(context, mapping, data, default=data) |
|
425 else: |
|
426 thing = func(context, mapping, data) |
|
427 return stringify(thing) |
|
428 |
|
429 _evalfuncbytype = { |
|
430 bool: evalboolean, |
|
431 bytes: evalstring, |
|
432 int: evalinteger, |
|
433 } |
|
434 |
|
435 def evalastype(context, mapping, arg, typ): |
|
436 """Evaluate given argument and coerce its type""" |
|
437 try: |
|
438 f = _evalfuncbytype[typ] |
|
439 except KeyError: |
|
440 raise error.ProgrammingError('invalid type specified: %r' % typ) |
|
441 return f(context, mapping, arg) |
|
442 |
|
443 def runinteger(context, mapping, data): |
|
444 return int(data) |
|
445 |
|
446 def runstring(context, mapping, data): |
|
447 return data |
|
448 |
|
449 def _recursivesymbolblocker(key): |
|
450 def showrecursion(**args): |
|
451 raise error.Abort(_("recursive reference '%s' in template") % key) |
|
452 return showrecursion |
|
453 |
|
454 def _runrecursivesymbol(context, mapping, key): |
366 def _runrecursivesymbol(context, mapping, key): |
455 raise error.Abort(_("recursive reference '%s' in template") % key) |
367 raise error.Abort(_("recursive reference '%s' in template") % key) |
456 |
368 |
457 def runsymbol(context, mapping, key, default=''): |
|
458 v = context.symbol(mapping, key) |
|
459 if v is None: |
|
460 # put poison to cut recursion. we can't move this to parsing phase |
|
461 # because "x = {x}" is allowed if "x" is a keyword. (issue4758) |
|
462 safemapping = mapping.copy() |
|
463 safemapping[key] = _recursivesymbolblocker(key) |
|
464 try: |
|
465 v = context.process(key, safemapping) |
|
466 except TemplateNotFound: |
|
467 v = default |
|
468 if callable(v) and getattr(v, '_requires', None) is None: |
|
469 # old templatekw: expand all keywords and resources |
|
470 props = context._resources.copy() |
|
471 props.update(mapping) |
|
472 return v(**pycompat.strkwargs(props)) |
|
473 if callable(v): |
|
474 # new templatekw |
|
475 try: |
|
476 return v(context, mapping) |
|
477 except ResourceUnavailable: |
|
478 # unsupported keyword is mapped to empty just like unknown keyword |
|
479 return None |
|
480 return v |
|
481 |
|
482 def buildtemplate(exp, context): |
369 def buildtemplate(exp, context): |
483 ctmpl = [compileexp(e, context, methods) for e in exp[1:]] |
370 ctmpl = [compileexp(e, context, methods) for e in exp[1:]] |
484 return (runtemplate, ctmpl) |
371 return (templateutil.runtemplate, ctmpl) |
485 |
|
486 def runtemplate(context, mapping, template): |
|
487 for arg in template: |
|
488 yield evalrawexp(context, mapping, arg) |
|
489 |
372 |
490 def buildfilter(exp, context): |
373 def buildfilter(exp, context): |
491 n = getsymbol(exp[2]) |
374 n = getsymbol(exp[2]) |
492 if n in context._filters: |
375 if n in context._filters: |
493 filt = context._filters[n] |
376 filt = context._filters[n] |
494 arg = compileexp(exp[1], context, methods) |
377 arg = compileexp(exp[1], context, methods) |
495 return (runfilter, (arg, filt)) |
378 return (templateutil.runfilter, (arg, filt)) |
496 if n in context._funcs: |
379 if n in context._funcs: |
497 f = context._funcs[n] |
380 f = context._funcs[n] |
498 args = _buildfuncargs(exp[1], context, methods, n, f._argspec) |
381 args = _buildfuncargs(exp[1], context, methods, n, f._argspec) |
499 return (f, args) |
382 return (f, args) |
500 raise error.ParseError(_("unknown function '%s'") % n) |
383 raise error.ParseError(_("unknown function '%s'") % n) |
501 |
384 |
502 def runfilter(context, mapping, data): |
|
503 arg, filt = data |
|
504 thing = evalfuncarg(context, mapping, arg) |
|
505 try: |
|
506 return filt(thing) |
|
507 except (ValueError, AttributeError, TypeError): |
|
508 sym = findsymbolicname(arg) |
|
509 if sym: |
|
510 msg = (_("template filter '%s' is not compatible with keyword '%s'") |
|
511 % (pycompat.sysbytes(filt.__name__), sym)) |
|
512 else: |
|
513 msg = (_("incompatible use of template filter '%s'") |
|
514 % pycompat.sysbytes(filt.__name__)) |
|
515 raise error.Abort(msg) |
|
516 |
|
517 def buildmap(exp, context): |
385 def buildmap(exp, context): |
518 darg = compileexp(exp[1], context, methods) |
386 darg = compileexp(exp[1], context, methods) |
519 targ = gettemplate(exp[2], context) |
387 targ = gettemplate(exp[2], context) |
520 return (runmap, (darg, targ)) |
388 return (templateutil.runmap, (darg, targ)) |
521 |
|
522 def runmap(context, mapping, data): |
|
523 darg, targ = data |
|
524 d = evalrawexp(context, mapping, darg) |
|
525 if util.safehasattr(d, 'itermaps'): |
|
526 diter = d.itermaps() |
|
527 else: |
|
528 try: |
|
529 diter = iter(d) |
|
530 except TypeError: |
|
531 sym = findsymbolicname(darg) |
|
532 if sym: |
|
533 raise error.ParseError(_("keyword '%s' is not iterable") % sym) |
|
534 else: |
|
535 raise error.ParseError(_("%r is not iterable") % d) |
|
536 |
|
537 for i, v in enumerate(diter): |
|
538 lm = mapping.copy() |
|
539 lm['index'] = i |
|
540 if isinstance(v, dict): |
|
541 lm.update(v) |
|
542 lm['originalnode'] = mapping.get('node') |
|
543 yield evalrawexp(context, lm, targ) |
|
544 else: |
|
545 # v is not an iterable of dicts, this happen when 'key' |
|
546 # has been fully expanded already and format is useless. |
|
547 # If so, return the expanded value. |
|
548 yield v |
|
549 |
389 |
550 def buildmember(exp, context): |
390 def buildmember(exp, context): |
551 darg = compileexp(exp[1], context, methods) |
391 darg = compileexp(exp[1], context, methods) |
552 memb = getsymbol(exp[2]) |
392 memb = getsymbol(exp[2]) |
553 return (runmember, (darg, memb)) |
393 return (templateutil.runmember, (darg, memb)) |
554 |
|
555 def runmember(context, mapping, data): |
|
556 darg, memb = data |
|
557 d = evalrawexp(context, mapping, darg) |
|
558 if util.safehasattr(d, 'tomap'): |
|
559 lm = mapping.copy() |
|
560 lm.update(d.tomap()) |
|
561 return runsymbol(context, lm, memb) |
|
562 if util.safehasattr(d, 'get'): |
|
563 return _getdictitem(d, memb) |
|
564 |
|
565 sym = findsymbolicname(darg) |
|
566 if sym: |
|
567 raise error.ParseError(_("keyword '%s' has no member") % sym) |
|
568 else: |
|
569 raise error.ParseError(_("%r has no member") % pycompat.bytestr(d)) |
|
570 |
394 |
571 def buildnegate(exp, context): |
395 def buildnegate(exp, context): |
572 arg = compileexp(exp[1], context, exprmethods) |
396 arg = compileexp(exp[1], context, exprmethods) |
573 return (runnegate, arg) |
397 return (templateutil.runnegate, arg) |
574 |
|
575 def runnegate(context, mapping, data): |
|
576 data = evalinteger(context, mapping, data, |
|
577 _('negation needs an integer argument')) |
|
578 return -data |
|
579 |
398 |
580 def buildarithmetic(exp, context, func): |
399 def buildarithmetic(exp, context, func): |
581 left = compileexp(exp[1], context, exprmethods) |
400 left = compileexp(exp[1], context, exprmethods) |
582 right = compileexp(exp[2], context, exprmethods) |
401 right = compileexp(exp[2], context, exprmethods) |
583 return (runarithmetic, (func, left, right)) |
402 return (templateutil.runarithmetic, (func, left, right)) |
584 |
|
585 def runarithmetic(context, mapping, data): |
|
586 func, left, right = data |
|
587 left = evalinteger(context, mapping, left, |
|
588 _('arithmetic only defined on integers')) |
|
589 right = evalinteger(context, mapping, right, |
|
590 _('arithmetic only defined on integers')) |
|
591 try: |
|
592 return func(left, right) |
|
593 except ZeroDivisionError: |
|
594 raise error.Abort(_('division by zero is not defined')) |
|
595 |
403 |
596 def buildfunc(exp, context): |
404 def buildfunc(exp, context): |
597 n = getsymbol(exp[1]) |
405 n = getsymbol(exp[1]) |
598 if n in context._funcs: |
406 if n in context._funcs: |
599 f = context._funcs[n] |
407 f = context._funcs[n] |