Mercurial > hg
changeset 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 |
files | mercurial/registrar.py mercurial/templater.py tests/test-command-template.t |
diffstat | 3 files changed, 47 insertions(+), 8 deletions(-) [+] |
line wrap: on
line diff
--- a/mercurial/registrar.py Mon Apr 03 20:55:55 2017 +0900 +++ b/mercurial/registrar.py Mon Apr 03 21:22:39 2017 +0900 @@ -234,7 +234,7 @@ 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 @@ 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 @@ Otherwise, explicit 'templater.loadfunction()' is needed. """ _getname = _funcregistrarbase._parsefuncdecl + + def _extrasetup(self, name, func, argspec=None): + func._argspec = argspec
--- a/mercurial/templater.py Mon Apr 03 20:55:55 2017 +0900 +++ b/mercurial/templater.py Mon Apr 03 21:22:39 2017 +0900 @@ -370,14 +370,15 @@ 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 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 @@ 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
--- a/tests/test-command-template.t Mon Apr 03 20:55:55 2017 +0900 +++ b/tests/test-command-template.t Mon Apr 03 21:22:39 2017 +0900 @@ -146,6 +146,13 @@ 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