Merge pull request #1928 from gaelicWizard/defaults

Refactor completion for `defaults` command
pull/2006/head^2
Noah Gorny 2022-01-01 16:46:14 +02:00 committed by GitHub
commit 475952dbb9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 544 additions and 174 deletions

View File

@ -1,175 +1,5 @@
# defaults
# Bash command line completion for defaults
#
# Created by Jonathon Mah on 2006-11-08.
# Copyright 2006 Playhaus. All rights reserved.
#
# Version 1.0 (2006-11-08)
# shellcheck shell=bash
_defaults_domains()
{
local cur
COMPREPLY=()
cur=${COMP_WORDS[COMP_CWORD]}
local domains=$( defaults domains | sed -e 's/, /:/g' | tr : '\n' | sed -e 's/ /\\ /g' | grep "^$cur" )
local IFS=$'\n'
COMPREPLY=( $domains )
if [[ $( echo '-app' | grep "^$cur" ) ]]; then
COMPREPLY[${#COMPREPLY[@]}]="-app"
fi
return 0
}
_defaults()
{
local cur prev host_opts cmds cmd domain keys key_index
cur=${COMP_WORDS[COMP_CWORD]}
prev=${COMP_WORDS[COMP_CWORD-1]}
host_opts='-currentHost -host'
cmds='read read-type write rename delete domains find help'
if [[ $COMP_CWORD -eq 1 ]]; then
COMPREPLY=( $( compgen -W "$host_opts $cmds" -- $cur ) )
return 0
elif [[ $COMP_CWORD -eq 2 ]]; then
if [[ "$prev" == "-currentHost" ]]; then
COMPREPLY=( $( compgen -W "$cmds" -- $cur ) )
return 0
elif [[ "$prev" == "-host" ]]; then
_known_hosts -a
return 0
else
_defaults_domains
return 0
fi
elif [[ $COMP_CWORD -eq 3 ]]; then
if [[ ${COMP_WORDS[1]} == "-host" ]]; then
_defaults_domains
return 0
fi
fi
# Both a domain and command have been specified
if [[ ${COMP_WORDS[1]} == [${cmds// /|}] ]]; then
cmd=${COMP_WORDS[1]}
domain=${COMP_WORDS[2]}
key_index=3
if [[ "$domain" == "-app" ]]; then
if [[ $COMP_CWORD -eq 3 ]]; then
# Completing application name. Can't help here, sorry
return 0
fi
domain="-app ${COMP_WORDS[3]}"
key_index=4
fi
elif [[ ${COMP_WORDS[2]} == "-currentHost" ]] && [[ ${COMP_WORDS[2]} == [${cmds// /|}] ]]; then
cmd=${COMP_WORDS[2]}
domain=${COMP_WORDS[3]}
key_index=4
if [[ "$domain" == "-app" ]]; then
if [[ $COMP_CWORD -eq 4 ]]; then
# Completing application name. Can't help here, sorry
return 0
fi
domain="-app ${COMP_WORDS[4]}"
key_index=5
fi
elif [[ ${COMP_WORDS[3]} == "-host" ]] && [[ ${COMP_WORDS[3]} == [${cmds// /|}] ]]; then
cmd=${COMP_WORDS[3]}
domain=${COMP_WORDS[4]}
key_index=5
if [[ "$domain" == "-app" ]]; then
if [[ $COMP_CWORD -eq 5 ]]; then
# Completing application name. Can't help here, sorry
return 0
fi
domain="-app ${COMP_WORDS[5]}"
key_index=6
fi
fi
keys=$( defaults read $domain 2>/dev/null | sed -n -e '/^ [^}) ]/p' | sed -e 's/^ \([^" ]\{1,\}\) = .*$/\1/g' -e 's/^ "\([^"]\{1,\}\)" = .*$/\1/g' | sed -e 's/ /\\ /g' )
case $cmd in
read|read-type)
# Complete key
local IFS=$'\n'
COMPREPLY=( $( echo "$keys" | grep -i "^${cur//\\/\\\\}" ) )
;;
write)
if [[ $key_index -eq $COMP_CWORD ]]; then
# Complete key
local IFS=$'\n'
COMPREPLY=( $( echo "$keys" | grep -i "^${cur//\\/\\\\}" ) )
elif [[ $((key_index+1)) -eq $COMP_CWORD ]]; then
# Complete value type
# Unfortunately ${COMP_WORDS[key_index]} fails on keys with spaces
local value_types='-string -data -integer -float -boolean -date -array -array-add -dict -dict-add'
local cur_type=$( defaults read-type $domain ${COMP_WORDS[key_index]} 2>/dev/null | sed -e 's/^Type is \(.*\)/-\1/' -e's/dictionary/dict/' | grep "^$cur" )
if [[ $cur_type ]]; then
COMPREPLY=( $cur_type )
else
COMPREPLY=( $( compgen -W "$value_types" -- $cur ) )
fi
elif [[ $((key_index+2)) -eq $COMP_CWORD ]]; then
# Complete value
# Unfortunately ${COMP_WORDS[key_index]} fails on keys with spaces
COMPREPLY=( $( defaults read $domain ${COMP_WORDS[key_index]} 2>/dev/null | grep -i "^${cur//\\/\\\\}" ) )
fi
;;
rename)
if [[ $key_index -eq $COMP_CWORD ]] ||
[[ $((key_index+1)) -eq $COMP_CWORD ]]; then
# Complete source and destination keys
local IFS=$'\n'
COMPREPLY=( $( echo "$keys" | grep -i "^${cur//\\/\\\\}" ) )
fi
;;
delete)
if [[ $key_index -eq $COMP_CWORD ]]; then
# Complete key
local IFS=$'\n'
COMPREPLY=( $( echo "$keys" | grep -i "^${cur//\\/\\\\}" ) )
fi
;;
esac
return 0
}
complete -F _defaults -o default defaults
# This file is licensed under the BSD license, as follows:
#
# Copyright (c) 2006, Playhaus
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the Playhaus nor the names of its contributors may be
# used to endorse or promote products derived from this software without
# specific prior written permission.
#
# This software is provided by the copyright holders and contributors "as is"
# and any express or implied warranties, including, but not limited to, the
# implied warranties of merchantability and fitness for a particular purpose are
# disclaimed. In no event shall the copyright owner or contributors be liable
# for any direct, indirect, incidental, special, exemplary, or consequential
# damages (including, but not limited to, procurement of substitute goods or
# services; loss of use, data, or profits; or business interruption) however
# caused and on any theory of liability, whether in contract, strict liability,
# or tort (including negligence or otherwise) arising in any way out of the use
# of this software, even if advised of the possibility of such damage.
if test -s "${BASH_IT?}/vendor/github.com/gaelicWizard/bash-progcomp/defaults.completion.bash"; then
source "$_"
fi

View File

@ -0,0 +1,40 @@
# EditorConfig is awesome: http://EditorConfig.org
[*]
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[**.md]
indent_style = space
indent_size = 2
trim_trailing_whitespace = false
[.git*]
indent_style = tab
[**.*sh]
indent_style = tab
indent_size = tab
shell_variant = bash
binary_next_line = true # like -bn
switch_case_indent = false # like -ci
space_redirects = true # like -sr
keep_padding = false # like -kp
function_next_line = true # like -fn
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[**.bats]
indent_style = tab
indent_size = tab
shell_variant = bash
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

View File

@ -0,0 +1,30 @@
BSD 3-Clause License
Copyright (c) 2006, Playhaus
Copyright (c) 2021, gaelicWizard.LLC
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,286 @@
# shellcheck shell=bash
# shellcheck disable=SC2207
#
# Bash command line completion for defaults
#
# Version 1.0 created by Jonathon Mah on 2006-11-08.
# Version 2.0 written by John Pell on 2021-09-11.
#
function matchpattern()
{
local PATTERN=${2:?$FUNCNAME: a pattern is required}
local SEP=${3:-|}
[[ -z "${PATTERN##*${SEP}${1}${SEP}*}" ]]
}
function _defaults_verbs()
{
local IFS=$'\n' # Treat only newlines as delimiters in string operations.
local LC_CTYPE='C' # Do not consider character set in string operations.
local LC_COLLATE='C' # Do not consider character set in pattern matching.
local cur="${COMP_WORDS[COMP_CWORD]}"
local prev="${COMP_WORDS[COMP_CWORD - 1]}"
COMPREPLY=()
case $COMP_CWORD in
1)
candidates=("${cmds// /$IFS}" "${host_opts[@]}")
;;
2 | 3)
candidates=("${cmds// /$IFS}")
;;
*)
return 1
;;
esac
COMPREPLY=($(compgen -W "${candidates[*]}" | grep -i "^${cur}"))
return 0
}
function _defaults_domains()
{
local IFS=$'\n' # Treat only newlines as delimiters in string operations.
local LC_CTYPE='C' # Do not consider character set in string operations.
local LC_COLLATE='C' # Do not consider character set in pattern matching.
local cur="${COMP_WORDS[COMP_CWORD]}"
local prev="${COMP_WORDS[COMP_CWORD - 1]}"
COMPREPLY=()
if [[ "$BASH_VERSINFO" -ge 4 ]]
then # Exponential performance issue on strings greater than about 10k.
local domains="$(defaults domains)"
local candidates=($(compgen -W "${domains//, /$IFS}" | grep -i "^${cur}"))
else
local domains="$(defaults domains | sed -e 's/, /^/g' | tr '^' '\n')"
local candidates=($(compgen -W "${domains}" | grep -i "^${cur}"))
fi
COMPREPLY=($(printf '%q\n' "${candidates[@]}"))
if grep -q "^$cur" <<< '-app'
then
COMPREPLY[${#COMPREPLY[@]}]="-app"
elif grep -q "^$cur" <<< '-g'
then
COMPREPLY[${#COMPREPLY[@]}]="-g"
fi
return 0
}
function _defaults()
{
local IFS=$'\n' # Treat only newlines as delimiters in string operations.
local LC_CTYPE='C' # Do not consider character set in string operations.
local LC_COLLATE='C' # Do not consider character set in pattern matching.
local cur="${COMP_WORDS[COMP_CWORD]}"
local prev="${COMP_WORDS[COMP_CWORD - 1]}"
COMPREPLY=()
local host_opts cmds cmd domain keys key_index candidates verbs value_types
host_opts=('-currentHost' '-host')
cmds=' delete domains export find help import read read-type rename write '
value_types=('-string' '-data' '-integer' '-float' '-boolean' '-date' '-array' '-array-add' '-dict' '-dict-add')
case $COMP_CWORD in
1)
_defaults_verbs
return "$?"
;;
2)
case $prev in
"-currentHost")
_defaults_verbs
;;
"-host")
_known_hosts -a
;;
*)
if matchpattern "$prev" "${cmds// /|}"
then
# TODO: not correct for verbs: domains, find, help
_defaults_domains
else
return 1 # verb is not recognized
fi
;;
esac
return "$?"
;;
3)
case ${COMP_WORDS[1]} in
"-currentHost")
_defaults_domains
return "$?"
;;
"-host")
_defaults_verbs
return "$?"
;;
esac
;;
4)
case ${COMP_WORDS[1]} in
"-host")
if matchpattern "$prev" "${cmds// /|}"
then
# TODO: not correct for verbs: domains, find, help
_defaults_domains
else
return 1 # verb is not recognized
fi
;;
esac
;;
esac
# Both a domain and command have been specified
case ${COMP_WORDS[1]} in
"-currentHost")
if matchpattern "${COMP_WORDS[2]}" "${cmds// /|}"
then
cmd="${COMP_WORDS[2]}"
domain="${COMP_WORDS[3]}"
key_index=4
if [[ "$domain" == "-app" ]]
then
if [[ $COMP_CWORD -eq 4 ]]
then
# Completing application name. Can't help here, sorry
return 0
fi
domain="-app ${COMP_WORDS[4]}"
key_index=5
fi
fi
;;
"-host")
if matchpattern "${COMP_WORDS[3]}" "${cmds// /|}"
then
cmd="${COMP_WORDS[3]}"
domain="${COMP_WORDS[4]}"
key_index=5
if [[ "$domain" == "-app" ]]
then
if [[ $COMP_CWORD -eq 5 ]]
then
# Completing application name. Can't help here, sorry
return 0
fi
domain="-app ${COMP_WORDS[5]}"
key_index=6
fi
fi
;;
*)
if matchpattern "${COMP_WORDS[1]}" "${cmds// /|}"
then
cmd="${COMP_WORDS[1]}"
domain="${COMP_WORDS[2]}"
key_index=3
if [[ "$domain" == "-app" ]]
then
if [[ $COMP_CWORD -eq 3 ]]
then
# Completing application name. Can't help here, sorry
return 0
fi
domain="-app ${COMP_WORDS[3]}"
key_index=4
fi
fi
;;
esac
keys=($(defaults read "$domain" 2> /dev/null | sed -n -e '/^ [^}) ]/p' | sed -e 's/^ \([^" ]\{1,\}\) = .*$/\1/g' -e 's/^ "\([^"]\{1,\}\)" = .*$/\1/g'))
case $cmd in
read | read-type)
# Complete key
if candidates=($(compgen -W "${keys[*]:-}" | grep -i "^${cur}"))
then
COMPREPLY=($(printf '%q\n' "${candidates[@]}"))
fi
;;
write)
if [[ $key_index -eq $COMP_CWORD ]]
then
# Complete key
if candidates=($(compgen -W "${keys[*]:-}" | grep -i "^${cur}"))
then
COMPREPLY=($(printf '%q\n' "${candidates[@]}"))
fi
elif [[ $((key_index + 1)) -eq $COMP_CWORD ]]
then
# Complete value type
local cur_type="$(defaults read-type "$domain" "${COMP_WORDS[key_index]}" 2> /dev/null | sed -e 's/^Type is \(.*\)/-\1/' -e's/dictionary/dict/' | grep "^$cur")"
if [[ $cur_type ]]
then
COMPREPLY=("$cur_type")
else
COMPREPLY=($(compgen -W "${value_types[*]}" -- "$cur"))
fi
elif [[ $((key_index + 2)) -eq $COMP_CWORD ]]
then
# Complete value
COMPREPLY=($(defaults read "$domain" "${COMP_WORDS[key_index]}" 2> /dev/null | grep -i "^${cur//\\/\\\\}"))
fi
;;
rename)
if [[ $key_index -eq $COMP_CWORD || $((key_index + 1)) -eq $COMP_CWORD ]]
then
# Complete source and destination keys
if candidates=($(compgen -W "${keys[*]:-}" | grep -i "^${cur}"))
then
COMPREPLY=($(printf '%q\n' "${candidates[@]}"))
fi
fi
;;
delete)
if [[ $key_index -eq $COMP_CWORD ]]
then
# Complete key
if candidates=($(compgen -W "${keys[*]:-}" | grep -i "^${cur}"))
then
COMPREPLY=($(printf '%q\n' "${candidates[@]}"))
fi
fi
;;
esac
return 0
}
complete -F _defaults -o default defaults
# This file is licensed under the BSD license, as follows:
#
# Copyright (c) 2006, Playhaus
# Copyright (c) 2021, gaelicWizard.LLC
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the names of the authors nor the names of its contributors may be
# used to endorse or promote products derived from this software without
# specific prior written permission.
#
# This software is provided by the copyright holders and contributors "as is"
# and any express or implied warranties, including, but not limited to, the
# implied warranties of merchantability and fitness for a particular purpose are
# disclaimed. In no event shall the copyright owner or contributors be liable
# for any direct, indirect, incidental, special, exemplary, or consequential
# damages (including, but not limited to, procurement of substitute goods or
# services; loss of use, data, or profits; or business interruption) however
# caused and on any theory of liability, whether in contract, strict liability,
# or tort (including negligence or otherwise) arising in any way out of the use
# of this software, even if advised of the possibility of such damage.

View File

@ -0,0 +1,184 @@
#!/usr/bin/env bats
load ../test_helper
function local_setup() {
load ../../completion/available/defaults.completion
function _known_hosts() { :; }
function defaults() { echo 'NSGlobalDomain, Bash It'; }
}
function __check_completion() {
# Get the parameters as a single value
COMP_LINE=$*
# Get the parameters as an array
eval set -- "$@"
COMP_WORDS=("$@")
# Index of the cursor in the line
COMP_POINT=${#COMP_LINE}
# Get the last character of the line that was entered
COMP_LAST=$((${COMP_POINT} - 1))
# If the last character was a space...
if [[ ${COMP_LINE:$COMP_LAST} = ' ' ]]; then
# ...then add an empty array item
COMP_WORDS+=('')
fi
# Word index of the last word
COMP_CWORD=$(( ${#COMP_WORDS[@]} - 1 ))
# Run the Bash-it completion function
_defaults
# Return the completion output
echo "${COMPREPLY[@]}"
}
@test "completion defaults: ensure that the _defaults function is available" {
type -a _defaults &> /dev/null
assert_success
}
@test "completion defaults: - show verbs and options" {
run __check_completion 'defaults '
assert_line -n 0 'delete domains export find help import read read-type rename write -currentHost -host'
}
@test "completion defaults: r* - show matching verbs" {
run __check_completion 'defaults r'
assert_line -n 0 'read read-type rename'
}
@test "completion defaults: R* - show matching verbs" {
run __check_completion 'defaults R'
assert_line -n 0 'read read-type rename'
}
@test "completion defaults: -* - show matching flags" {
run __check_completion 'defaults -'
assert_line -n 0 '-currentHost -host'
}
@test "completion defaults: -currentHost - show verbs" {
run __check_completion 'defaults -currentHost '
assert_line -n 0 'delete domains export find help import read read-type rename write'
}
@test "completion defaults: -host - show nothing" {
run __check_completion 'defaults -host '
assert_line -n 0 "$(_known_hosts -a)"
}
@test "completion defaults: -host some_computer_name - show verbs" {
run __check_completion 'defaults -host some_computer_name '
assert_line -n 0 'delete domains export find help import read read-type rename write'
}
@test "completion defaults: read - show all domains" {
run __check_completion 'defaults read '
assert_line -n 0 "NSGlobalDomain Bash\ It -app"
}
@test "completion defaults: read nsg* - show matching domains" {
run __check_completion 'defaults read nsg'
assert_line -n 0 "NSGlobalDomain"
}
@test "completion defaults: read NSG* - show matching domains" {
run __check_completion 'defaults read NSG'
assert_line -n 0 "NSGlobalDomain"
}
@test "completion defaults: read bash* - show matching domains" {
run __check_completion 'defaults read bash'
assert_line -n 0 "Bash\ It"
}
@test "completion defaults: read BASH* - show matching domains" {
run __check_completion 'defaults read BASH'
assert_line -n 0 "Bash\ It"
}
@test "completion defaults: read bash* - show matching domains (with spaces)" {
run __check_completion 'defaults read bash\ i'
assert_line -n 0 "Bash\ It"
}
@test "completion defaults: read BASH* - show matching domains (with spaces)" {
run __check_completion 'defaults read BASH\ I'
assert_line -n 0 "Bash\ It"
}
@test "completion defaults: -currentHost read - show all domains" {
run __check_completion 'defaults -currentHost read '
assert_line -n 0 "NSGlobalDomain Bash\ It -app"
}
@test "completion defaults: -currentHost read nsg* - show matching domains" {
run __check_completion 'defaults -currentHost read nsg'
assert_line -n 0 "NSGlobalDomain"
}
@test "completion defaults: -currentHost read NSG* - show matching domains" {
run __check_completion 'defaults -currentHost read NSG'
assert_line -n 0 "NSGlobalDomain"
}
@test "completion defaults: -currentHost read bash* - show matching domains" {
run __check_completion 'defaults -currentHost read bash'
assert_line -n 0 "Bash\ It"
}
@test "completion defaults: -currentHost read BASH* - show matching domains" {
run __check_completion 'defaults -currentHost read BASH'
assert_line -n 0 "Bash\ It"
}
@test "completion defaults: -currentHost read bash* - show matching domains (with spaces)" {
run __check_completion 'defaults -currentHost read bash\ i'
assert_line -n 0 "Bash\ It"
}
@test "completion defaults: -currentHost read BASH* - show matching domains (with spaces)" {
run __check_completion 'defaults -currentHost read BASH\ I'
assert_line -n 0 "Bash\ It"
}
@test "completion defaults: -host some.computer.name read - show all domains" {
run __check_completion 'defaults -host some.computer.name read '
assert_line -n 0 "NSGlobalDomain Bash\ It -app"
}
@test "completion defaults: -host some.computer.name read nsg* - show matching domains" {
run __check_completion 'defaults -host some.computer.name read nsg'
assert_line -n 0 "NSGlobalDomain"
}
@test "completion defaults: -host some.computer.name read NSG* - show matching domains" {
run __check_completion 'defaults -host some.computer.name read NSG'
assert_line -n 0 "NSGlobalDomain"
}
@test "completion defaults: -host some.computer.name read bash* - show matching domains" {
run __check_completion 'defaults -host some.computer.name read bash'
assert_line -n 0 "Bash\ It"
}
@test "completion defaults: -host some.computer.name read BASH* - show matching domains" {
run __check_completion 'defaults -host some.computer.name read BASH'
assert_line -n 0 "Bash\ It"
}
@test "completion defaults: -host some.computer.name read bash* - show matching domains (with spaces)" {
run __check_completion 'defaults -host some.computer.name read bash\ i'
assert_line -n 0 "Bash\ It"
}
@test "completion defaults: -host some.computer.name read BASH* - show matching domains (with spaces)" {
run __check_completion 'defaults -host some.computer.name read BASH\ I'
assert_line -n 0 "Bash\ It"
}