Patchwork [2,of,9] parser: extend buildargsdict() to support arbitrary number of **kwargs

login
register
mail settings
Submitter Yuya Nishihara
Date April 12, 2017, 3:53 p.m.
Message ID <46f15fc2b294842ae24c.1492012398@mimosa>
Download mbox | patch
Permalink /patch/20133/
State Accepted
Headers show

Comments

Yuya Nishihara - April 12, 2017, 3:53 p.m.
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1491224829 -32400
#      Mon Apr 03 22:07:09 2017 +0900
# Node ID 46f15fc2b294842ae24c51f8b9eedcbf6852d920
# Parent  350eec8e79a37c58dc56059d1cafee10a66b8aef
parser: extend buildargsdict() to support arbitrary number of **kwargs

Prepares for adding dict(key1=value1, ...) template function. More tests
will be added later.

Patch

diff --git a/mercurial/parser.py b/mercurial/parser.py
--- a/mercurial/parser.py
+++ b/mercurial/parser.py
@@ -94,41 +94,55 @@  class parser(object):
         return t
 
 def splitargspec(spec):
-    """Parse spec of function arguments into (poskeys, varkey, keys)
+    """Parse spec of function arguments into (poskeys, varkey, keys, optkey)
 
     >>> splitargspec('')
-    ([], None, [])
+    ([], None, [], None)
     >>> splitargspec('foo bar')
-    ([], None, ['foo', 'bar'])
-    >>> splitargspec('foo *bar baz')
-    (['foo'], 'bar', ['baz'])
+    ([], None, ['foo', 'bar'], None)
+    >>> splitargspec('foo *bar baz **qux')
+    (['foo'], 'bar', ['baz'], 'qux')
     >>> splitargspec('*foo')
-    ([], 'foo', [])
+    ([], 'foo', [], None)
+    >>> splitargspec('**foo')
+    ([], None, [], 'foo')
     """
-    pre, sep, post = spec.partition('*')
+    optkey = None
+    pre, sep, post = spec.partition('**')
+    if sep:
+        posts = post.split()
+        if not posts:
+            raise error.ProgrammingError('no **optkey name provided')
+        if len(posts) > 1:
+            raise error.ProgrammingError('excessive **optkey names provided')
+        optkey = posts[0]
+
+    pre, sep, post = pre.partition('*')
     pres = pre.split()
     posts = post.split()
     if sep:
         if not posts:
             raise error.ProgrammingError('no *varkey name provided')
-        return pres, posts[0], posts[1:]
-    return [], None, pres
+        return pres, posts[0], posts[1:], optkey
+    return [], None, pres, optkey
 
 def buildargsdict(trees, funcname, argspec, keyvaluenode, keynode):
     """Build dict from list containing positional and keyword arguments
 
-    Arguments are specified by a tuple of ``(poskeys, varkey, keys)`` where
+    Arguments are specified by a tuple of ``(poskeys, varkey, keys, optkey)``
+    where
 
     - ``poskeys``: list of names of positional arguments
     - ``varkey``: optional argument name that takes up remainder
     - ``keys``: list of names that can be either positional or keyword arguments
+    - ``optkey``: optional argument name that takes up excess keyword arguments
 
     If ``varkey`` specified, all ``keys`` must be given as keyword arguments.
 
     Invalid keywords, too few positional arguments, or too many positional
     arguments are rejected, but missing keyword arguments are just omitted.
     """
-    poskeys, varkey, keys = argspec
+    poskeys, varkey, keys, optkey = argspec
     kwstart = next((i for i, x in enumerate(trees) if x[0] == keyvaluenode),
                    len(trees))
     if kwstart < len(poskeys):
@@ -150,20 +164,26 @@  def buildargsdict(trees, funcname, argsp
         for k, x in zip(keys, trees[len(args):kwstart]):
             args[k] = x
     # remainder should be keyword arguments
+    if optkey:
+        args[optkey] = {}
     for x in trees[kwstart:]:
         if x[0] != keyvaluenode or x[1][0] != keynode:
             raise error.ParseError(_("%(func)s got an invalid argument")
                                    % {'func': funcname})
         k = x[1][1]
-        if k not in keys:
+        if k in keys:
+            d = args
+        elif not optkey:
             raise error.ParseError(_("%(func)s got an unexpected keyword "
                                      "argument '%(key)s'")
                                    % {'func': funcname, 'key': k})
-        if k in args:
+        else:
+            d = args[optkey]
+        if k in d:
             raise error.ParseError(_("%(func)s got multiple values for keyword "
                                      "argument '%(key)s'")
                                    % {'func': funcname, 'key': k})
-        args[k] = x[2]
+        d[k] = x[2]
     return args
 
 def unescapestr(s):
diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -467,7 +467,19 @@  def buildfunc(exp, context):
 
 def _buildfuncargs(exp, context, curmethods, funcname, argspec):
     """Compile parsed tree of function arguments into list or dict of
-    (func, data) pairs"""
+    (func, data) pairs
+
+    >>> context = engine(lambda t: (runsymbol, t))
+    >>> def fargs(expr, argspec):
+    ...     x = _parseexpr(expr)
+    ...     n = getsymbol(x[1])
+    ...     return _buildfuncargs(x[2], context, exprmethods, n, argspec)
+    >>> sorted(fargs('a(l=1, k=2)', 'k l m').keys())
+    ['k', 'l']
+    >>> args = fargs('a(opts=1, k=2)', '**opts')
+    >>> args.keys(), sorted(args['opts'].keys())
+    (['opts'], ['k', 'opts'])
+    """
     def compiledict(xs):
         return dict((k, compileexp(x, context, curmethods))
                     for k, x in xs.iteritems())
@@ -479,12 +491,14 @@  def _buildfuncargs(exp, context, curmeth
         return compilelist(getlist(exp))
 
     # function with argspec: return dict of named args
-    _poskeys, varkey, _keys = argspec = parser.splitargspec(argspec)
+    _poskeys, varkey, _keys, optkey = argspec = parser.splitargspec(argspec)
     treeargs = parser.buildargsdict(getlist(exp), funcname, argspec,
                                     keyvaluenode='keyvalue', keynode='symbol')
     compargs = {}
     if varkey:
         compargs[varkey] = compilelist(treeargs.pop(varkey))
+    if optkey:
+        compargs[optkey] = compiledict(treeargs.pop(optkey))
     compargs.update(compiledict(treeargs))
     return compargs