[PATCH 4 of 5] templater: add support for keyword arguments

Yuya Nishihara yuya at tcha.org
Sat Apr 8 07:37:34 EDT 2017


# HG changeset patch
# User Yuya Nishihara <yuya at tcha.org>
# Date 1491222159 -32400
#      Mon Apr 03 21:22:39 2017 +0900
# Node ID 3c73949652fca9dd142af5529fc8f190801b5913
# Parent  0a547405070213e075162d45f37a2d82877c61c3
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.

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
@@ -452,17 +452,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, n, f._argspec)
         return (f, args)
     if n in context._filters:
+        args = _buildfuncargs(exp[2], context, 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, 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, exprmethods))
+                    for k, x in xs.iteritems())
+    def compilelist(xs):
+        return [compileexp(x, context, exprmethods) 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"))
 
@@ -830,16 +854,16 @@ def rstdoc(context, mapping, args):
 
     return minirst.format(text, style=style, keep=['verbose'])
 
- at templatefunc('separate(sep, args)')
+ at 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


More information about the Mercurial-devel mailing list