上下文
我有一个名为arduino-cli包装器的ino脚本,它从以下位置读取目标/构建配置:
位于草图directory
中的
然后构造并exec相应的arduino-cli命令行。
问题
作为一个方便的包装脚本,ino并不打算支持arduino-cli的所有特性。因此,对于那些ino不自动执行的任务,用户可以使用ino的cli子命令间接调用ino。
例如,如果用户键入以下命令:
% ino cli update
% ino cli core list --allino脚本将获取cli后面的所有内容,并简单地将它们附加到arduino-cli可执行文件中。因此,它们相当于以下命令:
% arduino-cli update
% arduino-cli core list --all由于arduino-cli对它的所有子命令和标志都有很好的bash完成,所以我想为我的ino cli子命令劫持相同的完成功能。
我试过的
- [How do I autocomplete nested, multi-level subcommands?](https://stackoverflow.com/a/17881946/1054397)
- [Multi Level Bash Completion](https://stackoverflow.com/a/5303225/1054397)
- These helped me understand how to identify the current subcommand and discriminate the completion results based upon it.
- However, I couldn't figure out how to then invoke the `arduino-cli` completion handler using the remaining args.从接受的答案导出的
- [How do I get bash completion for command aliases?](https://unix.stackexchange.com/a/4220/100385)
- See my `ino` completion handler based on that answer below (_Reference_ _**1**_).
- This question/answer isn't quite the same, because they can basically just install a completion handler on their alias. I'm needing to "install" one _on an argument_ to a command/alias.
- This almost seems to work. Try it with `xtrace` option enabled (`set -x`), and you can see the `arduino-cli` command-line is appearing in the args ... but following `ino` at position `$0`.
- E.g., given `ino cli core list --all` to the wrapper handler, the `arduino-cli` handler receives `ino arduino-cli core list --all`. Not sure how to get rid of `$0`!参考文献
从基于别名的包装器派生的
ino完成包装器joinstr() {
local d=${1-} f=${2-}
shift 2 && printf %s "$f" "${@/#/$d}"
}
complete-subcmd() {
[[ ${#} -gt 2 ]] || {
printf "usage:\n\tcomplete-subcmd src-command... -- comp-func dst-command...\n"
return 1
}
# parse the command-line by splitting it into two command-lines
# of variable length, src-command and dst-command:
# 1. src-command is the trigger that invokes the real completion
# handler, comp-func.
# 2. dst-command is the leading args of the command-line passed
# to the real completion handler, comp-func, to produce the
# resulting completion choices.
unset -v dstparse
local -a srccmd dstcmd
local func
while [[ ${#} -gt 0 ]]; do
case "${1}" in
--)
# when we reach the delimiter, also shift in comp-func as
# the next argument (the real completion handler).
dstparse=1
shift
func=${1:-}
;;
*)
# if we aren't processing the delimiter, then all other
# args are appended to either src-command or dst-command.
if [[ -z ${dstparse} ]]; then
srccmd+=( "${1}" )
else
dstcmd+=( "${1}" )
fi
;;
esac
shift
done
# if the completer is dynamic and not yet loaded, try to load it
# automatically using the given command
if [[ $( type -t "${func}" ) != function ]]; then
type -p _completion_loader &> /dev/null &&
_completion_loader "${dstcmd[@]}"
fi
local wrap=$( joinstr _ "${srccmd[@]}" | tr -d -c '[A-Za-z_]' )
# replace our args with dst-command followed by whatever remains
# from the invoking command-line.
eval "
function _${wrap} {
(( COMP_CWORD+=$(( ${#dstcmd[@]} )) ))
COMP_WORDS=( "${dstcmd[@]}" \${COMP_WORDS[@]:1} )
"${func}"
return 0
}
"
# install this wrapper handler on the first word in src-command
complete -F "_${wrap}" "${srccmd[0]}"
}
complete-subcmd ino cli -- __start_arduino-cli arduino-clicompletion.bash来自arduino-cli# bash completion V2 for arduino-cli -*- shell-script -*-
__arduino-cli_debug()
{
if [[ -n ${BASH_COMP_DEBUG_FILE:-} ]]; then
echo "$*" >> "${BASH_COMP_DEBUG_FILE}"
fi
}
# Macs have bash3 for which the bash-completion package doesn't include
# _init_completion. This is a minimal version of that function.
__arduino-cli_init_completion()
{
COMPREPLY=()
_get_comp_words_by_ref "$@" cur prev words cword
}
# This function calls the arduino-cli program to obtain the completion
# results and the directive. It fills the 'out' and 'directive' vars.
__arduino-cli_get_completion_results() {
local requestComp lastParam lastChar args
# Prepare the command to request completions for the program.
# Calling ${words[0]} instead of directly arduino-cli allows to handle aliases
args=("${words[@]:1}")
requestComp="${words[0]} __completeNoDesc ${args[*]}"
lastParam=${words[$((${#words[@]}-1))]}
lastChar=${lastParam:$((${#lastParam}-1)):1}
__arduino-cli_debug "lastParam ${lastParam}, lastChar ${lastChar}"
if [ -z "${cur}" ] && [ "${lastChar}" != "=" ]; then
# If the last parameter is complete (there is a space following it)
# We add an extra empty parameter so we can indicate this to the go method.
__arduino-cli_debug "Adding extra empty parameter"
requestComp="${requestComp} ''"
fi
# When completing a flag with an = (e.g., arduino-cli -n=<TAB>)
# bash focuses on the part after the =, so we need to remove
# the flag part from $cur
if [[ "${cur}" == -*=* ]]; then
cur="${cur#*=}"
fi
__arduino-cli_debug "Calling ${requestComp}"
# Use eval to handle any environment variables and such
out=$(eval "${requestComp}" 2>/dev/null)
# Extract the directive integer at the very end of the output following a colon (:)
directive=${out##*:}
# Remove the directive
out=${out%:*}
if [ "${directive}" = "${out}" ]; then
# There is not directive specified
directive=0
fi
__arduino-cli_debug "The completion directive is: ${directive}"
__arduino-cli_debug "The completions are: ${out[*]}"
}
__arduino-cli_process_completion_results() {
local shellCompDirectiveError=1
local shellCompDirectiveNoSpace=2
local shellCompDirectiveNoFileComp=4
local shellCompDirectiveFilterFileExt=8
local shellCompDirectiveFilterDirs=16
if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then
# Error code. No completion.
__arduino-cli_debug "Received error from custom completion go code"
return
else
if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then
if [[ $(type -t compopt) = "builtin" ]]; then
__arduino-cli_debug "Activating no space"
compopt -o nospace
else
__arduino-cli_debug "No space directive not supported in this version of bash"
fi
fi
if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then
if [[ $(type -t compopt) = "builtin" ]]; then
__arduino-cli_debug "Activating no file completion"
compopt +o default
else
__arduino-cli_debug "No file completion directive not supported in this version of bash"
fi
fi
fi
if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then
# File extension filtering
local fullFilter filter filteringCmd
# Do not use quotes around the $out variable or else newline
# characters will be kept.
for filter in ${out[*]}; do
fullFilter+="$filter|"
done
filteringCmd="_filedir $fullFilter"
__arduino-cli_debug "File filtering command: $filteringCmd"
$filteringCmd
elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then
# File completion for directories only
# Use printf to strip any trailing newline
local subdir
subdir=$(printf "%s" "${out[0]}")
if [ -n "$subdir" ]; then
__arduino-cli_debug "Listing directories in $subdir"
pushd "$subdir" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 || return
else
__arduino-cli_debug "Listing directories in ."
_filedir -d
fi
else
__arduino-cli_handle_standard_completion_case
fi
__arduino-cli_handle_special_char "$cur" :
__arduino-cli_handle_special_char "$cur" =
}
__arduino-cli_handle_standard_completion_case() {
local tab comp
tab=$(printf '\t')
local longest=0
# Look for the longest completion so that we can format things nicely
while IFS='' read -r comp; do
# Strip any description before checking the length
comp=${comp%%$tab*}
# Only consider the completions that match
comp=$(compgen -W "$comp" -- "$cur")
if ((${#comp}>longest)); then
longest=${#comp}
fi
done < <(printf "%s\n" "${out[@]}")
local completions=()
while IFS='' read -r comp; do
if [ -z "$comp" ]; then
continue
fi
__arduino-cli_debug "Original comp: $comp"
comp="$(__arduino-cli_format_comp_descriptions "$comp" "$longest")"
__arduino-cli_debug "Final comp: $comp"
completions+=("$comp")
done < <(printf "%s\n" "${out[@]}")
while IFS='' read -r comp; do
COMPREPLY+=("$comp")
done < <(compgen -W "${completions[*]}" -- "$cur")
# If there is a single completion left, remove the description text
if [ ${#COMPREPLY[*]} -eq 1 ]; then
__arduino-cli_debug "COMPREPLY[0]: ${COMPREPLY[0]}"
comp="${COMPREPLY[0]%% *}"
__arduino-cli_debug "Removed description from single completion, which is now: ${comp}"
COMPREPLY=()
COMPREPLY+=("$comp")
fi
}
__arduino-cli_handle_special_char()
{
local comp="$1"
local char=$2
if [[ "$comp" == *${char}* && "$COMP_WORDBREAKS" == *${char}* ]]; then
local word=${comp%"${comp##*${char}}"}
local idx=${#COMPREPLY[*]}
while [[ $((--idx)) -ge 0 ]]; do
COMPREPLY[$idx]=${COMPREPLY[$idx]#"$word"}
done
fi
}
__arduino-cli_format_comp_descriptions()
{
local tab
tab=$(printf '\t')
local comp="$1"
local longest=$2
# Properly format the description string which follows a tab character if there is one
if [[ "$comp" == *$tab* ]]; then
desc=${comp#*$tab}
comp=${comp%%$tab*}
# $COLUMNS stores the current shell width.
# Remove an extra 4 because we add 2 spaces and 2 parentheses.
maxdesclength=$(( COLUMNS - longest - 4 ))
# Make sure we can fit a description of at least 8 characters
# if we are to align the descriptions.
if [[ $maxdesclength -gt 8 ]]; then
# Add the proper number of spaces to align the descriptions
for ((i = ${#comp} ; i < longest ; i++)); do
comp+=" "
done
else
# Don't pad the descriptions so we can fit more text after the completion
maxdesclength=$(( COLUMNS - ${#comp} - 4 ))
fi
# If there is enough space for any description text,
# truncate the descriptions that are too long for the shell width
if [ $maxdesclength -gt 0 ]; then
if [ ${#desc} -gt $maxdesclength ]; then
desc=${desc:0:$(( maxdesclength - 1 ))}
desc+="…"
fi
comp+=" ($desc)"
fi
fi
# Must use printf to escape all special characters
printf "%q" "${comp}"
}
__start_arduino-cli()
{
local cur prev words cword split
COMPREPLY=()
# Call _init_completion from the bash-completion package
# to prepare the arguments properly
if declare -F _init_completion >/dev/null 2>&1; then
_init_completion -n "=:" || return
else
__arduino-cli_init_completion -n "=:" || return
fi
__arduino-cli_debug
__arduino-cli_debug "========= starting completion logic =========="
__arduino-cli_debug "cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#words[@]}, cword is $cword"
# The user could have moved the cursor backwards on the command-line.
# We need to trigger completion from the $cword location, so we need
# to truncate the command-line ($words) up to the $cword location.
words=("${words[@]:0:$cword+1}")
__arduino-cli_debug "Truncated words[*]: ${words[*]},"
local out directive
__arduino-cli_get_completion_results
__arduino-cli_process_completion_results
}
if [[ $(type -t compopt) = "builtin" ]]; then
complete -o default -F __start_arduino-cli arduino-cli
else
complete -o default -o nospace -F __start_arduino-cli arduino-cli
fi
# ex: ts=4 sw=4 et filetype=sh发布于 2022-09-03 19:46:38
UPDATE2:
在我发布这篇文章之后,我查看了你的链接,在看到了被接受的答案here之后,我想我只是老了,忘记了我只是从那个链接复制了这段代码,而不是我写的。即使在这里使用的示例也是相同的,但是进一步研究代码时,我似乎确实编写了这个代码,并使用了不同的方法,也许这将帮助您了解正在发生的事情。正如我在下面的UPDATE1部分中提到的:您需要调优COMP变量,然后调用原始函数
原件:
几年前我写了一个“别名包装”脚本。其思想是使用原始bash完成与别名,甚至与参数。例如:
alias apti='apt-get install'
source alias-completion-wrapper _apt_get apti apt-get install
#here _apt_get is the original completion function现在您可以使用选项卡在apti之后完成包名,就像在apt-get install之后一样
#alias-completion-wrapper
#Example: . alias-completion-wrapper _apt_get apti apt-get install
comp_function_name="$1"
ali="$2"
shift 2
x="$@"
function_name=`echo _$@ |tr ' ' _`
function="
function $function_name {
_completion_loader $1
(( COMP_CWORD += $# - 1 ))
COMP_WORDS=( $@ \"\${COMP_WORDS[@]:1}\")
COMP_LINE=\"\${COMP_WORDS[@]}\"
let COMP_POINT=\${COMP_POINT}-${#ali}+${#x}
$comp_function_name
return 0
}"
eval "$function"
complete -F $function_name $ali
unset function function_name ali x老实说,我不记得它是如何工作的,我也没有评论脚本:),但我认为您可以根据您的需要对其进行调优。
UPDATE1:
当我稍微研究了一下代码时,它的想法似乎是调优COMP变量,然后调用原始函数:)
UPDATE3:
我有一些时间,所以你需要的修改是:
此${COMP_WORDS[@]}包含当前命令行。${COMP_WORDS[@]:1}切断了第一个单词,这个词最初是别名/命令。由于您想要在参数之后使用它,所以您也必须切断参数。
COMP_WORDS=( $@ \"\${COMP_WORDS[@]:2}\")我在另一种解决方案中看不到COMP_LINE和COMP_POINT,但我记得如果没有这些解决方案,它在某些情况下不能很好地工作。所以我想你需要:
COMP_LINE=\"\${COMP_WORDS[@]:1}\"这里,${#ali}是命令的长度。您需要用命令的长度替换为参数。例:"xcmd prm“-> 8(也计算空格)
let COMP_POINT=\${COMP_POINT}-${#ali}+${#x}不确定(( COMP_CWORD += $# - 1 ))是否删除-1或使用-2或保持原样:)
修改后,只需将eval更改为echo并删除complete -F行。和source脚本,如所述。这样,它将响应您可以插入到完成脚本中的函数。
https://stackoverflow.com/questions/73592708
复制相似问题