Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/argcomplete/completers.py: 44%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

79 statements  

1# Copyright 2012-2023, Andrey Kislyuk and argcomplete contributors. 

2# Licensed under the Apache License. See https://github.com/kislyuk/argcomplete for more info. 

3 

4from __future__ import annotations 

5 

6import argparse 

7import os 

8import subprocess 

9from collections.abc import Callable, Generator, Iterable, Mapping 

10from shlex import quote 

11from typing import Final 

12 

13_Ignored = object 

14 

15 

16def _call(*args, **kwargs): 

17 # TODO: replace "universal_newlines" with "text" once 3.6 support is dropped 

18 kwargs["universal_newlines"] = True 

19 try: 

20 return subprocess.check_output(*args, **kwargs).splitlines() 

21 except subprocess.CalledProcessError: 

22 return [] 

23 

24 

25class BaseCompleter: 

26 """ 

27 This is the base class that all argcomplete completers should subclass. 

28 """ 

29 

30 def __call__( 

31 self, *, prefix: str, action: argparse.Action, parser: argparse.ArgumentParser, parsed_args: argparse.Namespace 

32 ) -> Iterable[str]: 

33 raise NotImplementedError("This method should be implemented by a subclass.") 

34 

35 

36class ChoicesCompleter(BaseCompleter): 

37 choices: Final[Mapping[str, str | bytes]] 

38 

39 def __init__(self, choices: Mapping[str, str | bytes]) -> None: 

40 self.choices = choices 

41 

42 def _convert(self, choice): 

43 if not isinstance(choice, str): 

44 choice = str(choice) 

45 return choice 

46 

47 def __call__(self, **kwargs: _Ignored) -> Iterable[str]: 

48 return (self._convert(c) for c in self.choices) 

49 

50 

51EnvironCompleter: Final[ChoicesCompleter] = ChoicesCompleter(os.environ) 

52 

53 

54class FilesCompleter(BaseCompleter): 

55 """ 

56 File completer class, optionally takes a list of allowed extensions 

57 """ 

58 

59 allowednames: Final[list[str]] 

60 directories: Final[bool] 

61 

62 def __init__(self, allowednames: Iterable[str] | str = (), directories: bool = True) -> None: 

63 # Fix if someone passes in a string instead of a list 

64 if isinstance(allowednames, (str, bytes)): 

65 allowednames = [allowednames] 

66 

67 self.allowednames = [x.lstrip("*").lstrip(".") for x in allowednames] 

68 self.directories = directories 

69 

70 def __call__(self, prefix: str, **kwargs: _Ignored) -> list[str]: 

71 completion: list[str] = [] 

72 if self.allowednames: 

73 if self.directories: 

74 # Using 'bind' in this and the following commands is a workaround to a bug in bash 

75 # that was fixed in bash 5.3 but affects older versions. Environment variables are not treated 

76 # correctly in older versions and calling bind makes them available. For details, see 

77 # https://savannah.gnu.org/support/index.php?111125 

78 files = _call( 

79 ["bash", "-c", f"bind; compgen -A directory -- {quote(prefix)}"], 

80 stderr=subprocess.DEVNULL, 

81 ) 

82 completion += [f + "/" for f in files] 

83 for x in self.allowednames: 

84 completion += _call( 

85 ["bash", "-c", f"bind; compgen -A file -X '!*.{x}' -- {quote(prefix)}"], 

86 stderr=subprocess.DEVNULL, 

87 ) 

88 else: 

89 completion += _call(["bash", "-c", f"bind; compgen -A file -- {quote(prefix)}"], stderr=subprocess.DEVNULL) 

90 anticomp = _call( 

91 ["bash", "-c", f"bind; compgen -A directory -- {quote(prefix)}"], 

92 stderr=subprocess.DEVNULL, 

93 ) 

94 completion = list(set(completion) - set(anticomp)) 

95 

96 if self.directories: 

97 completion += [f + "/" for f in anticomp] 

98 return completion 

99 

100 

101class _FilteredFilesCompleter(BaseCompleter): 

102 predicate: Final[Callable[[str], bool]] 

103 

104 def __init__(self, predicate: Callable[[str], bool]) -> None: 

105 """ 

106 Create the completer 

107 

108 A predicate accepts as its only argument a candidate path and either 

109 accepts it or rejects it. 

110 """ 

111 assert predicate, "Expected a callable predicate" # type: ignore[truthy-function] 

112 self.predicate = predicate 

113 

114 def __call__(self, prefix: str, **kwargs: _Ignored) -> Generator[str]: 

115 """ 

116 Provide completions on prefix 

117 """ 

118 # Expand a leading "~" so that filesystem operations below work on a 

119 # real path (os.listdir("~") would otherwise raise FileNotFoundError). 

120 expanded_prefix = os.path.expanduser(prefix) 

121 target_dir = os.path.dirname(expanded_prefix) 

122 try: 

123 names = os.listdir(target_dir or ".") 

124 except Exception: 

125 return # empty iterator 

126 incomplete_part = os.path.basename(expanded_prefix) 

127 # Iterate on target_dir entries and filter on given predicate 

128 for name in names: 

129 if not name.startswith(incomplete_part): 

130 continue 

131 candidate = os.path.join(target_dir, name) 

132 if not self.predicate(candidate): 

133 continue 

134 yield candidate + "/" if os.path.isdir(candidate) else candidate 

135 

136 

137class DirectoriesCompleter(_FilteredFilesCompleter): 

138 def __init__(self) -> None: 

139 _FilteredFilesCompleter.__init__(self, predicate=os.path.isdir) 

140 

141 

142class SuppressCompleter(BaseCompleter): 

143 """ 

144 A completer used to suppress the completion of specific arguments 

145 """ 

146 

147 def __init__(self) -> None: 

148 pass 

149 

150 def suppress(self) -> bool: 

151 """ 

152 Decide if the completion should be suppressed 

153 """ 

154 return True