diff --git a/mercurial/registrar.py b/mercurial/registrar.py --- a/mercurial/registrar.py +++ b/mercurial/registrar.py @@ -234,7 +234,7 @@ class templatefunc(_templateregistrarbas templatefunc = registrar.templatefunc() - @templatefunc('myfunc(arg1, arg2[, arg3])') + @templatefunc('myfunc(arg1, arg2[, arg3])', argspec='arg1 arg2 arg3') def myfuncfunc(context, mapping, args): '''Explanation of this template function .... ''' @@ -242,6 +242,10 @@ class templatefunc(_templateregistrarbas The first string argument is used also in online help. + If optional 'argspec' is defined, the function will receive 'args' as + a dict of named arguments. Otherwise 'args' is a list of positional + arguments. + 'templatefunc' instance in example above can be used to decorate multiple functions. @@ -252,3 +256,6 @@ class templatefunc(_templateregistrarbas Otherwise, explicit 'templater.loadfunction()' is needed. """ _getname = _funcregistrarbase._parsefuncdecl + + def _extrasetup(self, name, func, argspec=None): + func._argspec = argspec diff --git a/mercurial/templater.py b/mercurial/templater.py --- a/mercurial/templater.py +++ b/mercurial/templater.py @@ -370,14 +370,15 @@ def runtemplate(context, mapping, templa yield func(context, mapping, data) def buildfilter(exp, context): - arg = compileexp(exp[1], context, methods) n = getsymbol(exp[2]) if n in context._filters: filt = context._filters[n] + arg = compileexp(exp[1], context, methods) return (runfilter, (arg, filt)) if n in funcs: f = funcs[n] - return (f, [arg]) + args = _buildfuncargs(exp[1], context, methods, n, f._argspec) + return (f, args) raise error.ParseError(_("unknown function '%s'") % n) def runfilter(context, mapping, data): @@ -452,17 +453,41 @@ def runarithmetic(context, mapping, data def buildfunc(exp, context): n = getsymbol(exp[1]) - args = [compileexp(x, context, exprmethods) for x in getlist(exp[2])] if n in funcs: f = funcs[n] + args = _buildfuncargs(exp[2], context, exprmethods, n, f._argspec) return (f, args) if n in context._filters: + args = _buildfuncargs(exp[2], context, exprmethods, n, argspec=None) if len(args) != 1: raise error.ParseError(_("filter %s expects one argument") % n) f = context._filters[n] return (runfilter, (args[0], f)) raise error.ParseError(_("unknown function '%s'") % n) +def _buildfuncargs(exp, context, curmethods, funcname, argspec): + """Compile parsed tree of function arguments into list or dict of + (func, data) pairs""" + def compiledict(xs): + return dict((k, compileexp(x, context, curmethods)) + for k, x in xs.iteritems()) + def compilelist(xs): + return [compileexp(x, context, curmethods) for x in xs] + + if not argspec: + # filter or function with no argspec: return list of positional args + return compilelist(getlist(exp)) + + # function with argspec: return dict of named args + _poskeys, varkey, _keys = argspec = parser.splitargspec(argspec) + treeargs = parser.buildargsdict(getlist(exp), funcname, argspec, + keyvaluenode='keyvalue', keynode='symbol') + compargs = {} + if varkey: + compargs[varkey] = compilelist(treeargs.pop(varkey)) + compargs.update(compiledict(treeargs)) + return compargs + def buildkeyvaluepair(exp, content): raise error.ParseError(_("can't use a key-value pair in this context")) @@ -832,16 +857,16 @@ def rstdoc(context, mapping, args): return minirst.format(text, style=style, keep=['verbose']) -@templatefunc('separate(sep, args)') +@templatefunc('separate(sep, args)', argspec='sep *args') def separate(context, mapping, args): """Add a separator between non-empty arguments.""" - if not args: + if 'sep' not in args: # i18n: "separate" is a keyword raise error.ParseError(_("separate expects at least one argument")) - sep = evalstring(context, mapping, args[0]) + sep = evalstring(context, mapping, args['sep']) first = True - for arg in args[1:]: + for arg in args['args']: argstr = evalstring(context, mapping, arg) if not argstr: continue diff --git a/tests/test-command-template.t b/tests/test-command-template.t --- a/tests/test-command-template.t +++ b/tests/test-command-template.t @@ -146,6 +146,13 @@ Keyword arguments: hg: parse error: can't use a key-value pair in this context [255] +Call function which takes named arguments by filter syntax: + + $ hg debugtemplate '{" "|separate}' + $ hg debugtemplate '{("not", "an", "argument", "list")|separate}' + hg: parse error: unknown method 'list' + [255] + Second branch starting at nullrev: $ hg update null