mercurial/parser.py
changeset 31921 2156934b7917
parent 31920 a98540ea1e42
child 31922 0f41f1e3c75c
--- a/mercurial/parser.py	Sat Apr 08 20:07:37 2017 +0900
+++ b/mercurial/parser.py	Mon Apr 03 22:07:09 2017 +0900
@@ -94,41 +94,55 @@
         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 @@
         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):