Skip to content

Instantly share code, notes, and snippets.

@OleksandrKucherenko
Created August 30, 2022 09:56
Embed
What would you like to do?
MACOSX helper functions for writing user friendly scripts. Contains: logger, execution time tracking, user input validation, inject of secrets, flags as script arguments detection;
#!/usr/bin/env bash
# shellcheck disable=SC2155,SC2034,SC2059
# get script directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# is allowed to use macOS extensions (script can be executed in *nix environment)
use_macos_extensions=false
if [[ "$OSTYPE" == "darwin"* ]]; then use_macos_extensions=true; fi
# colors
export cl_reset=$(tput sgr0)
export cl_red=$(tput setaf 1)
export cl_green=$(tput setaf 2)
export cl_yellow=$(tput setaf 3)
export cl_blue=$(tput setaf 4)
export cl_purple=$(tput setaf 5)
export cl_cyan=$(tput setaf 6)
export cl_white=$(tput setaf 7)
export cl_grey=$(tput setaf 8)
export cl_lred=$(tput setaf 9)
export cl_lgreen=$(tput setaf 10)
export cl_lyellow=$(tput setaf 11)
export cl_lblue=$(tput setaf 12)
export cl_lpurple=$(tput setaf 13)
export cl_lcyan=$(tput setaf 14)
export cl_lwhite=$(tput setaf 15)
export cl_black=$(tput setaf 16)
#
# Register debug logger functions that are controlled by DEBUG= environment variable
# Examples:
# DEBUG=* - print all logs
# DEBUG=*,-dependencies - print all logs except 'dependencies'
# DEBUG=common,token - print logs for 'common' and 'token'
#
function logger() {
#
# Usage:
# source "$SCRIPT_DIR/commons.sh" && logger tag "$@"
# echoTag "print only if DEBUG=tag is set"
# printfTag "print only if DEBUG=tag is set %s" "something"
#
local tag=${1}
local Suffix=${1^}
# keep it disabled by default
declare -g -A TAGS && TAGS+=([$tag]=0)
# declare logger functions
eval "$(
cat <<EOF
#
# begin
#
function echo${Suffix}() {
[[ "\${TAGS[$tag]}" == "1" ]] && builtin echo "\$@"
}
function printf${Suffix}() {
[[ "\${TAGS[$tag]}" == "1" ]] && builtin printf "\$@"
}
function configDebug${Suffix}() {
local args=("\$@")
IFS="," read -r -a tags <<<\$(echo "\$DEBUG")
[[ "\${args[*]}" =~ "--debug" ]] && TAGS+=([$tag]=1)
[[ "\${tags[*]}" =~ "$tag" ]] && TAGS+=([$tag]=1)
[[ "\${tags[*]}" =~ "*" ]] && TAGS+=([$tag]=1)
[[ "\${tags[*]}" =~ "-$tag" ]] && TAGS+=([$tag]=0)
# builtin echo "done! \${!TAGS[@]} \${TAGS[@]}"
}
#
# end
#
EOF
)"
# configure logger
eval "configDebug${Suffix}" "$@"
# dump created loggers
[[ "$tag" != "common" ]] && eval "echoCommon \"${cl_grey}Logger tags :\" \"\${!TAGS[@]}\" \"|\" \"\${TAGS[@]}\" \"${cl_reset}\"" 2>/dev/null
}
# register own logger
logger common "$@"
function now() {
echo "$EPOCHREALTIME" # <~ bash 5.0
#python -c 'import datetime; print datetime.datetime.now().strftime("%s.%f")'
}
# shellcheck disable=SC2155,SC2086
function print_time_diff() {
local diff="$(now) - $1"
bc <<<$diff
}
# shellcheck disable=SC2086
function validate_input() {
local variable=$1
local default=${2:-""}
local prompt=${3:-""}
local user_in=""
# Ctrl+C during read operation force error exit
trap 'exit 1' SIGINT
# execute at least once
while :; do
# allow macOs read command extension usage (default value -i)
if $use_macos_extensions; then
[[ -z "${prompt// /}" ]] || read -e -i "${default}" -p "${cl_purple}? ${cl_reset}${prompt}${cl_blue}" -r user_in
[[ -n "${prompt// /}" ]] || read -e -i "${default}" -r user_in
else
[[ -z "${prompt// /}" ]] || echo "${cl_purple}? ${cl_reset}${prompt}${cl_blue}"
read -r user_in
fi
printf "${cl_reset}"
[[ -z "${user_in// /}" ]] || break
done
local __resultvar=$variable
eval $__resultvar="'$user_in'"
}
# shellcheck disable=SC2086,SC2059
function validate_yn_input() {
local variable=$1
local default=${2:-""}
local prompt=${3:-""}
local user_in=false
while true; do
if $use_macos_extensions; then
[[ -z "${prompt// /}" ]] || read -e -i "${default}" -p "${cl_purple}? ${cl_reset}${prompt}${cl_blue}" -r yn
[[ -n "${prompt// /}" ]] || read -e -i "${default}" -r yn
else
[[ -z "${prompt// /}" ]] || echo "${cl_purple}? ${cl_reset}${prompt}${cl_blue}"
read -r yn
fi
printf "${cl_reset}"
case $yn in
[Yy]*)
user_in=true
break
;;
[Nn]*)
user_in=false
break
;;
*)
user_in=false
break
;;
esac
done
local __resultvar=$variable
eval $__resultvar="$user_in"
}
export ARGS_KNOWN_VARIABLES=()
export ARGS_KNOWN_SECRET_FILES=()
# shellcheck disable=SC2086
function env_variable_or_secret_file() {
#
# Usage:
# env_variable_or_secret_file "new_value" \
# "GITLAB_CI_INTEGRATION_TEST" \
# ".secrets/gitlab_ci_integration_test" \
# "{user friendly message}"
#
local name=$1
local variable=$2
local file=$3
local fallback=${4:-"No hints, check the documentation"}
local __result=$name
ARGS_KNOWN_VARIABLES+=("${variable}")
ARGS_KNOWN_SECRET_FILES+=("${file}")
if [[ -z "${!variable}" ]]; then
if [[ ! -f "$file" ]]; then
echo ""
echo "${cl_red}ERROR:${cl_reset} shell environment variable '\$$variable' or file '$file' should be provided"
echo ""
echo "Hint:"
echo " $fallback"
exit 1
else
echo "Using file: ${cl_green}$file${cl_reset} ~> $name"
eval $__result="'$(cat $file)'"
fi
else
echo "Using var : ${cl_green}\$$variable${cl_reset} ~> $name"
eval $__result="'${!variable}'"
fi
}
# shellcheck disable=SC2086
function optional_env_variable_or_secret_file() {
#
# Usage:
# optional_env_variable_or_secret_file "variable_name" \
# "GITLAB_CI_INTEGRATION_TEST" \
# ".secrets/gitlab_ci_integration_test"
#
# local resolved=$? # <~ return code of function call
# [[ $resolved == 1 ]] && injected="\$GITLAB_CI_INTEGRATION_TEST"
# [[ $resolved == 2 ]] && injected=".secrets/gitlab_ci_integration_test"
#
local name=$1
local variable=$2
local file=$3
local __result=$name
ARGS_KNOWN_VARIABLES+=("${variable}")
ARGS_KNOWN_SECRET_FILES+=("${file}")
if [[ -z "${!variable}" ]]; then
if [[ ! -f "$file" ]]; then
# NO variable, NO file
echo "${cl_yellow}Note:${cl_reset} shell environment variable '\$$variable' or file '$file' can be provided."
return 0
else
echo "Using file: ${cl_green}$file${cl_reset} ~> $name"
eval $__result="'$(cat $file)'"
return 2
fi
else
echo "Using var : ${cl_green}\$$variable${cl_reset} ~> $name"
eval $__result="'${!variable}'"
return 1
fi
}
# is script parameters/arguments has a request for HELP flag
function isHelp() {
local args=("$@")
if [[ "${args[*]}" =~ "--help" ]]; then echo true; else echo false; fi
}
# contains list of script arguments without flags '--*'
export ARGS_NO_FLAGS=()
function exclude_flags_from_args() {
local args=("$@")
# remove all flags from call
for i in "${!args[@]}"; do
if [[ ${args[i]} == --* ]]; then unset 'args[i]'; fi
done
echoCommon "${cl_grey}Filtered args:" "$@" "~>" "${args[*]}" "$cl_reset" >&2
# shellcheck disable=SC2116,SC2207
ARGS_NO_FLAGS=($(echo "${args[*]}"))
}
exclude_flags_from_args "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment