Details
Details
- Reviewers
yuja - Group Reviewers
hg-reviewers - Commits
- rHGef6215df2402: fancyopts: prevent mutation of the default value in customopts
Diff Detail
Diff Detail
- Repository
- rHG Mercurial
- Lint
Lint Skipped - Unit
Unit Tests Skipped
yuja |
hg-reviewers |
Lint Skipped |
Unit Tests Skipped |
Path | Packages | |||
---|---|---|---|---|
M | mercurial/fancyopts.py (16 lines) | |||
M | mercurial/help.py (2 lines) | |||
M | tests/test-help.t (26 lines) |
return parsedopts, parsedargs | return parsedopts, parsedargs | ||||
class customopt(object): | class customopt(object): | ||||
"""Manage defaults and mutations for any type of opt.""" | """Manage defaults and mutations for any type of opt.""" | ||||
__metaclass__ = abc.ABCMeta | __metaclass__ = abc.ABCMeta | ||||
def __init__(self, defaultvalue): | def __init__(self, defaultvalue): | ||||
self.defaultvalue = defaultvalue | self._defaultvalue = defaultvalue | ||||
def _isboolopt(self): | def _isboolopt(self): | ||||
return False | return False | ||||
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 | |||||
@abc.abstractmethod | @abc.abstractmethod | ||||
def newstate(self, oldstate, newparam, abort): | def newstate(self, oldstate, newparam, abort): | ||||
"""Adds newparam to oldstate and returns the new state. | """Adds newparam to oldstate and returns the new state. | ||||
On failure, abort can be called with a string error message.""" | On failure, abort can be called with a string error message.""" | ||||
class _simpleopt(customopt): | class _simpleopt(customopt): | ||||
def _isboolopt(self): | def _isboolopt(self): | ||||
return isinstance(self.defaultvalue, (bool, type(None))) | return isinstance(self._defaultvalue, (bool, type(None))) | ||||
def newstate(self, oldstate, newparam, abort): | def newstate(self, oldstate, newparam, abort): | ||||
return newparam | return newparam | ||||
class _callableopt(customopt): | class _callableopt(customopt): | ||||
def __init__(self, callablefn): | def __init__(self, callablefn): | ||||
self.callablefn = callablefn | self.callablefn = callablefn | ||||
super(_callableopt, self).__init__(None) | super(_callableopt, self).__init__(None) | ||||
def newstate(self, oldstate, newparam, abort): | def newstate(self, oldstate, newparam, abort): | ||||
return self.callablefn(newparam) | return self.callablefn(newparam) | ||||
class _listopt(customopt): | class _listopt(customopt): | ||||
def getdefaultvalue(self): | |||||
return self._defaultvalue[:] | |||||
def newstate(self, oldstate, newparam, abort): | def newstate(self, oldstate, newparam, abort): | ||||
oldstate.append(newparam) | oldstate.append(newparam) | ||||
return oldstate | return oldstate | ||||
class _intopt(customopt): | class _intopt(customopt): | ||||
def newstate(self, oldstate, newparam, abort): | def newstate(self, oldstate, newparam, abort): | ||||
try: | try: | ||||
return int(newparam) | return int(newparam) | ||||
name = name.replace('-', '_') | name = name.replace('-', '_') | ||||
argmap['-' + short] = name | argmap['-' + short] = name | ||||
for n in onames: | for n in onames: | ||||
argmap['--' + n] = name | argmap['--' + n] = name | ||||
defmap[name] = _defaultopt(default) | defmap[name] = _defaultopt(default) | ||||
# copy defaults to state | # copy defaults to state | ||||
state[name] = defmap[name].defaultvalue | state[name] = defmap[name].getdefaultvalue() | ||||
# does it take a parameter? | # does it take a parameter? | ||||
if not defmap[name]._isboolopt(): | if not defmap[name]._isboolopt(): | ||||
if short: | if short: | ||||
short += ':' | short += ':' | ||||
onames = [n + '=' for n in onames] | onames = [n + '=' for n in onames] | ||||
elif name not in nevernegate: | elif name not in nevernegate: | ||||
for n in onames: | for n in onames: |
continue | continue | ||||
so = '' | so = '' | ||||
if shortopt: | if shortopt: | ||||
so = '-' + shortopt | so = '-' + shortopt | ||||
lo = '--' + longopt | lo = '--' + longopt | ||||
if isinstance(default, fancyopts.customopt): | if isinstance(default, fancyopts.customopt): | ||||
default = default.defaultvalue | default = default.getdefaultvalue() | ||||
if default and not callable(default): | if default and not callable(default): | ||||
# default is of unknown type, and in Python 2 we abused | # default is of unknown type, and in Python 2 we abused | ||||
# the %s-shows-repr property to handle integers etc. To | # the %s-shows-repr property to handle integers etc. To | ||||
# match that behavior on Python 3, we do str(default) and | # match that behavior on Python 3, we do str(default) and | ||||
# then convert it to bytes. | # then convert it to bytes. | ||||
desc += _(" (default: %s)") % pycompat.bytestr(default) | desc += _(" (default: %s)") % pycompat.bytestr(default) | ||||
if isinstance(default, list): | if isinstance(default, list): |
[255] | [255] | ||||
$ hg pu.lh | $ hg pu.lh | ||||
hg: unknown command 'pu.lh' | hg: unknown command 'pu.lh' | ||||
(did you mean one of pull, push?) | (did you mean one of pull, push?) | ||||
[255] | [255] | ||||
$ cat > helpext.py <<EOF | $ cat > helpext.py <<EOF | ||||
> import os | > import os | ||||
> from mercurial import commands, registrar | > from mercurial import commands, fancyopts, registrar | ||||
> | > | ||||
> def func(arg): | |||||
> return '%sfoo' % arg | |||||
> class customopt(fancyopts.customopt): | |||||
> def newstate(self, oldstate, newparam, abort): | |||||
> return '%sbar' % oldstate | |||||
> cmdtable = {} | > cmdtable = {} | ||||
> command = registrar.command(cmdtable) | > command = registrar.command(cmdtable) | ||||
> | > | ||||
> @command(b'nohelp', | > @command(b'nohelp', | ||||
> [(b'', b'longdesc', 3, b'x'*90), | > [(b'', b'longdesc', 3, b'x'*67), | ||||
> (b'n', b'', None, b'normal desc'), | > (b'n', b'', None, b'normal desc'), | ||||
> (b'', b'newline', b'', b'line1\nline2')], | > (b'', b'newline', b'', b'line1\nline2'), | ||||
> (b'', b'callableopt', func, b'adds foo'), | |||||
> (b'', b'customopt', customopt(''), b'adds bar'), | |||||
> (b'', b'customopt-withdefault', customopt('foo'), b'adds bar')], | |||||
> b'hg nohelp', | > b'hg nohelp', | ||||
> norepo=True) | > norepo=True) | ||||
> @command(b'debugoptADV', [(b'', b'aopt', None, b'option is (ADVANCED)')]) | > @command(b'debugoptADV', [(b'', b'aopt', None, b'option is (ADVANCED)')]) | ||||
> @command(b'debugoptDEP', [(b'', b'dopt', None, b'option is (DEPRECATED)')]) | > @command(b'debugoptDEP', [(b'', b'dopt', None, b'option is (DEPRECATED)')]) | ||||
> @command(b'debugoptEXP', [(b'', b'eopt', None, b'option is (EXPERIMENTAL)')]) | > @command(b'debugoptEXP', [(b'', b'eopt', None, b'option is (EXPERIMENTAL)')]) | ||||
> def nohelp(ui, *args, **kwargs): | > def nohelp(ui, *args, **kwargs): | ||||
> pass | > pass | ||||
> | > | ||||
$ hg help nohelp | $ hg help nohelp | ||||
hg nohelp | hg nohelp | ||||
(no help text available) | (no help text available) | ||||
options: | options: | ||||
--longdesc VALUE xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | --longdesc VALUE | ||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (default: 3) | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | ||||
xxxxxxxxxxxxxxxxxxxxxxx (default: 3) | |||||
-n -- normal desc | -n -- normal desc | ||||
--newline VALUE line1 line2 | --newline VALUE line1 line2 | ||||
--callableopt VALUE adds foo | |||||
--customopt VALUE adds bar | |||||
--customopt-withdefault VALUE adds bar (default: foo) | |||||
(some details hidden, use --verbose to show complete help) | (some details hidden, use --verbose to show complete help) | ||||
$ hg help -k nohelp | $ hg help -k nohelp | ||||
Commands: | Commands: | ||||
nohelp hg nohelp | nohelp hg nohelp | ||||