diff --git a/argcomplete/_check_console_script.py b/argcomplete/_check_console_script.py index d2dc46a2..af522509 100644 --- a/argcomplete/_check_console_script.py +++ b/argcomplete/_check_console_script.py @@ -17,12 +17,12 @@ try: from importlib.metadata import entry_points as importlib_entry_points except ImportError: - from importlib_metadata import entry_points as importlib_entry_points # type:ignore + from importlib_metadata import entry_points as importlib_entry_points # type: ignore[import,no-redef] from ._check_module import ArgcompleteMarkerNotFound, find -def main(): +def main() -> None: # Argument is the full path to the console script. script_path = sys.argv[1] diff --git a/argcomplete/_check_module.py b/argcomplete/_check_module.py index 4958f026..7ec4c04c 100644 --- a/argcomplete/_check_module.py +++ b/argcomplete/_check_module.py @@ -11,7 +11,7 @@ import tokenize try: - from importlib.util import find_spec # type:ignore + from importlib.util import find_spec # type: ignore except ImportError: import typing as t from collections import namedtuple @@ -19,7 +19,7 @@ ModuleSpec = namedtuple("ModuleSpec", ["origin", "has_location", "submodule_search_locations"]) - def find_spec( # type:ignore + def find_spec( # type: ignore[misc] name: str, package: t.Optional[str] = None, ) -> t.Optional[ModuleSpec]: @@ -62,7 +62,7 @@ def find(name, return_package=False): return path + ".py" -def main(): +def main() -> None: try: name = sys.argv[1] except IndexError: diff --git a/argcomplete/completers.py b/argcomplete/completers.py index d89cdbfb..44714137 100644 --- a/argcomplete/completers.py +++ b/argcomplete/completers.py @@ -4,9 +4,11 @@ import argparse import os import subprocess +from collections.abc import Iterable +from typing import Any, Callable, List, Union -def _call(*args, **kwargs): +def _call(*args, **kwargs) -> List[str]: # TODO: replace "universal_newlines" with "text" once 3.6 support is dropped kwargs["universal_newlines"] = True try: @@ -22,20 +24,20 @@ class BaseCompleter: def __call__( self, *, prefix: str, action: argparse.Action, parser: argparse.ArgumentParser, parsed_args: argparse.Namespace - ): + ) -> Iterable[str]: raise NotImplementedError("This method should be implemented by a subclass.") class ChoicesCompleter(BaseCompleter): - def __init__(self, choices): + def __init__(self, choices: Iterable[Any]): self.choices = choices - def _convert(self, choice): + def _convert(self, choice: Any) -> str: if not isinstance(choice, str): - choice = str(choice) + return str(choice) return choice - def __call__(self, **kwargs): + def __call__(self, **kwargs) -> Iterable[str]: return (self._convert(c) for c in self.choices) @@ -47,15 +49,15 @@ class FilesCompleter(BaseCompleter): File completer class, optionally takes a list of allowed extensions """ - def __init__(self, allowednames=(), directories=True): + def __init__(self, allowednames: Union[str, Iterable[str]] = (), directories: bool = True): # Fix if someone passes in a string instead of a list - if isinstance(allowednames, (str, bytes)): + if isinstance(allowednames, str): allowednames = [allowednames] self.allowednames = [x.lstrip("*").lstrip(".") for x in allowednames] self.directories = directories - def __call__(self, prefix, **kwargs): + def __call__(self, *, prefix: str, **kwargs) -> Iterable[str]: completion = [] if self.allowednames: if self.directories: @@ -74,17 +76,16 @@ def __call__(self, prefix, **kwargs): class _FilteredFilesCompleter(BaseCompleter): - def __init__(self, predicate): + def __init__(self, predicate: Callable[[str], bool]): """ Create the completer A predicate accepts as its only argument a candidate path and either accepts it or rejects it. """ - assert predicate, "Expected a callable predicate" self.predicate = predicate - def __call__(self, prefix, **kwargs): + def __call__(self, *, prefix: str, **kwargs) -> Iterable[str]: """ Provide completions on prefix """ @@ -105,7 +106,7 @@ def __call__(self, prefix, **kwargs): class DirectoriesCompleter(_FilteredFilesCompleter): - def __init__(self): + def __init__(self) -> None: _FilteredFilesCompleter.__init__(self, predicate=os.path.isdir) @@ -114,10 +115,10 @@ class SuppressCompleter(BaseCompleter): A completer used to suppress the completion of specific arguments """ - def __init__(self): + def __init__(self) -> None: pass - def suppress(self): + def suppress(self) -> bool: """ Decide if the completion should be suppressed """ diff --git a/argcomplete/finders.py b/argcomplete/finders.py index 77d98910..552b4a9c 100644 --- a/argcomplete/finders.py +++ b/argcomplete/finders.py @@ -7,10 +7,10 @@ import os import sys from collections.abc import Mapping -from typing import Callable, Dict, List, Optional, Sequence, Union +from typing import Any, Callable, Dict, List, Optional, Sequence, Union from . import io as _io -from .completers import ChoicesCompleter, FilesCompleter, SuppressCompleter +from .completers import BaseCompleter, ChoicesCompleter, FilesCompleter, SuppressCompleter from .io import debug, mute_stderr from .lexers import split_line from .packages._argparse import IntrospectiveArgumentParser, action_is_greedy, action_is_open, action_is_satisfied @@ -26,7 +26,7 @@ } -def default_validator(completion, prefix): +def default_validator(completion: str, prefix: str) -> bool: return completion.startswith(prefix) @@ -40,12 +40,12 @@ class CompletionFinder(object): def __init__( self, argument_parser=None, - always_complete_options=True, - exclude=None, - validator=None, - print_suppressed=False, - default_completer=FilesCompleter(), - append_space=None, + always_complete_options: Union[bool, str] = True, + exclude: Optional[Sequence[str]] = None, + validator: Optional[Callable[[str, str], bool]] = None, + print_suppressed: bool = False, + default_completer: BaseCompleter = FilesCompleter(), + append_space: Optional[bool] = None, ): self._parser = argument_parser self.always_complete_options = always_complete_options @@ -65,14 +65,14 @@ def __call__( self, argument_parser: argparse.ArgumentParser, always_complete_options: Union[bool, str] = True, - exit_method: Callable = os._exit, + exit_method: Callable[[int], Any] = os._exit, output_stream=None, exclude: Optional[Sequence[str]] = None, - validator: Optional[Callable] = None, + validator: Optional[Callable[[str, str], bool]] = None, print_suppressed: bool = False, append_space: Optional[bool] = None, - default_completer=FilesCompleter(), - ): + default_completer: BaseCompleter = FilesCompleter(), + ) -> None: """ :param argument_parser: The argument parser to autocomplete on :param always_complete_options: @@ -103,7 +103,7 @@ def __call__( added to argcomplete.safe_actions, if their values are wanted in the ``parsed_args`` completer argument, or their execution is otherwise desirable. """ - self.__init__( # type: ignore + self.__init__( # type: ignore[misc] argument_parser, always_complete_options=always_complete_options, exclude=exclude, @@ -242,7 +242,7 @@ def patch(parser): continue # TODO: accomplish this with super - class IntrospectAction(action.__class__): # type: ignore + class IntrospectAction(action.__class__): # type: ignore[name-defined] def __call__(self, parser, namespace, values, option_string=None): debug("Action stub called on", self) debug("\targs:", parser, namespace, values, option_string) @@ -400,7 +400,7 @@ def _complete_active_option(self, parser, next_positional, cword_prefix, parsed_ else: debug("Completer is not callable, trying the readline completer protocol instead") for i in range(9999): - next_completion = completer.complete(cword_prefix, i) # type: ignore + next_completion = completer.complete(cword_prefix, i) if next_completion is None: break if self.validator(next_completion, cword_prefix): diff --git a/argcomplete/io.py b/argcomplete/io.py index df2c4f20..f3d76b3d 100644 --- a/argcomplete/io.py +++ b/argcomplete/io.py @@ -7,7 +7,7 @@ debug_stream = sys.stderr -def debug(*args): +def debug(*args) -> None: if _DEBUG: print(file=debug_stream, *args) @@ -33,7 +33,7 @@ def mute_stderr(): sys.stderr = stderr -def warn(*args): +def warn(*args) -> None: """ Prints **args** to standard error when running completions. This will interrupt the user's command line interaction; use it to indicate an error condition that is preventing your completer from working. diff --git a/argcomplete/lexers.py b/argcomplete/lexers.py index 72c378b5..8cd5c984 100644 --- a/argcomplete/lexers.py +++ b/argcomplete/lexers.py @@ -1,22 +1,25 @@ import os +from typing import List, Optional, Tuple from .exceptions import ArgcompleteException from .io import debug from .packages import _shlex -def split_line(line, point=None): - if point is None: - point = len(line) +_SplitResult = Tuple[str, str, str, List[str], Optional[int]] + + +def split_line(line: str, point: Optional[int] = None) -> _SplitResult: + point_ = len(line) if point is None else point line = line[:point] lexer = _shlex.shlex(line, posix=True) lexer.whitespace_split = True lexer.wordbreaks = os.environ.get("_ARGCOMPLETE_COMP_WORDBREAKS", "") words = [] - def split_word(word): + def split_word(word: str) -> _SplitResult: # TODO: make this less ugly - point_in_word = len(word) + point - lexer.instream.tell() + point_in_word = len(word) + point_ - lexer.instream.tell() if isinstance(lexer.state, (str, bytes)) and lexer.state in lexer.whitespace: point_in_word += 1 if point_in_word > len(word): diff --git a/argcomplete/shell_integration.py b/argcomplete/shell_integration.py index 6d1ba245..9ee22d3c 100644 --- a/argcomplete/shell_integration.py +++ b/argcomplete/shell_integration.py @@ -4,7 +4,9 @@ # files with source copies of this package and derivative works is **REQUIRED** as specified by the Apache License. # See https://github.com/kislyuk/argcomplete for more info. +from collections.abc import Iterable from shlex import quote +from typing import Optional bashcode = r""" # Run something, muting output or redirecting it to the debug stream @@ -118,16 +120,22 @@ shell_codes = {"bash": bashcode, "tcsh": tcshcode, "fish": fishcode, "powershell": powershell_code} -def shellcode(executables, use_defaults=True, shell="bash", complete_arguments=None, argcomplete_script=None): +def shellcode( + executables: Iterable[str], + use_defaults: bool = True, + shell: str = "bash", + complete_arguments: Optional[Iterable[str]] = None, + argcomplete_script: Optional[str] = None, +) -> str: """ Provide the shell code required to register a python executable for use with the argcomplete module. - :param list(str) executables: Executables to be completed (when invoked exactly with this name) + :param Iterable(str) executables: Executables to be completed (when invoked exactly with this name) :param bool use_defaults: Whether to fallback to readline's default completion when no matches are generated (affects bash only) :param str shell: Name of the shell to output code for :param complete_arguments: Arguments to call complete with (affects bash only) - :type complete_arguments: list(str) or None + :type complete_arguments: Iterable(str) or None :param argcomplete_script: Script to call complete with, if not the executable to complete. If supplied, will be used to complete *all* passed executables. :type argcomplete_script: str or None