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 = 0
127 $env:_ARGCOMPLETE_IFS = "`n"
128 $env:_ARGCOMPLETE_SHELL = "powershell"
129 %(argcomplete_script)s 2>&1 | Out-Null
130
131 Get-Content $completion_file | ForEach-Object {
132 [System.Management.Automation.CompletionResult]::new($_, $_, "ParameterValue", $_)
133 }
134 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
135}
136"""
137
138shell_codes = {"bash": bashcode, "tcsh": tcshcode, "fish": fishcode, "powershell": powershell_code}
139
140
141def shellcode(
142 executables: Iterable[str],
143 use_defaults: bool = True,
144 shell: str = "bash",
145 complete_arguments: Iterable[str] | None = None,
146 argcomplete_script: str | None = None,
147) -> str:
148 """
149 Provide the shell code required to register a python executable for use with the argcomplete module.
150
151 :param list(str) executables: Executables to be completed (when invoked exactly with this name)
152 :param bool use_defaults: Whether to fallback to readline's default completion when no matches are generated
153 (affects bash only)
154 :param str shell: Name of the shell to output code for
155 :param complete_arguments: Arguments to call complete with (affects bash only)
156 :type complete_arguments: list(str) or None
157 :param argcomplete_script: Script to call complete with, if not the executable to complete.
158 If supplied, will be used to complete *all* passed executables.
159 :type argcomplete_script: str or None
160 """
161
162 if complete_arguments is None:
163 complete_options = "-o nospace -o default -o bashdefault" if use_defaults else "-o nospace -o bashdefault"
164 else:
165 complete_options = " ".join(complete_arguments)
166
167 if shell == "bash" or shell == "zsh":
168 quoted_executables = [quote(i) for i in executables]
169 executables_list = " ".join(quoted_executables)
170 script = argcomplete_script
171 if script:
172 # If the script path contain a space, this would generate an invalid function name.
173 function_suffix = "_" + script.replace(" ", "_SPACE_")
174 else:
175 script = ""
176 function_suffix = ""
177 code = bashcode % {
178 "complete_opts": complete_options,
179 "executables": executables_list,
180 "argcomplete_script": script,
181 "function_suffix": function_suffix,
182 }
183 elif shell == "fish":
184 code = ""
185 for executable in executables:
186 script = argcomplete_script or executable
187 completion_arg = "--path" if "/" in executable else "--command" # use path for absolute paths
188 function_name = executable.replace("/", "_") # / not allowed in function name
189
190 code += fishcode % {
191 "executable": executable,
192 "argcomplete_script": script,
193 "completion_arg": completion_arg,
194 "function_name": function_name,
195 }
196 elif shell == "powershell":
197 code = ""
198 for executable in executables:
199 script = argcomplete_script or executable
200 code += powershell_code % {"executable": executable, "argcomplete_script": script}
201
202 else:
203 code = ""
204 for executable in executables:
205 script = argcomplete_script
206 # If no script was specified, default to the executable being completed.
207 if not script:
208 script = executable
209 code += shell_codes.get(shell, "") % {"executable": executable, "argcomplete_script": script}
210
211 return code