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 | ||||