comparison mercurial/templater.py @ 31886:bdda942f4b9c

templater: add support for keyword arguments Unlike revset, function arguments are pre-processed in templater. That's why we need to define argspec per function. An argspec field looks somewhat redundant in @templatefunc definition as a name field contains human-readable list of arguments. I'll make function doc be built from argspec later. Ported separate() function as an example.
author Yuya Nishihara <yuya@tcha.org>
date Mon, 03 Apr 2017 21:22:39 +0900
parents d18b624c1c06
children f7b3677f66cd
comparison
equal deleted inserted replaced
31885:d18b624c1c06 31886:bdda942f4b9c
368 def runtemplate(context, mapping, template): 368 def runtemplate(context, mapping, template):
369 for func, data in template: 369 for func, data in template:
370 yield func(context, mapping, data) 370 yield func(context, mapping, data)
371 371
372 def buildfilter(exp, context): 372 def buildfilter(exp, context):
373 arg = compileexp(exp[1], context, methods)
374 n = getsymbol(exp[2]) 373 n = getsymbol(exp[2])
375 if n in context._filters: 374 if n in context._filters:
376 filt = context._filters[n] 375 filt = context._filters[n]
376 arg = compileexp(exp[1], context, methods)
377 return (runfilter, (arg, filt)) 377 return (runfilter, (arg, filt))
378 if n in funcs: 378 if n in funcs:
379 f = funcs[n] 379 f = funcs[n]
380 return (f, [arg]) 380 args = _buildfuncargs(exp[1], context, methods, n, f._argspec)
381 return (f, args)
381 raise error.ParseError(_("unknown function '%s'") % n) 382 raise error.ParseError(_("unknown function '%s'") % n)
382 383
383 def runfilter(context, mapping, data): 384 def runfilter(context, mapping, data):
384 arg, filt = data 385 arg, filt = data
385 thing = evalfuncarg(context, mapping, arg) 386 thing = evalfuncarg(context, mapping, arg)
450 except ZeroDivisionError: 451 except ZeroDivisionError:
451 raise error.Abort(_('division by zero is not defined')) 452 raise error.Abort(_('division by zero is not defined'))
452 453
453 def buildfunc(exp, context): 454 def buildfunc(exp, context):
454 n = getsymbol(exp[1]) 455 n = getsymbol(exp[1])
455 args = [compileexp(x, context, exprmethods) for x in getlist(exp[2])]
456 if n in funcs: 456 if n in funcs:
457 f = funcs[n] 457 f = funcs[n]
458 args = _buildfuncargs(exp[2], context, exprmethods, n, f._argspec)
458 return (f, args) 459 return (f, args)
459 if n in context._filters: 460 if n in context._filters:
461 args = _buildfuncargs(exp[2], context, exprmethods, n, argspec=None)
460 if len(args) != 1: 462 if len(args) != 1:
461 raise error.ParseError(_("filter %s expects one argument") % n) 463 raise error.ParseError(_("filter %s expects one argument") % n)
462 f = context._filters[n] 464 f = context._filters[n]
463 return (runfilter, (args[0], f)) 465 return (runfilter, (args[0], f))
464 raise error.ParseError(_("unknown function '%s'") % n) 466 raise error.ParseError(_("unknown function '%s'") % n)
467
468 def _buildfuncargs(exp, context, curmethods, funcname, argspec):
469 """Compile parsed tree of function arguments into list or dict of
470 (func, data) pairs"""
471 def compiledict(xs):
472 return dict((k, compileexp(x, context, curmethods))
473 for k, x in xs.iteritems())
474 def compilelist(xs):
475 return [compileexp(x, context, curmethods) for x in xs]
476
477 if not argspec:
478 # filter or function with no argspec: return list of positional args
479 return compilelist(getlist(exp))
480
481 # function with argspec: return dict of named args
482 _poskeys, varkey, _keys = argspec = parser.splitargspec(argspec)
483 treeargs = parser.buildargsdict(getlist(exp), funcname, argspec,
484 keyvaluenode='keyvalue', keynode='symbol')
485 compargs = {}
486 if varkey:
487 compargs[varkey] = compilelist(treeargs.pop(varkey))
488 compargs.update(compiledict(treeargs))
489 return compargs
465 490
466 def buildkeyvaluepair(exp, content): 491 def buildkeyvaluepair(exp, content):
467 raise error.ParseError(_("can't use a key-value pair in this context")) 492 raise error.ParseError(_("can't use a key-value pair in this context"))
468 493
469 # dict of template built-in functions 494 # dict of template built-in functions
830 text = evalstring(context, mapping, args[0]) 855 text = evalstring(context, mapping, args[0])
831 style = evalstring(context, mapping, args[1]) 856 style = evalstring(context, mapping, args[1])
832 857
833 return minirst.format(text, style=style, keep=['verbose']) 858 return minirst.format(text, style=style, keep=['verbose'])
834 859
835 @templatefunc('separate(sep, args)') 860 @templatefunc('separate(sep, args)', argspec='sep *args')
836 def separate(context, mapping, args): 861 def separate(context, mapping, args):
837 """Add a separator between non-empty arguments.""" 862 """Add a separator between non-empty arguments."""
838 if not args: 863 if 'sep' not in args:
839 # i18n: "separate" is a keyword 864 # i18n: "separate" is a keyword
840 raise error.ParseError(_("separate expects at least one argument")) 865 raise error.ParseError(_("separate expects at least one argument"))
841 866
842 sep = evalstring(context, mapping, args[0]) 867 sep = evalstring(context, mapping, args['sep'])
843 first = True 868 first = True
844 for arg in args[1:]: 869 for arg in args['args']:
845 argstr = evalstring(context, mapping, arg) 870 argstr = evalstring(context, mapping, arg)
846 if not argstr: 871 if not argstr:
847 continue 872 continue
848 if first: 873 if first:
849 first = False 874 first = False