Mercurial > hg
changeset 36353:cc9d0763c8e9
fancyopts: add support for custom multi-arg opts in fancyopts.py
This allows for more complex multi-arg opt logic, such as "--sum 1 --sum 2"
-> 3, "--csv alice,bob --csv charlie" -> ["alice","bob","charlie"]. The
current support for callables is insufficient for this.
This is done by introducing a 'customopt' class which can be extended for
more powerful opts logic. All existing opt-types are converted to use this
class, simplifying the fancyopts() logic.
Differential Revision: https://phab.mercurial-scm.org/D2090
author | Daniel Ploch <dploch@google.com> |
---|---|
date | Wed, 21 Feb 2018 20:05:29 -0800 |
parents | 5dbeddbf164a |
children | 0d5eaa97676b |
files | mercurial/fancyopts.py |
diffstat | 1 files changed, 70 insertions(+), 22 deletions(-) [+] |
line wrap: on
line diff
--- a/mercurial/fancyopts.py Wed Feb 21 11:57:11 2018 -0500 +++ b/mercurial/fancyopts.py Wed Feb 21 20:05:29 2018 -0800 @@ -7,7 +7,9 @@ from __future__ import absolute_import +import abc import functools +import types from .i18n import _ from . import ( @@ -201,6 +203,64 @@ parsedargs.extend(args[pos:]) return parsedopts, parsedargs +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): + return isinstance(self.defaultvalue, (bool, types.NoneType)) + + 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) + def fancyopts(args, options, state, gnu=False, early=False, optaliases=None): """ read args, parse options, and store options in state @@ -220,6 +280,7 @@ list - parameter string is added to a list integer - parameter strings is stored as int function - call function with parameter + customopt - subclass of 'customopt' optaliases is a mapping from a canonical option name to a list of additional long options. This exists for preserving backward compatibility @@ -250,18 +311,13 @@ argmap['-' + short] = name for n in onames: argmap['--' + n] = name - defmap[name] = default + defmap[name] = _defaultopt(default) # copy defaults to state - if isinstance(default, list): - state[name] = default[:] - elif callable(default): - state[name] = None - else: - state[name] = default + state[name] = defmap[name].defaultvalue # does it take a parameter? - if not (default is None or default is True or default is False): + if not defmap[name]._isboolopt(): if short: short += ':' onames = [n + '=' for n in onames] @@ -301,21 +357,13 @@ boolval = False name = argmap[opt] obj = defmap[name] - t = type(obj) - if callable(obj): - state[name] = defmap[name](val) - elif t is type(1): - try: - state[name] = int(val) - except ValueError: - raise error.Abort(_('invalid value %r for option %s, ' - 'expected int') % (val, opt)) - elif t is type(''): - state[name] = val - elif t is type([]): - state[name].append(val) - elif t is type(None) or t is type(False): + if obj._isboolopt(): state[name] = boolval + 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) # return unparsed args return args