##// END OF EJS Templates
wireproto: raise ProgrammingError instead of Abort...
wireproto: raise ProgrammingError instead of Abort This isn't a user-facing error and can only be caused by bad Python code. Thanks to Yuya for spotting this. Differential Revision: https://phab.mercurial-scm.org/D2777

File last commit:

r36401:24b48166 default
r36858:ef68493d default
Show More
fancyopts.py
368 lines | 10.9 KiB | text/x-python | PythonLexer
Martin Geisler
fancyopts: add copyright and license header
r8230 # fancyopts.py - better command line parsing
#
# Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
#
# This software may be used and distributed according to the terms of the
Matt Mackall
Update license to GPLv2+
r10263 # GNU General Public License version 2 or any later version.
Martin Geisler
fancyopts: add copyright and license header
r8230
Gregory Szorc
fancyopts: use absolute_import
r25947 from __future__ import absolute_import
Daniel Ploch
fancyopts: add support for custom multi-arg opts in fancyopts.py...
r36373 import abc
Yuya Nishihara
fancyopts: add early-options parser compatible with getopt()...
r35179 import functools
Gregory Szorc
fancyopts: use absolute_import
r25947 from .i18n import _
Pulkit Goyal
py3: make a bytes version of getopt.getopt()...
r30578 from . import (
error,
pycompat,
)
mpm@selenic.com
Add back links from file revisions to changeset revisions...
r0
Augie Fackler
flags: allow specifying --no-boolean-flag on the command line (BC)...
r29947 # Set of flags to not apply boolean negation logic on
Martin von Zweigbergk
cleanup: use set literals...
r32291 nevernegate = {
Augie Fackler
flags: allow specifying --no-boolean-flag on the command line (BC)...
r29947 # avoid --no-noninteractive
'noninteractive',
# These two flags are special because they cause hg to do one
# thing and then exit, and so aren't suitable for use in things
# like aliases anyway.
'help',
'version',
Martin von Zweigbergk
cleanup: use set literals...
r32291 }
Augie Fackler
flags: allow specifying --no-boolean-flag on the command line (BC)...
r29947
Yuya Nishihara
fancyopts: add early-options parser compatible with getopt()...
r35179 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)
Yuya Nishihara
fancyopts: fix handling of "--" value in earlygetopt()
r35227 ([], ['--unknown', '--cwd=foo', '--', '--debugger'])
Yuya Nishihara
fancyopts: add early-options parser compatible with getopt()...
r35179
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', '')], [])
Yuya Nishihara
fancyopts: fix handling of "--" value in earlygetopt()
r35227 '--' may be a value:
>>> get([b'-R', b'--', b'x'])
([('-R', '--')], ['x'])
>>> get([b'--cwd', b'--', b'x'])
([('--cwd', '--')], ['x'])
Yuya Nishihara
fancyopts: add early-options parser compatible with getopt()...
r35179 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'])
"""
parsedopts = []
parsedargs = []
pos = 0
Yuya Nishihara
fancyopts: fix handling of "--" value in earlygetopt()
r35227 while pos < len(args):
Yuya Nishihara
fancyopts: add early-options parser compatible with getopt()...
r35179 arg = args[pos]
Yuya Nishihara
fancyopts: fix handling of "--" value in earlygetopt()
r35227 if arg == '--':
pos += not keepsep
break
Yuya Nishihara
fancyopts: add early-options parser compatible with getopt()...
r35179 flag, hasval, val, takeval = _earlyoptarg(arg, shortlist, namelist)
Yuya Nishihara
fancyopts: fix handling of "--" value in earlygetopt()
r35227 if not hasval and takeval and pos + 1 >= len(args):
Yuya Nishihara
fancyopts: add early-options parser compatible with getopt()...
r35179 # 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
Yuya Nishihara
fancyopts: fix handling of "--" value in earlygetopt()
r35227 parsedargs.extend(args[pos:])
Yuya Nishihara
fancyopts: add early-options parser compatible with getopt()...
r35179 return parsedopts, parsedargs
Daniel Ploch
fancyopts: add support for custom multi-arg opts in fancyopts.py...
r36373 class customopt(object):
"""Manage defaults and mutations for any type of opt."""
__metaclass__ = abc.ABCMeta
def __init__(self, defaultvalue):
self.defaultvalue = defaultvalue
def _isboolopt(self):
return False
@abc.abstractmethod
def newstate(self, oldstate, newparam, abort):
"""Adds newparam to oldstate and returns the new state.
On failure, abort can be called with a string error message."""
class _simpleopt(customopt):
def _isboolopt(self):
Pulkit Goyal
py3: replace types.NoneType with type(None)...
r36401 return isinstance(self.defaultvalue, (bool, type(None)))
Daniel Ploch
fancyopts: add support for custom multi-arg opts in fancyopts.py...
r36373
def newstate(self, oldstate, newparam, abort):
return newparam
class _callableopt(customopt):
def __init__(self, callablefn):
self.callablefn = callablefn
super(_callableopt, self).__init__(None)
def newstate(self, oldstate, newparam, abort):
return self.callablefn(newparam)
class _listopt(customopt):
def newstate(self, oldstate, newparam, abort):
oldstate.append(newparam)
return oldstate
class _intopt(customopt):
def newstate(self, oldstate, newparam, abort):
try:
return int(newparam)
except ValueError:
abort(_('expected int'))
def _defaultopt(default):
"""Returns a default opt implementation, given a default value."""
if isinstance(default, customopt):
return default
elif callable(default):
return _callableopt(default)
elif isinstance(default, list):
return _listopt(default[:])
elif type(default) is type(1):
return _intopt(default)
else:
return _simpleopt(default)
Yuya Nishihara
dispatch: alias --repo to --repository while parsing early options...
r35223 def fancyopts(args, options, state, gnu=False, early=False, optaliases=None):
Matt Mackall
fancyopts: lots of cleanups
r5638 """
read args, parse options, and store options in state
each option is a tuple of:
short option or ''
long option
default value
description
FUJIWARA Katsunori
help: show value requirement and multiple occurrence of options...
r11321 option value label(optional)
Matt Mackall
fancyopts: lots of cleanups
r5638
option types include:
boolean or none - option sets variable in state to true
string - parameter string is stored in state
list - parameter string is added to a list
integer - parameter strings is stored as int
function - call function with parameter
Daniel Ploch
fancyopts: add support for custom multi-arg opts in fancyopts.py...
r36373 customopt - subclass of 'customopt'
mpm@selenic.com
Add back links from file revisions to changeset revisions...
r0
Yuya Nishihara
dispatch: alias --repo to --repository while parsing early options...
r35223 optaliases is a mapping from a canonical option name to a list of
additional long options. This exists for preserving backward compatibility
of early options. If we want to use it extensively, please consider moving
the functionality to the options table (e.g separate long options by '|'.)
Matt Mackall
fancyopts: lots of cleanups
r5638 non-option args are returned
"""
Yuya Nishihara
dispatch: alias --repo to --repository while parsing early options...
r35223 if optaliases is None:
optaliases = {}
Matt Mackall
fancyopts: lots of cleanups
r5638 namelist = []
shortlist = ''
argmap = {}
defmap = {}
Augie Fackler
flags: allow specifying --no-boolean-flag on the command line (BC)...
r29947 negations = {}
alllong = set(o[1] for o in options)
Matt Mackall
fancyopts: lots of cleanups
r5638
FUJIWARA Katsunori
help: show value requirement and multiple occurrence of options...
r11321 for option in options:
if len(option) == 5:
short, name, default, comment, dummy = option
else:
short, name, default, comment = option
Matt Mackall
fancyopts: lots of cleanups
r5638 # convert opts to getopt format
Yuya Nishihara
dispatch: alias --repo to --repository while parsing early options...
r35223 onames = [name]
onames.extend(optaliases.get(name, []))
Matt Mackall
fancyopts: lots of cleanups
r5638 name = name.replace('-', '_')
Yuya Nishihara
dispatch: alias --repo to --repository while parsing early options...
r35223 argmap['-' + short] = name
for n in onames:
argmap['--' + n] = name
Daniel Ploch
fancyopts: add support for custom multi-arg opts in fancyopts.py...
r36373 defmap[name] = _defaultopt(default)
Matt Mackall
fancyopts: lots of cleanups
r5638
# copy defaults to state
Daniel Ploch
fancyopts: add support for custom multi-arg opts in fancyopts.py...
r36373 state[name] = defmap[name].defaultvalue
mpm@selenic.com
Add back links from file revisions to changeset revisions...
r0
Matt Mackall
fancyopts: lots of cleanups
r5638 # does it take a parameter?
Daniel Ploch
fancyopts: add support for custom multi-arg opts in fancyopts.py...
r36373 if not defmap[name]._isboolopt():
Matt Mackall
many, many trivial check-code fixups
r10282 if short:
short += ':'
Yuya Nishihara
dispatch: alias --repo to --repository while parsing early options...
r35223 onames = [n + '=' for n in onames]
elif name not in nevernegate:
for n in onames:
if n.startswith('no-'):
insert = n[3:]
else:
insert = 'no-' + n
# backout (as a practical example) has both --commit and
# --no-commit options, so we don't want to allow the
# negations of those flags.
if insert not in alllong:
assert ('--' + n) not in negations
negations['--' + insert] = '--' + n
namelist.append(insert)
Matt Mackall
fancyopts: lots of cleanups
r5638 if short:
shortlist += short
if name:
Yuya Nishihara
dispatch: alias --repo to --repository while parsing early options...
r35223 namelist.extend(onames)
Matt Mackall
fancyopts: lots of cleanups
r5638
# parse arguments
Yuya Nishihara
fancyopts: add early-options parser compatible with getopt()...
r35179 if early:
parse = functools.partial(earlygetopt, gnu=gnu)
elif gnu:
Yuya Nishihara
fancyopts: use getopt.gnu_getopt()...
r35226 parse = pycompat.gnugetoptb
Augie Fackler
fancyopts: Parse options that occur after arguments....
r7772 else:
Pulkit Goyal
py3: make a bytes version of getopt.getopt()...
r30578 parse = pycompat.getoptb
Augie Fackler
fancyopts: Parse options that occur after arguments....
r7772 opts, args = parse(args, shortlist, namelist)
mpm@selenic.com
Add back links from file revisions to changeset revisions...
r0
Matt Mackall
fancyopts: lots of cleanups
r5638 # transfer result to state
for opt, val in opts:
Augie Fackler
flags: allow specifying --no-boolean-flag on the command line (BC)...
r29947 boolval = True
negation = negations.get(opt, False)
if negation:
opt = negation
boolval = False
Matt Mackall
fancyopts: lots of cleanups
r5638 name = argmap[opt]
introom
fancyopts: allow all callable as default parameter value...
r25563 obj = defmap[name]
Daniel Ploch
fancyopts: add support for custom multi-arg opts in fancyopts.py...
r36373 if obj._isboolopt():
Augie Fackler
flags: allow specifying --no-boolean-flag on the command line (BC)...
r29947 state[name] = boolval
Daniel Ploch
fancyopts: add support for custom multi-arg opts in fancyopts.py...
r36373 else:
def abort(s):
raise error.Abort(
_('invalid value %r for option %s, %s') % (val, opt, s))
state[name] = defmap[name].newstate(state[name], val, abort)
mpm@selenic.com
Beginning of new command parsing interface...
r209
Matt Mackall
fancyopts: lots of cleanups
r5638 # return unparsed args
mpm@selenic.com
Add back links from file revisions to changeset revisions...
r0 return args