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