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.
--- 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