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:
parent
377f63085a
commit
87860600fb
|
@ -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
|
|
@ -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.
|
||||
|
|
|
@ -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)"
|
||||
|
||||
|
||||
|
|
@ -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"])
|
||||
|
|
Loading…
Reference in New Issue