@ -56,21 +56,29 @@ 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
g__DEBUG_EVAL = yes
# If 'yes' eval calls will be logged to the console
g__DEBUG_EVAL = no
@ -105,6 +113,13 @@ function g::internals::cleanup() {
g::lock::cleanupForExit
}
##
# Override the top-level caller reference.
#
function g::internals::setCaller( ) {
local caller = " $1 "
g__CALLER_PATH = " $( g::util::realpath $caller ) "
}
## ====================== UTILITIES ====================== ##
@ -128,7 +143,7 @@ function g::silence() {
# @return 0 if numeric, 1 otherwise
#
function g::util::isNumeric( ) {
local value = " $1 "
local value = " ${ 1 :- } "
local rex = '^[+-]?[0-9]+([.][0-9]+)?$'
@ -417,6 +432,11 @@ function g::path::resolveFrom() {
)
}
function g::path::cd( ) {
local resolved = " $( g::path::resolve " $@ " ) "
cd " $resolved "
}
function g::path::tmp( ) {
local name = " $( mktemp) "
g__FILES_TO_CLEANUP += ( " $name " )
@ -435,7 +455,10 @@ function g::path::tmpdir() {
#
function g::error::throw( ) {
local message = " $1 "
echo " $message " 1>& 2
echo ""
echo ""
g::util::trace " ERROR: $message " 2 1>& 2
echo ""
exit 1
}
@ -457,10 +480,11 @@ function g::now() {
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 = 1 ; i<$stack_size ; i++ ) ) ; do
for ( ( i = $skip ; i<$stack_size ; i++ ) ) ; do
local func = " ${ FUNCNAME [ $i ] } "
[ x$func = x ] && func = MAIN
@ -470,7 +494,7 @@ function g::util::trace() {
[ x" $src " = x ] && src = non_file_source
stack += $'\n' " at: "$func " " $src " " $linen
stack += $'\n' " at ( $func ) in $src (line $linen ) "
done
stack = " ${ message } ${ stack } "
@ -1256,6 +1280,12 @@ 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
@ -1278,12 +1308,14 @@ function g::source::resolve() {
fi
return $( g::util::false)
)
}
function g::source::exists( ) {
local path = " $1 "
local up = " ${ 2 :- 1 } "
local resolved = " $( g::source::resolve " $path " ) "
local resolved = " $( g::source::resolve " $path " $(( up + 1 )) ) "
local length = " $( g::str::length " $resolved " ) "
if [ [ " $length " -gt 0 ] ] ; then
@ -1295,9 +1327,10 @@ function g::source::exists() {
function g::source::force( ) {
local slug = " $1 "
local up = " ${ 2 :- 1 } "
local resolved = " $( g::source::resolve " $slug " ) "
if ! g::source::exists " $slug " ; then
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
@ -1319,8 +1352,10 @@ function g::source::force() {
function g::source::has( ) {
local slug = " $1 "
local up = " ${ 2 :- 1 } "
if g::arr::includes " $slug " " ${ g__IMPORTED_FILES [@] } " ; then
local resolved = " $( g::source::resolve " $slug " $(( up + 1 )) ) "
if g::arr::includes " $resolved " " ${ g__IMPORTED_FILES [@] } " ; then
return $( g::util::true)
fi
@ -1329,10 +1364,11 @@ function g::source::has() {
function g::source( ) {
local slug = " $1 "
local up = " ${ 2 :- 1 } "
if ! g::source::has " $slug " ; then
g::source::force " $slug "
g__IMPORTED_FILES += ( " $ slug " )
if ! g::source::has " $slug " $(( up + 1 )) ; then
g::source::force " $slug " $(( up + 1 ))
g__IMPORTED_FILES += ( " $ resolved " )
fi
}
@ -1448,9 +1484,10 @@ function g::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( ) {
@ -1533,6 +1570,26 @@ function g::app::command() {
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 } "
@ -1544,6 +1601,10 @@ function g::app::command::arg() {
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
@ -1685,6 +1746,24 @@ function g::app::command::parse::has::flag() {
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.
#
@ -1758,12 +1837,19 @@ function g::app::command::parse() {
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
@ -1852,8 +1938,15 @@ function g::app::command::parse() {
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 ))
@ -1866,6 +1959,10 @@ function g::app::command::parse() {
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 "
}
@ -1993,13 +2090,13 @@ function g::app::invoke() {
function g::args::buildAlias( ) {
local uuid = " $1 "
alias g::args::alias:: ${ uuid } = " g::args \" $uuid \" "
alias g::args::alias:: ${ uuid } ::arg= " g::arg \" $uuid \" "
alias g::args::alias:: ${ uuid } ::arg::has= " g::arg:has \" $uuid \" "
alias g::args::alias:: ${ uuid } ::flag= " g::flag \" $uuid \" "
alias g::args::alias:: ${ uuid } ::flag::has= " g::flag:has \" $uuid \" "
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 = " g::args::alias:: ${ uuid } "
g__APP_LAST_ALIAS = " ${ uuid } "
}
##
@ -2068,6 +2165,20 @@ function g::arg::has() {
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.
#