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.

--HG--
branch : argcomplete
This commit is contained in:
Anthon van der Neut 2013-07-30 11:26:15 +02:00
parent 377f63085a
commit 87860600fb
5 changed files with 154 additions and 9 deletions

68
_pytest/_argcomplete.py Normal file
View File

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

View File

@ -4,6 +4,7 @@ import py
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 @@ class Parser:
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 @@ class Parser:
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 @@ class MyOptionParser(py.std.argparse.ArgumentParser):
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 @@ class MyOptionParser(py.std.argparse.ArgumentParser):
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.

View File

@ -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)"

View File

@ -130,6 +130,21 @@ class TestParser:
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 @@ def test_addoption_parser_epilog(testdir):
#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"])

View File

@ -43,7 +43,7 @@ changedir=.
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=.