drop help for long options if longer versions with hyphens are available
--HG-- branch : opt-drop-non-hyphened-long-options
This commit is contained in:
parent
18fa7d866d
commit
007a77c2ba
|
@ -277,7 +277,13 @@ class OptionGroup:
|
||||||
self.parser = parser
|
self.parser = parser
|
||||||
|
|
||||||
def addoption(self, *optnames, **attrs):
|
def addoption(self, *optnames, **attrs):
|
||||||
""" add an option to this group. """
|
""" add an option to this group.
|
||||||
|
|
||||||
|
if a shortened version of a long option is specified it will
|
||||||
|
be suppressed in the help. addoption('--twowords', '--two-words')
|
||||||
|
results in help showing '--two-words' only, but --twowords gets
|
||||||
|
accepted **and** the automatic destination is in args.twowords
|
||||||
|
"""
|
||||||
option = Argument(*optnames, **attrs)
|
option = Argument(*optnames, **attrs)
|
||||||
self._addoption_instance(option, shortupper=False)
|
self._addoption_instance(option, shortupper=False)
|
||||||
|
|
||||||
|
@ -299,7 +305,7 @@ class MyOptionParser(py.std.argparse.ArgumentParser):
|
||||||
def __init__(self, parser):
|
def __init__(self, parser):
|
||||||
self._parser = parser
|
self._parser = parser
|
||||||
py.std.argparse.ArgumentParser.__init__(self, usage=parser._usage,
|
py.std.argparse.ArgumentParser.__init__(self, usage=parser._usage,
|
||||||
add_help=False)
|
add_help=False, formatter_class=DropShorterLongHelpFormatter)
|
||||||
|
|
||||||
def format_epilog(self, formatter):
|
def format_epilog(self, formatter):
|
||||||
hints = self._parser.hints
|
hints = self._parser.hints
|
||||||
|
@ -320,6 +326,67 @@ class MyOptionParser(py.std.argparse.ArgumentParser):
|
||||||
getattr(args, Config._file_or_dir).extend(argv)
|
getattr(args, Config._file_or_dir).extend(argv)
|
||||||
return args
|
return args
|
||||||
|
|
||||||
|
# #pylib 2013-07-31
|
||||||
|
# (12:05:53) anthon: hynek: can you get me a list of preferred py.test
|
||||||
|
# long-options with '-' inserted at the right places?
|
||||||
|
# (12:08:29) hynek: anthon, hpk: generally I'd love the following, decide
|
||||||
|
# yourself which you agree and which not:
|
||||||
|
# (12:10:51) hynek: --exit-on-first --full-trace --junit-xml --junit-prefix
|
||||||
|
# --result-log --collect-only --conf-cut-dir --trace-config
|
||||||
|
# --no-magic
|
||||||
|
# (12:18:21) hpk: hynek,anthon: makes sense to me.
|
||||||
|
# (13:40:30) hpk: hynek: let's not change names, rather only deal with
|
||||||
|
# hyphens for now
|
||||||
|
# (13:40:50) hynek: then --exit-first *shrug*
|
||||||
|
|
||||||
|
class DropShorterLongHelpFormatter(py.std.argparse.HelpFormatter):
|
||||||
|
"""shorten help for long options that differ only in extra hyphens
|
||||||
|
|
||||||
|
- collapse **long** options that are the same except for extra hyphens
|
||||||
|
- special action attribute map_long_option allows surpressing additional
|
||||||
|
long options
|
||||||
|
- shortcut if there are only two options and one of them is a short one
|
||||||
|
- cache result on action object as this is called at least 2 times
|
||||||
|
"""
|
||||||
|
def _format_action_invocation(self, action):
|
||||||
|
orgstr = py.std.argparse.HelpFormatter._format_action_invocation(self, action)
|
||||||
|
if orgstr and orgstr[0] != '-': # only optional arguments
|
||||||
|
return orgstr
|
||||||
|
res = getattr(action, '_formatted_action_invocation', None)
|
||||||
|
if res:
|
||||||
|
return res
|
||||||
|
options = orgstr.split(', ')
|
||||||
|
if len(options) == 2 and (len(options[0]) == 2 or len(options[1]) == 2):
|
||||||
|
# a shortcut for '-h, --help' or '--abc', '-a'
|
||||||
|
action._formatted_action_invocation = orgstr
|
||||||
|
return orgstr
|
||||||
|
return_list = []
|
||||||
|
option_map = getattr(action, 'map_long_option', {})
|
||||||
|
if option_map is None:
|
||||||
|
option_map = {}
|
||||||
|
short_long = {}
|
||||||
|
for option in options:
|
||||||
|
if len(option) == 2 or option[2] == ' ':
|
||||||
|
continue
|
||||||
|
if not option.startswith('--'):
|
||||||
|
raise ArgumentError('long optional argument without "--": [%s]'
|
||||||
|
% (option), self)
|
||||||
|
xxoption = option[2:]
|
||||||
|
if xxoption.split()[0] not in option_map:
|
||||||
|
shortened = xxoption.replace('-', '')
|
||||||
|
if shortened not in short_long or \
|
||||||
|
len(short_long[shortened]) < len(xxoption):
|
||||||
|
short_long[shortened] = xxoption
|
||||||
|
# now short_long has been filled out to the longest with dashes
|
||||||
|
# **and** we keep the right option ordering from add_argument
|
||||||
|
for option in options: #
|
||||||
|
if len(option) == 2 or option[2] == ' ':
|
||||||
|
return_list.append(option)
|
||||||
|
if option[2:] == short_long.get(option.replace('-', '')):
|
||||||
|
return_list.append(option)
|
||||||
|
action._formatted_action_invocation = ', '.join(return_list)
|
||||||
|
return action._formatted_action_invocation
|
||||||
|
|
||||||
|
|
||||||
class Conftest(object):
|
class Conftest(object):
|
||||||
""" the single place for accessing values and interacting
|
""" the single place for accessing values and interacting
|
||||||
|
|
|
@ -3,6 +3,10 @@ import py, pytest
|
||||||
from _pytest import config as parseopt
|
from _pytest import config as parseopt
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def parser():
|
||||||
|
return parseopt.Parser()
|
||||||
|
|
||||||
class TestParser:
|
class TestParser:
|
||||||
def test_no_help_by_default(self, capsys):
|
def test_no_help_by_default(self, capsys):
|
||||||
parser = parseopt.Parser(usage="xyz")
|
parser = parseopt.Parser(usage="xyz")
|
||||||
|
@ -10,7 +14,7 @@ class TestParser:
|
||||||
out, err = capsys.readouterr()
|
out, err = capsys.readouterr()
|
||||||
assert err.find("error: unrecognized arguments") != -1
|
assert err.find("error: unrecognized arguments") != -1
|
||||||
|
|
||||||
def test_argument(self):
|
def test_argument(self, parser):
|
||||||
with pytest.raises(parseopt.ArgumentError):
|
with pytest.raises(parseopt.ArgumentError):
|
||||||
# need a short or long option
|
# need a short or long option
|
||||||
argument = parseopt.Argument()
|
argument = parseopt.Argument()
|
||||||
|
@ -25,7 +29,7 @@ class TestParser:
|
||||||
argument = parseopt.Argument('-t', '--test', dest='abc')
|
argument = parseopt.Argument('-t', '--test', dest='abc')
|
||||||
assert argument.dest == 'abc'
|
assert argument.dest == 'abc'
|
||||||
|
|
||||||
def test_argument_type(self):
|
def test_argument_type(self, parser):
|
||||||
argument = parseopt.Argument('-t', dest='abc', type='int')
|
argument = parseopt.Argument('-t', dest='abc', type='int')
|
||||||
assert argument.type is int
|
assert argument.type is int
|
||||||
argument = parseopt.Argument('-t', dest='abc', type='string')
|
argument = parseopt.Argument('-t', dest='abc', type='string')
|
||||||
|
@ -46,22 +50,19 @@ class TestParser:
|
||||||
assert res['default'] == 42
|
assert res['default'] == 42
|
||||||
assert res['dest'] == 'abc'
|
assert res['dest'] == 'abc'
|
||||||
|
|
||||||
def test_group_add_and_get(self):
|
def test_group_add_and_get(self, parser):
|
||||||
parser = parseopt.Parser()
|
|
||||||
group = parser.getgroup("hello", description="desc")
|
group = parser.getgroup("hello", description="desc")
|
||||||
assert group.name == "hello"
|
assert group.name == "hello"
|
||||||
assert group.description == "desc"
|
assert group.description == "desc"
|
||||||
|
|
||||||
def test_getgroup_simple(self):
|
def test_getgroup_simple(self, parser):
|
||||||
parser = parseopt.Parser()
|
|
||||||
group = parser.getgroup("hello", description="desc")
|
group = parser.getgroup("hello", description="desc")
|
||||||
assert group.name == "hello"
|
assert group.name == "hello"
|
||||||
assert group.description == "desc"
|
assert group.description == "desc"
|
||||||
group2 = parser.getgroup("hello")
|
group2 = parser.getgroup("hello")
|
||||||
assert group2 is group
|
assert group2 is group
|
||||||
|
|
||||||
def test_group_ordering(self):
|
def test_group_ordering(self, parser):
|
||||||
parser = parseopt.Parser()
|
|
||||||
group0 = parser.getgroup("1")
|
group0 = parser.getgroup("1")
|
||||||
group1 = parser.getgroup("2")
|
group1 = parser.getgroup("2")
|
||||||
group1 = parser.getgroup("3", after="1")
|
group1 = parser.getgroup("3", after="1")
|
||||||
|
@ -75,8 +76,7 @@ class TestParser:
|
||||||
assert len(group.options) == 1
|
assert len(group.options) == 1
|
||||||
assert isinstance(group.options[0], parseopt.Argument)
|
assert isinstance(group.options[0], parseopt.Argument)
|
||||||
|
|
||||||
def test_group_shortopt_lowercase(self):
|
def test_group_shortopt_lowercase(self, parser):
|
||||||
parser = parseopt.Parser()
|
|
||||||
group = parser.getgroup("hello")
|
group = parser.getgroup("hello")
|
||||||
pytest.raises(ValueError, """
|
pytest.raises(ValueError, """
|
||||||
group.addoption("-x", action="store_true")
|
group.addoption("-x", action="store_true")
|
||||||
|
@ -85,27 +85,23 @@ class TestParser:
|
||||||
group._addoption("-x", action="store_true")
|
group._addoption("-x", action="store_true")
|
||||||
assert len(group.options) == 1
|
assert len(group.options) == 1
|
||||||
|
|
||||||
def test_parser_addoption(self):
|
def test_parser_addoption(self, parser):
|
||||||
parser = parseopt.Parser()
|
|
||||||
group = parser.getgroup("custom options")
|
group = parser.getgroup("custom options")
|
||||||
assert len(group.options) == 0
|
assert len(group.options) == 0
|
||||||
group.addoption("--option1", action="store_true")
|
group.addoption("--option1", action="store_true")
|
||||||
assert len(group.options) == 1
|
assert len(group.options) == 1
|
||||||
|
|
||||||
def test_parse(self):
|
def test_parse(self, parser):
|
||||||
parser = parseopt.Parser()
|
|
||||||
parser.addoption("--hello", dest="hello", action="store")
|
parser.addoption("--hello", dest="hello", action="store")
|
||||||
args = parser.parse(['--hello', 'world'])
|
args = parser.parse(['--hello', 'world'])
|
||||||
assert args.hello == "world"
|
assert args.hello == "world"
|
||||||
assert not getattr(args, parseopt.Config._file_or_dir)
|
assert not getattr(args, parseopt.Config._file_or_dir)
|
||||||
|
|
||||||
def test_parse2(self):
|
def test_parse2(self, parser):
|
||||||
parser = parseopt.Parser()
|
|
||||||
args = parser.parse([py.path.local()])
|
args = parser.parse([py.path.local()])
|
||||||
assert getattr(args, parseopt.Config._file_or_dir)[0] == py.path.local()
|
assert getattr(args, parseopt.Config._file_or_dir)[0] == py.path.local()
|
||||||
|
|
||||||
def test_parse_will_set_default(self):
|
def test_parse_will_set_default(self, parser):
|
||||||
parser = parseopt.Parser()
|
|
||||||
parser.addoption("--hello", dest="hello", default="x", action="store")
|
parser.addoption("--hello", dest="hello", default="x", action="store")
|
||||||
option = parser.parse([])
|
option = parser.parse([])
|
||||||
assert option.hello == "x"
|
assert option.hello == "x"
|
||||||
|
@ -113,8 +109,7 @@ class TestParser:
|
||||||
args = parser.parse_setoption([], option)
|
args = parser.parse_setoption([], option)
|
||||||
assert option.hello == "x"
|
assert option.hello == "x"
|
||||||
|
|
||||||
def test_parse_setoption(self):
|
def test_parse_setoption(self, parser):
|
||||||
parser = parseopt.Parser()
|
|
||||||
parser.addoption("--hello", dest="hello", action="store")
|
parser.addoption("--hello", dest="hello", action="store")
|
||||||
parser.addoption("--world", dest="world", default=42)
|
parser.addoption("--world", dest="world", default=42)
|
||||||
class A: pass
|
class A: pass
|
||||||
|
@ -124,14 +119,12 @@ class TestParser:
|
||||||
assert option.world == 42
|
assert option.world == 42
|
||||||
assert not args
|
assert not args
|
||||||
|
|
||||||
def test_parse_special_destination(self):
|
def test_parse_special_destination(self, parser):
|
||||||
parser = parseopt.Parser()
|
|
||||||
x = parser.addoption("--ultimate-answer", type=int)
|
x = parser.addoption("--ultimate-answer", type=int)
|
||||||
args = parser.parse(['--ultimate-answer', '42'])
|
args = parser.parse(['--ultimate-answer', '42'])
|
||||||
assert args.ultimate_answer == 42
|
assert args.ultimate_answer == 42
|
||||||
|
|
||||||
def test_parse_split_positional_arguments(self):
|
def test_parse_split_positional_arguments(self, parser):
|
||||||
parser = parseopt.Parser()
|
|
||||||
parser.addoption("-R", action='store_true')
|
parser.addoption("-R", action='store_true')
|
||||||
parser.addoption("-S", action='store_false')
|
parser.addoption("-S", action='store_false')
|
||||||
args = parser.parse(['-R', '4', '2', '-S'])
|
args = parser.parse(['-R', '4', '2', '-S'])
|
||||||
|
@ -162,6 +155,80 @@ class TestParser:
|
||||||
assert option.this == 42
|
assert option.this == 42
|
||||||
assert option.no is False
|
assert option.no is False
|
||||||
|
|
||||||
|
@pytest.mark.skipif("sys.version_info < (2,5)")
|
||||||
|
def test_drop_short_helper(self):
|
||||||
|
parser = py.std.argparse.ArgumentParser(formatter_class=parseopt.DropShorterLongHelpFormatter)
|
||||||
|
parser.add_argument('-t', '--twoword', '--duo', '--two-word', '--two',
|
||||||
|
help='foo').map_long_option = {'two': 'two-word'}
|
||||||
|
# throws error on --deux only!
|
||||||
|
parser.add_argument('-d', '--deuxmots', '--deux-mots',
|
||||||
|
action='store_true', help='foo').map_long_option = {'deux': 'deux-mots'}
|
||||||
|
parser.add_argument('-s', action='store_true', help='single short')
|
||||||
|
parser.add_argument('--abc', '-a',
|
||||||
|
action='store_true', help='bar')
|
||||||
|
parser.add_argument('--klm', '-k', '--kl-m',
|
||||||
|
action='store_true', help='bar')
|
||||||
|
parser.add_argument('-P', '--pq-r', '-p', '--pqr',
|
||||||
|
action='store_true', help='bar')
|
||||||
|
parser.add_argument('--zwei-wort', '--zweiwort', '--zweiwort',
|
||||||
|
action='store_true', help='bar')
|
||||||
|
parser.add_argument('-x', '--exit-on-first', '--exitfirst',
|
||||||
|
action='store_true', help='spam').map_long_option = {'exitfirst': 'exit-on-first'}
|
||||||
|
parser.add_argument('files_and_dirs', nargs='*')
|
||||||
|
args = parser.parse_args(['-k', '--duo', 'hallo', '--exitfirst'])
|
||||||
|
assert args.twoword == 'hallo'
|
||||||
|
assert args.klm is True
|
||||||
|
assert args.zwei_wort is False
|
||||||
|
assert args.exit_on_first is True
|
||||||
|
assert args.s is False
|
||||||
|
args = parser.parse_args(['--deux-mots'])
|
||||||
|
with pytest.raises(AttributeError):
|
||||||
|
assert args.deux_mots is True
|
||||||
|
assert args.deuxmots is True
|
||||||
|
args = parser.parse_args(['file', 'dir'])
|
||||||
|
assert '|'.join(args.files_and_dirs) == 'file|dir'
|
||||||
|
|
||||||
|
def test_drop_short_0(self, parser):
|
||||||
|
parser.addoption('--funcarg', '--func-arg', action='store_true')
|
||||||
|
parser.addoption('--abc-def', '--abc-def', action='store_true')
|
||||||
|
parser.addoption('--klm-hij', action='store_true')
|
||||||
|
args = parser.parse(['--funcarg', '--k'])
|
||||||
|
assert args.funcarg is True
|
||||||
|
assert args.abc_def is False
|
||||||
|
assert args.klm_hij is True
|
||||||
|
|
||||||
|
@pytest.mark.skipif("sys.version_info < (2,5)")
|
||||||
|
def test_drop_short_2(self, parser):
|
||||||
|
parser.addoption('--func-arg', '--doit', action='store_true')
|
||||||
|
args = parser.parse(['--doit'])
|
||||||
|
assert args.func_arg is True
|
||||||
|
|
||||||
|
@pytest.mark.skipif("sys.version_info < (2,5)")
|
||||||
|
def test_drop_short_3(self, parser):
|
||||||
|
parser.addoption('--func-arg', '--funcarg', '--doit', action='store_true')
|
||||||
|
args = parser.parse(['abcd'])
|
||||||
|
assert args.func_arg is False
|
||||||
|
assert args.file_or_dir == ['abcd']
|
||||||
|
|
||||||
|
@pytest.mark.skipif("sys.version_info < (2,5)")
|
||||||
|
def test_drop_short_help0(self, parser, capsys):
|
||||||
|
parser.addoption('--func-args', '--doit', help = 'foo',
|
||||||
|
action='store_true')
|
||||||
|
parser.parse([])
|
||||||
|
help = parser.optparser.format_help()
|
||||||
|
assert '--func-args, --doit foo' in help
|
||||||
|
|
||||||
|
# testing would be more helpful with all help generated
|
||||||
|
@pytest.mark.skipif("sys.version_info < (2,5)")
|
||||||
|
def test_drop_short_help1(self, parser, capsys):
|
||||||
|
group = parser.getgroup("general")
|
||||||
|
group.addoption('--doit', '--func-args', action='store_true', help='foo')
|
||||||
|
group._addoption("-h", "--help", action="store_true", dest="help",
|
||||||
|
help="show help message and configuration info")
|
||||||
|
parser.parse(['-h'])
|
||||||
|
help = parser.optparser.format_help()
|
||||||
|
assert '-doit, --func-args foo' in help
|
||||||
|
|
||||||
@pytest.mark.skipif("sys.version_info < (2,5)")
|
@pytest.mark.skipif("sys.version_info < (2,5)")
|
||||||
def test_addoption_parser_epilog(testdir):
|
def test_addoption_parser_epilog(testdir):
|
||||||
testdir.makeconftest("""
|
testdir.makeconftest("""
|
||||||
|
@ -173,7 +240,7 @@ def test_addoption_parser_epilog(testdir):
|
||||||
#assert result.ret != 0
|
#assert result.ret != 0
|
||||||
result.stdout.fnmatch_lines(["hint: hello world", "hint: from me too"])
|
result.stdout.fnmatch_lines(["hint: hello world", "hint: from me too"])
|
||||||
|
|
||||||
@pytest.mark.skipif("sys.version_info < (2,5)")
|
@pytest.mark.skipif("sys.version_info < (2,6)")
|
||||||
def test_argcomplete(testdir, monkeypatch):
|
def test_argcomplete(testdir, monkeypatch):
|
||||||
if not py.path.local.sysfind('bash'):
|
if not py.path.local.sysfind('bash'):
|
||||||
pytest.skip("bash not available")
|
pytest.skip("bash not available")
|
||||||
|
@ -196,17 +263,22 @@ def test_argcomplete(testdir, monkeypatch):
|
||||||
monkeypatch.setenv('COMP_LINE', "py.test " + arg)
|
monkeypatch.setenv('COMP_LINE', "py.test " + arg)
|
||||||
monkeypatch.setenv('COMP_POINT', str(len("py.test " + arg)))
|
monkeypatch.setenv('COMP_POINT', str(len("py.test " + arg)))
|
||||||
result = testdir.run('bash', str(script), arg)
|
result = testdir.run('bash', str(script), arg)
|
||||||
#print dir(result), result.ret
|
|
||||||
if result.ret == 255:
|
if result.ret == 255:
|
||||||
# argcomplete not found
|
# argcomplete not found
|
||||||
pytest.skip("argcomplete not available")
|
pytest.skip("argcomplete not available")
|
||||||
else:
|
else:
|
||||||
result.stdout.fnmatch_lines(["--funcargs", "--fulltrace"])
|
#print 'type ---------------', result.stdout, result.stdout.lines
|
||||||
|
if py.std.sys.version_info < (2,7):
|
||||||
|
result.stdout.lines = result.stdout.lines[0].split('\x0b')
|
||||||
|
result.stdout.fnmatch_lines(["--funcargs", "--fulltrace"])
|
||||||
|
else:
|
||||||
|
result.stdout.fnmatch_lines(["--funcargs", "--fulltrace"])
|
||||||
|
if py.std.sys.version_info < (2,7):
|
||||||
|
return
|
||||||
os.mkdir('test_argcomplete.d')
|
os.mkdir('test_argcomplete.d')
|
||||||
arg = 'test_argc'
|
arg = 'test_argc'
|
||||||
monkeypatch.setenv('COMP_LINE', "py.test " + arg)
|
monkeypatch.setenv('COMP_LINE', "py.test " + arg)
|
||||||
monkeypatch.setenv('COMP_POINT', str(len('py.test ' + arg)))
|
monkeypatch.setenv('COMP_POINT', str(len('py.test ' + arg)))
|
||||||
result = testdir.run('bash', str(script), arg)
|
result = testdir.run('bash', str(script), arg)
|
||||||
result.stdout.fnmatch_lines(["test_argcomplete", "test_argcomplete.d/"])
|
result.stdout.fnmatch_lines(["test_argcomplete", "test_argcomplete.d/"])
|
||||||
# restore environment
|
|
||||||
|
|
Loading…
Reference in New Issue