Skip to content

Instantly share code, notes, and snippets.

@adoublef
Created January 6, 2024 01:12
Show Gist options
  • Save adoublef/88ccd98563b85bcc1250b7b3a5fdb4b5 to your computer and use it in GitHub Desktop.
Save adoublef/88ccd98563b85bcc1250b7b3a5fdb4b5 to your computer and use it in GitHub Desktop.
Updated Bash CLI
#!/usr/bin/env bash
# Treat unset variables and parameters other than the special parameters ‘@’ or
# ‘*’ as an error when performing parameter expansion. An 'unbound variable'
# error message will be written to the standard error, and a non-interactive
# shell will exit.
set -o nounset
# Exit immediately if a pipeline returns non-zero.
set -o errexit
# Print a helpful message if a pipeline with non-zero exit code causes the
# script to exit as described above.
trap 'echo "Aborting due to errexit on line $LINENO. Exit code: $?" >&2' ERR
# Allow the above trap be inherited by all functions in the script.
set -o errtrace
# Return value of a pipeline is the value of the last (rightmost) command to
# exit with a non-zero status, or zero if all commands in the pipeline exit
# successfully.
set -o pipefail
IFS=$'\n\t'
# $_ME
#
# This program's basename.
_ME="$(basename "${0}")"
# $_VERSION
#
# Manually set this to to current version of the program. Adhere to the
_VERSION="0.1.0"
DEFAULT_SUBCOMMAND="${DEFAULT_SUBCOMMAND:-help}"
# _debug()
#
# Usage:
# _debug <command> <options>...
#
# Description:
# Execute a command and print to standard error. The command is expected to
# print a message and should typically be either `echo`, `printf`, or `cat`.
#
# Example:
# _debug printf "Debug info. Variable: %s\\n" "$0"
__USE_DEBUG=0
__DEBUG_COUNTER=0
_debug() {
if ((${__USE_DEBUG:-0}))
then
__DEBUG_COUNTER=$((__DEBUG_COUNTER+1))
{
printf "🐛 %s " "${__DEBUG_COUNTER}"
"${@}"
printf "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\\n"
} 1>&2
fi
}
_exit_1() {
{
printf "%s " "$(tput setaf 1)!$(tput sgr0)"
"${@}"
} 1>&2
exit 1
}
_warn() {
{
printf "%s " "$(tput setaf 1)!$(tput sgr0)"
"${@}"
} 1>&2
exit 1
}
# _has_function()
#
# Usage:
# _has_function <name>
#
# Exit / Error Status:
# 0 (success, true) If function with <name> is defined in the current environment
# 1 (error, false) If not.
_has_function() {
[ "$(type -t "${1}")" == 'function' ]
}
# _has_command()
#
# Usage:
# _has_command <name>
#
# Exit / Error Status:
# 0 (success, true) If a command with <name> is defined in the current environment
# 1 (error, false) If not.
_has_command() {
hash "${1}" 2>/dev/null
}
# _has_item()
#
# Usage:
# _has_item <query> <list-item>
#
# Exit / Error Status:
# 0 (success, true) If the item is included in the list.
# 1 (error, false) If not.
#
# Examples:
# _has_item "${_query}" "${_list[@]}"
_has_item() {
local _query="${1:-}"
shift
if [[ -z "${_query}" ]] || [[ -z "${*:-}" ]]; then
return 1
fi
for _element in "${@}"
do
[[ "${_element}" == "${_query}" ]] && return 0
done
return 1
}
# _join()
#
# Usage:
# _join <delimiter> <list-item>...
#
# Description:
# Print a string containing all <list-item> arguments separated by <delimeter>.
#
# Example:
# _join "${_delimeter}" "${list[@]}"
_join() {
local _delimiter="${1}"
shift
printf "%s" "${1}"
shift
printf "%s" "${@/#/${_delimiter}}" | tr -d '[:space:]'
}
# _blank()
#
# Usage:
# _blank <argument>
#
# Exit / Error Status:
# 0 (success, true) If <argument> is not present or null.
# 1 (error, false) If <argument> is present and not null.
_blank() {
[[ -z "${1:-}" ]]
}
# _present()
#
# Usage:
# _present <argument>
#
# Exit / Error Status:
# 0 (success, true) If <argument> is present and not null.
# 1 (error, false) If <argument> is not present or null.
_present() {
[[ -n "${1:-}" ]]
}
# _interactive_input()
#
# Usage:
# _interactive_input
#
# Exit / Error Status:
# 0 (success, true) If the current input is interactive (eg, a shell).
# 1 (error, false) If the current input is stdin / piped input.
_interactive_input() {
[[ -t 0 ]]
}
# _piped_input()
#
# Usage:
# _piped_input
#
# Exit / Error Status:
# 0 (success, true) If the current input is stdin / piped input.
# 1 (error, false) If the current input is interactive (eg, a shell).
_piped_input() {
! _interactive_input
}
# describe()
#
# Usage:
# describe <name> <description>
# describe --get <name>
#
# Options:
# --get Print the description for <name> if one has been set.
#
# Examples:
# ```
# describe "list" <<EOF
# Usage:
# ${_ME} list
#
# Description:
# List items.
# EOF
#
# describe --get "list"
# ```
describe() {
_debug printf "describe() \${*}: %s\\n" "$@"
[[ -z "${1:-}" ]] && _exit_1 printf "describe(): <name> required.\\n"
if [[ "${1}" == "--get" ]]
then # get
[[ -z "${2:-}" ]] && _exit_1 printf "describe(): <description> required.\\n"
local _name="${2:-}"
local _describe_var="___describe_${_name}"
if [[ -n "${!_describe_var:-}" ]]
then
printf "%s\\n" "${!_describe_var}"
else
printf "No additional information for \`%s\`\\n" "${_name}"
fi
else # set
if [[ -n "${2:-}" ]]
then
read -r -d '' "___describe_${1}" <<EOF
${2}
EOF
else
read -r -d '' "___describe_${1}" || true
fi
fi
}
# NOTE: The `getops` builtin command only parses short options and BSD `getopt`
# does not support long arguments (GNU `getopt` does), so use custom option
# normalization and parsing.
unset options
while ((${#}))
do
case "${1}" in
-[!-]?*) # if option is of type -ab
# loop over each character starting with the second
for ((i=1; i<${#1}; i++))
do
# extract 1 character from position 'i'
c="${1:i:1}"
# add current char to options
options+=("-${c}")
done
;;
--?*=*) # if option is of type --foo=bar, split on first '='
options+=("${1%%=*}" "${1#*=}")
;;
--) # end of options, stop breaking them up
options+=(--endopts)
shift
options+=("${@}")
break
;;
*) # otherwise, nothing special
options+=("${1}")
;;
esac
shift
done
set -- "${options[@]:-}"
unset options
__SUBCOMMAND=""
__SUBCOMMAND_ARGUMENTS=()
while ((${#}))
do
_opt="${1}"
shift
case "${_opt}" in
-h|--help)
__SUBCOMMAND="help"
;;
-v|--version)
__SUBCOMMAND="version"
;;
--debug)
__USE_DEBUG=1
;;
*)
if [[ -n "${__SUBCOMMAND}" ]]
then
__SUBCOMMAND_ARGUMENTS+=("${_opt}")
else
__SUBCOMMAND="${_opt}"
fi
;;
esac
done
__DEFINED_SUBCOMMANDS=()
# _main()
#
# Usage:
# _main
#
# Description:
# The primary function for starting the program.
_main() {
if [[ -z "${__SUBCOMMAND}" ]]
then
__SUBCOMMAND="${DEFAULT_SUBCOMMAND}"
fi
for _name in $(declare -F)
do
local _function_name
_function_name=$(printf "%s" "${_name}" | awk '{ print $3 }')
if ! { [[ -z "${_function_name:-}" ]] ||
[[ "${_function_name}" =~ _(.*) ]] ||
[[ "${_function_name}" == "bats_readlinkf" ]] ||
[[ "${_function_name}" == "describe" ]] ||
[[ "${_function_name}" == "shell_session_update" ]] }
then
__DEFINED_SUBCOMMANDS+=("${_function_name}")
fi
done
# If the subcommand is defined, run it, otherwise return an error.
if _has_item "${__SUBCOMMAND}" "${__DEFINED_SUBCOMMANDS[@]:-}"
then
${__SUBCOMMAND} "${__SUBCOMMAND_ARGUMENTS[@]:-}"
else
_exit_1 printf "Unknown subcommand: %s\\n" "${__SUBCOMMAND}"
fi
}
################################################################
# PROGRAME #
################################################################
describe "help" <<EOF
Usage:
${_ME} help [<subcommand>]
Description:
Display help information for ${_ME} or a specified subcommand.
EOF
help() {
if [[ "${1:-}" ]]
then
describe --get "${1}"
else
cat <<EOF
Tooling for building gRPC services.
Version: ${_VERSION}
Usage:
${_ME} <subcommand> [--subcommand-options] [<arguments>]
${_ME} -h | --help
${_ME} -v | --version
Options:
-h --help Display this help information.
-v --version Display version information.
Help:
${_ME} help [<subcommand>]
$(list --)
EOF
fi
}
describe "list" <<EOF
Usage:
${_ME} subcommands [--raw]
Options:
--raw Display the subcommand list without formatting.
Description:
Display the list of available subcommands.
EOF
list() {
if [[ "${1:-}" == "--raw" ]]
then
printf "%s\\n" "${__DEFINED_SUBCOMMANDS[@]}"
else
printf "List subcommands:\\n"
printf " %s\\n" "${__DEFINED_SUBCOMMANDS[@]}"
fi
}
describe "version" <<EOF
Usage:
${_ME} ( version | -v | --version )
Description:
Display the current program version.
To save you the trouble, the current version is ${_VERSION}
EOF
version() {
printf "%s\\n" "${_VERSION}"
}
_main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment