parser: extend buildargsdict() to support variable-length positional args
authorYuya Nishihara <yuya@tcha.org>
Mon, 09 Jan 2017 15:25:52 +0900
changeset 30753 c3a3896a9fa8
parent 30752 ffd324eaf994
child 30754 26209cb7184e
parser: extend buildargsdict() to support variable-length positional args This can simplify the argument parsing of followlines(). Tests are added by the next patch.
mercurial/parser.py
mercurial/revset.py
--- a/mercurial/parser.py	Mon Jan 09 15:15:21 2017 +0900
+++ b/mercurial/parser.py	Mon Jan 09 15:25:52 2017 +0900
@@ -90,22 +90,61 @@
             return self.eval(t)
         return t
 
-def buildargsdict(trees, funcname, keys, keyvaluenode, keynode):
+def splitargspec(spec):
+    """Parse spec of function arguments into (poskeys, varkey, keys)
+
+    >>> splitargspec('')
+    ([], None, [])
+    >>> splitargspec('foo bar')
+    ([], None, ['foo', 'bar'])
+    >>> splitargspec('foo *bar baz')
+    (['foo'], 'bar', ['baz'])
+    >>> splitargspec('*foo')
+    ([], 'foo', [])
+    """
+    pre, sep, post = spec.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
+
+def buildargsdict(trees, funcname, argspec, keyvaluenode, keynode):
     """Build dict from list containing positional and keyword arguments
 
-    Invalid keywords or too many positional arguments are rejected, but
-    missing arguments are just omitted.
+    Arguments are specified by a tuple of ``(poskeys, varkey, keys)`` 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
+
+    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
     kwstart = next((i for i, x in enumerate(trees) if x[0] == keyvaluenode),
                    len(trees))
-    if len(trees) > len(keys):
+    if kwstart < len(poskeys):
+        raise error.ParseError(_("%(func)s takes at least %(nargs)d positional "
+                                 "arguments")
+                               % {'func': funcname, 'nargs': len(poskeys)})
+    if not varkey and len(trees) > len(poskeys) + len(keys):
         raise error.ParseError(_("%(func)s takes at most %(nargs)d arguments")
-                               % {'func': funcname, 'nargs': len(keys)})
+                               % {'func': funcname,
+                                  'nargs': len(poskeys) + len(keys)})
     args = {}
     # consume positional arguments
-    for k, x in zip(keys, trees[:kwstart]):
+    for k, x in zip(poskeys, trees[:kwstart]):
         args[k] = x
-    assert len(args) == kwstart
+    if varkey:
+        args[varkey] = trees[len(args):kwstart]
+    else:
+        for k, x in zip(keys, trees[len(args):kwstart]):
+            args[k] = x
     # remainder should be keyword arguments
     for x in trees[kwstart:]:
         if x[0] != keyvaluenode or x[1][0] != keynode:
--- a/mercurial/revset.py	Mon Jan 09 15:15:21 2017 +0900
+++ b/mercurial/revset.py	Mon Jan 09 15:25:52 2017 +0900
@@ -326,7 +326,7 @@
     return l
 
 def getargsdict(x, funcname, keys):
-    return parser.buildargsdict(getlist(x), funcname, keys.split(),
+    return parser.buildargsdict(getlist(x), funcname, parser.splitargspec(keys),
                                 keyvaluenode='keyvalue', keynode='symbol')
 
 def getset(repo, subset, x):