You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2345 lines
49 KiB
2345 lines
49 KiB
#!/bin/bash -e
|
|
|
|
set -u
|
|
set +o histexpand
|
|
shopt -qs failglob
|
|
set -o pipefail
|
|
shopt -s expand_aliases
|
|
|
|
export PS4='+(${BASH_SOURCE##*/}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
|
|
|
|
## ====================== GLOBALS ====================== ##
|
|
export g__EXISTS=true
|
|
|
|
g__PATH="${BASH_SOURCE[0]}"
|
|
g__CALLER_PATH="${BASH_SOURCE[1]:-shell}"
|
|
|
|
# The global logging level. Lower the number, higher the priority.
|
|
g__LOGGING_LEVEL=2
|
|
|
|
# True if we should log messages to stdout
|
|
g__LOGGING_TO_STDOUT=false
|
|
|
|
# True if we should log messages to stderr
|
|
g__LOGGING_TO_STDERR=true
|
|
|
|
# True if we should log messages to a file
|
|
g__LOGGING_TO_FILE=false
|
|
|
|
# If file logging is enabled, the absolute path to the file
|
|
g__LOGGING_FILE='g.log'
|
|
|
|
# List of tags being debugged
|
|
g__LOGGING_TARGETS=()
|
|
|
|
# Enable all logging targets by default
|
|
g__LOGGING_ALL_TARGETS=no
|
|
|
|
# Array of g::source slugs that have been imported
|
|
g__IMPORTED_FILES=()
|
|
|
|
# Paths to search for g::source-able scripts
|
|
g__SOURCE_DIRS=("$HOME/.g.bash/src" "/etc/g.bash/src")
|
|
|
|
# The filesystem path separator
|
|
g__PATH_SEPARATOR='/'
|
|
|
|
# The directory where config files are stored
|
|
g__CONFIG_DIR="$HOME/.config"
|
|
|
|
# The default config file name (see g::config::setDefault and g::config::getDefault)
|
|
g__CONFIG_DEFAULT_NAME='g.bash'
|
|
|
|
# 'yes' if the framework has been bootstrapped, 'no' otherwise
|
|
g__BOOTSTRAPPED=no
|
|
|
|
# Array of file paths that should be deleted on exit
|
|
g__FILES_TO_CLEANUP=()
|
|
|
|
# Names of registered app namespaces
|
|
g__APP_NAMES=()
|
|
|
|
# The currently active app namespace
|
|
g__APP_CURRENT_NAME="app"
|
|
|
|
# UUID of the last argument set parsed
|
|
g__APP_LAST_ARGPARSE=''
|
|
|
|
# Prefix of the last argument set alias generated
|
|
g__APP_LAST_ALIAS=''
|
|
|
|
# Path to the directory where lock files can be created
|
|
g__LOCKING_FILE_DIR="/tmp"
|
|
|
|
# Array of lock files held by this process
|
|
g__LOCKING_HOLDS=()
|
|
|
|
# Amount of time (in seconds) to sleep between failed lock attempts
|
|
g__LOCKING_INTERVAL=0.25
|
|
|
|
# If 'yes' eval calls will be logged to the console
|
|
g__DEBUG_EVAL=no
|
|
|
|
|
|
|
|
##
|
|
# Initialize the framework
|
|
#
|
|
function g::internals::bootstrap() {
|
|
if [ "$g__BOOTSTRAPPED" == "yes" ]; then
|
|
return $(g::util::true)
|
|
fi
|
|
|
|
g__PATH="$(g::path::resolve "$g__PATH")"
|
|
|
|
trap g::internals::cleanup EXIT
|
|
|
|
g__BOOTSTRAPPED=yes
|
|
}
|
|
|
|
##
|
|
# Clean up before exit.
|
|
#
|
|
function g::internals::cleanup() {
|
|
if [ "$g__BOOTSTRAPPED" == "no" ]; then
|
|
echo "Called cleanup before bootstrap!";
|
|
exit 1;
|
|
fi
|
|
|
|
for file in "${g__FILES_TO_CLEANUP[@]}"; do
|
|
rm -rf "$file"
|
|
done
|
|
|
|
g::lock::cleanupForExit
|
|
}
|
|
|
|
##
|
|
# Override the top-level caller reference.
|
|
#
|
|
function g::internals::setCaller() {
|
|
local caller="$1"
|
|
g__CALLER_PATH="$(g::util::realpath $caller)"
|
|
}
|
|
|
|
|
|
## ====================== UTILITIES ====================== ##
|
|
|
|
function g::eval() {
|
|
if [[ "$g__DEBUG_EVAL" == yes ]]; then
|
|
g::log::stderr "eval: $*"
|
|
fi
|
|
|
|
eval "$@"
|
|
}
|
|
|
|
function g::silence() {
|
|
"$@" > /dev/null 2>&1
|
|
}
|
|
|
|
#
|
|
# Check if an input value is numeric. (Supports decimals.)
|
|
#
|
|
# @param value - the value to check
|
|
# @return 0 if numeric, 1 otherwise
|
|
#
|
|
function g::util::isNumeric() {
|
|
local value="${1:-}"
|
|
|
|
local rex='^[+-]?[0-9]+([.][0-9]+)?$'
|
|
|
|
if [[ "$value" =~ $rex ]]; then
|
|
return 0 # true
|
|
else
|
|
return 1 # false
|
|
fi
|
|
}
|
|
|
|
#
|
|
# Returns true if the given array contains a value.
|
|
#
|
|
# e.g. `g::arr::includes 'value' "${array[@]}"`
|
|
#
|
|
# @param value - the value to search for
|
|
# @param array - the array, by value
|
|
# @return 0 if included, 1 otherwise
|
|
#
|
|
function g::arr::includes() {
|
|
local value="$1"
|
|
shift
|
|
local array=("$@")
|
|
|
|
for element in "${array[@]}"; do
|
|
if [[ $element == "$value" ]]; then
|
|
return 0
|
|
fi
|
|
done
|
|
|
|
return 1
|
|
}
|
|
|
|
function g::arr::assoc::hasKey() {
|
|
local key="$1"
|
|
shift
|
|
local array="$@"
|
|
|
|
for possibleKey in "${!array[@]}"; do
|
|
if [[ "$key" == "$possibleKey" ]]; then
|
|
return $(g::util::true)
|
|
fi
|
|
done
|
|
|
|
return $(g::util::false)
|
|
}
|
|
|
|
##
|
|
# Join an array of values by some delimiter.
|
|
#
|
|
# @param delimiter
|
|
# @param ...array
|
|
# @echo the joined string
|
|
#
|
|
function g::arr::join() {
|
|
local delimiter=${1-}
|
|
local array=${2-}
|
|
if shift 2; then
|
|
printf %s "$array" "${@/#/$delimiter}"
|
|
fi
|
|
}
|
|
|
|
#
|
|
# Canonical true return value.
|
|
# @return true
|
|
#
|
|
function g::util::true() {
|
|
return 0;
|
|
}
|
|
|
|
#
|
|
# Canonical false return value.
|
|
# @return false
|
|
#
|
|
function g::util::false() {
|
|
return 1;
|
|
}
|
|
|
|
#
|
|
# Cast the return code of the command to a boolean echo output.
|
|
#
|
|
# @param ...cmd - the command to execute
|
|
# @echo 1 if return is true, 0 otherwise
|
|
#
|
|
function g::util::returnToEcho() {
|
|
local cmd="$@"
|
|
|
|
if g::eval "$cmd"; then
|
|
echo 1
|
|
else
|
|
echo 0
|
|
fi
|
|
}
|
|
|
|
##
|
|
# Generate a UUID.
|
|
#
|
|
# @echo the UUID
|
|
#
|
|
function g::util::uuid() {
|
|
## https://gist.github.com/markusfisch/6110640
|
|
local N B C='89ab'
|
|
|
|
for (( N=0; N < 16; ++N ))
|
|
do
|
|
B=$(( $RANDOM%256 ))
|
|
|
|
case $N in
|
|
6)
|
|
printf '4%x' $(( B%16 ))
|
|
;;
|
|
8)
|
|
printf '%c%x' ${C:$RANDOM%${#C}:1} $(( B%16 ))
|
|
;;
|
|
3 | 5 | 7 | 9)
|
|
printf '%02x-' $B
|
|
;;
|
|
*)
|
|
printf '%02x' $B
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
##
|
|
# Generate a UUID separated by underscores.
|
|
#
|
|
# @echo the UUID
|
|
#
|
|
function g::util::uuid::underscore() {
|
|
## https://gist.github.com/markusfisch/6110640
|
|
local N B C='89ab'
|
|
|
|
for (( N=0; N < 16; ++N ))
|
|
do
|
|
B=$(( $RANDOM%256 ))
|
|
|
|
case $N in
|
|
6)
|
|
printf '4%x' $(( B%16 ))
|
|
;;
|
|
8)
|
|
printf '%c%x' ${C:$RANDOM%${#C}:1} $(( B%16 ))
|
|
;;
|
|
3 | 5 | 7 | 9)
|
|
printf '%02x_' $B
|
|
;;
|
|
*)
|
|
printf '%02x' $B
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
##
|
|
# Bash-escape a value
|
|
function g::util::escape() {
|
|
local value="$1"
|
|
local escaped="$(echo "$value" | ( read -rsd '' x; echo ${x@Q} ))"
|
|
echo "$escaped"
|
|
}
|
|
|
|
#
|
|
# Append the input string to a given file.
|
|
#
|
|
# @param filename
|
|
# @param string
|
|
#
|
|
function g::file::appendString() {
|
|
local filename="$1"
|
|
local string="$2"
|
|
|
|
echo "$string" >> "$filename"
|
|
}
|
|
|
|
function g::file::exists() {
|
|
local filename="$1"
|
|
|
|
if [[ -f "$filename" ]]; then
|
|
return $(g::util::true)
|
|
else
|
|
return $(g::util::false)
|
|
fi
|
|
}
|
|
|
|
function g::file::directoryExists() {
|
|
local filename="$1"
|
|
|
|
if [[ -d "$filename" ]]; then
|
|
return $(g::util::true)
|
|
else
|
|
return $(g::util::false)
|
|
fi
|
|
}
|
|
|
|
function g::file::touch() {
|
|
local filename="$1"
|
|
echo '' >> "$filename"
|
|
}
|
|
|
|
function g::file::truncate() {
|
|
local filename="$1"
|
|
if g::file::exists "$filename"; then
|
|
echo '' > "$filename"
|
|
fi
|
|
}
|
|
|
|
function g::path::concat() {
|
|
local concat="$1"
|
|
shift
|
|
local paths=("$@")
|
|
|
|
if g::str::endsWith "$concat" "$g__PATH_SEPARATOR"; then
|
|
concat="$(g::str::substring "$concat" 0 -1)"
|
|
fi
|
|
|
|
for pathName in "${paths[@]}"; do
|
|
pathName="$(g::str::trim "$pathName")"
|
|
|
|
if g::str::endsWith "$pathName" "$g__PATH_SEPARATOR"; then
|
|
pathName="$(g::str::substring "$pathName" 0 -1)"
|
|
fi
|
|
|
|
if ! g::str::startsWith "$pathName" "$g__PATH_SEPARATOR"; then
|
|
pathName="${g__PATH_SEPARATOR}${pathName}"
|
|
fi
|
|
|
|
concat="${concat}${pathName}"
|
|
done
|
|
|
|
echo "$concat"
|
|
}
|
|
|
|
function g::util::realpath() {
|
|
local target="$1"
|
|
|
|
# Resolve the base path
|
|
if g::str::startsWith "$target" '..'; then
|
|
local strlen="$(g::str::length "$target")"
|
|
target="$(g::str::substring "$target" 2 $strlen)"
|
|
target="$(dirname $(pwd))${target}"
|
|
elif g::str::startsWith "$target" '.'; then
|
|
local strlen="$(g::str::length "$target")"
|
|
target="$(g::str::substring "$target" 1 $strlen)"
|
|
target="$(pwd)${target}"
|
|
elif ! g::str::startsWith "$target" '/'; then
|
|
target="$(pwd)/${target}"
|
|
fi
|
|
|
|
# Resolve relative paths
|
|
local tempname="$(mktemp -d)"
|
|
local temptarget="${tempname}${target}"
|
|
mkdir -p "${temptarget}"
|
|
|
|
(
|
|
cd "${temptarget}"
|
|
|
|
local temppwd="$(pwd)"
|
|
local targetLength="$(g::str::length "$target")"
|
|
local tempLength="$(g::str::length "$tempname")"
|
|
echo "$(g::str::substring "$temppwd" $tempLength $targetLength)"
|
|
|
|
rm -rf "${temptarget}"
|
|
)
|
|
}
|
|
|
|
function g::path::resolve() {
|
|
local paths=("$@")
|
|
|
|
local concat="$(g::path::concat "${paths[@]}")"
|
|
if ! g::str::startsWith "$concat" "$g__PATH_SEPARATOR"; then
|
|
concat="$(g::util::realpath "$concat")"
|
|
fi
|
|
|
|
echo "$concat"
|
|
}
|
|
|
|
function g::path::resolveFrom() {
|
|
local baseDir="$1"
|
|
shift
|
|
local paths=("$@")
|
|
|
|
(
|
|
cd "$baseDir"
|
|
echo "$(g::path::resolve "${paths[@]}")"
|
|
)
|
|
}
|
|
|
|
function g::path::cd() {
|
|
local resolved="$(g::path::resolve "$@")"
|
|
cd "$resolved"
|
|
}
|
|
|
|
function g::path::tmp() {
|
|
local name="$(mktemp)"
|
|
g__FILES_TO_CLEANUP+=("$name")
|
|
echo "$name"
|
|
}
|
|
|
|
function g::path::tmpdir() {
|
|
local name="$(mktemp -d)"
|
|
g__FILES_TO_CLEANUP+=("$name")
|
|
echo "$name"
|
|
}
|
|
|
|
#
|
|
# Throw a program error. Exits.
|
|
# @todo move to g::err
|
|
#
|
|
function g::error::throw() {
|
|
local message="$1"
|
|
echo ""
|
|
echo ""
|
|
g::util::trace "ERROR: $message" 2 1>&2
|
|
echo ""
|
|
exit 1
|
|
}
|
|
|
|
#
|
|
# Get the current date in UTC format.
|
|
#
|
|
# @echo the date string
|
|
#
|
|
function g::now() {
|
|
date -u +"%Y-%m-%dT%H:%M:%SZ"
|
|
}
|
|
|
|
#
|
|
# Get a stack trace.
|
|
#
|
|
# @param message - optional message to include
|
|
# @echo the stack trace
|
|
#
|
|
function g::util::trace() {
|
|
local stack=""
|
|
local i message="${1:-""}"
|
|
local skip="${2:-1}"
|
|
local stack_size=${#FUNCNAME[@]}
|
|
|
|
# to avoid noise we start with 1 to skip the trace function
|
|
for (( i=$skip; i<$stack_size; i++ )); do
|
|
local func="${FUNCNAME[$i]}"
|
|
|
|
[ x$func = x ] && func=MAIN
|
|
|
|
local linen="${BASH_LINENO[$(( i - 1 ))]}"
|
|
local src="${BASH_SOURCE[$i]}"
|
|
|
|
[ x"$src" = x ] && src=non_file_source
|
|
|
|
stack+=$'\n'" at ($func) in $src (line $linen)"
|
|
done
|
|
|
|
stack="${message}${stack}"
|
|
echo "${stack}"
|
|
}
|
|
|
|
##
|
|
# Pipe inputs to BC with the math module enabled.
|
|
#
|
|
function g::bc() {
|
|
local inputs="$@"
|
|
echo "$@" | bc -l
|
|
}
|
|
|
|
##
|
|
# Pipe inputs to an AWK `print` command.
|
|
function g::awk() {
|
|
local inputs="$@"
|
|
echo '' | awk "{ print $inputs }"
|
|
}
|
|
|
|
|
|
|
|
|
|
## ====================== STRINGS ====================== ##
|
|
|
|
function g::str::quote() {
|
|
local string="$1"
|
|
echo "'$string'"
|
|
}
|
|
|
|
function g::str::quote::eval() {
|
|
local cmd="$@"
|
|
local str="$(g::eval "$cmd")"
|
|
echo "$(g::str::quote "$str")"
|
|
}
|
|
|
|
function g::str::length() {
|
|
local string="$1"
|
|
echo "$(expr length "$string")"
|
|
}
|
|
|
|
function g::str::padLeft() {
|
|
local string="$1"
|
|
local padToLength="$2"
|
|
local padCharacter="${3:- }"
|
|
|
|
local length="$(expr length "$string")"
|
|
|
|
while [[ "$length" -lt "$padToLength" ]]; do
|
|
string="${padCharacter}${string}"
|
|
length="$(expr length "$string")"
|
|
done
|
|
|
|
echo "$string"
|
|
}
|
|
|
|
function g::str::padRight() {
|
|
local string="$1"
|
|
local padToLength="$2"
|
|
local padCharacter="${3:- }"
|
|
|
|
local length="$(expr length "$string")"
|
|
|
|
while [[ "$length" -lt "$padToLength" ]]; do
|
|
string="${string}${padCharacter}"
|
|
length="$(expr length "$string")"
|
|
done
|
|
|
|
echo "$string"
|
|
}
|
|
|
|
function g::str::padCenter() {
|
|
local string="$1"
|
|
local padToLength="$2"
|
|
local padCharacter="${3:- }"
|
|
|
|
local length="$(expr length "$string")"
|
|
local leftSide=1
|
|
|
|
while [[ "$length" -lt "$padToLength" ]]; do
|
|
if [[ "$leftSide" -eq "1" ]]; then
|
|
string="${padCharacter}${string}"
|
|
leftSide=0
|
|
else
|
|
string="${string}${padCharacter}"
|
|
leftSide=1
|
|
fi
|
|
|
|
length="$(expr length "$string")"
|
|
done
|
|
|
|
echo "$string"
|
|
}
|
|
|
|
function g::str::substring() {
|
|
local string="$1"
|
|
local startAt="$2"
|
|
local length="$3"
|
|
|
|
echo "${string:$startAt:$length}"
|
|
}
|
|
|
|
function g::str::offset() {
|
|
local string="$1"
|
|
local startAt="$2"
|
|
|
|
local length="$(g::str::length "$string")"
|
|
g::str::substring "$string" "$startAt" $length
|
|
}
|
|
|
|
function g::str::reverse() {
|
|
local input="$1"
|
|
|
|
local output=""
|
|
local length="$(g::str::length "$input")"
|
|
|
|
for (( i=$length; i>=0; i-- )); do
|
|
output="${output}${input:$i:1}"
|
|
done
|
|
|
|
echo "${output}"
|
|
}
|
|
|
|
function g::str::startsWith() {
|
|
local string="$1"
|
|
local startsWith="$2"
|
|
|
|
local startsLength="$(g::str::length "$startsWith")"
|
|
local substring="$(g::str::substring "$string" 0 $startsLength)"
|
|
|
|
if [[ "$substring" == "$startsWith" ]]; then
|
|
return $(g::util::true)
|
|
else
|
|
return $(g::util::false)
|
|
fi
|
|
}
|
|
|
|
function g::str::endsWith() {
|
|
local string="$1"
|
|
local endsWith="$2"
|
|
|
|
local revString="$(g::str::reverse "$string")"
|
|
local revEndsWith="$(g::str::reverse "$endsWith")"
|
|
|
|
if g::str::startsWith "$revString" "$revEndsWith"; then
|
|
return $(g::util::true)
|
|
else
|
|
return $(g::util::false)
|
|
fi
|
|
}
|
|
|
|
function g::str::trim() {
|
|
local string="$1"
|
|
echo "$string" | xargs
|
|
}
|
|
|
|
function g::str::indexOf() {
|
|
local string="$1"
|
|
local substring="$2"
|
|
|
|
local length="$(g::str::length "$string")"
|
|
for (( i=0; i<$length; i++ )); do
|
|
local sliced="$(g::str::offset "$string" $i)"
|
|
if g::str::startsWith "$sliced" "$substring"; then
|
|
printf $i
|
|
return $(g::util::true)
|
|
fi
|
|
done
|
|
|
|
return $(g::util::false)
|
|
}
|
|
|
|
function g::str::replace() {
|
|
local string="$1"
|
|
local find="$2"
|
|
local replaceWith="${3:-}"
|
|
|
|
printf "${string//${find}/${replaceWith}}"
|
|
}
|
|
|
|
function g::str::replace::once() {
|
|
local string="$1"
|
|
local find="$2"
|
|
local replaceWith="${3:-}"
|
|
|
|
printf "${string/${find}/${replaceWith}}"
|
|
}
|
|
|
|
|
|
|
|
|
|
## ====================== LOGGING ====================== ##
|
|
|
|
# output functions and logging
|
|
# capture output
|
|
# silence output
|
|
# colors
|
|
# powerline
|
|
# tables
|
|
# width-fill
|
|
|
|
function g::log::enableTarget() {
|
|
local name="$1"
|
|
g__LOGGING_TARGETS+=("$name")
|
|
}
|
|
|
|
function g::log::enableAllTargets() {
|
|
g__LOGGING_ALL_TARGETS=yes
|
|
}
|
|
|
|
function g::log::all() {
|
|
g::log::setLevel internal
|
|
g::log::enableAllTargets
|
|
}
|
|
|
|
#
|
|
# Convert a string-form logging level to its numeric form.
|
|
#
|
|
# @param name
|
|
# @echo the numeric form
|
|
#
|
|
function g::log::stringNameToNumber() {
|
|
local name="$1"
|
|
|
|
if [ "$name" = "error" ]; then
|
|
echo 1
|
|
elif [ "$name" = "warn" ]; then
|
|
echo 2
|
|
elif [ "$name" = "info" ]; then
|
|
echo 3
|
|
elif [ "$name" = "debug" ]; then
|
|
echo 4
|
|
elif [ "$name" = "verbose" ]; then
|
|
echo 5
|
|
elif [ "$name" = "internal" ]; then
|
|
echo 6
|
|
else
|
|
echo 0
|
|
fi
|
|
}
|
|
|
|
#
|
|
# Convert the numeric logging level to its string name.
|
|
#
|
|
# @param level
|
|
# @echo the string name
|
|
#
|
|
function g::log::numberToStringName() {
|
|
local level="$1"
|
|
|
|
if [ "$level" = "1" ]; then
|
|
echo error
|
|
elif [ "$level" = "2" ]; then
|
|
echo warn
|
|
elif [ "$level" = "3" ]; then
|
|
echo info
|
|
elif [ "$level" = "4" ]; then
|
|
echo debug
|
|
elif [ "$level" = "5" ]; then
|
|
echo verbose
|
|
elif [ "$level" = "6" ]; then
|
|
echo internal
|
|
else
|
|
echo none
|
|
fi
|
|
}
|
|
|
|
#
|
|
# Normalize a value that may be either a string- or numeric-form
|
|
# logging level to a numeric form.
|
|
#
|
|
# @param level
|
|
# @echo the numeric form
|
|
#
|
|
function g::log::normalizeLevelToNumber() {
|
|
local level="$1"
|
|
|
|
if g::util::isNumeric "$level"; then
|
|
echo $level
|
|
else
|
|
echo $(g::log::stringNameToNumber $level)
|
|
fi
|
|
}
|
|
|
|
#
|
|
# Get the current logging level, as a string.
|
|
#
|
|
# @echo the string name
|
|
#
|
|
function g::log::getLevel() {
|
|
echo $(g::log::numberToStringName $g__LOGGING_LEVEL)
|
|
}
|
|
|
|
#
|
|
# Set the logging level by string name.
|
|
#
|
|
# @param name
|
|
#
|
|
function g::log::setLevel() {
|
|
local name="$1"
|
|
|
|
local level=$(g::log::stringNameToNumber "$name")
|
|
|
|
g__LOGGING_LEVEL=$level
|
|
}
|
|
|
|
#
|
|
# Enable a particular logging format.
|
|
#
|
|
# @param target - stdout | stderr | file
|
|
# @param param - if target is "file", the path of the file to log to
|
|
# @throws if the target is invalid
|
|
#
|
|
function g::log::enable() {
|
|
local target="$1"
|
|
local param="$2"
|
|
|
|
if [[ $target == 'stdout' ]]; then
|
|
g__LOGGING_TO_STDOUT=true
|
|
elif [[ $target == 'stderr' ]]; then
|
|
g__LOGGING_TO_STDERR=true
|
|
elif [[ $target == 'file' ]]; then
|
|
g__LOGGING_TO_FILE=true
|
|
g__LOGGING_FILE="$(realpath "${param:-$g__LOGGING_FILE}")"
|
|
|
|
g::file::appendString "$g__LOGGING_FILE" "$(g::log::format 3 "====== Logging started. ======")"
|
|
else
|
|
g::error::throw "Invalid logging target: ${target}"
|
|
fi
|
|
}
|
|
|
|
#
|
|
# Disable a particular logging format.
|
|
#
|
|
# @param target - stdout | stderr | file
|
|
# @throws if the target is invalid
|
|
#
|
|
function g::log::disable() {
|
|
local target="$1"
|
|
|
|
if [[ $target == 'stdout' ]]; then
|
|
g__LOGGING_TO_STDOUT=false
|
|
elif [[ $target == 'stderr' ]]; then
|
|
g__LOGGING_TO_STDERR=false
|
|
elif [[ $target == 'file' ]]; then
|
|
g__LOGGING_TO_FILE=false
|
|
else
|
|
g::error::throw "Invalid logging target: ${target}"
|
|
fi
|
|
}
|
|
|
|
#
|
|
# Write to standard output.
|
|
#
|
|
# @param output
|
|
#
|
|
function g::log::stdout() {
|
|
local output="$1"
|
|
echo "$output"
|
|
}
|
|
|
|
#
|
|
# Write to standard error.
|
|
#
|
|
# @param output
|
|
#
|
|
function g::log::stderr() {
|
|
local output="$1"
|
|
echo "$output" 1>&2
|
|
}
|
|
|
|
#
|
|
# Write to the log, on all configured formats.
|
|
#
|
|
# @param output
|
|
#
|
|
function g::log::write() {
|
|
local output="$1"
|
|
|
|
if $g__LOGGING_TO_STDOUT; then
|
|
g::log::stdout "$output"
|
|
fi
|
|
|
|
if $g__LOGGING_TO_STDERR; then
|
|
g::log::stderr "$output"
|
|
fi
|
|
|
|
if $g__LOGGING_TO_FILE; then
|
|
g::file::appendString "$g__LOGGING_FILE" "$output"
|
|
fi
|
|
}
|
|
|
|
#
|
|
# Format an output string with logging information like log level and date.
|
|
#
|
|
# @param level - the numeric logging level
|
|
# @param output
|
|
#
|
|
function g::log::format() {
|
|
local level="$1"
|
|
local output="$2"
|
|
|
|
local levelDisplay="[$(g::log::numberToStringName $level)]"
|
|
local paddedLevelDisplay="$(g::str::padLeft "$levelDisplay" 10)"
|
|
echo "$paddedLevelDisplay [$(g::now)] $output"
|
|
}
|
|
|
|
#
|
|
# Log a message.
|
|
#
|
|
# @param inputLevel
|
|
# @param output
|
|
#
|
|
function g::log() {
|
|
local inputLevel="$1"
|
|
local output="$2"
|
|
local target="${3:-}"
|
|
|
|
local targetLength="$(g::str::length "$target")"
|
|
if [[ $targetLength -gt 0 ]] && [[ $g__LOGGING_ALL_TARGETS == no ]]; then
|
|
if ! g::arr::includes "$target" "${g__LOGGING_TARGETS[@]}"; then
|
|
return $(g::util::true)
|
|
fi
|
|
fi
|
|
|
|
local level="$(g::log::normalizeLevelToNumber $inputLevel)"
|
|
|
|
if [ $level -le $g__LOGGING_LEVEL ]; then
|
|
g::log::write "$(g::log::format $level "$output")"
|
|
fi
|
|
}
|
|
|
|
#
|
|
# Log an error message.
|
|
#
|
|
# @param output
|
|
#
|
|
function g::error() {
|
|
local output="$1"
|
|
local target="${2:-}"
|
|
|
|
g::log error "$output" "$target"
|
|
}
|
|
|
|
#
|
|
# Log a warning message.
|
|
#
|
|
# @param output
|
|
#
|
|
function g::warn() {
|
|
local output="$1"
|
|
local target="${2:-}"
|
|
|
|
g::log warn "$output" "$target"
|
|
}
|
|
|
|
#
|
|
# Log an informational message.
|
|
#
|
|
# @param output
|
|
#
|
|
function g::info() {
|
|
local output="$1"
|
|
local target="${2:-}"
|
|
|
|
g::log info "$output" "$target"
|
|
}
|
|
|
|
#
|
|
# Log a debugging message.
|
|
#
|
|
# @param output
|
|
#
|
|
function g::debug() {
|
|
local output="$1"
|
|
local target="${2:-}"
|
|
|
|
g::log debug "$output" "$target"
|
|
}
|
|
|
|
#
|
|
# Log a verbose message.
|
|
#
|
|
# @param output
|
|
#
|
|
function g::verbose() {
|
|
local output="$1"
|
|
local target="${2:-}"
|
|
|
|
g::log verbose "$output" "$target"
|
|
}
|
|
|
|
#
|
|
# Log a verbose message.
|
|
#
|
|
# @param output
|
|
#
|
|
function g::internal() {
|
|
local output="$1"
|
|
local target="${2:-}"
|
|
|
|
g::log internal "$output" "$target"
|
|
}
|
|
|
|
function g::log::rotate() {
|
|
local logfile="$g__LOGGING_FILE"
|
|
if g::file::exists "$logfile"; then
|
|
cp "$logfile" "${logfile}-$(g::now)"
|
|
g::file::truncate "$logfile"
|
|
fi
|
|
}
|
|
|
|
|
|
|
|
|
|
## ====================== ERROR HANDLING ====================== ##
|
|
|
|
declare -ig g__INSIDE_TRY_CATCH=0
|
|
declare -g g__PRESET_SHELL_OPTS="$-"
|
|
|
|
alias try='[[ $g__INSIDE_TRY_CATCH -eq 0 ]] || g__PRESET_SHELL_OPTS="$(echo $- | sed 's/[is]//g')"; g__INSIDE_TRY_CATCH+=1; set +e; ( set -e; true; '
|
|
alias catch='); declare g__TRY_RESULT=$?; g__INSIDE_TRY_CATCH+=-1; [[ $g__INSIDE_TRY_CATCH -lt 1 ]] || set -${g__PRESET_SHELL_OPTS:-e} && g::err::parse $g__TRY_RESULT || '
|
|
|
|
function g::err::parse() {
|
|
local tryCode="$1"
|
|
unset g__TRY_RESULT;
|
|
|
|
if [[ $tryCode -gt 0 ]]; then
|
|
local IFS=$'\n'
|
|
return $tryCode
|
|
else
|
|
return 0
|
|
fi
|
|
}
|
|
|
|
|
|
|
|
## ====================== MATH CONSTANTS ====================== ##
|
|
function g::math::c::e() {
|
|
echo 0.577215665
|
|
}
|
|
|
|
function g::math::c::ln2() {
|
|
echo 0.69314718056
|
|
}
|
|
|
|
function g::math::c::ln10() {
|
|
echo 2.30258509299
|
|
}
|
|
|
|
function g::math::c::pi() {
|
|
echo 3.14159265359
|
|
}
|
|
|
|
function g::math::c::sqrt05() {
|
|
echo 0.70710678119
|
|
}
|
|
|
|
function g::math::c::sqrt2() {
|
|
echo 1.41421356237
|
|
}
|
|
|
|
|
|
|
|
|
|
## ====================== MATH HELPERS ====================== ##
|
|
|
|
function g::math::abs() {
|
|
local input="$1"
|
|
echo "${input#-}"
|
|
}
|
|
|
|
function g::math::cubeRoot() {
|
|
local input="$1"
|
|
echo "$input" | awk '{ print $1^(1/3) }'
|
|
}
|
|
|
|
function g::math::squareRoot() {
|
|
local input="$1"
|
|
echo "$input" | awk '{ print $1^(1/2) }'
|
|
}
|
|
|
|
function g::math::lessThan() {
|
|
local left="$1"
|
|
local right="$2"
|
|
|
|
if (( $(echo "$left < $right" | bc -l) )); then
|
|
return $(g::util::true)
|
|
else
|
|
return $(g::util::false)
|
|
fi
|
|
}
|
|
|
|
function g::math::greaterThan() {
|
|
local left="$1"
|
|
local right="$2"
|
|
|
|
if (( $(echo "$left > $right" | bc -l) )); then
|
|
return $(g::util::true)
|
|
else
|
|
return $(g::util::false)
|
|
fi
|
|
}
|
|
|
|
function g::math::equalTo() {
|
|
local left="$1"
|
|
local right="$2"
|
|
|
|
if (( $(echo "$left == $right" | bc -l) )); then
|
|
return $(g::util::true)
|
|
else
|
|
return $(g::util::false)
|
|
fi
|
|
}
|
|
|
|
function g::math::isPositive() {
|
|
local input="$1"
|
|
|
|
if g::math::greaterThan "$input" 0; then
|
|
return $(g::util::true)
|
|
else
|
|
return $(g::util::false)
|
|
fi
|
|
}
|
|
|
|
function g::math::isNegative() {
|
|
local input="$1"
|
|
|
|
if g::math::lessThan "$input" 0; then
|
|
return $(g::util::true)
|
|
else
|
|
return $(g::util::false)
|
|
fi
|
|
}
|
|
|
|
function g::math::signOf() {
|
|
local input="$1"
|
|
if g::math::isNegative "$input"; then
|
|
echo -
|
|
else
|
|
echo +
|
|
fi
|
|
}
|
|
|
|
function g::math::mod() {
|
|
local input="$1"
|
|
local modulus="$2"
|
|
echo "oldscale=scale; scale=0; $input%$modulus; scale=oldscale; l(2)" | bc -l | head -n 1
|
|
}
|
|
|
|
function g::math::ceiling() {
|
|
local input="$1"
|
|
|
|
local decimal="$(g::math::mod "$input" 1)"
|
|
if g::math::equalTo "$decimal" 0; then
|
|
echo "$input"
|
|
return
|
|
fi
|
|
|
|
local plusOne="$(g::bc $input + 1)"
|
|
echo "$(g::math::truncate "$plusOne")"
|
|
}
|
|
|
|
function g::math::truncate() {
|
|
local input="$1"
|
|
echo "${input%.*}"
|
|
}
|
|
|
|
function g::math::floor() {
|
|
local input="$1"
|
|
echo "$(g::math::truncate "$input")"
|
|
}
|
|
|
|
function g::math::exponent() {
|
|
local base="$1"
|
|
local exponent="$2"
|
|
echo "$(g::awk $base^$exponent)"
|
|
}
|
|
|
|
function g::math::cos() {
|
|
local input="$1"
|
|
echo "$(g::awk cos\($input\))"
|
|
}
|
|
|
|
function g::math::sin() {
|
|
local input="$1"
|
|
echo "$(g::awk sin\($input\))"
|
|
}
|
|
|
|
function g::math::tan() {
|
|
local input="$1"
|
|
echo "$(g::awk tan\($input\))"
|
|
}
|
|
|
|
function g::math::ln() {
|
|
local input="$1"
|
|
echo "$(g::bc "l($input)")"
|
|
}
|
|
|
|
function g::math::log10() {
|
|
local input="$1"
|
|
echo "$(g::bc "l($input)/l(10)")"
|
|
}
|
|
|
|
function g::math::log2() {
|
|
local input="$1"
|
|
echo "$(g::bc "l($input)/l(2)")"
|
|
}
|
|
|
|
function g::math::log() {
|
|
local input="$1"
|
|
local base="${2:-10}"
|
|
echo "$(g::bc "l($input)/l($base)")"
|
|
}
|
|
|
|
function g::math::random() {
|
|
echo "$(g::bc "scale=16 ; ${RANDOM}/32767")"
|
|
}
|
|
|
|
function g::math::max() {
|
|
local array=("$@")
|
|
|
|
local max="${array[0]}"
|
|
for num in "${array[@]}"; do
|
|
if g::math::greaterThan "$num" "$max"; then
|
|
max="$num"
|
|
fi
|
|
done
|
|
|
|
echo "$max"
|
|
}
|
|
|
|
function g::math::min() {
|
|
local array=("$@")
|
|
|
|
local min="${array[0]}"
|
|
for num in "${array[@]}"; do
|
|
if g::math::lessThan "$num" "$min"; then
|
|
min="$num"
|
|
fi
|
|
done
|
|
|
|
echo "$min"
|
|
}
|
|
|
|
##
|
|
# Round a number to the specified precision.
|
|
#
|
|
# @param input - the number to round
|
|
# @param [toScale=0] - the number of decimals to round
|
|
# @echo the result
|
|
#
|
|
function g::math::round() {
|
|
local input="$1"
|
|
local toScale="${2:-0}"
|
|
bc <<MATH
|
|
h=0
|
|
scale=0
|
|
|
|
/* the magnitude of the result scale */
|
|
t=(10 ^ $toScale)
|
|
|
|
/* work with an extra digit */
|
|
scale=$toScale + 1
|
|
|
|
/* your math into var: m */
|
|
m=($input)
|
|
|
|
/* rounding and output */
|
|
if (m < 0) h=-0.5
|
|
if (m > 0) h=0.5
|
|
|
|
a=(m * t + h)
|
|
|
|
scale=$toScale
|
|
a / t
|
|
MATH
|
|
}
|
|
|
|
|
|
|
|
|
|
function g::source::resolve() {
|
|
local path="$1"
|
|
local up="${2:-1}"
|
|
|
|
(
|
|
local reldir="$(dirname ${BASH_SOURCE[$up]})"
|
|
g::internal "Changing to dir to resolve source: $reldir"
|
|
g::path::cd "$reldir"
|
|
|
|
local fsPath="$(realpath "${path}.bash")"
|
|
if g::file::exists "$fsPath"; then
|
|
echo "$fsPath"
|
|
return $(g::util::true)
|
|
elif g::file::directoryExists "$fsPath"; then
|
|
echo "$fsPath"
|
|
return $(g::util::true)
|
|
else
|
|
for dir in "${g__SOURCE_DIRS[@]}"; do
|
|
local resolved="$(g::path::resolve "$dir" "${path}.bash")"
|
|
if g::file::exists "$resolved"; then
|
|
echo "$resolved"
|
|
return $(g::util::true)
|
|
elif g::file::directoryExists "$resolved"; then
|
|
echo "$resolved"
|
|
return $(g::util::true)
|
|
fi
|
|
done
|
|
fi
|
|
|
|
return $(g::util::false)
|
|
)
|
|
}
|
|
|
|
function g::source::exists() {
|
|
local path="$1"
|
|
local up="${2:-1}"
|
|
|
|
local resolved="$(g::source::resolve "$path" $(( up + 1 )))"
|
|
local length="$(g::str::length "$resolved")"
|
|
|
|
if [[ "$length" -gt 0 ]]; then
|
|
return $(g::util::true)
|
|
fi
|
|
|
|
return $(g::util::false)
|
|
}
|
|
|
|
function g::source::force() {
|
|
local slug="$1"
|
|
local up="${2:-1}"
|
|
|
|
local resolved="$(g::source::resolve "$slug" $(( up + 1 )))"
|
|
if ! g::source::exists "$slug" $(( up + 1 )); then
|
|
g::error::throw "Cannot resolve source to include: $slug"
|
|
fi
|
|
|
|
if g::file::exists "$resolved"; then
|
|
source "$resolved"
|
|
return $(g::util::true)
|
|
fi
|
|
|
|
if g::file::directoryExists "$resolved"; then
|
|
for file in "$(g::path::resolve "$resolved" './*.bash')"; do
|
|
source "$file"
|
|
done
|
|
|
|
return $(g::util::true)
|
|
fi
|
|
|
|
return $(g::util::false)
|
|
}
|
|
|
|
function g::source::has() {
|
|
local slug="$1"
|
|
local up="${2:-1}"
|
|
|
|
local resolved="$(g::source::resolve "$slug" $(( up + 1 )))"
|
|
if g::arr::includes "$resolved" "${g__IMPORTED_FILES[@]}"; then
|
|
return $(g::util::true)
|
|
fi
|
|
|
|
return $(g::util::false)
|
|
}
|
|
|
|
function g::source() {
|
|
local slug="$1"
|
|
local up="${2:-1}"
|
|
|
|
if ! g::source::has "$slug" $(( up + 1 )); then
|
|
g::source::force "$slug" $(( up + 1 ))
|
|
g__IMPORTED_FILES+=("$resolved")
|
|
fi
|
|
}
|
|
|
|
|
|
|
|
|
|
##
|
|
# Set the default config file name.
|
|
function g::config::setDefault() {
|
|
local name="$1"
|
|
g__CONFIG_DEFAULT_NAME="${name}"
|
|
}
|
|
|
|
##
|
|
# Get the default config file name.
|
|
function g::config::getDefault() {
|
|
echo "$g__CONFIG_DEFAULT_NAME"
|
|
}
|
|
|
|
##
|
|
# Initialize a config file with the given name.
|
|
function g::config::init() {
|
|
local filename="${1:-$g__CONFIG_DEFAULT_NAME}"
|
|
|
|
if ! g::file::directoryExists "$g__CONFIG_DIR"; then
|
|
mkdir -p "$g__CONFIG_DIR"
|
|
fi
|
|
|
|
if ! g::file::exists "$(g::path::resolve "$g__CONFIG_DIR" "$filename")"; then
|
|
g::file::touch "$(g::path::resolve "$g__CONFIG_DIR" "$filename")"
|
|
fi
|
|
}
|
|
|
|
##
|
|
# Get a value in the default config file.
|
|
function g::config::get() {
|
|
local name="$1"
|
|
local defaultValue="${2:-}"
|
|
|
|
g::config::init
|
|
|
|
(
|
|
local varname="g__config__${name}"
|
|
source "$(g::path::resolve "$g__CONFIG_DIR" "$g__CONFIG_DEFAULT_NAME")"
|
|
echo "${!varname:-$defaultValue}"
|
|
)
|
|
}
|
|
|
|
##
|
|
# Get a value in a config, by file.
|
|
function g::config::get::forFile() {
|
|
local file="$1"
|
|
shift 1
|
|
local args="$@"
|
|
|
|
local oldDefault="$(g::config::getDefault)"
|
|
g::config::setDefault "$file"
|
|
|
|
local value="$(g::config::get "$@")"
|
|
|
|
g::config::setDefault "$oldDefault"
|
|
echo "$value"
|
|
}
|
|
|
|
##
|
|
# Set a value in the default config file.
|
|
function g::config::set() {
|
|
local name="$1"
|
|
local value="$2"
|
|
|
|
g::config::init
|
|
|
|
local filepath="$(g::path::resolve "$g__CONFIG_DIR" "$g__CONFIG_DEFAULT_NAME")"
|
|
local contents="$(cat "$filepath")"
|
|
local newlines=()
|
|
|
|
(
|
|
cat "$filepath" | readarray -t lines
|
|
for line in "${lines[@]}"; do
|
|
if ! g::str::startsWith "$line" "g__config__$name"; then
|
|
newlines+=("$line")
|
|
fi
|
|
done
|
|
)
|
|
|
|
local escaped="$(g::util::escape "$value")"
|
|
newlines+=("g__config__$name=$escaped")
|
|
|
|
local newcontents="$(g::arr::join '\n' "${newlines[@]}")"
|
|
echo "$newcontents" > "$filepath"
|
|
}
|
|
|
|
##
|
|
# Set a value in a config, by file.
|
|
function g::config::set::forFile() {
|
|
local file="$1"
|
|
shift 1
|
|
local args="$@"
|
|
|
|
local oldDefault="$(g::config::getDefault)"
|
|
g::config::setDefault "$file"
|
|
|
|
g::config::set "$@"
|
|
|
|
g::config::setDefault "$oldDefault"
|
|
}
|
|
|
|
|
|
|
|
|
|
function g::app() {
|
|
local name="${1:-app}"
|
|
local description="${2:-}"
|
|
|
|
g__APP_CURRENT_NAME="$name"
|
|
if ! g::arr::includes "$name" "${g__APP_NAMES[@]}"; then
|
|
g__APP_NAMES+=("$name")
|
|
g::app::set DESCRIPTION "$description"
|
|
fi
|
|
}
|
|
|
|
function g::app::var() {
|
|
local varname="$1"
|
|
printf "g__appnamespace__${g__APP_CURRENT_NAME}_${varname}"
|
|
}
|
|
|
|
function g::app::get() {
|
|
local varname="$1"
|
|
local defaultValue="${2:-}"
|
|
|
|
local resolved="$(g::app::var "$varname")"
|
|
g::internal "[g::app::get] $resolved" g::app::vars
|
|
echo "${!resolved:-$defaultValue}"
|
|
}
|
|
|
|
function g::app::set() {
|
|
local varname="$1"
|
|
local value="$2"
|
|
|
|
local resolved="$(g::app::var "$varname")"
|
|
local escaped="$(g::util::escape "$value")"
|
|
g::internal "[g::app::set] $resolved" g::app::vars
|
|
g::eval "export ${resolved}=${escaped}"
|
|
}
|
|
|
|
function g::app::has() {
|
|
local varname="$1"
|
|
|
|
local resolved="$(g::app::var "$varname")"
|
|
if [[ -v "$resolved" ]]; then
|
|
g::internal "[g::app::has] $resolved - YES" g::app::vars
|
|
return $(g::util::true)
|
|
fi
|
|
|
|
g::internal "[g::app::has] $resolved - NO" g::app::vars
|
|
return $(g::util::false)
|
|
}
|
|
|
|
function g::app::command::var() {
|
|
local currentCommand="$(g::app::get DEFINE_CURRENT_COMMAND)"
|
|
|
|
local name="$1"
|
|
local command="${2:-$currentCommand}"
|
|
|
|
printf "COMMAND__${command}__${name}"
|
|
}
|
|
|
|
function g::app::command::exists() {
|
|
local name="$1"
|
|
|
|
local cmdsVar="$(g::app::var AVAILABLE_COMMANDS)"
|
|
|
|
if g::arr::includes "$name" $(g::eval "echo \"\${$cmdsVar[@]}\""); then
|
|
return $(g::util::true)
|
|
fi
|
|
|
|
return $(g::util::false)
|
|
}
|
|
|
|
function g::app::command::description() {
|
|
local name="$1"
|
|
|
|
local varname="$(g::app::command::var DESCRIPTION "$name")"
|
|
g::app::get "$varname"
|
|
}
|
|
|
|
function g::app::command() {
|
|
local name="$1"
|
|
local description="${2:-$name}"
|
|
|
|
g::app::set DEFINE_CURRENT_COMMAND "$name"
|
|
local cmdsVar="$(g::app::var AVAILABLE_COMMANDS)"
|
|
|
|
if ! [[ -v $cmdsVar ]]; then
|
|
g::eval "export ${cmdsVar}=()"
|
|
fi
|
|
|
|
g::eval "${cmdsVar}+=(\"$name\")"
|
|
g::app::set "$(g::app::command::var DESCRIPTION)" "$description"
|
|
}
|
|
|
|
function g::app::command::rest() {
|
|
local name="$1"
|
|
local description="${2:-$name}"
|
|
|
|
local restVarName="$(g::app::command::var REST_ARG_NAME)"
|
|
local restVarDescription="$(g::app::command::var REST_ARG_DESCRIPTION)"
|
|
|
|
if ! g::app::has "$restVarName"; then
|
|
g::app::set "$restVarName" "$name"
|
|
g::app::set "$restVarDescription" "$description"
|
|
else
|
|
g::error::throw "Cannot register rest argument '$name': command already has rest argument"
|
|
fi
|
|
}
|
|
|
|
function g::app::command::hasRest() {
|
|
local restVarName="$(g::app::command::var REST_ARG_NAME)"
|
|
return $(g::app::has "$restVarName")
|
|
}
|
|
|
|
function g::app::command::arg() {
|
|
local name="$1"
|
|
local description="${2:-$name}"
|
|
|
|
local hasDefaultValue=no
|
|
local defaultValue
|
|
if [[ -v 3 ]]; then
|
|
hasDefaultValue=yes
|
|
defaultValue="$3"
|
|
fi
|
|
|
|
if g::app::command::hasRest; then
|
|
g::error::throw "Cannot register positional argument '$name' after rest argument."
|
|
fi
|
|
|
|
local currentArgVar="$(g::app::command::var CURRENT_ARG_NUM)"
|
|
if ! g::app::has "$currentArgVar"; then
|
|
g::app::set "$currentArgVar" 0
|
|
fi
|
|
|
|
local currentArg="$(g::app::get $currentArgVar)"
|
|
local nextArg=$(( $currentArg + 1 ))
|
|
|
|
g::app::set "$currentArgVar" $nextArg
|
|
|
|
local nameVar="$(g::app::command::var ARG_${nextArg}_NAME)"
|
|
local descriptionVar="$(g::app::command::var ARG_${nextArg}_DESCRIPTION)"
|
|
local hasDefaultVar="$(g::app::command::var ARG_${nextArg}_HAS_DEFAULT)"
|
|
local defaultVar="$(g::app::command::var ARG_${nextArg}_DEFAULT)"
|
|
|
|
g::app::set "$nameVar" "$name"
|
|
g::app::set "$descriptionVar" "$description"
|
|
g::app::set "$hasDefaultVar" "$hasDefaultValue"
|
|
|
|
if [[ $hasDefaultValue == 'yes' ]]; then
|
|
g::app::set "$defaultVar" "$defaultValue"
|
|
fi
|
|
}
|
|
|
|
function g::app::command::flag() {
|
|
local flag="$1"
|
|
local description="${2:-$name}"
|
|
|
|
local hasValue=no
|
|
if g::str::endsWith "$flag" =; then
|
|
hasValue=yes
|
|
flag="$(g::str::substring "$flag" 0 -1)"
|
|
fi
|
|
|
|
local currentFlagVar="$(g::app::command::var CURRENT_FLAG_NUM)"
|
|
if ! g::app::has "$currentFlagVar"; then
|
|
g::app::set "$currentFlagVar" 0
|
|
fi
|
|
|
|
local currentFlag="$(g::app::get $currentFlagVar)"
|
|
local nextFlag=$(( $currentFlag + 1 ))
|
|
|
|
g::app::set "$currentFlagVar" $nextFlag
|
|
|
|
local flagVar="$(g::app::command::var FLAG_${nextFlag}_NAME)"
|
|
local descriptionVar="$(g::app::command::var FLAG_${nextFlag}_DESCRIPTION)"
|
|
local hasValueVar="$(g::app::command::var FLAG_${nextFlag}_HAS_VALUE)"
|
|
|
|
g::internal "Setting flag name: ${flagVar}" g::app::command::parse
|
|
g::app::set "$flagVar" "$flag"
|
|
g::app::set "$descriptionVar" "$description"
|
|
g::app::set "$hasValueVar" "$hasValue"
|
|
}
|
|
|
|
function g::app::command::flag::getIndexByName() {
|
|
local flagName="$1"
|
|
local cmdName="$2"
|
|
|
|
local currentFlagVar="$(g::app::command::var CURRENT_FLAG_NUM "$cmdName")"
|
|
if ! g::app::has "$currentFlagVar"; then
|
|
g::app::set "$currentFlagVar" 0
|
|
fi
|
|
|
|
local currentFlag="$(g::app::get $currentFlagVar)"
|
|
for (( i=0; i<=$currentFlag; i++ )); do
|
|
local flagVar="$(g::app::command::var FLAG_${i}_NAME "${cmdName}")"
|
|
|
|
if g::app::has "$flagVar"; then
|
|
local possibleFlagName="$(g::app::get "$flagVar")"
|
|
if [[ "$possibleFlagName" == "$flagName" ]] || [[ "$possibleFlagName" == "${flagName}=" ]]
|
|
then
|
|
printf $i
|
|
return $(g::util::true)
|
|
fi
|
|
fi
|
|
done
|
|
|
|
return $(g::util::false)
|
|
}
|
|
|
|
function g::app::command::arg::getIndexByName() {
|
|
local argName="$1"
|
|
local cmdName="$2"
|
|
|
|
local currentArgVar="$(g::app::command::var CURRENT_ARG_NUM "$cmdName")"
|
|
if ! g::app::has "$currentArgVar"; then
|
|
g::app::set "$currentArgVar" 0
|
|
fi
|
|
|
|
local currentArg="$(g::app::get $currentArgVar)"
|
|
for (( i=0; i<=$currentArg; i++ )); do
|
|
local argVar="$(g::app::command::var ARG_${i}_NAME "${cmdName}")"
|
|
|
|
if g::app::has "$argVar"; then
|
|
local possibleArgName="$(g::app::get "$argVar")"
|
|
if [[ "$possibleArgName" == "$argName" ]]; then
|
|
printf $i
|
|
return $(g::util::true)
|
|
fi
|
|
fi
|
|
done
|
|
|
|
return $(g::util::false)
|
|
}
|
|
|
|
function g::app::command::parse::var() {
|
|
local name="$1"
|
|
local uuid="$2"
|
|
printf "COMMAND_PARSE__${uuid}__${name}"
|
|
}
|
|
|
|
function g::app::command::parse::register::flag() {
|
|
local flag="$1"
|
|
local value="$2"
|
|
local uuid="$3"
|
|
|
|
local varName="$(g::app::command::parse::var "FLAG_${flag}_VALUE" "$uuid")"
|
|
g::app::set "$varName" "$value"
|
|
}
|
|
|
|
function g::app::command::parse::get::flag() {
|
|
local position="$1"
|
|
local uuid="$2"
|
|
|
|
local varName="$(g::app::command::parse::var "FLAG_${position}_VALUE" "$uuid")"
|
|
g::internal "Getting: $varName" g::app::command::parse
|
|
g::app::get "$varName"
|
|
}
|
|
|
|
function g::app::command::parse::has::flag() {
|
|
local flag="$1"
|
|
local uuid="$2"
|
|
|
|
local varName="$(g::app::command::parse::var "FLAG_${flag}_VALUE" "$uuid")"
|
|
if g::app::has "$varName"; then
|
|
return $(g::util::true)
|
|
fi
|
|
|
|
return $(g::util::false)
|
|
}
|
|
|
|
function g::app::command::parse::register::rest() {
|
|
local uuid="$1"
|
|
shift
|
|
local value="$@"
|
|
|
|
local varName="$(g::app::command::parse::var REST_ARGS "$uuid")"
|
|
g::internal "Registering rest args: $varName" g::app::command::parse
|
|
g::app::set "$varName" "$value"
|
|
}
|
|
|
|
function g::app::command::parse::get::rest() {
|
|
local uuid="$1"
|
|
|
|
local varName="$(g::app::command::parse::var REST_ARGS "$uuid")"
|
|
g::internal "Getting rest args" g::app::command::parse
|
|
g::app::get "$varName"
|
|
}
|
|
|
|
##
|
|
# Register the value of a positional argument in an argument set.
|
|
#
|
|
# @param position
|
|
# @param value
|
|
# @param uuid - the identifier of the argument set
|
|
#
|
|
function g::app::command::parse::register::arg() {
|
|
local position="$1"
|
|
local value="$2"
|
|
local uuid="$3"
|
|
|
|
local varName="$(g::app::command::parse::var "ARG_${position}_VALUE" "$uuid")"
|
|
g::internal "Registering: $varName" g::app::command::parse
|
|
g::app::set "$varName" "$value"
|
|
}
|
|
|
|
##
|
|
# Get the value of a positional argument in an argument set.
|
|
#
|
|
# @param position - the argument position
|
|
# @param uuid - the argument set identifier
|
|
# @echo the argument value
|
|
#
|
|
function g::app::command::parse::get::arg() {
|
|
local position="$1"
|
|
local uuid="$2"
|
|
|
|
local varName="$(g::app::command::parse::var "ARG_${position}_VALUE" "$uuid")"
|
|
g::internal "Getting: $varName" g::app::command::parse
|
|
g::app::get "$varName"
|
|
}
|
|
|
|
##
|
|
# Check if an argument set has an argument for the given position.
|
|
#
|
|
# @param position - the argument position
|
|
# @param uuid - the argument set identifier
|
|
# @return true if the argument exists
|
|
#
|
|
function g::app::command::parse::has::arg() {
|
|
local position="$1"
|
|
local uuid="$2"
|
|
|
|
local varName="$(g::app::command::parse::var "ARG_${position}_VALUE" "$uuid")"
|
|
g::internal "Checking: $varName" g::app::command::parse
|
|
if g::app::has "$varName"; then
|
|
return $(g::util::true)
|
|
fi
|
|
|
|
return $(g::util::false)
|
|
}
|
|
|
|
##
|
|
# Parse a raw argument string for the given command and create a new argument set.
|
|
#
|
|
# Sets the argument set identifier in $g__APP_LAST_ARGPARSE.
|
|
#
|
|
# @param cmdName - the identifier of the command the arguments are for
|
|
# @param ...args - the raw arguments
|
|
#
|
|
function g::app::command::parse() {
|
|
local cmdName="$1"
|
|
shift 1
|
|
local args=("$@")
|
|
|
|
local groupUuid=$(g::util::uuid::underscore)
|
|
local cmdNameVar="$(g::app::command::parse::var "COMMAND_NAME" "$groupUuid")"
|
|
local allVar="$(g::app::command::parse::var "ALL_ARGS" "$groupUuid")"
|
|
g::app::set "$allVar" "${args[*]}"
|
|
g::app::set "$cmdNameVar" "$cmdName"
|
|
|
|
local nextPositionalIndex=1
|
|
local trapRestArgs=no
|
|
local restArgs=()
|
|
local trappedFlag=no
|
|
local trappedFlagName
|
|
local trappedFlagIndex
|
|
for arg in "${args[@]}"; do
|
|
g::internal "Parsing argument: ${arg}" g::app::command::parse
|
|
|
|
if [[ $trapRestArgs == yes ]]; then
|
|
restArgs+=("$arg")
|
|
continue
|
|
fi
|
|
|
|
local isFlag=no
|
|
local flagOffset=0
|
|
# If the string starts with -- or -, then it is a flag parameter
|
|
if g::str::startsWith "$arg" "--"; then
|
|
isFlag=yes
|
|
flagOffset=2
|
|
elif g::str::startsWith "$arg" "-"; then
|
|
isFlag=yes
|
|
flagOffset=1
|
|
fi
|
|
|
|
if [[ $trappedFlag == yes ]]; then
|
|
# If we had a trapped flag expecting a value, then set the value
|
|
g::internal "Got value '$arg' for trapped flag '$trappedFlagName'" g::app::command::parse
|
|
g::app::command::parse::register::flag "$trappedFlagIndex" "$arg" "$groupUuid"
|
|
trappedFlag=no
|
|
continue
|
|
fi
|
|
|
|
if [[ $isFlag == yes ]]; then
|
|
local parsedFlag="$(g::str::offset "$arg" $flagOffset)"
|
|
local parsedValue
|
|
|
|
# Parse out the flag name (remove the -- or -) and split off the setter
|
|
# (e.g. --flag=setter) if one exists
|
|
local hasSetter=no
|
|
local indexOfSetter="$(g::str::indexOf "$parsedFlag" '=')"
|
|
if g::util::isNumeric $indexOfSetter; then
|
|
hasSetter=yes
|
|
parsedValue="$(g::str::offset "$parsedFlag" $(( $indexOfSetter + 1 )))"
|
|
parsedFlag="$(g::str::substring "$parsedFlag" 0 $indexOfSetter)"
|
|
fi
|
|
|
|
g::internal "Parsed flag: $parsedFlag (has setter? $hasSetter)" g::app::command::parse
|
|
if [[ $hasSetter == yes ]]; then
|
|
g::internal "Parsed value: $parsedValue" g::app::command::parse
|
|
fi
|
|
|
|
# Look up the index of the flag in the command definition
|
|
local flagIndex="$(g::app::command::flag::getIndexByName "$parsedFlag" "$cmdName")"
|
|
g::internal "Flag index: ${flagIndex}" g::app::command::parse
|
|
|
|
# Compute the flag variable names
|
|
local flagVar="$(g::app::command::var "FLAG_${flagIndex}_NAME" "$cmdName")"
|
|
local descriptionVar="$(g::app::command::var "FLAG_${flagIndex}_DESCRIPTION" "$cmdName")"
|
|
local hasValueVar="$(g::app::command::var "FLAG_${flagIndex}_HAS_VALUE" "$cmdName")"
|
|
g::internal "Flag var: $flagVar" g::app::command::parse
|
|
|
|
# Make sure the flag is defined. Flag name will always exist.
|
|
if ! g::app::has "$flagVar"; then
|
|
g::error::throw "Invalid flag: ${parsedFlag}"
|
|
fi
|
|
|
|
# Check if we've already gotten this flag, to prevent duplicates
|
|
if g::app::command::parse::has::flag "$flagIndex" "$groupUuid"; then
|
|
g::error::throw "Duplicate flag specified: ${parsedFlag}"
|
|
fi
|
|
|
|
# Register the flag
|
|
local flagHasValue="$(g::app::get "$hasValueVar")"
|
|
if [[ "$flagHasValue" == yes ]]; then
|
|
if [[ $hasSetter == yes ]]; then
|
|
# Register the flag value
|
|
g::app::command::parse::register::flag "$flagIndex" "$parsedValue" "$groupUuid"
|
|
else
|
|
# Set a trap to register the next value as the flag value
|
|
trappedFlag=yes
|
|
trappedFlagName="$parsedFlag"
|
|
trappedFlagIndex="$flagIndex"
|
|
fi
|
|
else
|
|
if [[ $hasSetter == yes ]]; then
|
|
# Don't allow params for non-param flags
|
|
g::error::throw "Flag '$parsedFlag' does not accept an argument."
|
|
else
|
|
# The flag doesn't accept a parameter, so mark it as true
|
|
g::app::command::parse::register::flag "$flagIndex" true "$groupUuid"
|
|
fi
|
|
fi
|
|
else
|
|
# This is a positional argument
|
|
local currentArgVar="$(g::app::command::var CURRENT_ARG_NUM "${cmdName}")"
|
|
if ! g::app::has "$currentArgVar"; then
|
|
g::app::set "$currentArgVar" 0
|
|
fi
|
|
|
|
local currentArg="$(g::app::get $currentArgVar)"
|
|
if (( $nextPositionalIndex > $currentArg )); then
|
|
local restVarName="$(g::app::command::var REST_ARG_NAME "${cmdName}")"
|
|
if g::app::has "$restVarName"; then
|
|
trapRestArgs=yes
|
|
restArgs+=("$arg")
|
|
continue
|
|
else
|
|
g::error::throw "Unexpected positional argument: $arg"
|
|
fi
|
|
fi
|
|
|
|
g::app::command::parse::register::arg $nextPositionalIndex "$arg" "$groupUuid"
|
|
nextPositionalIndex=$(($nextPositionalIndex + 1))
|
|
fi
|
|
done
|
|
|
|
if [[ $trappedFlag == yes ]]; then
|
|
# We got a flag expecting a value as the last argument,
|
|
# but never got an argument for it.
|
|
g::error::throw "Missing required value for flag: ${trappedFlagName}"
|
|
fi
|
|
|
|
if [[ $trapRestArgs == yes ]]; then
|
|
g::app::command::parse::register::rest "$groupUuid" "${restArgs[@]}"
|
|
fi
|
|
|
|
g::app::command::validate "$cmdName" "$groupUuid"
|
|
g__APP_LAST_ARGPARSE="$groupUuid"
|
|
}
|
|
|
|
##
|
|
# Validate the parsed arguments for the given argument set.
|
|
#
|
|
# @param cmdName - the identifier of the command the arguments are for
|
|
# @param groupUuid - the identifier of the argument set
|
|
#
|
|
function g::app::command::validate() {
|
|
local cmdName="$1"
|
|
local groupUuid="$2"
|
|
|
|
# Set defauls for positional arguments and make sure all are present
|
|
local currentArgVar="$(g::app::command::var CURRENT_ARG_NUM "${cmdName}")"
|
|
if ! g::app::has "$currentArgVar"; then
|
|
g::app::set "$currentArgVar" 0
|
|
fi
|
|
|
|
local currentArg="$(g::app::get "$currentArgVar")"
|
|
|
|
for (( i=1; i<=$currentArg; i++ )); do
|
|
g::internal "Considering positional argument: $i" g::app::command::parse
|
|
local nameVar="$(g::app::command::var ARG_${i}_NAME "$cmdName")"
|
|
local hasDefaultVar="$(g::app::command::var ARG_${i}_HAS_DEFAULT "$cmdName")"
|
|
local defaultVar="$(g::app::command::var ARG_${i}_DEFAULT "$cmdName")"
|
|
|
|
if ! g::app::command::parse::has::arg $i "$groupUuid"; then
|
|
local hasDefault="$(g::app::get "$hasDefaultVar")"
|
|
if [[ $hasDefault == yes ]]; then
|
|
local argDefault="$(g::app::get "$defaultVar")"
|
|
g::app::command::parse::register::arg $i "$argDefault" "$groupUuid"
|
|
else
|
|
local argName="$(g::app::get "$nameVar")"
|
|
g::error::throw "Missing required positional argument: $argName"
|
|
fi
|
|
fi
|
|
done
|
|
}
|
|
|
|
##
|
|
# Print a usage message for the current application.
|
|
#
|
|
# @echo the message
|
|
#
|
|
function g::app::usage() {
|
|
local header="$g__APP_CURRENT_NAME"
|
|
local description="$(g::app::get DESCRIPTION)"
|
|
if [[ "$(g::str::length "$description")" -gt 0 ]]; then
|
|
header="${header}: ${description}"
|
|
fi
|
|
|
|
echo "$header"
|
|
echo ""
|
|
echo "Usage: $(basename "$g__CALLER_PATH") <command> [...arguments]"
|
|
echo ""
|
|
|
|
local commandsString=""
|
|
local cmdsVar="$(g::app::var AVAILABLE_COMMANDS)"
|
|
|
|
for cmdName in $(g::eval "echo \"\${$cmdsVar[@]}\""); do
|
|
local cmdDescVar="$(g::app::command::var DESCRIPTION "${cmdName}")"
|
|
local cmdDescription="$(g::app::get "$cmdDescVar")"
|
|
cmdString=" ${cmdName}: ${cmdDescription}"
|
|
|
|
commandsString="${commandsString}${cmdString}
|
|
"
|
|
done
|
|
|
|
commandsString="${commandsString} help: display usage information
|
|
"
|
|
|
|
echo "Available commands:"
|
|
echo ""
|
|
echo "$commandsString"
|
|
}
|
|
|
|
##
|
|
# Invoke a command in the current application with some arguments.
|
|
#
|
|
# @param commandName - the identifier of the command
|
|
# @param ...args - the arguments
|
|
#
|
|
function g::app::invoke() {
|
|
local commandName="${1:-}"
|
|
shift 1
|
|
local args="$@"
|
|
|
|
if [[ "$commandName" == "" ]] || [[ "$commandName" == "help" ]]; then
|
|
g::app::usage
|
|
return $(g::util::true)
|
|
fi
|
|
|
|
local cmdsVar="$(g::app::var AVAILABLE_COMMANDS)"
|
|
for registeredCommandName in $(g::eval "echo \"\${$cmdsVar[@]}\""); do
|
|
if [[ "$registeredCommandName" == "$commandName" ]]; then
|
|
g::internal "Matched command: $registeredCommandName"
|
|
|
|
if [[ $(type -t app::${g__APP_CURRENT_NAME}::${registeredCommandName}) != function ]]; then
|
|
g::debug "No function found with name: app::${g__APP_CURRENT_NAME}::${registeredCommandName}"
|
|
g::error::throw "No command handler registered for ${registeredCommandName}!"
|
|
fi
|
|
|
|
(
|
|
# parse MUST be called in the SAME subshell as the invoke
|
|
# because argparse relies on global variables
|
|
g::app::command::parse "$registeredCommandName" "$@"
|
|
local groupUuid="$g__APP_LAST_ARGPARSE"
|
|
|
|
g::args::buildAlias "$groupUuid"
|
|
|
|
"app::${g__APP_CURRENT_NAME}::${registeredCommandName}" "$g__APP_LAST_ALIAS"
|
|
)
|
|
|
|
return $(g::util::true)
|
|
fi
|
|
done
|
|
|
|
g::error "Invalid command: ${commandName}"
|
|
g::error "Run '$(basename $g__CALLER_PATH) help' to view usage."
|
|
}
|
|
|
|
## WIP
|
|
function g::args::buildAlias() {
|
|
local uuid="$1"
|
|
|
|
alias ${uuid}="g::args \"$uuid\""
|
|
alias ${uuid}::arg="g::arg \"$uuid\""
|
|
alias ${uuid}::arg::has="g::arg:has \"$uuid\""
|
|
alias ${uuid}::flag="g::flag \"$uuid\""
|
|
alias ${uuid}::flag::has="g::flag:has \"$uuid\""
|
|
|
|
g__APP_LAST_ALIAS="${uuid}"
|
|
}
|
|
|
|
##
|
|
# Get the raw arguments for a given argument set.
|
|
# This is akin to $@ in a normal invocation.
|
|
#
|
|
# @param uuid - the identifier of the argument set
|
|
# @echo the raw argument string
|
|
#
|
|
function g::args() {
|
|
local uuid="$1"
|
|
|
|
local allVar="$(g::app::command::parse::var "ALL_ARGS" "$uuid")"
|
|
if ! g::app::has "$allVar"; then
|
|
g::error::throw "Invalid argument parse reference: ${uuid}"
|
|
fi
|
|
|
|
g::app::get "$allVar"
|
|
}
|
|
|
|
##
|
|
# Get the value of a given argument in an argument set.
|
|
#
|
|
# @param uuid - the identifier of the argument set
|
|
# @param nameOrPosition - the argument name or index
|
|
# @echo the argument value
|
|
#
|
|
function g::arg() {
|
|
local uuid="$1"
|
|
local nameOrPosition="$2"
|
|
|
|
local cmdNameVar="$(g::app::command::parse::var "COMMAND_NAME" "$uuid")"
|
|
local cmdName="$(g::app::get "$cmdNameVar")"
|
|
|
|
if ! g::util::isNumeric "$nameOrPosition"; then
|
|
nameOrPosition="$(g::app::command::arg::getIndexByName "$nameOrPosition" "$cmdName")"
|
|
else
|
|
nameOrPosition=$(( $nameOrPosition + 1 ))
|
|
fi
|
|
|
|
g::internal "Getting positional arg: $nameOrPosition for command: $cmdName" g::arg
|
|
g::app::command::parse::get::arg "$nameOrPosition" "$uuid"
|
|
}
|
|
|
|
##
|
|
# Check if the given argument set has the given argument.
|
|
#
|
|
# @param uuid - the identifier of the argument set
|
|
# @param nameOrPosition - the argument name or index
|
|
# @return true if the argument is set
|
|
#
|
|
function g::arg::has() {
|
|
local uuid="$1"
|
|
local nameOrPosition="$2"
|
|
|
|
local cmdNameVar="$(g::app::command::parse::var "COMMAND_NAME" "$uuid")"
|
|
local cmdName="$(g::app::get "$cmdNameVar")"
|
|
|
|
if ! g::util::isNumeric "$nameOrPosition"; then
|
|
nameOrPosition="$(g::app::command::arg::getIndexByName "$nameOrPosition" "$cmdName")"
|
|
else
|
|
nameOrPosition=$(( $nameOrPosition + 1 ))
|
|
fi
|
|
|
|
g::internal "Checking positional arg: $nameOrPosition for command: $cmdName" g::arg
|
|
return $(g::app::command::parse::has::arg "$nameOrPosition" "$uuid")
|
|
}
|
|
|
|
function g::arg::rest() {
|
|
local uuid="$1"
|
|
|
|
local cmdNameVar="$(g::app::command::parse::var "COMMAND_NAME" "$uuid")"
|
|
local cmdName="$(g::app::get "$cmdNameVar")"
|
|
|
|
local restVarName="$(g::app::command::var REST_ARG_NAME "$cmdName")"
|
|
if ! g::app::has "$restVarName"; then
|
|
g::error::throw "Unable to get rest arguments for command: $cmdName: command has no rest argument configured"
|
|
fi
|
|
|
|
g::app::command::parse::get::rest "$uuid"
|
|
}
|
|
|
|
##
|
|
# Get the value of a given flag in an argument set.
|
|
#
|
|
# @param uuid - the identifier of the argument set
|
|
# @param nameOrPosition - the flag name
|
|
# @echo the flag value
|
|
#
|
|
function g::flag() {
|
|
local uuid="$1"
|
|
local nameOrPosition="$2"
|
|
|
|
local cmdNameVar="$(g::app::command::parse::var "COMMAND_NAME" "$uuid")"
|
|
local cmdName="$(g::app::get "$cmdNameVar")"
|
|
|
|
if ! g::util::isNumeric "$nameOrPosition"; then
|
|
nameOrPosition="$(g::app::command::flag::getIndexByName "$nameOrPosition" "$cmdName")"
|
|
else
|
|
nameOrPosition=$(( $nameOrPosition + 1 ))
|
|
fi
|
|
|
|
g::internal "Getting flag value: $nameOrPosition for command: $cmdName" g::arg
|
|
g::app::command::parse::get::flag "$nameOrPosition" "$uuid"
|
|
}
|
|
|
|
##
|
|
# Check if the given argument set has a given flag.
|
|
#
|
|
# @param uuid - the identifier of the argument set
|
|
# @param nameOrPosition - the flag name
|
|
# @return true if the flag is set
|
|
#
|
|
function g::flag::has() {
|
|
local uuid="$1"
|
|
local nameOrPosition="$2"
|
|
|
|
local cmdNameVar="$(g::app::command::parse::var "COMMAND_NAME" "$uuid")"
|
|
local cmdName="$(g::app::get "$cmdNameVar")"
|
|
|
|
if ! g::util::isNumeric "$nameOrPosition"; then
|
|
nameOrPosition="$(g::app::command::flag::getIndexByName "$nameOrPosition" "$cmdName")"
|
|
else
|
|
nameOrPosition=$(( $nameOrPosition + 1 ))
|
|
fi
|
|
|
|
g::internal "Checking flag value: $nameOrPosition for command: $cmdName" g::arg
|
|
return $(g::app::command::parse::has::flag "$nameOrPosition" "$uuid")
|
|
}
|
|
|
|
|
|
|
|
|
|
##
|
|
# Try to acquire the given lock.
|
|
#
|
|
# @param lockName
|
|
# @return true if acquired
|
|
#
|
|
function g::lock::try() {
|
|
local lockName="$1"
|
|
|
|
if g::lock::holds "$lockName"; then
|
|
return $(g::util::true)
|
|
fi
|
|
|
|
local lockFile="$(g::path::resolve "$g__LOCKING_FILE_DIR" "${lockName}.lock")"
|
|
local tempFile="$(mktemp --suffix .lock)"
|
|
if g::silence ln "$tempFile" "$lockFile"; then
|
|
g__LOCKING_HOLDS+=("$lockName")
|
|
return $(g::util::true)
|
|
fi
|
|
|
|
return $(g::util::false)
|
|
}
|
|
|
|
##
|
|
# Acquire the given lock, sleeping between retries.
|
|
#
|
|
# @param lockName
|
|
#
|
|
function g::lock::acquire() {
|
|
local lockName="$1"
|
|
while ! g::lock::try "$lockName"; do
|
|
sleep "$g__LOCKING_INTERVAL"
|
|
done
|
|
}
|
|
|
|
##
|
|
# Returns true if the current process holds the lock.
|
|
#
|
|
# @param lockName
|
|
# @return true if held
|
|
#
|
|
function g::lock::holds() {
|
|
local lockName="$1"
|
|
return $(g::arr::includes "$lockName" "${g__LOCKING_HOLDS[@]}")
|
|
}
|
|
|
|
##
|
|
# Release a lock if held. Throw otherwise.
|
|
#
|
|
# @param lockName
|
|
#
|
|
function g::lock::release() {
|
|
local lockName="$1"
|
|
|
|
if ! g::lock::holds "$lockName"; then
|
|
g::error::throw "Cannot release lock '$lockName': not held"
|
|
fi
|
|
|
|
local lockFile="$(g::path::resolve "$g__LOCKING_FILE_DIR" "${lockName}.lock")"
|
|
rm -f "$lockFile"
|
|
|
|
g__LOCKING_HOLDS=("${g__LOCKING_HOLDS[@]/$lockName}")
|
|
}
|
|
|
|
##
|
|
# Release all held locks before exit.
|
|
#
|
|
function g::lock::cleanupForExit() {
|
|
for lockFile in "${g__LOCKING_HOLDS[@]}"; do
|
|
g::internal "Releasing lockfile: $lockFile"
|
|
rm -f "$lockFile"
|
|
done
|
|
}
|
|
|
|
##
|
|
# Try to acquire the lock for this script's unique caller name.
|
|
# Throw an error if we are unable to acquire it.
|
|
#
|
|
function g::lock::singleton() {
|
|
if ! g::lock::try "$(g::lock::uniqueCallerName)"; then
|
|
g::error::throw "Another instance of this script is already running."
|
|
fi
|
|
}
|
|
|
|
##
|
|
# Acquire the lock for this script's unique caller name.
|
|
#
|
|
function g::lock::singleton::acquire() {
|
|
g::lock::acquire "$(g::lock::uniqueCallerName)"
|
|
}
|
|
|
|
##
|
|
# Generate a file-safe name unique to the caller script.
|
|
#
|
|
# @echo the unique name
|
|
#
|
|
function g::lock::uniqueCallerName() {
|
|
g::str::replace "$g__CALLER_PATH" '/' '__'
|
|
}
|
|
|
|
# dependency declarations
|
|
|
|
# output helpers
|
|
|
|
# exception handling
|
|
|
|
# JSON parsing, INI parsing, rich data types (named parameters & arrays/maps)
|
|
|
|
# sendmail support
|
|
|
|
# Bootstrap the framework
|
|
g::internals::bootstrap
|