--- a/mercurial/fancyopts.py Sun Nov 12 23:45:14 2017 -0500
+++ b/mercurial/fancyopts.py Thu Nov 23 22:04:53 2017 +0900
@@ -7,6 +7,8 @@
from __future__ import absolute_import
+import functools
+
from .i18n import _
from . import (
error,
@@ -24,6 +26,179 @@
'version',
}
+def _earlyoptarg(arg, shortlist, namelist):
+ """Check if the given arg is a valid unabbreviated option
+
+ Returns (flag_str, has_embedded_value?, embedded_value, takes_value?)
+
+ >>> def opt(arg):
+ ... return _earlyoptarg(arg, b'R:q', [b'cwd=', b'debugger'])
+
+ long form:
+
+ >>> opt(b'--cwd')
+ ('--cwd', False, '', True)
+ >>> opt(b'--cwd=')
+ ('--cwd', True, '', True)
+ >>> opt(b'--cwd=foo')
+ ('--cwd', True, 'foo', True)
+ >>> opt(b'--debugger')
+ ('--debugger', False, '', False)
+ >>> opt(b'--debugger=') # invalid but parsable
+ ('--debugger', True, '', False)
+
+ short form:
+
+ >>> opt(b'-R')
+ ('-R', False, '', True)
+ >>> opt(b'-Rfoo')
+ ('-R', True, 'foo', True)
+ >>> opt(b'-q')
+ ('-q', False, '', False)
+ >>> opt(b'-qfoo') # invalid but parsable
+ ('-q', True, 'foo', False)
+
+ unknown or invalid:
+
+ >>> opt(b'--unknown')
+ ('', False, '', False)
+ >>> opt(b'-u')
+ ('', False, '', False)
+ >>> opt(b'-ufoo')
+ ('', False, '', False)
+ >>> opt(b'--')
+ ('', False, '', False)
+ >>> opt(b'-')
+ ('', False, '', False)
+ >>> opt(b'-:')
+ ('', False, '', False)
+ >>> opt(b'-:foo')
+ ('', False, '', False)
+ """
+ if arg.startswith('--'):
+ flag, eq, val = arg.partition('=')
+ if flag[2:] in namelist:
+ return flag, bool(eq), val, False
+ if flag[2:] + '=' in namelist:
+ return flag, bool(eq), val, True
+ elif arg.startswith('-') and arg != '-' and not arg.startswith('-:'):
+ flag, val = arg[:2], arg[2:]
+ i = shortlist.find(flag[1:])
+ if i >= 0:
+ return flag, bool(val), val, shortlist.startswith(':', i + 1)
+ return '', False, '', False
+
+def earlygetopt(args, shortlist, namelist, gnu=False, keepsep=False):
+ """Parse options like getopt, but ignores unknown options and abbreviated
+ forms
+
+ If gnu=False, this stops processing options as soon as a non/unknown-option
+ argument is encountered. Otherwise, option and non-option arguments may be
+ intermixed, and unknown-option arguments are taken as non-option.
+
+ If keepsep=True, '--' won't be removed from the list of arguments left.
+ This is useful for stripping early options from a full command arguments.
+
+ >>> def get(args, gnu=False, keepsep=False):
+ ... return earlygetopt(args, b'R:q', [b'cwd=', b'debugger'],
+ ... gnu=gnu, keepsep=keepsep)
+
+ default parsing rules for early options:
+
+ >>> get([b'x', b'--cwd', b'foo', b'-Rbar', b'-q', b'y'], gnu=True)
+ ([('--cwd', 'foo'), ('-R', 'bar'), ('-q', '')], ['x', 'y'])
+ >>> get([b'x', b'--cwd=foo', b'y', b'-R', b'bar', b'--debugger'], gnu=True)
+ ([('--cwd', 'foo'), ('-R', 'bar'), ('--debugger', '')], ['x', 'y'])
+ >>> get([b'--unknown', b'--cwd=foo', b'--', '--debugger'], gnu=True)
+ ([('--cwd', 'foo')], ['--unknown', '--debugger'])
+
+ restricted parsing rules (early options must come first):
+
+ >>> get([b'--cwd', b'foo', b'-Rbar', b'x', b'-q', b'y'], gnu=False)
+ ([('--cwd', 'foo'), ('-R', 'bar')], ['x', '-q', 'y'])
+ >>> get([b'--cwd=foo', b'x', b'y', b'-R', b'bar', b'--debugger'], gnu=False)
+ ([('--cwd', 'foo')], ['x', 'y', '-R', 'bar', '--debugger'])
+ >>> get([b'--unknown', b'--cwd=foo', b'--', '--debugger'], gnu=False)
+ ([], ['--unknown', '--cwd=foo', '--debugger'])
+
+ stripping early options (without loosing '--'):
+
+ >>> get([b'x', b'-Rbar', b'--', '--debugger'], gnu=True, keepsep=True)[1]
+ ['x', '--', '--debugger']
+
+ last argument:
+
+ >>> get([b'--cwd'])
+ ([], ['--cwd'])
+ >>> get([b'--cwd=foo'])
+ ([('--cwd', 'foo')], [])
+ >>> get([b'-R'])
+ ([], ['-R'])
+ >>> get([b'-Rbar'])
+ ([('-R', 'bar')], [])
+ >>> get([b'-q'])
+ ([('-q', '')], [])
+ >>> get([b'-q', b'--'])
+ ([('-q', '')], [])
+
+ value passed to bool options:
+
+ >>> get([b'--debugger=foo', b'x'])
+ ([], ['--debugger=foo', 'x'])
+ >>> get([b'-qfoo', b'x'])
+ ([], ['-qfoo', 'x'])
+
+ short option isn't separated with '=':
+
+ >>> get([b'-R=bar'])
+ ([('-R', '=bar')], [])
+
+ ':' may be in shortlist, but shouldn't be taken as an option letter:
+
+ >>> get([b'-:', b'y'])
+ ([], ['-:', 'y'])
+
+ '-' is a valid non-option argument:
+
+ >>> get([b'-', b'y'])
+ ([], ['-', 'y'])
+ """
+ # ignoring everything just after '--' isn't correct as '--' may be an
+ # option value (e.g. ['-R', '--']), but we do that consistently.
+ try:
+ argcount = args.index('--')
+ except ValueError:
+ argcount = len(args)
+
+ parsedopts = []
+ parsedargs = []
+ pos = 0
+ while pos < argcount:
+ arg = args[pos]
+ flag, hasval, val, takeval = _earlyoptarg(arg, shortlist, namelist)
+ if not hasval and takeval and pos + 1 >= argcount:
+ # missing last argument
+ break
+ if not flag or hasval and not takeval:
+ # non-option argument or -b/--bool=INVALID_VALUE
+ if gnu:
+ parsedargs.append(arg)
+ pos += 1
+ else:
+ break
+ elif hasval == takeval:
+ # -b/--bool or -s/--str=VALUE
+ parsedopts.append((flag, val))
+ pos += 1
+ else:
+ # -s/--str VALUE
+ parsedopts.append((flag, args[pos + 1]))
+ pos += 2
+
+ parsedargs.extend(args[pos:argcount])
+ parsedargs.extend(args[argcount + (not keepsep):])
+ return parsedopts, parsedargs
+
def gnugetopt(args, options, longoptions):
"""Parse options mostly like getopt.gnu_getopt.
@@ -51,7 +226,7 @@
return opts, args
-def fancyopts(args, options, state, gnu=False):
+def fancyopts(args, options, state, gnu=False, early=False):
"""
read args, parse options, and store options in state
@@ -124,7 +299,9 @@
namelist.append(oname)
# parse arguments
- if gnu:
+ if early:
+ parse = functools.partial(earlygetopt, gnu=gnu)
+ elif gnu:
parse = gnugetopt
else:
parse = pycompat.getoptb