3 new commits in pytest: https://bitbucket.org/hpk42/pytest/commits/8c92bc4c65aa/ Changeset: 8c92bc4c65aa Branch: argcomplete User: Anthon van der Neut Date: 2013-07-30 11:26:15 Summary: Fixes for argcomplete
- separate out most argcomplete related stuff in new file _argcomplete.py (could probably be in the py library) - allow positional arguments to be interspaced with optional arguments ( + test in test_parseopt.py ) - removed double argument in tox.ini - add documentation on installing argcomplete (>=0.5.7 as needed for Python 3), might need improving/incorporation in index. This does not work on 2.5 yet. I have patches for argcomplete (with/print()/"".format) but I am not sure they will be accepted. Agreed with hpk not to push for that. Removing argcomplete and leaving completion code active now works by early exit, so <TAB> no longer re-runs the programs without parameters (which took long for py.test) test calls bash with a script that redirects filedescriptor 8 (as used by argcomplete), so the result can be tested. Affected #: 5 files diff -r 1530d0e3abf3e60a37cfc91aac47a5545a11628c -r 8c92bc4c65aa426cf9e795dbf4a2654cef71b46c _pytest/_argcomplete.py --- /dev/null +++ b/_pytest/_argcomplete.py @@ -0,0 +1,68 @@ + +"""allow bash-completion for argparse with argcomplete if installed +needs argcomplete>=0.5.6 for python 3.2/3.3 (older versions fail +to find the magic string, so _ARGCOMPLETE env. var is never set, and +this does not need special code. + +argcomplete does not support python 2.5 (although the changes for that +are minor). + +Function try_argcomplete(parser) should be called directly before +the call to ArgumentParser.parse_args(). + +The filescompleter is what you normally would use on the positional +arguments specification, in order to get "dirname/" after "dirn<TAB>" +instead of the default "dirname ": + + optparser.add_argument(Config._file_or_dir, nargs='*' + ).completer=filescompleter + +Other, application specific, completers should go in the file +doing the add_argument calls as they need to be specified as .completer +attributes as well. (If argcomplete is not installed, the function the +attribute points to will not be used). + +--- +To include this support in another application that has setup.py generated +scripts: +- add the line: + # PYTHON_ARGCOMPLETE_OK + near the top of the main python entry point +- include in the file calling parse_args(): + from _argcomplete import try_argcomplete, filescompleter + , call try_argcomplete just before parse_args(), and optionally add + filescompleter to the positional arguments' add_argument() +If things do not work right away: +- switch on argcomplete debugging with (also helpful when doing custom + completers): + export _ARC_DEBUG=1 +- run: + python-argcomplete-check-easy-install-script $(which appname) + echo $? + will echo 0 if the magic line has been found, 1 if not +- sometimes it helps to find early on errors using: + _ARGCOMPLETE=1 _ARC_DEBUG=1 appname + which should throw a KeyError: 'COMPLINE' (which is properly set by the + global argcomplete script). + +""" + +import sys +import os + +if os.environ.get('_ARGCOMPLETE'): + # argcomplete 0.5.6 is not compatible with python 2.5.6: print/with/format + if sys.version_info[:2] < (2, 6): + sys.exit(1) + try: + import argcomplete + import argcomplete.completers + except ImportError: + sys.exit(-1) + filescompleter = argcomplete.completers.FilesCompleter() + + def try_argcomplete(parser): + argcomplete.autocomplete(parser) +else: + def try_argcomplete(parser): pass + filescompleter = None diff -r 1530d0e3abf3e60a37cfc91aac47a5545a11628c -r 8c92bc4c65aa426cf9e795dbf4a2654cef71b46c _pytest/config.py --- a/_pytest/config.py +++ b/_pytest/config.py @@ -4,6 +4,7 @@ import sys, os from _pytest.core import PluginManager import pytest +from _argcomplete import try_argcomplete, filescompleter # enable after some grace period for plugin writers TYPE_WARN = False @@ -91,7 +92,9 @@ n = option.names() a = option.attrs() arggroup.add_argument(*n, **a) - optparser.add_argument(Config._file_or_dir, nargs='*') + # bash like autocompletion for dirs (appending '/') + optparser.add_argument(Config._file_or_dir, nargs='*' + ).completer=filescompleter try_argcomplete(self.optparser) return self.optparser.parse_args([str(x) for x in args]) @@ -115,13 +118,6 @@ self._inidict[name] = (help, type, default) self._ininames.append(name) -def try_argcomplete(parser): - try: - import argcomplete - except ImportError: - pass - else: - argcomplete.autocomplete(parser) class ArgumentError(Exception): """ @@ -304,6 +300,7 @@ self._parser = parser py.std.argparse.ArgumentParser.__init__(self, usage=parser._usage, add_help=False) + def format_epilog(self, formatter): hints = self._parser.hints if hints: @@ -312,6 +309,18 @@ return s return "" + def parse_args(self, args=None, namespace=None): + """allow splitting of positional arguments""" + args, argv = self.parse_known_args(args, namespace) + if argv: + for arg in argv: + if arg and arg[0] == '-': + msg = py.std.argparse._('unrecognized arguments: %s') + self.error(msg % ' '.join(argv)) + getattr(args, Config._file_or_dir).extend(argv) + return args + + class Conftest(object): """ the single place for accessing values and interacting towards conftest modules from py.test objects. diff -r 1530d0e3abf3e60a37cfc91aac47a5545a11628c -r 8c92bc4c65aa426cf9e795dbf4a2654cef71b46c doc/en/bash-completion.txt --- /dev/null +++ b/doc/en/bash-completion.txt @@ -0,0 +1,28 @@ + +.. _bash_completion: + +Setting up bash completion +========================== + +When using bash as your shell, ``py.test`` can use argcomplete +(https://argcomplete.readthedocs.org/) for auto-completion. +For this ``argcomplete`` needs to be installed **and** enabled. + +Install argcomplete using:: + + sudo pip install 'argcomplete>=0.5.7' + +For global activation of all argcomplete enabled python applications run:: + + sudo activate-global-python-argcomplete + +For permanent (but not global) ``py.test`` activation, use:: + + register-python-argcomplete py.test >> ~/.bashrc + +For one-time activation of argcomplete for ``py.test`` only, use:: + + eval "$(register-python-argcomplete py.test)" + + + diff -r 1530d0e3abf3e60a37cfc91aac47a5545a11628c -r 8c92bc4c65aa426cf9e795dbf4a2654cef71b46c testing/test_parseopt.py --- a/testing/test_parseopt.py +++ b/testing/test_parseopt.py @@ -130,6 +130,21 @@ args = parser.parse(['--ultimate-answer', '42']) assert args.ultimate_answer == 42 + def test_parse_split_positional_arguments(self): + parser = parseopt.Parser() + parser.addoption("-R", action='store_true') + parser.addoption("-S", action='store_false') + args = parser.parse(['-R', '4', '2', '-S']) + assert getattr(args, parseopt.Config._file_or_dir) == ['4', '2'] + args = parser.parse(['-R', '-S', '4', '2', '-R']) + assert getattr(args, parseopt.Config._file_or_dir) == ['4', '2'] + assert args.R == True + assert args.S == False + args = parser.parse(['-R', '4', '-S', '2']) + assert getattr(args, parseopt.Config._file_or_dir) == ['4', '2'] + assert args.R == True + assert args.S == False + def test_parse_defaultgetter(self): def defaultget(option): if not hasattr(option, 'type'): @@ -158,3 +173,28 @@ #assert result.ret != 0 result.stdout.fnmatch_lines(["hint: hello world", "hint: from me too"]) +@pytest.mark.skipif("sys.version_info < (2,5)") +def test_argcomplete(testdir): + import os + p = py.path.local.make_numbered_dir(prefix="test_argcomplete-", + keep=None, rootdir=testdir.tmpdir) + script = p._fastjoin('test_argcomplete') + with open(str(script), 'w') as fp: + # redirect output from argcomplete to stdin and stderr is not trivial + # http://stackoverflow.com/q/12589419/1307905 + # so we use bash + fp.write('COMP_WORDBREAKS="$COMP_WORDBREAKS" $(which py.test) ' + '8>&1 9>&2') + os.environ['_ARGCOMPLETE'] = "1" + os.environ['_ARGCOMPLETE_IFS'] = "\x0b" + os.environ['COMP_LINE'] = "py.test --fu" + os.environ['COMP_POINT'] = "12" + os.environ['COMP_WORDBREAKS'] = ' \\t\\n"\\\'><=;|&(:' + + result = testdir.run('bash', str(script), '--fu') + print dir(result), result.ret + if result.ret == 255: + # argcomplete not found + assert True + else: + result.stdout.fnmatch_lines(["--funcargs", "--fulltrace"]) diff -r 1530d0e3abf3e60a37cfc91aac47a5545a11628c -r 8c92bc4c65aa426cf9e795dbf4a2654cef71b46c tox.ini --- a/tox.ini +++ b/tox.ini @@ -43,7 +43,7 @@ deps=twisted pexpect commands= - py.test -rsxf testing/test_unittest.py \ + py.test -rsxf \ --junitxml={envlogdir}/junit-{envname}.xml {posargs:testing/test_unittest.py} [testenv:doctest] changedir=. https://bitbucket.org/hpk42/pytest/commits/f44d44a4142b/ Changeset: f44d44a4142b Branch: argcomplete User: Anthon van der Neut Date: 2013-07-30 12:33:38 Summary: minor adjustment, added test for positional argument completion Affected #: 2 files diff -r 8c92bc4c65aa426cf9e795dbf4a2654cef71b46c -r f44d44a4142b72a260007e413e6419549b3c1dc1 _pytest/_argcomplete.py --- a/_pytest/_argcomplete.py +++ b/_pytest/_argcomplete.py @@ -55,7 +55,6 @@ if sys.version_info[:2] < (2, 6): sys.exit(1) try: - import argcomplete import argcomplete.completers except ImportError: sys.exit(-1) diff -r 8c92bc4c65aa426cf9e795dbf4a2654cef71b46c -r f44d44a4142b72a260007e413e6419549b3c1dc1 testing/test_parseopt.py --- a/testing/test_parseopt.py +++ b/testing/test_parseopt.py @@ -175,10 +175,10 @@ @pytest.mark.skipif("sys.version_info < (2,5)") def test_argcomplete(testdir): + if not py.path.local.sysfind('bash'): + pytest.skip("bash not available") import os - p = py.path.local.make_numbered_dir(prefix="test_argcomplete-", - keep=None, rootdir=testdir.tmpdir) - script = p._fastjoin('test_argcomplete') + script = os.path.join(os.getcwd(), 'test_argcomplete') with open(str(script), 'w') as fp: # redirect output from argcomplete to stdin and stderr is not trivial # http://stackoverflow.com/q/12589419/1307905 @@ -187,14 +187,22 @@ '8>&1 9>&2') os.environ['_ARGCOMPLETE'] = "1" os.environ['_ARGCOMPLETE_IFS'] = "\x0b" - os.environ['COMP_LINE'] = "py.test --fu" - os.environ['COMP_POINT'] = "12" os.environ['COMP_WORDBREAKS'] = ' \\t\\n"\\\'><=;|&(:' - result = testdir.run('bash', str(script), '--fu') + arg = '--fu' + os.environ['COMP_LINE'] = "py.test " + arg + os.environ['COMP_POINT'] = str(len(os.environ['COMP_LINE'])) + result = testdir.run('bash', str(script), arg) print dir(result), result.ret if result.ret == 255: # argcomplete not found - assert True + pytest.skip("argcomplete not available") else: result.stdout.fnmatch_lines(["--funcargs", "--fulltrace"]) + + os.mkdir('test_argcomplete.d') + arg = 'test_argc' + os.environ['COMP_LINE'] = "py.test " + arg + os.environ['COMP_POINT'] = str(len(os.environ['COMP_LINE'])) + result = testdir.run('bash', str(script), arg) + result.stdout.fnmatch_lines(["test_argcomplete", "test_argcomplete.d/"]) https://bitbucket.org/hpk42/pytest/commits/73015defd88f/ Changeset: 73015defd88f User: hpk42 Date: 2013-07-31 07:51:07 Summary: Merged in anthon_van_der_neut/pytest/argcomplete (pull request #50) Fixes for argcomplete Affected #: 5 files diff -r 1530d0e3abf3e60a37cfc91aac47a5545a11628c -r 73015defd88f6a45ba44fd6e109b03bc8b3e82e9 _pytest/_argcomplete.py --- /dev/null +++ b/_pytest/_argcomplete.py @@ -0,0 +1,67 @@ + +"""allow bash-completion for argparse with argcomplete if installed +needs argcomplete>=0.5.6 for python 3.2/3.3 (older versions fail +to find the magic string, so _ARGCOMPLETE env. var is never set, and +this does not need special code. + +argcomplete does not support python 2.5 (although the changes for that +are minor). + +Function try_argcomplete(parser) should be called directly before +the call to ArgumentParser.parse_args(). + +The filescompleter is what you normally would use on the positional +arguments specification, in order to get "dirname/" after "dirn<TAB>" +instead of the default "dirname ": + + optparser.add_argument(Config._file_or_dir, nargs='*' + ).completer=filescompleter + +Other, application specific, completers should go in the file +doing the add_argument calls as they need to be specified as .completer +attributes as well. (If argcomplete is not installed, the function the +attribute points to will not be used). + +--- +To include this support in another application that has setup.py generated +scripts: +- add the line: + # PYTHON_ARGCOMPLETE_OK + near the top of the main python entry point +- include in the file calling parse_args(): + from _argcomplete import try_argcomplete, filescompleter + , call try_argcomplete just before parse_args(), and optionally add + filescompleter to the positional arguments' add_argument() +If things do not work right away: +- switch on argcomplete debugging with (also helpful when doing custom + completers): + export _ARC_DEBUG=1 +- run: + python-argcomplete-check-easy-install-script $(which appname) + echo $? + will echo 0 if the magic line has been found, 1 if not +- sometimes it helps to find early on errors using: + _ARGCOMPLETE=1 _ARC_DEBUG=1 appname + which should throw a KeyError: 'COMPLINE' (which is properly set by the + global argcomplete script). + +""" + +import sys +import os + +if os.environ.get('_ARGCOMPLETE'): + # argcomplete 0.5.6 is not compatible with python 2.5.6: print/with/format + if sys.version_info[:2] < (2, 6): + sys.exit(1) + try: + import argcomplete.completers + except ImportError: + sys.exit(-1) + filescompleter = argcomplete.completers.FilesCompleter() + + def try_argcomplete(parser): + argcomplete.autocomplete(parser) +else: + def try_argcomplete(parser): pass + filescompleter = None diff -r 1530d0e3abf3e60a37cfc91aac47a5545a11628c -r 73015defd88f6a45ba44fd6e109b03bc8b3e82e9 _pytest/config.py --- a/_pytest/config.py +++ b/_pytest/config.py @@ -4,6 +4,7 @@ import sys, os from _pytest.core import PluginManager import pytest +from _argcomplete import try_argcomplete, filescompleter # enable after some grace period for plugin writers TYPE_WARN = False @@ -91,7 +92,9 @@ n = option.names() a = option.attrs() arggroup.add_argument(*n, **a) - optparser.add_argument(Config._file_or_dir, nargs='*') + # bash like autocompletion for dirs (appending '/') + optparser.add_argument(Config._file_or_dir, nargs='*' + ).completer=filescompleter try_argcomplete(self.optparser) return self.optparser.parse_args([str(x) for x in args]) @@ -115,13 +118,6 @@ self._inidict[name] = (help, type, default) self._ininames.append(name) -def try_argcomplete(parser): - try: - import argcomplete - except ImportError: - pass - else: - argcomplete.autocomplete(parser) class ArgumentError(Exception): """ @@ -304,6 +300,7 @@ self._parser = parser py.std.argparse.ArgumentParser.__init__(self, usage=parser._usage, add_help=False) + def format_epilog(self, formatter): hints = self._parser.hints if hints: @@ -312,6 +309,18 @@ return s return "" + def parse_args(self, args=None, namespace=None): + """allow splitting of positional arguments""" + args, argv = self.parse_known_args(args, namespace) + if argv: + for arg in argv: + if arg and arg[0] == '-': + msg = py.std.argparse._('unrecognized arguments: %s') + self.error(msg % ' '.join(argv)) + getattr(args, Config._file_or_dir).extend(argv) + return args + + class Conftest(object): """ the single place for accessing values and interacting towards conftest modules from py.test objects. diff -r 1530d0e3abf3e60a37cfc91aac47a5545a11628c -r 73015defd88f6a45ba44fd6e109b03bc8b3e82e9 doc/en/bash-completion.txt --- /dev/null +++ b/doc/en/bash-completion.txt @@ -0,0 +1,28 @@ + +.. _bash_completion: + +Setting up bash completion +========================== + +When using bash as your shell, ``py.test`` can use argcomplete +(https://argcomplete.readthedocs.org/) for auto-completion. +For this ``argcomplete`` needs to be installed **and** enabled. + +Install argcomplete using:: + + sudo pip install 'argcomplete>=0.5.7' + +For global activation of all argcomplete enabled python applications run:: + + sudo activate-global-python-argcomplete + +For permanent (but not global) ``py.test`` activation, use:: + + register-python-argcomplete py.test >> ~/.bashrc + +For one-time activation of argcomplete for ``py.test`` only, use:: + + eval "$(register-python-argcomplete py.test)" + + + diff -r 1530d0e3abf3e60a37cfc91aac47a5545a11628c -r 73015defd88f6a45ba44fd6e109b03bc8b3e82e9 testing/test_parseopt.py --- a/testing/test_parseopt.py +++ b/testing/test_parseopt.py @@ -130,6 +130,21 @@ args = parser.parse(['--ultimate-answer', '42']) assert args.ultimate_answer == 42 + def test_parse_split_positional_arguments(self): + parser = parseopt.Parser() + parser.addoption("-R", action='store_true') + parser.addoption("-S", action='store_false') + args = parser.parse(['-R', '4', '2', '-S']) + assert getattr(args, parseopt.Config._file_or_dir) == ['4', '2'] + args = parser.parse(['-R', '-S', '4', '2', '-R']) + assert getattr(args, parseopt.Config._file_or_dir) == ['4', '2'] + assert args.R == True + assert args.S == False + args = parser.parse(['-R', '4', '-S', '2']) + assert getattr(args, parseopt.Config._file_or_dir) == ['4', '2'] + assert args.R == True + assert args.S == False + def test_parse_defaultgetter(self): def defaultget(option): if not hasattr(option, 'type'): @@ -158,3 +173,36 @@ #assert result.ret != 0 result.stdout.fnmatch_lines(["hint: hello world", "hint: from me too"]) +@pytest.mark.skipif("sys.version_info < (2,5)") +def test_argcomplete(testdir): + if not py.path.local.sysfind('bash'): + pytest.skip("bash not available") + import os + script = os.path.join(os.getcwd(), 'test_argcomplete') + with open(str(script), 'w') as fp: + # redirect output from argcomplete to stdin and stderr is not trivial + # http://stackoverflow.com/q/12589419/1307905 + # so we use bash + fp.write('COMP_WORDBREAKS="$COMP_WORDBREAKS" $(which py.test) ' + '8>&1 9>&2') + os.environ['_ARGCOMPLETE'] = "1" + os.environ['_ARGCOMPLETE_IFS'] = "\x0b" + os.environ['COMP_WORDBREAKS'] = ' \\t\\n"\\\'><=;|&(:' + + arg = '--fu' + os.environ['COMP_LINE'] = "py.test " + arg + os.environ['COMP_POINT'] = str(len(os.environ['COMP_LINE'])) + result = testdir.run('bash', str(script), arg) + print dir(result), result.ret + if result.ret == 255: + # argcomplete not found + pytest.skip("argcomplete not available") + else: + result.stdout.fnmatch_lines(["--funcargs", "--fulltrace"]) + + os.mkdir('test_argcomplete.d') + arg = 'test_argc' + os.environ['COMP_LINE'] = "py.test " + arg + os.environ['COMP_POINT'] = str(len(os.environ['COMP_LINE'])) + result = testdir.run('bash', str(script), arg) + result.stdout.fnmatch_lines(["test_argcomplete", "test_argcomplete.d/"]) diff -r 1530d0e3abf3e60a37cfc91aac47a5545a11628c -r 73015defd88f6a45ba44fd6e109b03bc8b3e82e9 tox.ini --- a/tox.ini +++ b/tox.ini @@ -43,7 +43,7 @@ deps=twisted pexpect commands= - py.test -rsxf testing/test_unittest.py \ + py.test -rsxf \ --junitxml={envlogdir}/junit-{envname}.xml {posargs:testing/test_unittest.py} [testenv:doctest] changedir=. Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. _______________________________________________ pytest-commit mailing list pytest-commit@python.org http://mail.python.org/mailman/listinfo/pytest-commit