diff --git a/LICENSE b/LICENSE index c8748f7..48ccdbb 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2020 Wenhao Ji +Copyright (c) 2020-2022 Wenhao Ji Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/bin/kubectl_complete-tmux_exec b/bin/kubectl_complete-tmux_exec new file mode 100755 index 0000000..5f8a93a --- /dev/null +++ b/bin/kubectl_complete-tmux_exec @@ -0,0 +1,214 @@ +#!/usr/bin/env bash + +# Copyright (c) 2022 Wenhao Ji + +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +declare -ra KUBECTL_ARG_SHORT_OPTS=( + '-s' +) + +declare -ra KUBECTL_ARG_LONG_OPTS=( + '--container' + '--cluster' + '--password' + '--request-timeout' + '--server' + '--token' + '--user' + '--username' + '--kubeconfig' +) + +declare -ra KUBECTL_NOARG_LONG_OPTS=( + '--insecure-skip-tls-verify' +) + +declare -ra ALL_NOARG_SHORT_OPTS=( + '-A' + '-h' + '-V' + '-i' + '-t' + '-d' + '-C' +) + +declare -ra ALL_ARG_SHORT_OPTS=( + '-c' + '-l' + '-f' + '-n' + "${KUBECTL_ARG_SHORT_OPTS[@]}" +) + +declare -ra ALL_SHORT_OPTS=( + "${ALL_NOARG_SHORT_OPTS[@]}" + "${ALL_ARG_SHORT_OPTS[@]}" +) + +declare -ra ALL_NOARG_LONG_OPTS=( + '--help' + '--version' + '--dry-run' + '--stdin' + '--tty' + '--detach' + '--remain-on-exit' + '--all-namespaces' + '--enable-control-mode' + "${KUBECTL_NOARG_LONG_OPTS[@]}" +) + +declare -ra ALL_ARG_LONG_OPTS=( + '--container' + '--selector' + '--select-layout' + '--session-mode' + '--file' + '--namespace' + '--context' + "${KUBECTL_ARG_LONG_OPTS[@]}" +) + +declare -ra ALL_LONG_OPTS=( + "${ALL_NOARG_LONG_OPTS[@]}" + "${ALL_ARG_LONG_OPTS[@]}" +) + +declare -ra ALL_ARG_OPTS=( + "${ALL_ARG_SHORT_OPTS[@]}" + "${ALL_ARG_LONG_OPTS[@]}" +) + +declare -ra FILE_OPTS=( + '-f' + '--file' + '--kubeconfig' +) + +declare -ra ALL_OPTS=( + "${ALL_SHORT_OPTS[@]}" + "${ALL_LONG_OPTS[@]}" +) + +declare -ra TMUX_LAYOUTS=( + 'even-horizontal' + 'even-vertical' + 'main-horizontal' + 'main-vertical' + 'tiled' +) + +declare -ra SESSION_MODES=( + 'auto' + 'new-session' + 'current-session' +) + +# Cobra's Shell Comp Directive Constants +# also see: https://github.com/spf13/cobra/blob/main/completions.go +readonly NO_FILE_COMP_DIR=':4' +readonly FILTER_FILE_COMP_DIR=':8' + +function array_contains() { + local occur="$1" + local arr=("${@:2}") + for e in "${arr[@]}"; do + if [[ "${e}" == "${occur}" ]]; then + return 0 + fi + done + return 1 +} + +function __complete() { + if [[ "$#" -eq 0 ]]; then + exit 1 + fi + + local state='INITIAL' + while [[ "$#" -gt 0 ]]; do + local arg="$1" + shift + case "${state}" in + 'INITIAL') + case "${arg}" in + '') + ;; + '-') + state='INITIAL' + ;; + '--') + state='LONG_OPTS_COMP' + ;; + '--session-mode') + state='SESSION_MODE_COMP' + ;; + '--select-layout') + state='TMUX_LAYOUT_COMP' + ;; + *) + if array_contains "${arg}" "${FILE_OPTS[@]}"; then + state='FILE_COMP' + elif array_contains "${arg}" "${ALL_ARG_OPTS[@]}"; then + state='NO_COMP' + fi + ;; + esac + ;; + 'LONG_OPTS_COMP') + state='EXEC_CMD_COMP' + ;; + 'SESSION_MODE_COMP' | 'TMUX_LAYOUT_COMP' | 'FILE_COMP' | 'NO_COMP') + case "${arg}" in + '') + ;; + *) + state='INITIAL' + ;; + esac + ;; + *) + ;; + esac + done + + case "${state}" in + 'INITIAL') + printf -- '%s\n' "${ALL_OPTS[@]}" + ;; + 'LONG_OPTS_COMP') + printf -- '%s\n' "${ALL_LONG_OPTS[@]}" + ;; + 'SESSION_MODE_COMP') + printf -- '%s\n' "${SESSION_MODES[@]}" + ;; + 'TMUX_LAYOUT_COMP') + printf -- '%s\n' "${TMUX_LAYOUTS[@]}" + ;; + 'EXEC_CMD_COMP' | 'NO_COMP') + echo "${NO_FILE_COMP_DIR}" + ;; + 'FILE_COMP') + echo "${FILTER_FILE_COMP_DIR}" + ;; + esac +} + +__complete "$@" diff --git a/test/complete.bats b/test/complete.bats new file mode 100644 index 0000000..e50896e --- /dev/null +++ b/test/complete.bats @@ -0,0 +1,212 @@ +#!/usr/bin/env bats + +readonly ALL_OPTIONS="\ +-A +-h +-V +-i +-t +-d +-C +-c +-l +-f +-n +-s +--help +--version +--dry-run +--stdin +--tty +--detach +--remain-on-exit +--all-namespaces +--enable-control-mode +--insecure-skip-tls-verify +--container +--selector +--select-layout +--session-mode +--file +--namespace +--context +--container +--cluster +--password +--request-timeout +--server +--token +--user +--username +--kubeconfig" + +readonly ALL_LONG_OPTIONS="\ +--help +--version +--dry-run +--stdin +--tty +--detach +--remain-on-exit +--all-namespaces +--enable-control-mode +--insecure-skip-tls-verify +--container +--selector +--select-layout +--session-mode +--file +--namespace +--context +--container +--cluster +--password +--request-timeout +--server +--token +--user +--username +--kubeconfig" + +@test "kubectl tmux-exec [tab]" { + local output + output=$(bin/kubectl_complete-tmux_exec '') + [ $? -eq 0 ] + + local expected_output="${ALL_OPTIONS}" + [ "${expected_output}" = "${output}" ] +} + +@test "kubectl tmux-exec -[tab]" { + local output + output=$(bin/kubectl_complete-tmux_exec '') + [ $? -eq 0 ] + + local expected_output="${ALL_OPTIONS}" + [ "${expected_output}" = "${output}" ] +} + +@test "kubectl tmux-exec -A[tab]" { + local output + output=$(bin/kubectl_complete-tmux_exec '-A') + [ $? -eq 0 ] + + local expected_output="${ALL_OPTIONS}" + [ "${expected_output}" = "${output}" ] +} + +@test "kubectl tmux-exec --[tab]" { + local output + output=$(bin/kubectl_complete-tmux_exec '--') + [ $? -eq 0 ] + + local expected_output="${ALL_LONG_OPTIONS}" + [ "${expected_output}" = "${output}" ] +} + +@test "kubectl tmux-exec -- [tab]" { + local output + output=$(bin/kubectl_complete-tmux_exec '--' '') + [ $? -eq 0 ] + + local expected_output=":4" + [ "${expected_output}" = "${output}" ] +} + +@test "kubectl tmux-exec --context [tab]" { + local output + output=$(bin/kubectl_complete-tmux_exec '--context') + [ $? -eq 0 ] + + local expected_output=":4" + [ "${expected_output}" = "${output}" ] +} + +@test "kubectl tmux-exec -l [tab]" { + local output + output=$(bin/kubectl_complete-tmux_exec '-l' '') + [ $? -eq 0 ] + + local expected_output=":4" + [ "${expected_output}" = "${output}" ] +} + +@test "kubectl tmux-exec --file[tab]" { + local output + output=$(bin/kubectl_complete-tmux_exec '--file') + [ $? -eq 0 ] + + local expected_output=":8" + [ "${expected_output}" = "${output}" ] +} + +@test "kubectl tmux-exec --file [tab]" { + local output + output=$(bin/kubectl_complete-tmux_exec '--file' '') + [ $? -eq 0 ] + + local expected_output=":8" + [ "${expected_output}" = "${output}" ] +} + +@test "kubectl tmux-exec --file - --session-mode [tab]" { + local output + output=$(bin/kubectl_complete-tmux_exec '--file' '-' '--session-mode') + [ $? -eq 0 ] + + local expected_output="\ +auto +new-session +current-session" + [ "${expected_output}" = "${output}" ] +} + +@test "kubectl tmux-exec --select-layout [tab]" { + local output + output=$(bin/kubectl_complete-tmux_exec '--select-layout' '') + [ $? -eq 0 ] + + local expected_output="\ +even-horizontal +even-vertical +main-horizontal +main-vertical +tiled" + [ "${expected_output}" = "${output}" ] +} + +@test "kubectl tmux-exec --namespace ns1 --namespace [tab]" { + local output + output=$(bin/kubectl_complete-tmux_exec '--namespace' 'ns1' '--namespace' '') + [ $? -eq 0 ] + + local expected_output=":4" + [ "${expected_output}" = "${output}" ] +} + +@test "kubectl tmux-exec --namespace ns1 --namespace ns2 [tab]" { + local output + output=$(bin/kubectl_complete-tmux_exec '--namespace' 'ns1' '--namespace' 'ns2' '') + [ $? -eq 0 ] + + local expected_output="${ALL_OPTIONS}" + [ "${expected_output}" = "${output}" ] +} + +@test "kubectl tmux-exec --kubeconfig [tab]" { + local output + output=$(bin/kubectl_complete-tmux_exec '--kubeconfig' '') + [ $? -eq 0 ] + + local expected_output=":8" + [ "${expected_output}" = "${output}" ] +} + +@test "kubectl tmux-exec --kubeconfig ~/.kube/config -l foo=bar -- [tab]" { + local output + output=$(bin/kubectl_complete-tmux_exec '--kubeconfig' '~/.kube/config' '-l' 'foo=bar' '--' '') + [ $? -eq 0 ] + + local expected_output=":4" + [ "${expected_output}" = "${output}" ] +}