* CI: disable Ubuntu 16.04 as it's EOL https://github.blog/changelog/2021-04-29-github-actions-ubuntu-16-04-lts-virtual-environment-will-be-removed-on-september-20-2021/ * main: lint false positive * install: lint * plugins/cmd-returned-notify: don't `export` * plugins/xterm: lint * plugins/git: lint * plugins/goenv: lint * plugins/alias-completion: lint false positives * plugins/alias-completion: fix SC2155, SC2154 Declare `locals` at the top of the function * completion: lint completions using `bash_completion` functions Match the style of the existing code * completion/knife: lint false positives * completion/knife: lint * completion/sdkman: lint * completion/composer: lint * Move `.shellcheckrc` under `themes/` * lib/theme: fix SC2155, SC2154, SC2034 * lib/colors: don't warn on unused variables We assign a large number of variables here and they may or may not be used anywhere else, so disable SC2034 for this file (only). Alsö disable SC2005 as the functions in this file were written before `printf` was invented and have to do some fancy metascripting to get escape sequences interpreted reliably. I’m not smart enough to fix this to use `printf`, so leave it for now. * themes/agnoster: lint * themes: disable SC2154 for colors Each one of these themes will need it’s own fix for SC2154, possibly upstream. Due to the way themes are, it's entirely normal to have a *lot* of false positives for SC2034. So much so, that I have to admit that it is probably just not worth linting for SC2034 despite my dislike of blanket ignore rules. * themes: disable SC2154, fix SC2155 Each one of these themes will need it’s own fix for SC2154, possibly upstream. Due to the way themes are, it's entirely normal to have a *lot* of false positives for SC2034. So much so, that I have to admit that it is probably just not worth linting for SC2034 despite my dislike of blanket ignore rules. * Delete `.shellcheckrc` * remove executable bit
206 lines
7.9 KiB
Bash
206 lines
7.9 KiB
Bash
# shellcheck shell=bash
|
|
|
|
# Published originally as public domain code at https://github.com/wk8/knife-bash-autocomplete
|
|
|
|
##############
|
|
### CONFIG ###
|
|
##############
|
|
### feel free to change those constants
|
|
# the dir where to store the cache (must be writable and readable by the current user)
|
|
# must be an absolute path
|
|
_KNIFE_AUTOCOMPLETE_CACHE_DIR="$HOME/.knife_autocomplete_cache"
|
|
# the maximum # of _seconds_ after which a cache will be considered stale
|
|
# (a cache is refreshed whenever it is used! this is only for caches that might not have been used for a long time)
|
|
# WARNING: keep that value > 100
|
|
_KNIFE_AUTOCOMPLETE_MAX_CACHE_AGE=86400
|
|
|
|
###############################################
|
|
### END OF CONFIG - DON'T CHANGE CODE BELOW ###
|
|
###############################################
|
|
|
|
### init
|
|
_KAC_CACHE_TMP_DIR="$_KNIFE_AUTOCOMPLETE_CACHE_DIR/tmp"
|
|
# make sure the cache dir exists
|
|
mkdir -p "$_KAC_CACHE_TMP_DIR"
|
|
|
|
##############################
|
|
### Cache helper functions ###
|
|
##############################
|
|
|
|
# GNU or BSD stat?
|
|
stat -c %Y /dev/null > /dev/null 2>&1 && _KAC_STAT_COMMAND="stat -c %Y" || _KAC_STAT_COMMAND="stat -f %m"
|
|
|
|
# returns 0 iff the file whose path is given as 1st argument
|
|
# exists and has last been modified in the last $2 seconds
|
|
# returns 1 otherwise
|
|
_KAC_is_file_newer_than() {
|
|
[ -f "$1" ] || return 1
|
|
[ $(($(date +%s) - $($_KAC_STAT_COMMAND "$1"))) -gt "$2" ] && return 1 || return 0
|
|
}
|
|
|
|
# helper function for _KAC_get_and_regen_cache, see doc below
|
|
_KAC_regen_cache() {
|
|
local CACHE_NAME=$1
|
|
local CACHE_PATH="$_KNIFE_AUTOCOMPLETE_CACHE_DIR/$CACHE_NAME"
|
|
# shellcheck disable=SC2155
|
|
local TMP_FILE=$(mktemp "$_KAC_CACHE_TMP_DIR/$CACHE_NAME.XXXX")
|
|
shift 1
|
|
# discard the temp file if it's empty AND the previous command didn't exit successfully, but still mark the cache as updated
|
|
if ! "$@" > "$TMP_FILE" 2> /dev/null; then
|
|
[[ $(wc -l "$TMP_FILE") == 0 ]] && rm -f "$TMP_FILE" && touch "$CACHE_PATH" && return 1
|
|
else
|
|
mv -f "$TMP_FILE" "$CACHE_PATH"
|
|
fi
|
|
}
|
|
|
|
# cached files can't have spaces in their names
|
|
_KAC_get_cache_name_from_command() {
|
|
echo "${@/ /_SPACE_}"
|
|
}
|
|
|
|
# the reverse operation from the function above
|
|
_KAC_get_command_from_cache_name() {
|
|
echo "${@/_SPACE_/ }"
|
|
}
|
|
|
|
# given a command as argument, it fetches the cache for that command if it can find it
|
|
# otherwise it waits for the cache to be generated
|
|
# in either case, it regenerates the cache, and sets the _KAC_CACHE_PATH env variable
|
|
# for obvious reason, do NOT call that in a sub-shell (in particular, no piping)
|
|
# shellcheck disable=SC2155
|
|
_KAC_get_and_regen_cache() {
|
|
# the cache name can't have space in it
|
|
local CACHE_NAME=$(_KAC_get_cache_name_from_command "$@")
|
|
local REGEN_CMD="_KAC_regen_cache $CACHE_NAME $*"
|
|
_KAC_CACHE_PATH="$_KNIFE_AUTOCOMPLETE_CACHE_DIR/$CACHE_NAME"
|
|
# no need to wait for the regen if the file already exists
|
|
if [[ -f "$_KAC_CACHE_PATH" ]]; then
|
|
($REGEN_CMD &)
|
|
else
|
|
$REGEN_CMD
|
|
fi
|
|
}
|
|
|
|
# performs two things: first, deletes all obsolete temp files
|
|
# then refreshes stale caches that haven't been called in a long time
|
|
_KAC_clean_cache() {
|
|
local FILE CMD
|
|
# delete all obsolete temp files, could be lingering there for any kind of crash in the caching process
|
|
for FILE in "$_KAC_CACHE_TMP_DIR"/*; do
|
|
_KAC_is_file_newer_than "$FILE" "$_KNIFE_AUTOCOMPLETE_MAX_CACHE_AGE" || rm -f "$FILE"
|
|
done
|
|
# refresh really stale caches
|
|
find "$_KNIFE_AUTOCOMPLETE_CACHE_DIR" -maxdepth 1 -type f -not -name '.*' \
|
|
| while read -r FILE; do
|
|
_KAC_is_file_newer_than "$FILE" "$_KNIFE_AUTOCOMPLETE_MAX_CACHE_AGE" && continue
|
|
# first let's get the original command
|
|
CMD=$(_KAC_get_command_from_cache_name "$(basename "$FILE")")
|
|
# then regen the cache
|
|
_KAC_get_and_regen_cache "$CMD" > /dev/null
|
|
done
|
|
}
|
|
|
|
# perform a cache cleaning when loading this file
|
|
# On big systems this could baloon up to a 30 second run or more, so not enabling by default.
|
|
[[ -n "${KNIFE_CACHE_CLEAN}" ]] && _KAC_clean_cache
|
|
|
|
#####################################
|
|
### End of cache helper functions ###
|
|
#####################################
|
|
|
|
# returns all the possible knife sub-commands
|
|
_KAC_knife_commands() {
|
|
knife --help | grep -E "^knife" | sed -E 's/ \(options\)//g'
|
|
}
|
|
|
|
# rebuilds the knife base command currently being completed, and assigns it to $_KAC_CURRENT_COMMAND
|
|
# additionnally, returns 1 iff the current base command is not complete, 0 otherwise
|
|
# also sets $_KAC_CURRENT_COMMAND_NB_WORDS if the base command is complete
|
|
_KAC_get_current_base_command() {
|
|
local PREVIOUS="knife"
|
|
local I=1
|
|
local CURRENT
|
|
while [[ "${I}" -le "${COMP_CWORD}" ]]; do
|
|
# command words are all lower-case
|
|
echo "${COMP_WORDS[$I]}" | grep -E "^[a-z]+$" > /dev/null || break
|
|
CURRENT="$PREVIOUS ${COMP_WORDS[$I]}"
|
|
grep -E "^$CURRENT" "$_KAC_CACHE_PATH" > /dev/null || break
|
|
PREVIOUS=$CURRENT
|
|
I=$((I + 1))
|
|
done
|
|
_KAC_CURRENT_COMMAND=$PREVIOUS
|
|
[[ "${I}" -le "${COMP_CWORD}" ]] && _KAC_CURRENT_COMMAND_NB_WORDS="${I}"
|
|
}
|
|
|
|
# searches the position of the currently completed argument in the current base command
|
|
# (i.e. handles "plural" arguments such as knife cookbook upload cookbook1 cookbook2 and so on...)
|
|
# assumes the current base command is complete
|
|
# shellcheck disable=SC2155
|
|
_KAC_get_current_arg_position() {
|
|
local CURRENT_ARG_POS=$((_KAC_CURRENT_COMMAND_NB_WORDS + 1))
|
|
local COMPLETE_COMMAND=$(grep -E "^$_KAC_CURRENT_COMMAND" "$_KAC_CACHE_PATH")
|
|
local CURRENT_ARG
|
|
while [ "$CURRENT_ARG_POS" -le "$COMP_CWORD" ]; do
|
|
CURRENT_ARG=$(echo "$COMPLETE_COMMAND" | cut -d ' ' -f "$CURRENT_ARG_POS")
|
|
# we break if the current arg is a "plural" arg
|
|
echo "$CURRENT_ARG" | grep -E "^\\[[^]]+(\\.\\.\\.\\]|$)" > /dev/null && break
|
|
CURRENT_ARG_POS=$((CURRENT_ARG_POS + 1))
|
|
done
|
|
echo "$CURRENT_ARG_POS"
|
|
}
|
|
|
|
# the actual auto-complete function
|
|
_knife() {
|
|
_KAC_get_and_regen_cache _KAC_knife_commands
|
|
local RAW_LIST ITEM REGEN_CMD ARG_POSITION
|
|
# shellcheck disable=SC2034
|
|
COMREPLY=()
|
|
# get correct command & arg pos
|
|
_KAC_get_current_base_command && ARG_POSITION=$(_KAC_get_current_arg_position) || ARG_POSITION=$((COMP_CWORD + 1))
|
|
RAW_LIST=$(grep -E "^${_KAC_CURRENT_COMMAND}" "${_KAC_CACHE_PATH}" | cut -d ' ' -f "${ARG_POSITION}" | uniq)
|
|
|
|
# we need to process that raw list a bit, most notably for placeholders
|
|
# NOTE: I chose to explicitely fetch & cache _certain_ informations for the server (cookbooks & node names, etc)
|
|
# as opposed to a generic approach by trying to find a 'list' knife command corresponding to the
|
|
# current base command - that might limit my script in some situation, but that way I'm sure it caches only
|
|
# not-sensitive stuff (a generic approach could be pretty bad e.g. with the knife-rackspace plugin)
|
|
LIST=""
|
|
for ITEM in $RAW_LIST; do
|
|
# always relevant if only lower-case chars : continuation of the base command
|
|
echo "$ITEM" | grep -E "^[a-z]+$" > /dev/null && LIST="$LIST $ITEM" && continue
|
|
case "$ITEM" in
|
|
*COOKBOOK*)
|
|
# special case for cookbooks : from site or local
|
|
[[ ${COMP_WORDS[2]} == 'site' ]] && REGEN_CMD="knife cookbook site list" || REGEN_CMD="knife cookbook list"
|
|
_KAC_get_and_regen_cache "$REGEN_CMD"
|
|
LIST="$LIST $(cut -d ' ' -f 1 < "$_KAC_CACHE_PATH")"
|
|
continue
|
|
;;
|
|
*ITEM*)
|
|
# data bag item : another special case
|
|
local DATA_BAG_NAME=${COMP_WORDS[$((COMP_CWORD - 1))]}
|
|
REGEN_CMD="knife data bag show $DATA_BAG_NAME"
|
|
;;
|
|
*INDEX*)
|
|
# see doc @ http://docs.opscode.com/knife_search.html
|
|
LIST="$LIST client environment node role"
|
|
REGEN_CMD="knife data bag list"
|
|
;;
|
|
*BAG*) REGEN_CMD="knife data bag list" ;;
|
|
*CLIENT*) REGEN_CMD="knife client list" ;;
|
|
*NODE*) REGEN_CMD="knife node list" ;;
|
|
*ENVIRONMENT*) REGEN_CMD="knife environment list" ;;
|
|
*ROLE*) REGEN_CMD="knife role list" ;;
|
|
*USER*) REGEN_CMD="knife user list" ;;
|
|
# not a generic argument we support...
|
|
*) continue ;;
|
|
esac
|
|
_KAC_get_and_regen_cache "$REGEN_CMD"
|
|
LIST="$LIST $(cat "$_KAC_CACHE_PATH")"
|
|
done
|
|
# shellcheck disable=SC2207,SC2086
|
|
COMPREPLY=($(compgen -W "${LIST}" -- ${COMP_WORDS[COMP_CWORD]}))
|
|
}
|
|
|
|
complete -F _knife knife
|