3 changed files with 669 additions and 0 deletions
@ -0,0 +1,17 @@ |
|||
#!/bin/bash |
|||
|
|||
source src/g.bash |
|||
|
|||
g::log::enable file g.log |
|||
g::log::setLevel internal |
|||
|
|||
g::info "Starting try-catch..." |
|||
|
|||
try { |
|||
ls -lah baddir |
|||
} catch { |
|||
declare local error="$?" |
|||
g::error "Error code: $error" |
|||
} |
|||
|
|||
g::info "After try-catch..." |
@ -0,0 +1,650 @@ |
|||
#!/bin/bash -e |
|||
|
|||
set -u |
|||
set +o histexpand |
|||
shopt -qs failglob |
|||
shopt -s expand_aliases |
|||
|
|||
## ====================== GLOBALS ====================== ## |
|||
export g__EXISTS=true |
|||
|
|||
# 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=true |
|||
|
|||
# True if we should log messages to stderr |
|||
g__LOGGING_TO_STDERR=false |
|||
|
|||
# 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' |
|||
|
|||
|
|||
|
|||
|
|||
## ====================== UTILITIES ====================== ## |
|||
|
|||
# |
|||
# 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 |
|||
} |
|||
|
|||
# |
|||
# 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 eval "$cmd"; then |
|||
echo 1 |
|||
else |
|||
echo 0 |
|||
fi |
|||
} |
|||
|
|||
# |
|||
# 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 |
|||
} |
|||
|
|||
# |
|||
# Throw a program error. Exits. |
|||
# @todo move to g::err |
|||
# |
|||
function g::error::throw() { |
|||
local message="$1" |
|||
echo "$message" 1>&2 |
|||
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 stack_size=${#FUNCNAME[@]} |
|||
|
|||
# to avoid noise we start with 1 to skip the trace function |
|||
for (( i=1; 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" "$src" "$linen |
|||
done |
|||
|
|||
stack="${message}${stack}" |
|||
echo "${stack}" |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
## ====================== STRINGS ====================== ## |
|||
|
|||
function g::str::quote() { |
|||
local string="$1" |
|||
echo "'$string'" |
|||
} |
|||
|
|||
function g::str::quote::eval() { |
|||
local cmd="$@" |
|||
local str="$(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::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 |
|||
} |
|||
|
|||
# string helpers |
|||
# string split |
|||
|
|||
|
|||
|
|||
|
|||
## ====================== LOGGING ====================== ## |
|||
|
|||
# output functions and logging |
|||
# capture output |
|||
# silence output |
|||
# colors |
|||
# powerline |
|||
# tables |
|||
# width-fill |
|||
|
|||
# |
|||
# 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 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" |
|||
g::log error "$output" |
|||
} |
|||
|
|||
# |
|||
# Log a warning message. |
|||
# |
|||
# @param output |
|||
# |
|||
function g::warn() { |
|||
local output="$1" |
|||
g::log warn "$output" |
|||
} |
|||
|
|||
# |
|||
# Log an informational message. |
|||
# |
|||
# @param output |
|||
# |
|||
function g::info() { |
|||
local output="$1" |
|||
g::log info "$output" |
|||
} |
|||
|
|||
# |
|||
# Log a debugging message. |
|||
# |
|||
# @param output |
|||
# |
|||
function g::debug() { |
|||
local output="$1" |
|||
g::log debug "$output" |
|||
} |
|||
|
|||
# |
|||
# Log a verbose message. |
|||
# |
|||
# @param output |
|||
# |
|||
function g::internal() { |
|||
local output="$1" |
|||
g::log verbose "$output" |
|||
} |
|||
|
|||
# |
|||
# Log a verbose message. |
|||
# |
|||
# @param output |
|||
# |
|||
function g::internal() { |
|||
local output="$1" |
|||
g::log internal "$output" |
|||
} |
|||
|
|||
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 helpers |
|||
|
|||
# dependency declarations |
|||
# package/script imports & singleton requires |
|||
|
|||
# output helpers |
|||
|
|||
# argument parsing |
|||
|
|||
# try-catch & exception handling |
|||
|
|||
# named parameters & arrays/maps |
|||
|
|||
# JSON parsing, INI parsing, rich data types |
Loading…
Reference in new issue