##// END OF EJS Templates
interfaces: convert `imanifestrevisionwritable` to a Protocol class...
interfaces: convert `imanifestrevisionwritable` to a Protocol class Same as cdd4bc69bfc1 for `imanifestrevisionstored`.

File last commit:

r52756:f4733654 default
r53377:01818a59 default
Show More
fancyopts.py
391 lines | 11.3 KiB | text/x-python | PythonLexer
Martin Geisler
fancyopts: add copyright and license header
r8230 # fancyopts.py - better command line parsing
#
Raphaël Gomès
contributor: change mentions of mpm to olivia...
r47575 # Copyright 2005-2009 Olivia Mackall <olivia@selenic.com> and others
Martin Geisler
fancyopts: add copyright and license header
r8230 #
# 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
Matt Harbison
typing: add `from __future__ import annotations` to most files...
r52756 from __future__ import annotations
Gregory Szorc
fancyopts: use absolute_import
r25947
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
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'noninteractive',
Augie Fackler
flags: allow specifying --no-boolean-flag on the command line (BC)...
r29947 # 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.
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'help',
b'version',
Martin von Zweigbergk
cleanup: use set literals...
r32291 }
Augie Fackler
flags: allow specifying --no-boolean-flag on the command line (BC)...
r29947
Augie Fackler
formatting: blacken the codebase...
r43346
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)
"""
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if arg.startswith(b'--'):
flag, eq, val = arg.partition(b'=')
Yuya Nishihara
fancyopts: add early-options parser compatible with getopt()...
r35179 if flag[2:] in namelist:
return flag, bool(eq), val, False
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if flag[2:] + b'=' in namelist:
Yuya Nishihara
fancyopts: add early-options parser compatible with getopt()...
r35179 return flag, bool(eq), val, True
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif arg.startswith(b'-') and arg != b'-' and not arg.startswith(b'-:'):
Yuya Nishihara
fancyopts: add early-options parser compatible with getopt()...
r35179 flag, val = arg[:2], arg[2:]
i = shortlist.find(flag[1:])
if i >= 0:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 return flag, bool(val), val, shortlist.startswith(b':', i + 1)
return b'', False, b'', False
Yuya Nishihara
fancyopts: add early-options parser compatible with getopt()...
r35179
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
fancyopts: add early-options parser compatible with getopt()...
r35179 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]
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if arg == b'--':
Yuya Nishihara
fancyopts: fix handling of "--" value in earlygetopt()
r35227 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
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
py3: use class X: instead of class X(object):...
r49801 class customopt: # pytype: disable=ignored-metaclass
Daniel Ploch
fancyopts: add support for custom multi-arg opts in fancyopts.py...
r36373 """Manage defaults and mutations for any type of opt."""
__metaclass__ = abc.ABCMeta
def __init__(self, defaultvalue):
Daniel Ploch
fancyopts: prevent mutation of the default value in customopts...
r37110 self._defaultvalue = defaultvalue
Daniel Ploch
fancyopts: add support for custom multi-arg opts in fancyopts.py...
r36373
def _isboolopt(self):
return False
Daniel Ploch
fancyopts: prevent mutation of the default value in customopts...
r37110 def getdefaultvalue(self):
"""Returns the default value for this opt.
Subclasses should override this to return a new value if the value type
is mutable."""
return self._defaultvalue
Daniel Ploch
fancyopts: add support for custom multi-arg opts in fancyopts.py...
r36373 @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."""
Augie Fackler
formatting: blacken the codebase...
r43346
Daniel Ploch
fancyopts: add support for custom multi-arg opts in fancyopts.py...
r36373 class _simpleopt(customopt):
def _isboolopt(self):
Daniel Ploch
fancyopts: prevent mutation of the default value in customopts...
r37110 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
Augie Fackler
formatting: blacken the codebase...
r43346
Daniel Ploch
fancyopts: add support for custom multi-arg opts in fancyopts.py...
r36373 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)
Augie Fackler
formatting: blacken the codebase...
r43346
Daniel Ploch
fancyopts: add support for custom multi-arg opts in fancyopts.py...
r36373 class _listopt(customopt):
Daniel Ploch
fancyopts: prevent mutation of the default value in customopts...
r37110 def getdefaultvalue(self):
return self._defaultvalue[:]
Daniel Ploch
fancyopts: add support for custom multi-arg opts in fancyopts.py...
r36373 def newstate(self, oldstate, newparam, abort):
oldstate.append(newparam)
return oldstate
Augie Fackler
formatting: blacken the codebase...
r43346
Daniel Ploch
fancyopts: add support for custom multi-arg opts in fancyopts.py...
r36373 class _intopt(customopt):
def newstate(self, oldstate, newparam, abort):
try:
return int(newparam)
except ValueError:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 abort(_(b'expected int'))
Daniel Ploch
fancyopts: add support for custom multi-arg opts in fancyopts.py...
r36373
Augie Fackler
formatting: blacken the codebase...
r43346
Daniel Ploch
fancyopts: add support for custom multi-arg opts in fancyopts.py...
r36373 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)
Augie Fackler
formatting: blacken the codebase...
r43346
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 = []
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 shortlist = b''
Matt Mackall
fancyopts: lots of cleanups
r5638 argmap = {}
defmap = {}
Augie Fackler
flags: allow specifying --no-boolean-flag on the command line (BC)...
r29947 negations = {}
Augie Fackler
cleanup: run pyupgrade on our source tree to clean up varying things...
r44937 alllong = {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, []))
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 name = name.replace(b'-', b'_')
Matt Mackall
fancyopts: lots of cleanups
r5638
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 argmap[b'-' + short] = name
Yuya Nishihara
dispatch: alias --repo to --repository while parsing early options...
r35223 for n in onames:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 argmap[b'--' + 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: prevent mutation of the default value in customopts...
r37110 state[name] = defmap[name].getdefaultvalue()
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:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 short += b':'
onames = [n + b'=' for n in onames]
Yuya Nishihara
dispatch: alias --repo to --repository while parsing early options...
r35223 elif name not in nevernegate:
for n in onames:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if n.startswith(b'no-'):
Yuya Nishihara
dispatch: alias --repo to --repository while parsing early options...
r35223 insert = n[3:]
else:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 insert = b'no-' + n
Yuya Nishihara
dispatch: alias --repo to --repository while parsing early options...
r35223 # 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:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 assert (b'--' + n) not in negations
negations[b'--' + insert] = b'--' + n
Yuya Nishihara
dispatch: alias --repo to --repository while parsing early options...
r35223 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:
Augie Fackler
formatting: blacken the codebase...
r43346
Daniel Ploch
fancyopts: add support for custom multi-arg opts in fancyopts.py...
r36373 def abort(s):
Martin von Zweigbergk
errors: raise InputError in fancyopts...
r46441 raise error.InputError(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 _(b'invalid value %r for option %s, %s')
Augie Fackler
formatting: blacken the codebase...
r43346 % (pycompat.maybebytestr(val), opt, s)
)
Daniel Ploch
fancyopts: add support for custom multi-arg opts in fancyopts.py...
r36373 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