Skip to content

Commit

Permalink
Add shell completions by shtab
Browse files Browse the repository at this point in the history
See #1992

pytest --print-completion bash | sudo tee /usr/share/bash-completion/completions/pytest
pytest --print-completion tcsh | sudo tee /etc/profile.d/pytest.completion.csh
pytest --print-completion zsh | sudo tee /usr/share/zsh/site-functions/_pytest
  • Loading branch information
Freed-Wu committed Sep 18, 2022
1 parent 5bd41be commit 6b86d6e
Show file tree
Hide file tree
Showing 11 changed files with 66 additions and 4 deletions.
3 changes: 2 additions & 1 deletion .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ exclude_lines =
^\s*return NotImplemented\b
^\s*assert False(,|$)
^\s*assert_never\(

^\s*from . import _shtab as shtab
^\s*Action.complete = None # type: ignore
^\s*if TYPE_CHECKING:
^\s*@overload( |$)
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,7 @@ Wil Cooley
William Lee
Wim Glenn
Wouter van Ackooy
Wu Zhenyu
Xixi Zhao
Xuan Luong
Xuecong Liao
Expand Down
1 change: 1 addition & 0 deletions changelog/10304.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added shell completions by shtab
3 changes: 3 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,16 @@ console_scripts =
py.test=pytest:console_main

[options.extras_require]
completion =
shtab
testing =
argcomplete
hypothesis>=3.56
mock
nose
pygments>=2.7.2
requests
shtab
xmlschema

[options.package_data]
Expand Down
23 changes: 22 additions & 1 deletion src/_pytest/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__all__ = ["__version__", "version_tuple"]
__all__ = ["__version__", "version_tuple", "shtab", "XML_FILE", "PREAMBLE"]

try:
from ._version import version as __version__, version_tuple
Expand All @@ -7,3 +7,24 @@
# unknown only works because we do poor mans version compare
__version__ = "unknown"
version_tuple = (0, 0, "unknown") # type:ignore[assignment]

try:
import shtab
except ImportError:
from . import _shtab as shtab

# https://github.com/iterative/shtab/blob/5358dda86e8ea98bf801a43a24ad73cd9f820c63/examples/customcomplete.py#L11-L22
XML_FILE = {
"bash": "_shtab_greeter_compgen_xml_files",
"zsh": "_files -g '*.xml'",
"tcsh": "f:*.xml",
}
PREAMBLE = {
"bash": """
# $1=COMP_WORDS[1]
_shtab_greeter_compgen_xml_files() {
compgen -d -- $1 # recurse into subdirs
compgen -f -X '!*?.xml' -- $1
}
"""
}
13 changes: 13 additions & 0 deletions src/_pytest/_shtab.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from argparse import Action
from argparse import ArgumentParser
from typing import Any
from typing import Dict
from typing import List

FILE = None
DIRECTORY = DIR = None


def add_argument_to(parser: ArgumentParser, *args: List[Any], **kwargs: Dict[str, Any]):
Action.complete = None # type: ignore
return parser
13 changes: 12 additions & 1 deletion src/_pytest/config/argparsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
from typing import Union

import _pytest._io
from _pytest import PREAMBLE
from _pytest import shtab
from _pytest.compat import final
from _pytest.config.exceptions import UsageError
from _pytest.deprecated import ARGUMENT_PERCENT_DEFAULT
Expand All @@ -27,6 +29,7 @@
if TYPE_CHECKING:
from typing_extensions import Literal


FILE_OR_DIR = "file_or_dir"


Expand Down Expand Up @@ -124,11 +127,19 @@ def _getparser(self) -> "MyOptionParser":
if group.options:
desc = group.description or group.name
arggroup = optparser.add_argument_group(desc)
if group.name == "debugconfig":
shtab.add_argument_to(arggroup, preamble=PREAMBLE)
for option in group.options:
n = option.names()
a = option.attrs()
arggroup.add_argument(*n, **a)
complete = a.get("complete")
if complete:
del a["complete"] # type: ignore
action = arggroup.add_argument(*n, **a)
if complete:
action.complete = complete # type: ignore
file_or_dir_arg = optparser.add_argument(FILE_OR_DIR, nargs="*")
file_or_dir_arg.complete = shtab.FILE # type: ignore
# bash like autocompletion for dirs (appending '/')
# Type ignored because typeshed doesn't know about argcomplete.
file_or_dir_arg.completer = filescompleter # type: ignore
Expand Down
2 changes: 2 additions & 0 deletions src/_pytest/helpconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from typing import Union

import pytest
from _pytest import shtab
from _pytest.config import Config
from _pytest.config import ExitCode
from _pytest.config import PrintHelp
Expand Down Expand Up @@ -86,6 +87,7 @@ def pytest_addoption(parser: Parser) -> None:
help="Store internal tracing debug information in this log file. "
"This file is opened with 'w' and truncated as a result, care advised. "
"Default: pytestdebug.log.",
complete=shtab.FILE,
)
group._addoption(
"-o",
Expand Down
3 changes: 2 additions & 1 deletion src/_pytest/junitxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from typing import Union

import pytest
from . import XML_FILE
from _pytest import nodes
from _pytest import timing
from _pytest._code.code import ExceptionRepr
Expand All @@ -33,7 +34,6 @@
from _pytest.stash import StashKey
from _pytest.terminal import TerminalReporter


xml_key = StashKey["LogXML"]()


Expand Down Expand Up @@ -390,6 +390,7 @@ def pytest_addoption(parser: Parser) -> None:
type=functools.partial(filename_arg, optname="--junitxml"),
default=None,
help="Create junit-xml style report file at given path",
complete=XML_FILE,
)
group.addoption(
"--junitprefix",
Expand Down
3 changes: 3 additions & 0 deletions src/_pytest/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from typing import Union

from _pytest import nodes
from _pytest import shtab
from _pytest._io import TerminalWriter
from _pytest.capture import CaptureManager
from _pytest.compat import final
Expand All @@ -35,6 +36,7 @@
from _pytest.stash import StashKey
from _pytest.terminal import TerminalReporter


if TYPE_CHECKING:
logging_StreamHandler = logging.StreamHandler[StringIO]

Expand Down Expand Up @@ -272,6 +274,7 @@ def add_option_ini(option, dest, default=None, type=None, **kwargs):
dest="log_file",
default=None,
help="Path to a file when logging will be written to",
complete=shtab.FILE,
)
add_option_ini(
"--log-file-level",
Expand Down
5 changes: 5 additions & 0 deletions src/_pytest/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import _pytest._code
from _pytest import nodes
from _pytest import shtab
from _pytest.compat import final
from _pytest.compat import overload
from _pytest.config import Config
Expand Down Expand Up @@ -128,6 +129,7 @@ def pytest_addoption(parser: Parser) -> None:
dest="inifilename",
help="Load configuration from `file` instead of trying to locate one of the "
"implicit configuration files",
complete=shtab.FILE,
)
group._addoption(
"--continue-on-collection-errors",
Expand All @@ -143,6 +145,7 @@ def pytest_addoption(parser: Parser) -> None:
help="Define root directory for tests. Can be relative path: 'root_dir', './root_dir', "
"'root_dir/another_dir/'; absolute path: '/home/user/root_dir'; path with variables: "
"'$HOME/root_dir'.",
complete=shtab.DIR,
)

group = parser.getgroup("collect", "collection")
Expand Down Expand Up @@ -183,6 +186,7 @@ def pytest_addoption(parser: Parser) -> None:
metavar="dir",
type=functools.partial(directory_arg, optname="--confcutdir"),
help="Only load conftest.py's relative to specified dir",
complete=shtab.DIR,
)
group.addoption(
"--noconftest",
Expand Down Expand Up @@ -226,6 +230,7 @@ def pytest_addoption(parser: Parser) -> None:
"Base temporary directory for this test run. "
"(Warning: this directory is removed if it exists.)"
),
complete=shtab.DIR,
)


Expand Down

0 comments on commit 6b86d6e

Please sign in to comment.