Created
January 6, 2024 01:12
-
-
Save adoublef/88ccd98563b85bcc1250b7b3a5fdb4b5 to your computer and use it in GitHub Desktop.
Updated Bash CLI
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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