1# Copyright 2012-2023, Andrey Kislyuk and argcomplete contributors. Licensed under the terms of the
2# `Apache License, Version 2.0 <http://www.apache.org/licenses/LICENSE-2.0>`_. Distribution of the LICENSE and NOTICE
3# files with source copies of this package and derivative works is **REQUIRED** as specified by the Apache License.
4# See https://github.com/kislyuk/argcomplete for more info.
5
6from __future__ import annotations
7
8from collections.abc import Iterable
9from shlex import quote
10
11bashcode = r"""#compdef %(executables)s
12# Run something, muting output or redirecting it to the debug stream
13# depending on the value of _ARC_DEBUG.
14# If ARGCOMPLETE_USE_TEMPFILES is set, use tempfiles for IPC.
15__python_argcomplete_run() {
16 if [[ -z "${ARGCOMPLETE_USE_TEMPFILES-}" ]]; then
17 __python_argcomplete_run_inner "$@"
18 return
19 fi
20 local tmpfile="$(mktemp)"
21 _ARGCOMPLETE_STDOUT_FILENAME="$tmpfile" __python_argcomplete_run_inner "$@"
22 local code=$?
23 cat "$tmpfile"
24 rm "$tmpfile"
25 return $code
26}
27
28__python_argcomplete_run_inner() {
29 if [[ -z "${_ARC_DEBUG-}" ]]; then
30 "$@" 8>&1 9>&2 1>/dev/null 2>&1 </dev/null
31 else
32 "$@" 8>&1 9>&2 1>&9 2>&1 </dev/null
33 fi
34}
35
36_python_argcomplete%(function_suffix)s() {
37 local IFS=$'\013'
38 local script="%(argcomplete_script)s"
39 if [[ -n "${ZSH_VERSION-}" ]]; then
40 local completions
41 completions=($(IFS="$IFS" \
42 COMP_LINE="$BUFFER" \
43 COMP_POINT="$CURSOR" \
44 _ARGCOMPLETE=1 \
45 _ARGCOMPLETE_SHELL="zsh" \
46 _ARGCOMPLETE_SUPPRESS_SPACE=1 \
47 __python_argcomplete_run ${script:-${words[1]}}))
48 local nosort=()
49 local nospace=()
50 if is-at-least 5.8; then
51 nosort=(-o nosort)
52 fi
53 if [[ "${completions-}" =~ ([^\\\\]): && "${match[1]}" =~ [=/:] ]]; then
54 nospace=(-S '')
55 fi
56 _describe "${words[1]}" completions "${nosort[@]}" "${nospace[@]}"
57 else
58 local SUPPRESS_SPACE=0
59 if compopt +o nospace 2> /dev/null; then
60 SUPPRESS_SPACE=1
61 fi
62 COMPREPLY=($(IFS="$IFS" \
63 COMP_LINE="$COMP_LINE" \
64 COMP_POINT="$COMP_POINT" \
65 COMP_TYPE="$COMP_TYPE" \
66 _ARGCOMPLETE_COMP_WORDBREAKS="$COMP_WORDBREAKS" \
67 _ARGCOMPLETE=1 \
68 _ARGCOMPLETE_SHELL="bash" \
69 _ARGCOMPLETE_SUPPRESS_SPACE=$SUPPRESS_SPACE \
70 __python_argcomplete_run ${script:-$1}))
71 if [[ $? != 0 ]]; then
72 unset COMPREPLY
73 elif [[ $SUPPRESS_SPACE == 1 ]] && [[ "${COMPREPLY-}" =~ [=/:]$ ]]; then
74 compopt -o nospace
75 fi
76 fi
77}
78if [[ -z "${ZSH_VERSION-}" ]]; then
79 complete %(complete_opts)s -F _python_argcomplete%(function_suffix)s %(executables)s
80else
81 # When called by the Zsh completion system, this will end with
82 # "loadautofunc" when initially autoloaded and "shfunc" later on, otherwise,
83 # the script was "eval"-ed so use "compdef" to register it with the
84 # completion system
85 autoload is-at-least
86 if [[ $zsh_eval_context == *func ]]; then
87 _python_argcomplete%(function_suffix)s "$@"
88 else
89 compdef _python_argcomplete%(function_suffix)s %(executables)s
90 fi
91fi
92"""
93
94tcshcode = """\
95complete "%(executable)s" 'p@*@`python-argcomplete-tcsh "%(argcomplete_script)s"`@' ;
96"""
97
98fishcode = r"""
99function __fish_%(function_name)s_complete
100 set -lx _ARGCOMPLETE 1
101 set -lx _ARGCOMPLETE_DFS \t
102 set -lx _ARGCOMPLETE_IFS \n
103 set -lx _ARGCOMPLETE_SUPPRESS_SPACE 1
104 set -lx _ARGCOMPLETE_SHELL fish
105 set -lx COMP_LINE (commandline -p)
106 set -lx COMP_POINT (string length (commandline -cp))
107 set -lx COMP_TYPE
108 if set -q _ARC_DEBUG
109 %(argcomplete_script)s 8>&1 9>&2 1>&9 2>&1
110 else
111 %(argcomplete_script)s 8>&1 9>&2 1>/dev/null 2>&1
112 end
113end
114complete %(completion_arg)s %(executable)s -f -a '(__fish_%(function_name)s_complete)'
115"""
116
117powershell_code = r"""
118Register-ArgumentCompleter -Native -CommandName %(executable)s -ScriptBlock {
119 param($commandName, $wordToComplete, $cursorPosition)
120 $completion_file = New-TemporaryFile
121 $env:ARGCOMPLETE_USE_TEMPFILES = 1
122 $env:_ARGCOMPLETE_STDOUT_FILENAME = $completion_file
123 $env:COMP_LINE = $wordToComplete
124 $env:COMP_POINT = $cursorPosition
125 $env:_ARGCOMPLETE = 1
126 $env:_ARGCOMPLETE_SUPPRESS_SPACE = 1
127 $env:_ARGCOMPLETE_IFS = "`n"
128 $env:_ARGCOMPLETE_SHELL = "powershell"
129 %(argcomplete_script)s 2>&1 | Out-Null
130
131 Get-Content -Encoding UTF8 $completion_file | ForEach-Object {
132 $escaped = [System.Management.Automation.Language.CodeGeneration]::EscapeSingleQuotedStringContent($_)
133 $quoted = "'" + $escaped + "'"
134 [System.Management.Automation.CompletionResult]::new($quoted, $_, "ParameterValue", $_)
135 }
136 Remove-Item $completion_file, Env:\_ARGCOMPLETE_STDOUT_FILENAME, Env:\ARGCOMPLETE_USE_TEMPFILES, Env:\COMP_LINE, Env:\COMP_POINT, Env:\_ARGCOMPLETE, Env:\_ARGCOMPLETE_SUPPRESS_SPACE, Env:\_ARGCOMPLETE_IFS, Env:\_ARGCOMPLETE_SHELL
137}
138"""
139
140shell_codes = {"bash": bashcode, "tcsh": tcshcode, "fish": fishcode, "powershell": powershell_code}
141
142
143def shellcode(
144 executables: Iterable[str],
145 use_defaults: bool = True,
146 shell: str = "bash",
147 complete_arguments: Iterable[str] | None = None,
148 argcomplete_script: str | None = None,
149) -> str:
150 """
151 Provide the shell code required to register a python executable for use with the argcomplete module.
152
153 :param list(str) executables: Executables to be completed (when invoked exactly with this name)
154 :param bool use_defaults: Whether to fallback to readline's default completion when no matches are generated
155 (affects bash only)
156 :param str shell: Name of the shell to output code for
157 :param complete_arguments: Arguments to call complete with (affects bash only)
158 :type complete_arguments: list(str) or None
159 :param argcomplete_script: Script to call complete with, if not the executable to complete.
160 If supplied, will be used to complete *all* passed executables.
161 :type argcomplete_script: str or None
162 """
163
164 if complete_arguments is None:
165 complete_options = "-o nospace -o default -o bashdefault" if use_defaults else "-o nospace -o bashdefault"
166 else:
167 complete_options = " ".join(complete_arguments)
168
169 if shell == "bash" or shell == "zsh":
170 quoted_executables = [quote(i) for i in executables]
171 executables_list = " ".join(quoted_executables)
172 script = argcomplete_script
173 if script:
174 # If the script path contain a space, this would generate an invalid function name.
175 function_suffix = "_" + script.replace(" ", "_SPACE_")
176 else:
177 script = ""
178 function_suffix = ""
179 code = bashcode % {
180 "complete_opts": complete_options,
181 "executables": executables_list,
182 "argcomplete_script": script,
183 "function_suffix": function_suffix,
184 }
185 elif shell == "fish":
186 code = ""
187 for executable in executables:
188 script = argcomplete_script or executable
189 completion_arg = "--path" if "/" in executable else "--command" # use path for absolute paths
190 function_name = executable.replace("/", "_") # / not allowed in function name
191
192 code += fishcode % {
193 "executable": executable,
194 "argcomplete_script": script,
195 "completion_arg": completion_arg,
196 "function_name": function_name,
197 }
198 elif shell == "powershell":
199 code = ""
200 for executable in executables:
201 script = argcomplete_script or executable
202 code += powershell_code % {"executable": executable, "argcomplete_script": script}
203
204 else:
205 code = ""
206 for executable in executables:
207 script = argcomplete_script
208 # If no script was specified, default to the executable being completed.
209 if not script:
210 script = executable
211 code += shell_codes.get(shell, "") % {"executable": executable, "argcomplete_script": script}
212
213 return code