v5.1.0
===================================================================== A follow-up to V5 focused on hardening the environment system and expanding the ZSH logging/caching toolkit. No migration required from v5.0.x, though note the log-level reorganization below may quiet some messages at lower verbosities. --- New Features ------------------------- - persistent cross-runtime cache for ZSH-type scwrypts; extends the v5.0 runtime speedup cache to survive between runs with automatic hash-based invalidation (toggle via SCWRYPTS__ZSH_CACHE_ENABLED) - new 'trace' log level (5) and echo.trace, plus '-v VARNAME' state injection for echo.debug / echo.trace to surface variable values inline - additional scwrypts output formats: 'raw' and 'logfmt' (alongside pretty and json) - normalize.boolean utility for consistent truthy/falsey handling across ZSH-type scwrypts - utils.fzf.file-select for directory-scoped file selection prompts - automatic dependency wrappers providing default arguments, GNU coreutil resolution, and scwrypts trace on wrapped commands --- Changes ------------------------------ - log levels reorganized; success / status / reminder messages now emit at level 3, so lower verbosities will show fewer of these - environment library restructured around get-user-json as the single source of truth (replaces the previous user module and cache-output) - utility renames for consistency: user.Yn / user.yN (from utils.*) - utils.fail and utils.abort deprecated in favor of echo.error and echo.error.user-abort --- Bug Fixes ---------------------------- - CI runs now resolve config lookup-paths (.dotted.path) to their environment variable before reading, fixing false "not set" reports and invalid-export errors for lookup-path checks in CI - hardened environment type detection so array-valued configs reprint correctly after their first check - corrected exit-code capture in the cache layer (now reflects the cached command, not the downstream output filter) - scwrypt exit code now survives the logging pipeline - assorted script fixes (efs unmount file listing, postgres run-sql file discovery)
This commit is contained in:
@@ -28,8 +28,8 @@ ABORT >> utils.abort
|
||||
|
||||
CHECK_ERRORS >> utils.check-errors
|
||||
|
||||
Yn >> utils.Yn
|
||||
yN >> utils.yN
|
||||
Yn >> user.Yn
|
||||
yN >> user.yN
|
||||
|
||||
EDIT >> utils.io.edit
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#####################################################################
|
||||
command -v compdef >/dev/null 2>&1 || return 0
|
||||
command -v compdef &>/dev/null || return 0
|
||||
#####################################################################
|
||||
|
||||
for CLI in kubectl helm flux
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[[ $SCWRYPTS_KUBECTL_DRIVER_READY -eq 1 ]] && return 0
|
||||
|
||||
unalias k h f >/dev/null 2>&1
|
||||
unalias k h f &>/dev/null
|
||||
k() { _SCWRYPTS_KUBECTL_DRIVER kubectl $@; }
|
||||
h() { _SCWRYPTS_KUBECTL_DRIVER helm $@; }
|
||||
f() { _SCWRYPTS_KUBECTL_DRIVER flux $@; }
|
||||
@@ -11,7 +11,7 @@ _SCWRYPTS_KUBECTL_DRIVER() {
|
||||
return 1
|
||||
}
|
||||
|
||||
which kube.redis >/dev/null 2>&1 \
|
||||
which kube.redis &>/dev/null \
|
||||
|| eval "$(scwrypts -n --name meta/get-static-redis-definition --type zsh --group kube)"
|
||||
|
||||
local CLI="$1"; shift 1
|
||||
|
||||
@@ -87,7 +87,7 @@ kube.redis ping 2>/dev/null | grep -qi pong || {
|
||||
--detach \
|
||||
--name scwrypts-kubectl-redis \
|
||||
--publish $SCWRYPTS_KUBECTL_REDIS_PORT__managed:6379 \
|
||||
redis >/dev/null 2>&1
|
||||
redis &>/dev/null
|
||||
|
||||
echo.status 'awaiting redis connection'
|
||||
until kube.redis ping 2>/dev/null | grep -qi pong; do sleep 0.5; done
|
||||
|
||||
@@ -7,13 +7,18 @@ use scwrypts/environment
|
||||
use scwrypts/list-available
|
||||
use scwrypts/get-runstring
|
||||
|
||||
[[ "${SCWRYPTS_TEMP_PATH}" ]] && [[ -d "${SCWRYPTS_TEMP_PATH}" ]] \
|
||||
&& EXIT_CODE_FILE="${SCWRYPTS_TEMP_PATH}/exit-code" \
|
||||
|| EXIT_CODE_FILE="$(mktemp)" \
|
||||
;
|
||||
|
||||
#####################################################################
|
||||
() {
|
||||
cd "$(scwrypts.config.group scwrypts root)"
|
||||
GIT_SCWRYPTS() { git -C "$(scwrypts.config.group scwrypts root)" $@; }
|
||||
git.scwrypts() { git -C "$(scwrypts.config.group scwrypts root)" "${@}"; }
|
||||
|
||||
local ERRORS=0
|
||||
local USAGE='
|
||||
local USAGE="
|
||||
usage: scwrypts [...options...] [...patterns...] -- [...script options...]
|
||||
|
||||
options:
|
||||
@@ -27,22 +32,23 @@ use scwrypts/get-runstring
|
||||
-y, --yes auto-accept all [yn] prompts through current scwrypt
|
||||
-e, --env <env-name> set environment; overwrites SCWRYPTS_ENV
|
||||
|
||||
-n shorthand for "--log-level 0"
|
||||
-v, --log-level <0-4> set incremental scwrypts log level to one of the following:
|
||||
-n shorthand for \"--log-level 0\"
|
||||
-v, --log-level <0-5> set incremental scwrypts log level to one of the following:
|
||||
0 : only command output and critical failures; skips logfile
|
||||
1 : include success / failure messages
|
||||
2 : include status update messages
|
||||
3 : (default) include warning messages
|
||||
1 : include error messages
|
||||
2 : include warnings
|
||||
3 : (default) include success, status, and user information
|
||||
4 : include debug messages
|
||||
5 : include trace
|
||||
|
||||
-o, --output <format> specify output format; one of: pretty,json (default: pretty)
|
||||
-o, --output <format> specify output format; one of: pretty,json,raw,logfmt (default: pretty)
|
||||
|
||||
alternate commands
|
||||
-h, --help display this message and exit
|
||||
-l, --list print out command list and exit
|
||||
--list-envs print out environment list and exit
|
||||
--list-groups print out configured scwrypts groups and exit
|
||||
--config "eval"-ed to enable config and "use" import in non-scwrypts environments
|
||||
--config \"eval\"-ed to enable config and \"use\" import in non-scwrypts environments
|
||||
--root print out scwrypts.config.group.scwrypts.root and exit
|
||||
--update update scwrypts library to latest version
|
||||
--version print out scwrypts version and exit
|
||||
@@ -51,29 +57,40 @@ use scwrypts/get-runstring
|
||||
- a list of glob patterns to loose-match a scwrypt by name
|
||||
|
||||
script options:
|
||||
- everything after "--" is forwarded to the scwrypt you run
|
||||
("-- --help" will provide more information)
|
||||
'
|
||||
- everything after \"--\" is forwarded to the scwrypt you run
|
||||
(\"-- --help\" will provide more information)
|
||||
"
|
||||
|
||||
#####################################################################
|
||||
### cli argument parsing and global configuration ###################
|
||||
#####################################################################
|
||||
|
||||
local ENV_NAME="${SCWRYPTS_ENV}"
|
||||
local SEARCH_PATTERNS=()
|
||||
local target_scwrypts_env="${SCWRYPTS_ENV}"
|
||||
local search_patterns=()
|
||||
|
||||
local VARSPLIT SEARCH_GROUP SEARCH_TYPE SEARCH_NAME
|
||||
local search_group search_type search_name
|
||||
|
||||
[ ! ${SCWRYPTS_LOG_LEVEL} ] && local SCWRYPTS_LOG_LEVEL=3
|
||||
[[ "${SCWRYPTS_LOG_LEVEL}" ]] || local SCWRYPTS_LOG_LEVEL=3
|
||||
|
||||
local SHIFT_COUNT
|
||||
local _s
|
||||
while [[ $# -gt 0 ]]
|
||||
do
|
||||
SHIFT_COUNT=1
|
||||
_s=1
|
||||
case $1 in
|
||||
( -[a-z][a-z]* )
|
||||
VARSPLIT=$(echo "$1 " | sed 's/^\(-.\)\(.*\) /\1 -\2/')
|
||||
set -- throw-away $(echo " ${VARSPLIT} ") ${@:2}
|
||||
local varsplit_first="${1:0:2}" varsplit_remaining="-${1:2}"
|
||||
shift 1
|
||||
set -- "${varsplit_first}" "${varsplit_remaining}" "${@}"
|
||||
unset varsplit_first varsplit_remaining
|
||||
_s=0
|
||||
;;
|
||||
|
||||
( --*=* )
|
||||
local varsplit_first="${1%%=*}" varsplit_remaining="${1#*=}"
|
||||
shift 1
|
||||
set -- "${varsplit_first}" "${varsplit_remaining}" "${@}"
|
||||
unset varsplit_first varsplit_remaining
|
||||
_s=0
|
||||
;;
|
||||
|
||||
### alternate commands ###################
|
||||
@@ -99,8 +116,8 @@ use scwrypts/get-runstring
|
||||
;;
|
||||
|
||||
( --version )
|
||||
case ${SCWRYPTS_INSTALLATION_TYPE} in
|
||||
( manual ) echo "scwrypts $(GIT_SCWRYPTS describe --tags) (via GIT)" ;;
|
||||
case "${SCWRYPTS_INSTALLATION_TYPE}" in
|
||||
( manual ) echo "scwrypts $(git.scwrypts describe --tags) (via GIT)" ;;
|
||||
( * ) echo "scwrypts $(cat "$(scwrypts.config.group scwrypts root)/VERSION")" ;;
|
||||
esac
|
||||
return 0
|
||||
@@ -119,34 +136,34 @@ use scwrypts/get-runstring
|
||||
;;
|
||||
|
||||
( --update )
|
||||
case ${SCWRYPTS_INSTALLATION_TYPE} in
|
||||
aur )
|
||||
case "${SCWRYPTS_INSTALLATION_TYPE}" in
|
||||
( aur )
|
||||
echo.reminder --force-print "
|
||||
This installation is built from the AUR. Update through 'makepkg' or use
|
||||
your preferred AUR package management tool (e.g. 'yay -Syu scwrypts')
|
||||
"
|
||||
;;
|
||||
|
||||
homebrew )
|
||||
( homebrew )
|
||||
echo.reminder --force-print "This installation is managed by homebrew. Update me with 'brew update'"
|
||||
;;
|
||||
|
||||
manual )
|
||||
GIT_SCWRYPTS fetch --quiet origin main
|
||||
GIT_SCWRYPTS fetch --quiet origin main --tags
|
||||
local SYNC_STATUS=$?
|
||||
( manual )
|
||||
git.scwrypts fetch --quiet origin main
|
||||
git.scwrypts fetch --quiet origin main --tags
|
||||
local sync_status="${?}"
|
||||
|
||||
GIT_SCWRYPTS diff --exit-code origin/main -- . >/dev/null 2>&1
|
||||
local DIFF_STATUS=$?
|
||||
git.scwrypts diff --exit-code origin/main -- . &>/dev/null
|
||||
local diff_status="${?}"
|
||||
|
||||
[[ ${SYNC_STATUS} -eq 0 ]] && [[ ${DIFF_STATUS} -eq 0 ]] && {
|
||||
[[ "${sync_status}" -eq 0 ]] && [[ "${diff_status}" -eq 0 ]] && {
|
||||
echo.success 'already up-to-date with origin/main'
|
||||
} || {
|
||||
GIT_SCWRYPTS rebase --autostash origin/main \
|
||||
git.scwrypts rebase --autostash origin/main \
|
||||
&& echo.success 'up-to-date with origin/main' \
|
||||
&& GIT_SCWRYPTS log -n1 \
|
||||
&& git.scwrypts log -n1 \
|
||||
|| {
|
||||
GIT_SCWRYPTS rebase --abort
|
||||
git.scwrypts rebase --abort
|
||||
echo.error 'unable to update scwrypts; please try manual upgrade'
|
||||
echo.reminder "installation in '$(scwrypts.config.group scwrypts root)'"
|
||||
}
|
||||
@@ -165,25 +182,9 @@ use scwrypts/get-runstring
|
||||
|
||||
### scwrypts filters #####################
|
||||
|
||||
( -m | --name )
|
||||
((SHIFT_COUNT+=1))
|
||||
[ $2 ] || { echo.error "missing value for argument $1"; break; }
|
||||
SEARCH_NAME=$2
|
||||
;;
|
||||
|
||||
( -g | --group )
|
||||
((SHIFT_COUNT+=1))
|
||||
[ $2 ] || { echo.error "missing value for argument $1"; break; }
|
||||
SEARCH_GROUP=$2
|
||||
GROUP=$2
|
||||
;;
|
||||
|
||||
( -t | --type )
|
||||
((SHIFT_COUNT+=1))
|
||||
[ $2 ] || { echo.error "missing value for argument $1"; break; }
|
||||
SEARCH_TYPE=$2
|
||||
TYPE=$2
|
||||
;;
|
||||
( -m | --name ) _s=2; search_name=$2 ;;
|
||||
( -g | --group ) _s=2; search_group=$2; GROUP=$2 ;;
|
||||
( -t | --type ) _s=2; search_type=$2; TYPE=$2 ;;
|
||||
|
||||
### runtime settings #####################
|
||||
|
||||
@@ -191,49 +192,46 @@ use scwrypts/get-runstring
|
||||
|
||||
( -n ) SCWRYPTS_LOG_LEVEL=0 ;;
|
||||
|
||||
( -v | --log-level )
|
||||
((SHIFT_COUNT+=1))
|
||||
[[ $2 =~ ^[0-4]$ ]] || echo.error "invalid setting for log-level '$2'"
|
||||
SCWRYPTS_LOG_LEVEL=$2
|
||||
( -v | --log-level ) _s=2
|
||||
SCWRYPTS_LOG_LEVEL="${2}"
|
||||
|
||||
# allows CLI user to use the word lookup (e.g. "debug" or "trace")
|
||||
# number is preferred, but this is here for user ease
|
||||
[[ "${SCWRYPTS_LOG_LEVEL}" =~ ^[0-${#SCWRYPTS_LOG_LEVELS[@]}]$ ]] \
|
||||
|| SCWRYPTS_LOG_LEVEL="${(k)SCWRYPTS_LOG_LEVELS[(r)${SCWRYPTS_LOG_LEVEL:l}]}"
|
||||
|
||||
[[ "${SCWRYPTS_LOG_LEVEL}" =~ ^[0-${#SCWRYPTS_LOG_LEVELS[@]}]$ ]] \
|
||||
|| echo.error "invalid setting for log-level '${2}'"
|
||||
;;
|
||||
|
||||
( -o | --output )
|
||||
((SHIFT_COUNT+=1))
|
||||
export SCWRYPTS_OUTPUT_FORMAT=$2
|
||||
case ${SCWRYPTS_OUTPUT_FORMAT} in
|
||||
( pretty | json ) ;;
|
||||
* ) echo.error "unsupported format '${SCWRYPTS_OUTPUT_FORMAT}'" ;;
|
||||
esac
|
||||
( -o | --output ) _s=2
|
||||
export SCWRYPTS_OUTPUT_FORMAT="${2:l}"
|
||||
utils.io.print 'test' --force-print &>/dev/null \
|
||||
|| echo.error "unsupported format '${SCWRYPTS_OUTPUT_FORMAT}'"
|
||||
;;
|
||||
|
||||
( -e | --env )
|
||||
((SHIFT_COUNT+=1))
|
||||
[ $2 ] || { echo.error "missing value for argument $1"; break; }
|
||||
( -e | --env ) _s=2
|
||||
[[ "${target_scwrypts_env}" ]] \
|
||||
&& echo.debug "overwriting session environment to ${2}" -v target_scwrypts_env
|
||||
|
||||
[ ${ENV_NAME} ] && echo.debug 'overwriting session environment'
|
||||
|
||||
ENV_NAME="$2"
|
||||
echo.status "using CLI environment '${ENV_NAME}'"
|
||||
target_scwrypts_env="${2}"
|
||||
;;
|
||||
|
||||
##########################################
|
||||
|
||||
( -- ) shift 1; break ;; # pass arguments after '--' to the scwrypt
|
||||
( --* ) echo.error "unrecognized argument '$1'" ;;
|
||||
( * ) SEARCH_PATTERNS+=($1) ;;
|
||||
( --* ) echo.error "unrecognized argument '${1}'" ;;
|
||||
( * ) search_patterns+=(${1}) ;;
|
||||
esac
|
||||
[[ ${SHIFT_COUNT} -le $# ]] \
|
||||
&& shift ${SHIFT_COUNT} \
|
||||
|| echo.error "missing argument for '$1'" \
|
||||
|| shift $# \
|
||||
;
|
||||
|
||||
shift "${_s}" 2>/dev/null || echo.error "missing argument for '${1}'" || shift "${#}"
|
||||
done
|
||||
|
||||
[ ${SCWRYPTS_OUTPUT_FORMAT} ] || export SCWRYPTS_OUTPUT_FORMAT=pretty
|
||||
[[ "${SCWRYPTS_OUTPUT_FORMAT}" ]] || export SCWRYPTS_OUTPUT_FORMAT=pretty
|
||||
|
||||
[ ${SEARCH_NAME} ] && {
|
||||
[ ${SEARCH_TYPE} ] || echo.error '--name requires --type argument'
|
||||
[ ${SEARCH_GROUP} ] || echo.error '--name requires --group argument'
|
||||
[[ "${search_name}" ]] && {
|
||||
[[ "${search_type}" ]] || echo.error '--name requires --type argument'
|
||||
[[ "${search_group}" ]] || echo.error '--name requires --group argument'
|
||||
}
|
||||
|
||||
utils.check-errors --fail
|
||||
@@ -243,283 +241,336 @@ use scwrypts/get-runstring
|
||||
### scwrypts selection / filtering ##################################
|
||||
#####################################################################
|
||||
|
||||
local SCWRYPTS_AVAILABLE=$(scwrypts.list-available)
|
||||
local scwrypts_available="$(scwrypts.list-available)"
|
||||
|
||||
##########################################
|
||||
local scwrypts_available_header="$(echo "${scwrypts_available}" | head -n1)"
|
||||
local scwrypts_available="$(echo "${scwrypts_available}" | sed '1d')"
|
||||
|
||||
[ ${SEARCH_NAME} ] && SCWRYPTS_AVAILABLE=$({
|
||||
echo ${SCWRYPTS_AVAILABLE} | head -n1
|
||||
echo ${SCWRYPTS_AVAILABLE} | utils.colors.remove | grep "^${SEARCH_NAME} *${SEARCH_TYPE} *${SEARCH_GROUP}\$"
|
||||
}) || {
|
||||
[ ${SEARCH_TYPE} ] && {
|
||||
SCWRYPTS_AVAILABLE=$(\
|
||||
{
|
||||
echo ${SCWRYPTS_AVAILABLE} | head -n1
|
||||
echo ${SCWRYPTS_AVAILABLE} | grep ' [^/]*'${SEARCH_TYPE}'[^/]* '
|
||||
} \
|
||||
[[ "${search_name}" ]] && scwrypts_available="$({
|
||||
echo "${scwrypts_available}" | utils.colors.remove | grep -- "^${search_name} *${search_type} *${search_group}\$"
|
||||
})" || {
|
||||
[[ "${search_type}" ]] && scwrypts_available="$(\
|
||||
echo "${scwrypts_available}" \
|
||||
| grep -- ' [^/]*'${search_type}'[^/]* ' \
|
||||
| sed 's/ \+$/'$(utils.colors.reset)'/; s/ \+/^/g' \
|
||||
| column -ts '^'
|
||||
)
|
||||
}
|
||||
| column -ts '^' \
|
||||
)"
|
||||
|
||||
[ ${SEARCH_GROUP} ] && {
|
||||
SCWRYPTS_AVAILABLE=$(
|
||||
{
|
||||
echo ${SCWRYPTS_AVAILABLE} | head -n1
|
||||
echo ${SCWRYPTS_AVAILABLE} | grep "${SEARCH_GROUP}"'[^/ ]*$'
|
||||
} \
|
||||
[[ "${search_group}" ]] && scwrypts_available="$(
|
||||
echo "${scwrypts_available}" \
|
||||
| grep -- "${search_group}"'[^/ ]*$' \
|
||||
| sed 's/ \+$/'$(utils.colors.reset)'/; s/ \+/^/g' \
|
||||
| column -ts '^'
|
||||
)
|
||||
}
|
||||
| column -ts '^' \
|
||||
)"
|
||||
|
||||
[[ ${#SEARCH_PATTERNS[@]} -gt 0 ]] && {
|
||||
POTENTIAL_ERROR+="\n PATTERNS : ${SEARCH_PATTERNS}"
|
||||
local P
|
||||
for P in ${SEARCH_PATTERNS[@]}
|
||||
[[ "${#search_patterns[@]}" -gt 0 ]] && {
|
||||
local search_pattern
|
||||
for search_pattern in "${search_patterns[@]}"
|
||||
do
|
||||
SCWRYPTS_AVAILABLE=$(
|
||||
{
|
||||
echo ${SCWRYPTS_AVAILABLE} | head -n1
|
||||
echo ${SCWRYPTS_AVAILABLE} | grep ${P}
|
||||
}
|
||||
)
|
||||
scwrypts_available="$(echo "${scwrypts_available}" | grep -- "${search_pattern}")"
|
||||
done
|
||||
unset search_pattern
|
||||
}
|
||||
}
|
||||
|
||||
[[ $(echo ${SCWRYPTS_AVAILABLE} | wc -l) -lt 2 ]] && {
|
||||
utils.fail 1 "$(echo "
|
||||
no such scwrypt exists
|
||||
NAME : '${SEARCH_NAME}'
|
||||
TYPE : '${SEARCH_TYPE}'
|
||||
GROUP : '${SEARCH_GROUP}'
|
||||
PATTERNS : '${SEARCH_PATTERNS}'
|
||||
" | sed "1d; \$d; /''$/d")"
|
||||
}
|
||||
[[ $(echo "${scwrypts_available}" | wc -l) -gt 0 ]] \
|
||||
|| echo.error "$(echo "
|
||||
no such scwrypt exists
|
||||
NAME : '${search_name}'
|
||||
TYPE : '${search_type}'
|
||||
GROUP : '${search_group}'
|
||||
PATTERNS : '${search_patterns[@]}'
|
||||
" | sed "1d; \$d; /''$/d")" \
|
||||
|| return 1
|
||||
|
||||
##########################################
|
||||
|
||||
[[ $(echo ${SCWRYPTS_AVAILABLE} | wc -l) -eq 2 ]] \
|
||||
&& SCWRYPT_SELECTION=$(echo ${SCWRYPTS_AVAILABLE} | tail -n1) \
|
||||
|| SCWRYPT_SELECTION=$(echo ${SCWRYPTS_AVAILABLE} | utils.fzf "select a script to run" --header-lines 1) \
|
||||
[[ "$(echo "${scwrypts_available}" | wc -l)" -eq 1 ]] \
|
||||
&& scwrypt_selection="$(echo "${scwrypts_available}" | tail -n1)" \
|
||||
|| scwrypt_selection="$(echo "${scwrypts_available_header}\n${scwrypts_available}" | utils.fzf "select a script to run" --header-lines 1)" \
|
||||
;
|
||||
|
||||
[ ${SCWRYPT_SELECTION} ] || utils.abort
|
||||
[[ "${scwrypt_selection}" ]] || utils.abort
|
||||
|
||||
##########################################
|
||||
|
||||
() {
|
||||
set -- $(echo $@ | utils.colors.remove)
|
||||
export SCWRYPT_NAME=$1
|
||||
export SCWRYPT_TYPE=$2
|
||||
export SCWRYPT_GROUP=$3
|
||||
} ${SCWRYPT_SELECTION}
|
||||
|
||||
echo "${scwrypt_selection}" | utils.colors.remove | read -r SCWRYPT_NAME SCWRYPT_TYPE SCWRYPT_GROUP
|
||||
export SCWRYPT_NAME SCWRYPT_TYPE SCWRYPT_GROUP
|
||||
|
||||
#####################################################################
|
||||
### environment variables and configuration validation ##############
|
||||
#####################################################################
|
||||
|
||||
local ENV_REQUIRED=true \
|
||||
&& [ ! ${CI} ] \
|
||||
&& [[ ! ${SCWRYPT_NAME} =~ scwrypts/logs ]] \
|
||||
&& [[ ! ${SCWRYPT_NAME} =~ scwrypts/environment ]] \
|
||||
|| ENV_REQUIRED=false
|
||||
local env_required \
|
||||
&& [[ ! "${CI}" ]] \
|
||||
&& [[ ! "${SCWRYPT_NAME}" =~ scwrypts/logs ]] \
|
||||
&& [[ ! "${SCWRYPT_NAME}" =~ scwrypts/environment ]] \
|
||||
&& env_required=true \
|
||||
|| env_required=false \
|
||||
;
|
||||
|
||||
local REQUIRED_ENVIRONMENT_REGEX="$(scwrypts.config.group ${SCWRYPT_GROUP} required_environment_regex)"
|
||||
local required_environment_regex="$(scwrypts.config.group "${SCWRYPT_GROUP}" required_environment_regex)"
|
||||
|
||||
[ ${ENV_NAME} ] && [ ${REQUIRED_ENVIRONMENT_REGEX} ] && {
|
||||
[[ ${ENV_NAME} =~ ${REQUIRED_ENVIRONMENT_REGEX} ]] \
|
||||
|| utils.fail 5 "group '${SCWRYPT_GROUP}' requires current environment name to match '${REQUIRED_ENVIRONMENT_REGEX}' (currently ${ENV_NAME})"
|
||||
[[ "${target_scwrypts_env}" ]] && [[ "${required_environment_regex}" ]] && {
|
||||
[[ "${target_scwrypts_env}" =~ "${required_environment_regex}" ]] \
|
||||
|| echo.error "group '${SCWRYPT_GROUP}' requires current environment name to match '${required_environment_regex}' (currently ${target_scwrypts_env})" \
|
||||
|| return 5
|
||||
}
|
||||
|
||||
[[ ${ENV_REQUIRED} =~ true ]] && {
|
||||
[ ! ${ENV_NAME} ] && {
|
||||
scwrypts.environment.init \
|
||||
|| echo.error "failed to initialize scwrypts environments (see above)" \
|
||||
|| return 1 \
|
||||
case "${env_required}" in
|
||||
( false ) ;;
|
||||
( true )
|
||||
[[ "${target_scwrypts_env}" ]] || {
|
||||
scwrypts.environment.init \
|
||||
|| echo.error "failed to initialize scwrypts environments (see above)" \
|
||||
|| return 1
|
||||
|
||||
target_scwrypts_env="$(scwrypts.environment.select-env)"
|
||||
[[ "${target_scwrypts_env}" ]] || echo.error.user-abort || return 1
|
||||
}
|
||||
|
||||
local group required_group_regex
|
||||
for group in ${SCWRYPTS_GROUPS[@]}
|
||||
do
|
||||
required_group_regex="$(scwrypts.config.group ${group} required_environment_regex)"
|
||||
[[ "${required_group_regex}" ]] && {
|
||||
[[ "${target_scwrypts_env}" =~ ${required_group_regex} ]] || continue
|
||||
}
|
||||
|
||||
for f in $(find "$(scwrypts.config.group ${group} root)/.config/static" -type f 2>/dev/null)
|
||||
do
|
||||
source "${f}" || echo.error "invalid static config '${f}'" || return 5
|
||||
done
|
||||
done
|
||||
unset group required_group_regex
|
||||
;;
|
||||
esac
|
||||
|
||||
export SCWRYPTS_ENV="${target_scwrypts_env}"
|
||||
|
||||
##########################################
|
||||
|
||||
[[ ! "${SUBSCWRYPT}" ]] && export SUBSCWRYPT=0
|
||||
|
||||
local scwrypts_execution_warning_message
|
||||
case "${SCWRYPTS_INSTALLATION_TYPE}" in
|
||||
( manual )
|
||||
local sync_with_main_required \
|
||||
&& [[ "${SUBSCWRYPT}" -eq 0 ]] \
|
||||
&& [[ "${SCWRYPTS_ENV}" =~ prod ]] \
|
||||
&& [[ "${SCWRYPTS_LOG_LEVEL}" -gt 0 ]] \
|
||||
&& sync_with_main_required=true \
|
||||
|| sync_with_main_required=false \
|
||||
;
|
||||
|
||||
ENV_NAME=$(scwrypts.environment.select-env)
|
||||
[ "${ENV_NAME}" ] || user.abort
|
||||
}
|
||||
case "${sync_with_main_required}" in
|
||||
( false ) ;;
|
||||
( true )
|
||||
echo.status "on '${SCWRYPTS_ENV}'; checking diff against origin/main"
|
||||
|
||||
for GROUP in ${SCWRYPTS_GROUPS[@]}
|
||||
do
|
||||
local REQUIRED_REGEX="$(scwrypts.config.group ${GROUP} required_environment_regex)"
|
||||
[ ${REQUIRED_REGEX} ] && {
|
||||
[[ ${ENV_NAME} =~ ${REQUIRED_REGEX} ]] || continue
|
||||
}
|
||||
|
||||
for f in $(find "$(scwrypts.config.group ${GROUP} root)/.config/static" -type f 2>/dev/null)
|
||||
do
|
||||
source "${f}" || utils.fail 5 "invalid static config '${f}'"
|
||||
done
|
||||
done
|
||||
}
|
||||
|
||||
[ ${REQUIRED_ENVIRONMENT_REGEX} ] && {
|
||||
[[ ${ENV_NAME} =~ ${REQUIRED_ENVIRONMENT_REGEX} ]] \
|
||||
|| utils.fail 5 "group '${SCWRYPT_GROUP}' requires current environment name to match '${REQUIRED_ENVIRONMENT_REGEX}' (currently ${ENV_NAME})"
|
||||
}
|
||||
|
||||
export SCWRYPTS_ENV=${ENV_NAME}
|
||||
|
||||
##########################################
|
||||
|
||||
[ ! ${SUBSCWRYPT} ] && export SUBSCWRYPT=0
|
||||
|
||||
[[ ${SCWRYPTS_INSTALLATION_TYPE} =~ ^manual$ ]] && {
|
||||
[[ ${SUBSCWRYPT} -eq 0 ]] && [[ ${SCWRYPTS_ENV} =~ prod ]] && [[ ${SCWRYPTS_LOG_LEVEL} -gt 0 ]] && {
|
||||
echo.status "on '${SCWRYPTS_ENV}'; checking diff against origin/main"
|
||||
|
||||
local WARNING_MESSAGE
|
||||
|
||||
[ ! ${WARNING_MESSAGE} ] && {
|
||||
GIT_SCWRYPTS fetch --quiet origin main \
|
||||
|| WARNING_MESSAGE='I am unable to verify your scwrypts version'
|
||||
}
|
||||
|
||||
[ ! ${WARNING_MESSAGE} ] && {
|
||||
GIT_SCWRYPTS diff --exit-code origin/main -- . >/dev/null 2>&1 \
|
||||
|| WARNING_MESSAGE='your scwrypts is currently out-of-date'
|
||||
}
|
||||
|
||||
[ ${WARNING_MESSAGE} ] && {
|
||||
[[ ${SCWRYPTS_LOG_LEVEL} -lt 3 ]] && {
|
||||
echo.reminder "you are running in $(utils.colors.bright-red)production$(utils.colors.bright-magenta) and ${WARNING_MESSAGE}"
|
||||
} || {
|
||||
GIT_SCWRYPTS diff --exit-code origin/main -- . >&2
|
||||
echo.warning "you are trying to run in $(utils.colors.bright-red)production$(echo.warning.color) but ${WARNING_MESSAGE} (relevant diffs and errors above)"
|
||||
yN 'continue?' || {
|
||||
echo.reminder "you can use 'scwrypts --update' to quickly update scwrypts to latest"
|
||||
user.abort
|
||||
[[ ! "${scwrypts_execution_warning_message}" ]] && {
|
||||
git.scwrypts fetch --quiet origin main &>/dev/null \
|
||||
|| scwrypts_execution_warning_message='I am unable to verify your scwrypts version'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[ ! "${scwrypts_execution_warning_message}" ]] && {
|
||||
git.scwrypts diff --exit-code origin/main -- . &>/dev/null \
|
||||
|| scwrypts_execution_warning_message='your scwrypts is currently out-of-date'
|
||||
}
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
|
||||
[[ "${scwrypts_execution_warning_message}" ]] && {
|
||||
case "${SCWRYPTS_LOG_LEVEL}" in
|
||||
( 0 | 1 | 2 )
|
||||
echo.warning "
|
||||
you are running in $(utils.colors.bright-red)production$(echo.warning.color),
|
||||
and ${scwrypts_execution_warning_message}
|
||||
"
|
||||
;;
|
||||
( * )
|
||||
git.scwrypts diff --exit-code origin/main -- . >&2
|
||||
echo.warning "you are trying to run in $(utils.colors.bright-red)production$(echo.warning.color) but ${scwrypts_execution_warning_message} (relevant diffs and errors above)"
|
||||
yN 'continue?' || echo.error.user-abort || return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
##########################################
|
||||
|
||||
local RUN_STRING=$(scwrypts.get-runstring ${SCWRYPT_NAME} ${SCWRYPT_TYPE} ${SCWRYPT_GROUP})
|
||||
[ "${RUN_STRING}" ] || return 42
|
||||
local scwrypts_run_string="$(scwrypts.get-runstring "${SCWRYPT_NAME}" "${SCWRYPT_TYPE}" "${SCWRYPT_GROUP}")"
|
||||
[[ "${scwrypts_run_string}" ]] || return 42
|
||||
|
||||
|
||||
#####################################################################
|
||||
### logging and pretty header/footer setup ##########################
|
||||
#####################################################################
|
||||
|
||||
local RUN_MODE=normal
|
||||
[[ ${SCWRYPT_NAME} =~ interactive ]] && RUN_MODE=interactive
|
||||
local scwrypts_execution_mode=normal
|
||||
[[ "${SCWRYPT_NAME}" =~ interactive ]] && scwrypts_execution_mode=interactive
|
||||
|
||||
local LOGFILE \
|
||||
&& [[ ${RUN_MODE} =~ normal ]] \
|
||||
&& [[ ${SCWRYPTS_LOG_LEVEL} -gt 0 ]] \
|
||||
&& [[ ${SUBSCWRYPT} -eq 0 ]] \
|
||||
&& [[ ! ${SCWRYPT_NAME} =~ scwrypts/logs ]] \
|
||||
&& LOGFILE="${SCWRYPTS_LOG_PATH}/$(echo ${GROUP}/${TYPE}/${NAME} | sed 's/^\.\///; s/\//\%/g').log" \
|
||||
&& [[ "${scwrypts_execution_mode}" =~ normal ]] \
|
||||
&& [[ "${SCWRYPTS_LOG_LEVEL}" -gt 0 ]] \
|
||||
&& [[ "${SUBSCWRYPT}" -eq 0 ]] \
|
||||
&& [[ ! "${SCWRYPT_NAME}" =~ scwrypts/logs ]] \
|
||||
&& LOGFILE="${SCWRYPTS_LOG_PATH}/$(echo ${SCWRYPT_GROUP}/${SCWRYPT_TYPE}/${SCWRYPT_NAME} | sed 's/^\.\///; s/\//\%/g').log" \
|
||||
|| LOGFILE='/dev/null' \
|
||||
;
|
||||
|
||||
local HEADER FOOTER
|
||||
[[ ${SCWRYPTS_LOG_LEVEL} -ge 2 ]] && {
|
||||
case ${SCWRYPTS_OUTPUT_FORMAT} in
|
||||
scwrypts.runner.header-and-footer() {
|
||||
local output_type="${1}"
|
||||
case "${output_type}" in
|
||||
( header | footer ) ;;
|
||||
( * ) return 1 ;;
|
||||
esac
|
||||
|
||||
local header footer
|
||||
|
||||
[[ "${SCWRYPTS_LOG_LEVEL}" -ge 3 ]] && case "${SCWRYPTS_OUTPUT_FORMAT}" in
|
||||
( raw )
|
||||
HEADER="--- start scwrypt ${SCWRYPT_GROUP}/${SCWRYPT_TYPE} ${SCWRYPT_NAME} in ${SCWRYPTS_ENV} ---"
|
||||
FOOTER="--- end scwrypt ---"
|
||||
header="--- start scwrypt ${SCWRYPT_GROUP}/${SCWRYPT_TYPE} ${SCWRYPT_NAME} in ${SCWRYPTS_ENV} ---"
|
||||
footer="--- end scwrypt ---"
|
||||
;;
|
||||
|
||||
( pretty )
|
||||
HEADER=$(
|
||||
header=$(
|
||||
echo "
|
||||
=====================================================================
|
||||
scwrypt : ${SCWRYPT_GROUP} ${SCWRYPT_TYPE} ${SCWRYPT_NAME}
|
||||
run at : $(date)
|
||||
config : ${SCWRYPTS_ENV}
|
||||
log level : ${SCWRYPTS_LOG_LEVEL}
|
||||
log level : ${SCWRYPTS_LOG_LEVEL} (${SCWRYPTS_LOG_LEVELS[${SCWRYPTS_LOG_LEVEL}]})
|
||||
$(utils.colors.print bright-yellow '--- SCWRYPT BEGIN ---------------------------------------------------')
|
||||
" | sed 's/^\s\+//; 1d'
|
||||
)
|
||||
|
||||
FOOTER="$(utils.colors.print bright-yellow '--- SCWRYPT END ---------------------------------------------------')"
|
||||
footer="$(utils.colors.print bright-yellow '--- SCWRYPT END ---------------------------------------------------')"
|
||||
;;
|
||||
|
||||
( json )
|
||||
HEADER=$(echo '{}' | jq -c ".
|
||||
| .timestamp = \"$(date +%s)\"
|
||||
| .runtime = \"${SCWRYPTS_RUNTIME_ID}\"
|
||||
| .scwrypt = \"start of ${SCWRYPT_NAME} ${SCWRYPT_GROUP} ${SCWRYPT_TYPE}\"
|
||||
| .config = \"${SCWRYPTS_ENV}\"
|
||||
| .logLevel = \"${SCWRYPTS_LOG_LEVEL}\"
|
||||
| .subscwrypt = ${SUBSCWRYPT}
|
||||
")
|
||||
header=$(jo \
|
||||
timestamp="$(date +%s)" \
|
||||
config="${SCWRYPTS_ENV}" \
|
||||
logLevel="${SCWRYPTS_LOG_LEVEL}" \
|
||||
subscwrypt="${SUBSCWRYPT}" \
|
||||
runtime="${SCWRYPTS_RUNTIME_ID}" \
|
||||
scwryptName="${SCWRYPT_NAME}" \
|
||||
scwryptGroup="${SCWRYPT_GROUP}" \
|
||||
scwryptType="${SCWRYPT_TYPE}" \
|
||||
message="start of execution" \
|
||||
)
|
||||
|
||||
footer=$(jo \
|
||||
timestamp="$(date +%s)" \
|
||||
config="${SCWRYPTS_ENV}" \
|
||||
logLevel="${SCWRYPTS_LOG_LEVEL}" \
|
||||
subscwrypt="${SUBSCWRYPT}" \
|
||||
runtime="${SCWRYPTS_RUNTIME_ID}" \
|
||||
scwryptName="${SCWRYPT_NAME}" \
|
||||
scwryptGroup="${SCWRYPT_GROUP}" \
|
||||
scwryptType="${SCWRYPT_TYPE}" \
|
||||
message="end of execution" \
|
||||
)
|
||||
;;
|
||||
|
||||
( logfmt )
|
||||
header="ts=$(date -u +%Y-%m-%dT%H:%M:%SZ) level=status runtime=${SCWRYPTS_RUNTIME_ID} config=${SCWRYPTS_ENV} logLevel=${SCWRYPTS_LOG_LEVEL} subscwrypt=${SUBSCWRYPT} scwryptName=${SCWRYPT_NAME} scwryptGroup=${SCWRYPT_GROUP} scwryptType=${SCWRYPT_TYPE} msg=\"start of execution\""
|
||||
footer="ts=$(date -u +%Y-%m-%dT%H:%M:%SZ) level=status runtime=${SCWRYPTS_RUNTIME_ID} config=${SCWRYPTS_ENV} logLevel=${SCWRYPTS_LOG_LEVEL} subscwrypt=${SUBSCWRYPT} scwryptName=${SCWRYPT_NAME} scwryptGroup=${SCWRYPT_GROUP} scwryptType=${SCWRYPT_TYPE} msg=\"end of execution\""
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
[[ ${SUBSCWRYPT} -eq 0 ]] || {
|
||||
case ${SCWRYPTS_OUTPUT_FORMAT} in
|
||||
[[ "${SUBSCWRYPT}" -ne 0 ]] && case "${SCWRYPTS_OUTPUT_FORMAT}" in
|
||||
( raw )
|
||||
header="--- (${SUBSCWRYPT}) begin ${SCWRYPT_GROUP} ${SCWRYPT_TYPE} ${SCWRYPT_NAME} ---"
|
||||
footer="--- (${SUBSCWRYPT}) end ${SCWRYPT_GROUP} ${SCWRYPT_TYPE} ${SCWRYPT_NAME} ---"
|
||||
;;
|
||||
|
||||
( pretty )
|
||||
HEADER="$(utils.colors.print yellow "--- (${SUBSCWRYPT}) BEGIN ${SCWRYPT_GROUP} ${SCWRYPT_TYPE} ${SCWRYPT_NAME} ---")"
|
||||
FOOTER="$(utils.colors.print yellow "--- (${SUBSCWRYPT}) END ${SCWRYPT_GROUP} ${SCWRYPT_TYPE} ${SCWRYPT_NAME} ---")"
|
||||
header="$(utils.colors.print yellow "--- (${SUBSCWRYPT}) BEGIN ${SCWRYPT_GROUP} ${SCWRYPT_TYPE} ${SCWRYPT_NAME} ---")"
|
||||
footer="$(utils.colors.print yellow "--- (${SUBSCWRYPT}) END ${SCWRYPT_GROUP} ${SCWRYPT_TYPE} ${SCWRYPT_NAME} ---")"
|
||||
;;
|
||||
|
||||
( json )
|
||||
header="$(jo \
|
||||
timestamp="$(date +%s)" \
|
||||
subscwryptLevel="${SUBSCWRYPT}" \
|
||||
runtime="${SCWRYPTS_RUNTIME_ID}" \
|
||||
scwryptName="${SCWRYPT_NAME}" \
|
||||
scwryptGroup="${SCWRYPT_GROUP}" \
|
||||
scwryptType="${SCWRYPT_TYPE}" \
|
||||
message='start of subscwrypt execution' \
|
||||
)"
|
||||
|
||||
footer="$(jo \
|
||||
timestamp="$(date +%s)" \
|
||||
subscwryptLevel="${SUBSCWRYPT}" \
|
||||
runtime="${SCWRYPTS_RUNTIME_ID}" \
|
||||
scwryptName="${SCWRYPT_NAME}" \
|
||||
scwryptGroup="${SCWRYPT_GROUP}" \
|
||||
scwryptType="${SCWRYPT_TYPE}" \
|
||||
message='end of subscwrypt execution' \
|
||||
)"
|
||||
;;
|
||||
|
||||
( logfmt )
|
||||
header="ts=$(date -u +%Y-%m-%dT%H:%M:%SZ) level=status runtime=${SCWRYPTS_RUNTIME_ID} config=${SCWRYPTS_ENV} logLevel=${SCWRYPTS_LOG_LEVEL} subscwrypt=${SUBSCWRYPT} scwryptName=${SCWRYPT_NAME} scwryptGroup=${SCWRYPT_GROUP} scwryptType=${SCWRYPT_TYPE} msg=\"start of subscwrypt execution\""
|
||||
footer="ts=$(date -u +%Y-%m-%dT%H:%M:%SZ) level=status runtime=${SCWRYPTS_RUNTIME_ID} config=${SCWRYPTS_ENV} logLevel=${SCWRYPTS_LOG_LEVEL} subscwrypt=${SUBSCWRYPT} scwryptName=${SCWRYPT_NAME} scwryptGroup=${SCWRYPT_GROUP} scwryptType=${SCWRYPT_TYPE} msg=\"end of subscwrypt execution\""
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
[[ "${(P)output_type}" ]] && echo "${(P)output_type}"
|
||||
return 0
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
### run the scwrypt #################################################
|
||||
#####################################################################
|
||||
|
||||
set -o pipefail
|
||||
{
|
||||
[[ ${SCWRYPTS_LOG_LEVEL} -ge 2 ]] && __SCWRYPTS_PRINT_EXIT_CODE=true
|
||||
local print_exit_code=false
|
||||
[[ "${SCWRYPTS_LOG_LEVEL}" -ge 3 ]] && print_exit_code=true
|
||||
|
||||
[ ${HEADER} ] && echo ${HEADER} >&2
|
||||
scwrypts.runner.header-and-footer header >&2
|
||||
|
||||
(
|
||||
case ${RUN_MODE} in
|
||||
( normal )
|
||||
eval "${RUN_STRING} $(printf "%q " "$@")"
|
||||
;;
|
||||
( interactive )
|
||||
eval "${RUN_STRING} $(printf "%q " "$@")" </dev/tty &>/dev/tty
|
||||
;;
|
||||
esac
|
||||
)
|
||||
case "${scwrypts_execution_mode}" in
|
||||
( normal ) eval "${scwrypts_run_string} ${(q)@}" ;;
|
||||
( interactive ) eval "${scwrypts_run_string} ${(q)@}" </dev/tty &>/dev/tty ;;
|
||||
esac
|
||||
|
||||
EXIT_CODE=$?
|
||||
local exit_code="${?}"
|
||||
echo "${exit_code}" > "${EXIT_CODE_FILE}"
|
||||
|
||||
[ ${FOOTER} ] && echo ${FOOTER} >&2
|
||||
scwrypts.runner.header-and-footer footer >&2
|
||||
|
||||
[[ ${__SCWRYPTS_PRINT_EXIT_CODE} =~ true ]] && {
|
||||
EXIT_COLOR=$( [[ ${EXIT_CODE} -eq 0 ]] && utils.colors.bright-green || utils.colors.bright-red )
|
||||
case ${SCWRYPTS_OUTPUT_FORMAT} in
|
||||
( raw )
|
||||
echo "terminated with code ${EXIT_CODE}" >&2
|
||||
;;
|
||||
[[ ${print_exit_code} =~ true ]] && {
|
||||
local exit_color="$([[ ${exit_code} -eq 0 ]] && utils.colors.bright-green || utils.colors.bright-red)"
|
||||
case "${SCWRYPTS_OUTPUT_FORMAT}" in
|
||||
( pretty )
|
||||
echo "terminated with ${EXIT_COLOR}code ${EXIT_CODE}$(utils.colors.reset)" >&2
|
||||
echo "terminated with ${exit_color}code ${exit_code}$(utils.colors.reset)" >&2
|
||||
;;
|
||||
( json )
|
||||
[[ ${EXIT_CODE} =~ 0 ]] \
|
||||
&& echo.success --force-print "terminated with code ${EXIT_CODE}" \
|
||||
|| echo.error --force-print "terminated with code ${EXID_CODE}" \
|
||||
|
||||
( * )
|
||||
[[ "${exit_code}" -eq 0 ]] \
|
||||
&& echo.success --force-print "terminated with code ${exit_code}" \
|
||||
|| echo.error --force-print "terminated with code ${exit_code}" \
|
||||
;
|
||||
;;
|
||||
esac
|
||||
}
|
||||
} > >(tee --append "${LOGFILE}") 2> >(tee --append "${LOGFILE}" >&2)
|
||||
} "${@}"
|
||||
|
||||
return ${EXIT_CODE}
|
||||
} | tee --append "${LOGFILE}"
|
||||
} $@
|
||||
EXIT_CODE="${?}"
|
||||
[[ -f "${EXIT_CODE_FILE}" ]] && EXIT_CODE="$(cat -- "${EXIT_CODE_FILE}")"
|
||||
|
||||
EXIT_CODE=$?
|
||||
[[ "${SCWRYPTS_TEMP_PATH}" ]] && [[ -d "${SCWRYPTS_TEMP_PATH}" ]] && {
|
||||
rm -- $(find "${SCWRYPTS_TEMP_PATH}" -mindepth 1 -maxdepth 1 -type f)
|
||||
rmdir "${SCWRYPTS_TEMP_PATH}"
|
||||
} &>/dev/null
|
||||
|
||||
[ "${SCWRYPTS_TEMP_PATH}" ] && [ -d "${SCWRYPTS_TEMP_PATH}" ] \
|
||||
&& {
|
||||
rm -- $(find "${SCWRYPTS_TEMP_PATH}" -mindepth 1 -maxdepth 1 -type f)
|
||||
rmdir "${SCWRYPTS_TEMP_PATH}"
|
||||
} &>/dev/null
|
||||
[[ -f "${EXIT_CODE_FILE}" ]] && rm "${EXIT_CODE_FILE}"
|
||||
|
||||
return ${EXIT_CODE}
|
||||
return "${EXIT_CODE}"
|
||||
|
||||
@@ -84,7 +84,7 @@ readonly ${scwryptsgroup}__color=$(utils.colors.green)
|
||||
#
|
||||
|
||||
|
||||
readonly ${scwryptsgroup}__zshlibrary=
|
||||
#readonly ${scwryptsgroup}__zshlibrary=
|
||||
#
|
||||
# ${scwryptsgroup}__zshlibrary (optional) (default = *see below*)
|
||||
#
|
||||
|
||||
+3
-3
@@ -66,15 +66,15 @@ Of the `echo` family, there are two unique functions:
|
||||
|
||||
#### Yes / No Prompts
|
||||
|
||||
The two helpers `utils.Yn` and `utils.yN` take a user-friendly yes/no question as an argument.
|
||||
The two helpers `user.Yn` and `user.yN` take a user-friendly yes/no question as an argument.
|
||||
|
||||
- when the user responds "yes", the command returns 0 / success / `&&`
|
||||
- when the user responds "no", the command returns 1 / error / `||`
|
||||
- when the user responds with _nothing_ (e.g. just presses enter), the _default_ is used
|
||||
|
||||
The two commands work identically; however, the capitalization denotes the default:
|
||||
- `utils.Yn` = default "yes"
|
||||
- `utils.yN` = default "no"
|
||||
- `user.Yn` = default "yes"
|
||||
- `user.yN` = default "no"
|
||||
|
||||
#### Select from a List Prompt
|
||||
|
||||
|
||||
@@ -14,13 +14,13 @@ MAIN() {
|
||||
eval "$(utils.parse.autosetup)"
|
||||
##########################################
|
||||
|
||||
[ ! -d "${AWS__EFS__LOCAL_MOUNT_POINT}" ] && {
|
||||
[[ -d "${AWS__EFS__LOCAL_MOUNT_POINT}" ]] || {
|
||||
echo.status 'no efs currently mounted'
|
||||
return 0
|
||||
}
|
||||
|
||||
local MOUNTED=$(cd -- "${AWS__EFS__LOCAL_MOUNT_POINT}" | find . -type -f | sed 's|^\./.||')
|
||||
[ "${MOUNTED}" ] && {
|
||||
local MOUNTED=$(cd -- "${AWS__EFS__LOCAL_MOUNT_POINT}" && find . -type f | sed 's|^\./||')
|
||||
[[ "${MOUNTED}" ]] || {
|
||||
echo.status 'no efs currently mounted'
|
||||
return 0
|
||||
}
|
||||
@@ -28,12 +28,12 @@ MAIN() {
|
||||
utils.io.getsudo || return 1
|
||||
|
||||
local SELECTED=$(echo ${MOUNTED} | utils.fzf 'select a file system to unmount')
|
||||
[ "${SELECTED}" ] || user.abort
|
||||
[ "${SELECTED}" ] || echo.error.user-abort || return 1
|
||||
|
||||
local EFS="${AWS__EFS__LOCAL_MOUNT_POINT}/${SELECTED}"
|
||||
echo.status "unmounting '${SELECTED}'"
|
||||
sudo umount "${EFS}" >/dev/null 2>&1
|
||||
sudo umount "${EFS}" &>/dev/null
|
||||
sudo rmdir -- "${EFS}" \
|
||||
&& echo.success "done" \
|
||||
|| utils.fail 2 "failed to unmount '${EFS}'"
|
||||
|| echo.error "failed to unmount '${EFS}'"
|
||||
}
|
||||
|
||||
@@ -8,19 +8,22 @@ use cloud/aws/cli
|
||||
#####################################################################
|
||||
|
||||
RDS__SELECT_DATABASE() {
|
||||
local DATABASES=$(_RDS__GET_AVAILABLE_DATABASES)
|
||||
[ ! $DATABASES ] && utils.fail 1 'no databases available'
|
||||
local DATABASES="$(_RDS__GET_AVAILABLE_DATABASES)"
|
||||
[[ "${DATABASES}" ]] \
|
||||
|| echo.error 'no databases available' \
|
||||
|| return 1
|
||||
|
||||
local ID=$(\
|
||||
echo $DATABASES | jq -r '.instance + " @ " + .cluster' \
|
||||
local ID="$(\
|
||||
echo "${DATABASES}" | jq -r '.instance + " @ " + .cluster' \
|
||||
| utils.fzf 'select a database (instance@cluster)' \
|
||||
)
|
||||
[ ! $ID ] && user.abort
|
||||
)"
|
||||
|
||||
local INSTANCE=$(echo $ID | sed 's/ @ .*$//')
|
||||
local CLUSTER=$(echo $ID | sed 's/^.* @ //')
|
||||
[[ "${ID}" ]] || echo.user-abort || return 1
|
||||
|
||||
echo $DATABASES | jq "select (.instance == \"$INSTANCE\" and .cluster == \"$CLUSTER\")"
|
||||
local INSTANCE="$(echo "${ID}" | sed 's/ @ .*$//')"
|
||||
local CLUSTER="$(echo "${ID}" | sed 's/^.* @ //')"
|
||||
|
||||
echo "${DATABASES}" | jq "select (.instance == \"${INSTANCE}\" and .cluster == \"${CLUSTER}\")"
|
||||
}
|
||||
|
||||
_RDS__GET_AVAILABLE_DATABASES() {
|
||||
@@ -52,19 +55,18 @@ RDS__GET_DATABASE_CREDENTIALS() {
|
||||
shift 1
|
||||
done
|
||||
|
||||
utils.check-errors --fail
|
||||
utils.check-errors || return "${ERRORS}"
|
||||
|
||||
##########################################
|
||||
|
||||
local DATABASE=$(RDS__SELECT_DATABASE)
|
||||
[ ! $DATABASE ] && user.abort
|
||||
local DATABASE="$(RDS__SELECT_DATABASE)"
|
||||
[[ "${DATABASE}" ]] || echo.user-abort || return 1
|
||||
|
||||
DB_HOST="$(echo $DATABASE | jq -r '.host')"
|
||||
[ ! $DB_HOST ] && { echo.error 'unable to find host'; return 2; }
|
||||
DB_HOST="$(echo "${DATABASE}" | jq -r '.host')"
|
||||
[[ "${DB_HOST}" ]] || echo.error 'unable to find host' || return 2
|
||||
|
||||
DB_PORT="$(echo $DATABASE | jq -r '.port')"
|
||||
[ ! $DB_PORT ] && DB_PORT=5432
|
||||
[[ $DB_PORT =~ ^null$ ]] && DB_PORT=5432
|
||||
DB_PORT="$(echo "${DATABASE}" | jq -r '.port' | grep -v ^null$)"
|
||||
[[ "${DB_PORT}" ]] || DB_PORT=5432
|
||||
|
||||
##########################################
|
||||
|
||||
@@ -72,46 +74,46 @@ RDS__GET_DATABASE_CREDENTIALS() {
|
||||
echo "iam\nsecretsmanager\nuser-input" \
|
||||
| utils.fzf 'select an authentication method' \
|
||||
)
|
||||
[ ! $AUTH_METHOD ] && user.abort
|
||||
[[ "${AUTH_METHOD}" ]] || echo.user-abort || return 1
|
||||
|
||||
case $AUTH_METHOD in
|
||||
case "${AUTH_METHOD}" in
|
||||
( iam ) _RDS_AUTH__iam ;;
|
||||
( secretsmanager ) _RDS_AUTH__secretsmanager ;;
|
||||
( user-input ) _RDS_AUTH__userinput ;;
|
||||
esac
|
||||
|
||||
[[ $PRINT_PASSWORD -eq 1 ]] && echo.debug "password : $DB_PASS"
|
||||
[[ "${PRINT_PASSWORD}" -eq 1 ]] && echo.debug "password : ${DB_PASS}"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
_RDS_AUTH__iam() {
|
||||
DB_PASS=$(\
|
||||
DB_PASS="$(\
|
||||
AWS rds generate-db-auth-token \
|
||||
--hostname $DB_HOST \
|
||||
--port $DB_PORT \
|
||||
--username $DB_USER \
|
||||
)
|
||||
--hostname "${DB_HOST}" \
|
||||
--port "${DB_PORT}" \
|
||||
--username "${DB_USER}" \
|
||||
)"
|
||||
}
|
||||
|
||||
_RDS_AUTH__secretsmanager() {
|
||||
local CREDENTIALS=$(_RDS__GET_SECRETSMANAGER_CREDENTIALS)
|
||||
echo $CREDENTIALS | jq -e '.pass' >/dev/null 2>&1 \
|
||||
echo "${CREDENTIALS}" | jq -e '.pass' &>/dev/null \
|
||||
&& DB_PASS="$(echo $CREDENTIALS | jq -r '.pass')"
|
||||
|
||||
echo $CREDENTIALS | jq -e '.password' >/dev/null 2>&1 \
|
||||
echo "${CREDENTIALS}" | jq -e '.password' &>/dev/null \
|
||||
&& DB_PASS="$(echo $CREDENTIALS | jq -r '.password')"
|
||||
|
||||
echo $CREDENTIALS | jq -e '.user' >/dev/null 2>&1 \
|
||||
echo "${CREDENTIALS}" | jq -e '.user' &>/dev/null \
|
||||
&& DB_USER=$(echo $CREDENTIALS | jq -r '.user')
|
||||
|
||||
echo $CREDENTIALS | jq -e '.username' >/dev/null 2>&1 \
|
||||
echo "${CREDENTIALS}" | jq -e '.username' &>/dev/null \
|
||||
&& DB_USER=$(echo $CREDENTIALS | jq -r '.username')
|
||||
|
||||
echo $CREDENTIALS | jq -e '.name' >/dev/null 2>&1 \
|
||||
echo "${CREDENTIALS}" | jq -e '.name' &>/dev/null \
|
||||
&& DB_NAME=$(echo $CREDENTIALS | jq -r '.name')
|
||||
|
||||
echo $CREDENTIALS | jq -e '.dbname' >/dev/null 2>&1 \
|
||||
echo "${CREDENTIALS}" | jq -e '.dbname' &>/dev/null \
|
||||
&& DB_NAME=$(echo $CREDENTIALS | jq -r '.dbname')
|
||||
}
|
||||
|
||||
@@ -121,9 +123,8 @@ _RDS__GET_SECRETSMANAGER_CREDENTIALS() {
|
||||
| jq -r '.[] | .[] | .Name' \
|
||||
| utils.fzf 'select a secret' \
|
||||
)
|
||||
[ ! $ID ] && return 1
|
||||
[[ "${ID}" ]] || return 1
|
||||
|
||||
AWS secretsmanager get-secret-value --secret-id "$ID" \
|
||||
AWS secretsmanager get-secret-value --secret-id "${ID}" \
|
||||
| jq -r '.SecretString' | jq
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ scwrypts.config() {
|
||||
[ $1 ] || return 1
|
||||
|
||||
case $1 in
|
||||
( python.versions ) echo "3.13\n3.12\n3.11\n3.10" ;;
|
||||
( python.versions ) echo "3.14\n3.13\n3.12\n3.11\n3.10" ;;
|
||||
( nodejs.version ) echo "22.0.0" ;;
|
||||
( * ) return 1 ;;
|
||||
esac
|
||||
|
||||
+23
-10
@@ -50,15 +50,28 @@ readonly SCWRYPTS_DATA_PATH="${XDG_DATA_HOME:-${HOME}/.local/share}/scwrypts"
|
||||
readonly SCWRYPTS_STATE_PATH="${XDG_STATE_HOME:-${HOME}/.local/state}/scwrypts"
|
||||
readonly SCWRYPTS_LOG_PATH="${SCWRYPTS_STATE_PATH}/logs"
|
||||
|
||||
[ -d /tmp ] \
|
||||
&& readonly SCWRYPTS_TEMP_PATH="/tmp/scwrypts/${SCWRYPTS_RUNTIME_ID}" \
|
||||
|| readonly SCWRYPTS_TEMP_PATH="${XDG_RUNTIME_DIR:-/run/user/${UID}}/scwrypts/${SCWRYPTS_RUNTIME_ID}" \
|
||||
;
|
||||
if [[ -d /tmp ]]
|
||||
then
|
||||
readonly SCWRYPTS_TEMP_PATH="/tmp/scwrypts/${SCWRYPTS_RUNTIME_ID}"
|
||||
readonly SCWRYPTS_TEMP_PATH_SOCKET="/tmp/scwrypts/socket"
|
||||
else
|
||||
readonly SCWRYPTS_TEMP_PATH="${XDG_RUNTIME_DIR:-/run/user/${UID}}/scwrypts/${SCWRYPTS_RUNTIME_ID}"
|
||||
readonly SCWRYPTS_TEMP_PATH_SOCKET="${XDG_RUNTIME_DIR:-/run/user/${UID}}/scwrypts/socket"
|
||||
fi
|
||||
|
||||
mkdir -p \
|
||||
readonly SCWRYPTS_CACHE_PATH__runtime="${SCWRYPTS_TEMP_PATH}/cache"
|
||||
readonly SCWRYPTS_CACHE_PATH__persistent="${SCWRYPTS_TEMP_PATH_SOCKET}/cache"
|
||||
|
||||
mkdir -p -- \
|
||||
"${SCWRYPTS_CONFIG_PATH}" \
|
||||
"${SCWRYPTS_ENV_PATH}" \
|
||||
"${SCWRYPTS_DATA_PATH}" \
|
||||
"${SCWRYPTS_STATE_PATH}" \
|
||||
"${SCWRYPTS_LOG_PATH}" \
|
||||
"${SCWRYPTS_TEMP_PATH}" \
|
||||
"${SCWRYPTS_TEMP_PATH_SOCKET}" \
|
||||
"${SCWRYPTS_CACHE_PATH__runtime}" \
|
||||
"${SCWRYPTS_CACHE_PATH__persistent}" \
|
||||
;
|
||||
|
||||
DEFAULT_CONFIG="${__SCWRYPTS_ROOT}/zsh/config.user.zsh"
|
||||
@@ -80,9 +93,8 @@ source "${__SCWRYPTS_ROOT}/zsh/config.global.zsh"
|
||||
|
||||
SCWRYPTS_GROUPS=()
|
||||
|
||||
command -v echo.warning &>/dev/null || WARNING() { echo "echo.warning : $@" >&2; }
|
||||
command -v echo.error &>/dev/null || ERROR() { echo "echo.error : $@" >&2; return 1; }
|
||||
command -v utils.fail &>/dev/null || FAIL() { echo.error "${@:2}"; exit $1; }
|
||||
command -v echo.warning &>/dev/null || echo.warning() { echo "echo.warning : $@" >&2; }
|
||||
command -v echo.error &>/dev/null || echo.error() { echo "echo.error : $@" >&2; return 1; }
|
||||
|
||||
__SCWRYPTS_GROUP_LOADERS=(
|
||||
"${__SCWRYPTS_ROOT}/scwrypts.scwrypts.zsh"
|
||||
@@ -146,7 +158,7 @@ do
|
||||
|| echo.warning "error encountered when loading group '${__SCWRYPTS_GROUP_NAME}'" \
|
||||
;
|
||||
|
||||
[[ ! ${__SCWRYPTS_GROUP_NAME} =~ ^scwrypts$ ]] && [ ! "$(scwrypts.config.group ${__SCWRYPTS_GROUP_NAME} zshlibrary)" ] && {
|
||||
[ ! "$(scwrypts.config.group ${__SCWRYPTS_GROUP_NAME} zshlibrary)" ] && {
|
||||
case $(scwrypts.config.group ${__SCWRYPTS_GROUP_NAME} type) in
|
||||
( zsh )
|
||||
[ -d "${scwryptsgrouproot}/lib" ] \
|
||||
@@ -165,7 +177,8 @@ do
|
||||
done
|
||||
|
||||
[[ ${SCWRYPTS_GROUPS[1]} =~ ^scwrypts$ ]] \
|
||||
|| utils.fail 69 "encountered error when loading essential group 'scwrypts'; aborting"
|
||||
|| echo.error "encountered error when loading essential group 'scwrypts'; aborting" \
|
||||
|| exit 69
|
||||
|
||||
#####################################################################
|
||||
### cleanup #########################################################
|
||||
|
||||
@@ -94,7 +94,7 @@ PG_RESTORE() {
|
||||
make sure your database is ready to accept the database file!
|
||||
"
|
||||
|
||||
yN 'continue?' || user.abort
|
||||
yN 'continue?' || echo.error.user-abort || return 1
|
||||
|
||||
PSQL < "$INPUT_FILE"
|
||||
EXIT_CODE=$?
|
||||
|
||||
+23
-20
@@ -3,39 +3,42 @@ use db/postgres
|
||||
#####################################################################
|
||||
|
||||
MAIN() {
|
||||
echo.warning " \nthis function is in a beta state\n "
|
||||
echo.warning "---\nthis function is in a beta state\n---"
|
||||
|
||||
local _PASS _ARGS=()
|
||||
POSTGRES__SET_LOGIN_ARGS $@
|
||||
POSTGRES__SET_LOGIN_ARGS "${@}"
|
||||
|
||||
local INPUT_FILE="$FILENAME"
|
||||
local input_file="${FILENAME}"
|
||||
|
||||
local SQL_DIR="$SCWRYPTS_DATA_PATH/sql"
|
||||
[ ! -d $SQL_DIR ] && mkdir -p $SQL_DIR
|
||||
local sql_path="${SCWRYPTS_DATA_PATH}/sql"
|
||||
mkdir -p -- "${sql_path}"
|
||||
|
||||
cd $SQL_DIR
|
||||
cd -- "${sql_path}"
|
||||
|
||||
[[ $(ls "*.sql" 2>&1 | wc -l) -eq 0 ]] && {
|
||||
[[ "$(find . -type f -name \*.sql 2>/dev/null | wc -l)" -eq 0 ]] && {
|
||||
echo.error "you haven't made any SQL commands yet"
|
||||
echo.reminder "add '.sql' files here: '$SQL_DIR/'"
|
||||
echo.reminder "add '.sql' files here: '${sql_path}/'"
|
||||
return 1
|
||||
}
|
||||
|
||||
[ ! $INPUT_FILE ] && INPUT_FILE=$(utils.fzf 'select a sql file to run')
|
||||
[ ! $INPUT_FILE ] && user.abort
|
||||
[[ "${input_file}" ]] || input_file="$(utils.fzf.file-select --prompt 'select a sql file to run' "${sql_path}")"
|
||||
[[ "${input_file}" ]] || echo.error.user-abort || return 2
|
||||
|
||||
[ ! -f "$INPUT_FILE" ] && utils.fail 2 "no such sql file '$SQL_DIR/$INPUT_FILE'"
|
||||
[[ -f "${input_file}" ]] \
|
||||
|| echo.error "no such sql file '${input_file}'" -v sql_path \
|
||||
|| return 2
|
||||
|
||||
echo.status "loading '$INPUT_FILE' preview..."
|
||||
LESS "$INPUT_FILE"
|
||||
echo.status "loading '${input_file}' preview..."
|
||||
utils.less "${input_file}"
|
||||
|
||||
echo.status "login : $_USER@$_HOST:$_PORT/$_NAME"
|
||||
echo.status "command : '$INPUT_FILE'"
|
||||
echo.status "login : ${_USER}@${_HOST}:${_PORT}/${_NAME}"
|
||||
echo.status "command : '${input_file}'"
|
||||
|
||||
yN 'run this command?' || user.abort
|
||||
yN 'run this command?' || echo.error.user-abort || return 1
|
||||
|
||||
echo.status "running '$INPUT_FILE'"
|
||||
echo.status "running '${input_file}'"
|
||||
|
||||
PSQL < $INPUT_FILE \
|
||||
&& echo.success "finished running '$INPUT_FILE'" \
|
||||
|| utils.fail 3 "something went wrong running '$INPUT_FILE' (see above)"
|
||||
PSQL < "${input_file}" \
|
||||
&& echo.success "finished running '${input_file}'" \
|
||||
|| echo.error "something went wrong running '${input_file}' (see above)"
|
||||
}
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ MAIN() {
|
||||
pruned resources are PERMANENTLY DELETED
|
||||
"
|
||||
|
||||
utils.yN 'continue?' || utils.abort
|
||||
user.yN 'continue?' || utils.abort
|
||||
|
||||
echo.success "$(
|
||||
for RESOURCE in ${RESOURCES[@]}
|
||||
|
||||
+1
-1
@@ -2,5 +2,5 @@
|
||||
#####################################################################
|
||||
|
||||
MAIN() {
|
||||
echo.success 'Hello, World!'
|
||||
echo.success 'Hello, World!' --force-print
|
||||
}
|
||||
|
||||
@@ -251,8 +251,7 @@ use.compute-scwrypts-library-hash() {
|
||||
LC_ALL=POSIX find "${SCWRYPTS_LIBRARY_ROOT}" -type f -name \*.module.zsh -print0 \
|
||||
| sort -z \
|
||||
| xargs -0 sha1sum \
|
||||
| sha1sum \
|
||||
| awk '{print $1;}' \
|
||||
| utils.sha1sum \
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
+15
-21
@@ -15,39 +15,33 @@ USAGE__description='
|
||||
#####################################################################
|
||||
|
||||
MAIN() {
|
||||
local OUTPUT_FUNCTION=success
|
||||
local EXIT_CODE=0
|
||||
local MESSAGE='Hello, world!'
|
||||
local output_function=success
|
||||
local exit_code=0
|
||||
local message='Hello, world!'
|
||||
|
||||
eval "${ZSHPARSEARGS}"
|
||||
|
||||
##########################################
|
||||
|
||||
[[ ${OUTPUT_FUNCTION} =~ ^fail$ ]] && utils.fail ${EXIT_CODE} "${MESSAGE}"
|
||||
command -v "echo.${output_function}" &>/dev/null || {
|
||||
echo.warning "no such function echo.${output_function} exists; swapping to echo.status"
|
||||
output_function=status
|
||||
}
|
||||
|
||||
echo.${OUTPUT_FUNCTION} "${MESSAGE}"
|
||||
return ${EXIT_CODE}
|
||||
"echo.${output_function}" "${message}"
|
||||
return ${exit_code}
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
|
||||
MAIN.parse() {
|
||||
local PARSED=0
|
||||
local parsed=0
|
||||
|
||||
case $1 in
|
||||
( -e | --exit-code )
|
||||
PARSED=2
|
||||
EXIT_CODE="$2"
|
||||
;;
|
||||
( -f | --output-function )
|
||||
PARSED=2
|
||||
OUTPUT_FUNCTION="$2"
|
||||
;;
|
||||
( -m | --message )
|
||||
PARSED=2
|
||||
MESSAGE="$2"
|
||||
;;
|
||||
case "${1}" in
|
||||
( -e | --exit-code ) parsed=2; exit_code="${2}" ;;
|
||||
( -f | --output-function ) parsed=2; output_function="${2}" ;;
|
||||
( -m | --message ) parsed=2; message="${2}" ;;
|
||||
esac
|
||||
|
||||
return ${PARSED}
|
||||
return "${parsed}"
|
||||
}
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
#####################################################################
|
||||
|
||||
${scwryptsmodule}() {
|
||||
eval "$(usage.reset)"
|
||||
local USAGE__description="
|
||||
Caches any successful shell operation for the current scwrypts runtime;
|
||||
useful for operations which require heavy processing, but frequent access.
|
||||
|
||||
Keep in mind the following:
|
||||
- Operations are hashed exclusively by the --cache-file argument; use of the
|
||||
same cache file for multiple different commands will result in erratic behavior,
|
||||
so make sure the filename is unique across the full scwrypts runtime
|
||||
|
||||
- Operations which have a failed shell exit code are not cached; operations
|
||||
MUST succeed the first time in order to be cached
|
||||
|
||||
- The full stdout of the operation is cached to RAM
|
||||
"
|
||||
|
||||
local \
|
||||
USE_CACHE=true CACHE_ARGS_COUNT=0 CACHE_FILE \
|
||||
ARGS=() ARGS_FORCE=allowed \
|
||||
;
|
||||
|
||||
eval "$ZSHPARSEARGS"
|
||||
|
||||
local CACHE_FILE_FULLPATH="$SCWRYPTS_TEMP_PATH/$CACHE_FILE"
|
||||
|
||||
case $USE_CACHE in
|
||||
( bypass ) ${ARGS[@]} ;;
|
||||
( true | reset )
|
||||
local EXIT_CODE=0
|
||||
|
||||
[[ $USE_CACHE =~ reset ]] && rm -- "$CACHE_FILE_FULLPATH" &>/dev/null
|
||||
|
||||
[ -f "$CACHE_FILE_FULLPATH" ] || {
|
||||
${ARGS[@]} > "$CACHE_FILE_FULLPATH"
|
||||
EXIT_CODE=$?
|
||||
}
|
||||
|
||||
case $EXIT_CODE in
|
||||
( 0 ) cat "$CACHE_FILE_FULLPATH"
|
||||
;;
|
||||
( * ) cat "$CACHE_FILE_FULLPATH" 2>/dev/null
|
||||
echo.error "error running '${ARGS[@]}'"
|
||||
rm -- "$CACHE_FILE_FULLPATH" &>/dev/null
|
||||
;;
|
||||
esac
|
||||
|
||||
return $EXIT_CODE
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
|
||||
${scwryptsmodule}.parse() {
|
||||
# local USE_CACHE CACHE_ARGS_COUNT=0 CACHE_FILE
|
||||
local PARSED=0
|
||||
case $1 in
|
||||
( --use-cache )
|
||||
PARSED=2
|
||||
case $2 in
|
||||
( true | reset | bypass )
|
||||
((CACHE_ARGS_COUNT+=1))
|
||||
USE_CACHE=$2
|
||||
;;
|
||||
( * ) echo.error "cannot set USE_CACHE to '$2'" ;;
|
||||
esac
|
||||
;;
|
||||
|
||||
( --cache-file )
|
||||
PARSED=2
|
||||
CACHE_FILE=$2
|
||||
;;
|
||||
esac
|
||||
return $PARSED
|
||||
}
|
||||
|
||||
${scwryptsmodule}.parse.usage() {
|
||||
USAGE__options+="\n
|
||||
--cache-file (required) runtime-unique filename for cached data
|
||||
for cache to be automatically cleared, make sure this is a simple filename (no directories)
|
||||
|
||||
--reset-cache clear the cache before performing the operation
|
||||
--bypass-cache skip the cache for a clean operation, but don't clear existing or set new cache data
|
||||
"
|
||||
|
||||
USAGE__args+="\n
|
||||
remaining arguments are executed as a shell command
|
||||
"
|
||||
}
|
||||
|
||||
${scwryptsmodule}.parse.validate() {
|
||||
case $USE_CACHE in
|
||||
( true | reset | bypass ) ;;
|
||||
( * ) echo.error "invalid value '$USE_CACHE' for USE_CACHE (are you missing 'local USE_CACHE=true'?)" ;;
|
||||
esac
|
||||
|
||||
[[ "$CACHE_FILE" ]] && {
|
||||
mkdir -p -- "$SCWRYPTS_TEMP_PATH/$(dirname -- "$CACHE_FILE")" \
|
||||
|| echo.error "unable to create base directory '$SCWRYPTS_TEMP_PATH/$(dirname -- "$CACHE_FILE")'"
|
||||
|
||||
true
|
||||
} || echo.error "missing cache file"
|
||||
|
||||
[[ ${#ARGS[@]} -gt 0 ]] \
|
||||
|| echo.error "no command provided"
|
||||
|
||||
[[ $CACHE_ARGS_COUNT -le 1 ]] \
|
||||
|| echo.error "too many '--use-cache' flags used"
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
|
||||
${scwryptsmodule}.zshparse.args() {
|
||||
# local CACHE_ARGS=()
|
||||
local PARSED=0
|
||||
case $1 in
|
||||
( --use-cache )
|
||||
PARSED=2
|
||||
case $2 in
|
||||
( true | reset | bypass )
|
||||
CACHE_ARGS+=(--use-cache $2)
|
||||
;;
|
||||
|
||||
( * ) echo.error "invalid --use-cache value '$2'" ;;
|
||||
esac
|
||||
esac
|
||||
return $PARSED
|
||||
}
|
||||
|
||||
${scwryptsmodule}.zshparse.args.usage() {
|
||||
USAGE__options+="\n
|
||||
--use-cache (default true) one of {'true', 'reset', 'bypass'}
|
||||
"
|
||||
|
||||
USAGE__description+="\n
|
||||
(this function uses functions cached per scwrypts runtime)
|
||||
"
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
@@ -5,59 +5,106 @@ DEPENDENCIES+=(grep jq sed sort yq)
|
||||
#####################################################################
|
||||
|
||||
${scwryptsmodule}.get-env-names() {
|
||||
[ $REQUIRED_ENVIRONMENT_REGEX ] && {
|
||||
scwrypts.environment.common.find-env-names \
|
||||
| grep "$REQUIRED_ENVIRONMENT_REGEX"
|
||||
return $?
|
||||
}
|
||||
|
||||
scwrypts.environment.common.find-env-names
|
||||
scwrypts.environment.common.find-env-names \
|
||||
| grep "${REQUIRED_ENVIRONMENT_REGEX:-.}"
|
||||
}
|
||||
|
||||
${scwryptsmodule}.get-env-filename() { # provides the fully qualified path to the group config file
|
||||
local NAME="$1"
|
||||
local GROUP="$2"
|
||||
[ $NAME ] && [ $GROUP ] \
|
||||
|| echo.error 'cannot determine environment filename without name ($1) and group ($2)' \
|
||||
${scwryptsmodule}.get-env-file() { # provides the fully qualified path to the group config file
|
||||
local environment_name="${1}" group="${2}"
|
||||
[[ "${environment_name}" ]] && [[ "${group}" ]] \
|
||||
|| echo.error 'cannot determine environment file without name ($1) and group ($2)' \
|
||||
|| return 1
|
||||
|
||||
echo "$SCWRYPTS_ENV_PATH/$NAME.$GROUP.env.yaml"
|
||||
echo "${SCWRYPTS_ENV_PATH}/${environment_name}.${group}.env.yaml"
|
||||
}
|
||||
|
||||
${scwryptsmodule}.get-parent-env-names() { # deepest parent first; e.g. for 'a.b.c.d', returns (a a.b a.b.c)
|
||||
local NAME="$1"
|
||||
[[ $NAME =~ . ]] || return 0
|
||||
#####################################################################
|
||||
|
||||
local PARENT_ENV_NAMES=()
|
||||
while [ $NAME ]
|
||||
${scwryptsmodule}.get-parent-env-names() { # highest-level parent first; e.g. for 'a.b.c.d', returns (a a.b a.b.c)
|
||||
local environment_name="${1}"
|
||||
[[ "${environment_name}" ]] || return 0
|
||||
|
||||
local parent_environment_names=()
|
||||
while [[ "${environment_name}" ]]
|
||||
do
|
||||
NAME="$(echo $NAME | sed -n 's/\.[^.]\+$//p')"
|
||||
[ $NAME ] && PARENT_ENV_NAMES+=($NAME)
|
||||
environment_name="$(echo "${environment_name}" | sed -n 's/\.[^.]\+$//p')"
|
||||
[[ "${environment_name}" ]] && parent_environment_names+=("${environment_name}")
|
||||
done
|
||||
|
||||
echo ${PARENT_ENV_NAMES[@]} | sed 's/\s\+/\n/g' | sort
|
||||
printf '%s\n' "${parent_environment_names[@]}" | sort
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
|
||||
${scwryptsmodule}.find-env-files() {
|
||||
find "$SCWRYPTS_ENV_PATH/" -mindepth 1 -maxdepth 1 -type f -name \*.env.yaml 2>/dev/null
|
||||
find "${SCWRYPTS_ENV_PATH}/" -mindepth 1 -maxdepth 1 -type f -name \*.env.yaml 2>/dev/null
|
||||
}
|
||||
|
||||
${scwryptsmodule}.find-env-names() {
|
||||
scwrypts.environment.common.find-env-files \
|
||||
| sed "s|^$SCWRYPTS_ENV_PATH/||; s|\\.[^.]\\+\\.env\\.yaml$||" \
|
||||
| sed "s|^${SCWRYPTS_ENV_PATH}/||; s|\\.[^.]\\+\\.env\\.yaml$||" \
|
||||
| sort --reverse --unique \
|
||||
;
|
||||
}
|
||||
|
||||
${scwryptsmodule}.find-env-files-by-name() {
|
||||
local NAME="$1"
|
||||
[ $NAME ] || return 1
|
||||
local environment_name="${1}"
|
||||
[[ "${environment_name}" ]] || return 1
|
||||
|
||||
find "$SCWRYPTS_ENV_PATH/" -mindepth 1 -maxdepth 1 -type f -name $NAME.\*.env.yaml 2>/dev/null
|
||||
find "${SCWRYPTS_ENV_PATH}/" -mindepth 1 -maxdepth 1 -type f -name ${environment_name}.\*.env.yaml 2>/dev/null
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
|
||||
${scwryptsmodule}.combine-template-files() {
|
||||
utils.yq eval-all '. as $item ireduce ({}; . * $item)' \
|
||||
| sed 's/: {}$/:/' \
|
||||
| utils.yq 'sort_keys(...)' \
|
||||
# combine files & set 'somethingEmpty: {}' to 'somethingEmpty:'
|
||||
utils.yq.combine-files | sed 's/: {}$/:/'
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
|
||||
${scwryptsmodule}.get-all-template-files() {
|
||||
# produces a complete list of template env.yaml across all known scwrypts groups
|
||||
local files=()
|
||||
|
||||
local group file
|
||||
for group in "${SCWRYPTS_GROUPS[@]}"
|
||||
do
|
||||
file="$(scwrypts.config.group "${group}" root)/.config/env.yaml"
|
||||
[[ -f "${file}" ]] && files+=("${group}:${file}")
|
||||
done
|
||||
|
||||
printf '%s\n' "${files[@]}"
|
||||
}
|
||||
|
||||
${scwryptsmodule}.get-all-template-files.only-filenames() {
|
||||
scwrypts.environment.common.get-all-template-files | sed 's/^[^:]\+://'
|
||||
}
|
||||
|
||||
${scwryptsmodule}.get-all-configuration-files() {
|
||||
# produces a complete list of user environment configurations for the target environment
|
||||
local PARSERS=(scwrypts.zshparse.environment-name)
|
||||
eval "$(utils.parse.autosetup)"
|
||||
##########################################
|
||||
|
||||
local files=()
|
||||
|
||||
local _environment_name file group group_config_file
|
||||
for _environment_name in \
|
||||
$(scwrypts.environment.common.get-parent-env-names "${environment_name}") \
|
||||
${environment_name} \
|
||||
;
|
||||
do
|
||||
for group in "${SCWRYPTS_GROUPS[@]}"
|
||||
do
|
||||
group_config_file="$(scwrypts.environment.common.get-env-file "${_environment_name}" "${group}")"
|
||||
[[ -f "${group_config_file}" ]] && files+=("${group_config_file}")
|
||||
done
|
||||
done
|
||||
|
||||
printf '%s\n' "${files[@]}"
|
||||
}
|
||||
|
||||
${scwryptsmodule}.get-environment-module-files() {
|
||||
find "$(scwrypts.config.group scwrypts zshlibrary)/scwrypts/environment" -type f
|
||||
}
|
||||
|
||||
@@ -7,26 +7,17 @@
|
||||
# common lookups used by all environment logic
|
||||
use scwrypts/environment/common
|
||||
|
||||
|
||||
# allows utils.fzf selection of environments
|
||||
use scwrypts/environment/select-env
|
||||
|
||||
# primary source of truth for configuration values in the environment
|
||||
use scwrypts/environment/get-user-json
|
||||
|
||||
# initialize environments, or skip if already initialized
|
||||
use scwrypts/environment/init
|
||||
|
||||
# allows utils.fzf selection of environments
|
||||
use scwrypts/environment/select-env
|
||||
|
||||
# injects metadata and unifies environment template
|
||||
# creates a merged template file across all known scwrypts groups' env.yaml
|
||||
use scwrypts/environment/get-full-template
|
||||
|
||||
|
||||
# lookup environment variables by config path
|
||||
use scwrypts/environment/get-envvar-lookup-map
|
||||
|
||||
|
||||
# generates local/non-CI configuration
|
||||
use scwrypts/environment/user
|
||||
|
||||
|
||||
# create/edit/delete operations on local configurations
|
||||
use scwrypts/environment/update
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
#####################################################################
|
||||
|
||||
use scwrypts/environment/get-enriched-user-yaml
|
||||
|
||||
#####################################################################
|
||||
|
||||
${scwryptsmodule}() {
|
||||
local DESCRIPTION="
|
||||
returns json which contains shell-compatible lookup values
|
||||
- moves inherited .PARENTVALUE to value (if value is empty)
|
||||
- moves inherited .PARENTSELECTION to selection (if selection is empty)
|
||||
"
|
||||
|
||||
local PARSERS=(scwrypts.zshparse.environment-name)
|
||||
|
||||
eval "$(utils.parse.autosetup)"
|
||||
|
||||
##########################################
|
||||
|
||||
scwrypts.environment.get-enriched-user-yaml "${@}" \
|
||||
| utils.yq '..
|
||||
|= select(
|
||||
((has ("value") | not) or .value == null or .value | length == 0) and has (".PARENTVALUE")
|
||||
).value = .".PARENTVALUE"
|
||||
' \
|
||||
| utils.yq '..
|
||||
|= select(
|
||||
((has ("selection") | not) or .selection == null or .selection | length == 0) and has (".PARENTSELECTION")
|
||||
).selection = .".PARENTSELECTION"
|
||||
' \
|
||||
| utils.yq '.
|
||||
| del(.. | select(has(".PARENTVALUE")).".PARENTVALUE")
|
||||
| del(.. | select(has(".PARENTSELECTION")).".PARENTSELECTION")
|
||||
| del(.. | select(has(".GROUP")).".GROUP")
|
||||
| del(.. | select(has(".DESCRIPTION")).".DESCRIPTION")
|
||||
| del(.. | select(has("value") and (.value == null or .value | length == 0)).value)
|
||||
| del(.. | select(has("selection") and (.selection == null or .selection | length == 0)).selection)
|
||||
| del(.. | select(has("value") and has("selection")).selection)
|
||||
' \
|
||||
;
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
#####################################################################
|
||||
|
||||
use scwrypts/environment/common
|
||||
use scwrypts/zshparse
|
||||
|
||||
use zsh-cache
|
||||
|
||||
#####################################################################
|
||||
|
||||
${scwryptsmodule}() {
|
||||
local DESCRIPTION="
|
||||
Generates a metadata-enriched scwrypts environment YAML.
|
||||
"
|
||||
local PARSERS=(scwrypts.zshparse.environment-name)
|
||||
|
||||
eval "$(utils.parse.autosetup)"
|
||||
|
||||
##########################################
|
||||
|
||||
zsh-cache --auto -- "${environment_name}"
|
||||
}
|
||||
|
||||
${scwryptsmodule}.zsh-cache.get-hash() {
|
||||
local environment_name="${1:-${SCWRYPTS_ENV}}"
|
||||
{
|
||||
scwrypts.environment.common.get-environment-module-files
|
||||
scwrypts.environment.common.get-all-template-files.only-filenames
|
||||
scwrypts.environment.common.get-all-configuration-files --environment-name "${environment_name}"
|
||||
} | utils.sha1sum.filelist
|
||||
}
|
||||
|
||||
${scwryptsmodule}.zsh-cache() {
|
||||
local ERRORS=0 WARNINGS=0
|
||||
|
||||
local environment_name="${SCWRYPTS_ENV}"
|
||||
local loading_original_env=true
|
||||
|
||||
local _s _p
|
||||
while [[ "${#}" -gt 0 ]]
|
||||
do
|
||||
_s=1
|
||||
case "${1}" in
|
||||
( --parent ) loading_original_env=false ;;
|
||||
( * )
|
||||
case "$((_p+=1))" in
|
||||
( 1 ) environment_name="${1}" ;;
|
||||
( * ) echo.error "unknown argument '${1}'" ;;
|
||||
esac
|
||||
esac
|
||||
shift "${_s}" || echo.error "missing argument for '${1}'" || shift "${#}"
|
||||
done
|
||||
unset _s _p
|
||||
|
||||
utils.check-errors || return "${?}"
|
||||
|
||||
##########################################
|
||||
|
||||
case "${loading_original_env}" in
|
||||
( false ) ;; # only display help for originally requested environment
|
||||
( true )
|
||||
local show_help="$(normalize.boolean --variable SCWRYPTS_ENVIRONMENT__SHOW_ENV_HELP --default true --mode echo)"
|
||||
case "${show_help}" in
|
||||
( false )
|
||||
echo "--- # current scwrypts environment = ${environment_name}"
|
||||
;;
|
||||
|
||||
( true )
|
||||
echo "
|
||||
#
|
||||
# current scwrypts environment = ${environment_name}
|
||||
#
|
||||
# - metadata tags are READONLY; changes to any key which starts with a
|
||||
# '.' followed by all-caps (e.g. '.DESCRIPTION') will be ignored on
|
||||
# save
|
||||
#
|
||||
# - value precedence is as follows (lower number is higher priority):
|
||||
# 0. runtime environment variable '__override' value
|
||||
# 1. the value from the 'value' key
|
||||
# 2. the value from the '.PARENTVALUE' (if 'value' key is null)
|
||||
# 3. a user-selected value from the 'selection' list
|
||||
# 4. a user-selected value from the '.PARENTSELECTION' list
|
||||
#
|
||||
# - values are used like environment variables, although most scalar
|
||||
# values are permitted, they are converted to strings or string arrays
|
||||
# before use; string 'null' is OK, but null-type means not configured
|
||||
#
|
||||
# - for 'value' null-type / empty = not configured
|
||||
# - for 'selection' null-type / empty / empty-list = not configured
|
||||
#
|
||||
---
|
||||
"
|
||||
;;
|
||||
esac | sed 's/\(^\s\+\|\s\+$\)//g; /^$/d'
|
||||
;;
|
||||
esac
|
||||
|
||||
{
|
||||
# the full template ensures all empty keys are available in the edit UI
|
||||
scwrypts.environment.get-full-template.with-value-keys
|
||||
|
||||
case "${loading_original_env}" in
|
||||
( false ) ;; # don't loop through parents outside of original environment
|
||||
( true )
|
||||
local parent_environment_name
|
||||
for parent_environment_name in $(scwrypts.environment.common.get-parent-env-names "${environment_name}")
|
||||
do
|
||||
echo ---
|
||||
scwrypts.environment.get-enriched-user-yaml.zsh-cache --parent "${parent_environment_name}" \
|
||||
| sed '
|
||||
s/^\(\s\+\)\(value\|selection\):/\1.PARENT\U\2:/
|
||||
' \
|
||||
| utils.yq '.
|
||||
| del(.. | select(has(".PARENTVALUE") and .".PARENTVALUE" == null).".PARENTVALUE")
|
||||
| del(.. | select(has(".PARENTSELECTION") and (.".PARENTSELECTION" | length) == 0).".PARENTSELECTION")
|
||||
| del(.. | select(has(".PARENTSELECTION") and has(".PARENTVALUE")).".PARENTSELECTION")
|
||||
' \
|
||||
;
|
||||
done
|
||||
unset parent_environment_name
|
||||
;;
|
||||
esac
|
||||
|
||||
local group group_config_file
|
||||
for group in ${SCWRYPTS_GROUPS[@]}
|
||||
do
|
||||
group_config_file="$(scwrypts.environment.common.get-env-file "${environment_name}" "${group}")"
|
||||
|
||||
touch -- "${group_config_file}"
|
||||
|
||||
[[ "$(head -n1 "${group_config_file}")" =~ ^---$ ]] \
|
||||
|| echo ---
|
||||
|
||||
cat -- "${group_config_file}"
|
||||
done
|
||||
} \
|
||||
| scwrypts.environment.common.combine-template-files \
|
||||
| utils.yq -P \
|
||||
| sed -z 's/\n[a-z]/\n&/g' \
|
||||
| sed 's/value: null$/value:/; /\svalue:/G' \
|
||||
;
|
||||
}
|
||||
@@ -1,13 +1,12 @@
|
||||
#####################################################################
|
||||
|
||||
use scwrypts/cache-output
|
||||
use scwrypts/environment/get-full-template
|
||||
|
||||
#####################################################################
|
||||
|
||||
${scwryptsmodule}() {
|
||||
eval "$(usage.reset)"
|
||||
local USAGE__description="
|
||||
local ARGS=() ARGS_FORCE=true
|
||||
local DESCRIPTION="
|
||||
outputs a JSON map which can be used to lookup config-file query
|
||||
paths from environment variable names; GET_FULL_TEMPLATE flags OK
|
||||
|
||||
@@ -15,20 +14,15 @@ ${scwryptsmodule}() {
|
||||
value : jq-style query path
|
||||
"
|
||||
|
||||
local \
|
||||
PARSERS=(
|
||||
scwrypts.cache-output.zshparse.args # passthrough
|
||||
)
|
||||
|
||||
eval "$ZSHPARSEARGS"
|
||||
eval "$(utils.parse.autosetup)"
|
||||
|
||||
##########################################
|
||||
|
||||
scwrypts.environment.get-full-template $@ \
|
||||
scwrypts.environment.get-full-template "${ARGS[@]}" \
|
||||
| utils.yq -P '
|
||||
..
|
||||
| select(. == "*")
|
||||
| {(.): "." + (path | join(".") + ".value")}
|
||||
| {(.): "." + (path | join("."))}
|
||||
'\
|
||||
| sed -n 's/\.\.ENVIRONMENT//p' \
|
||||
;
|
||||
|
||||
@@ -1,48 +1,54 @@
|
||||
#####################################################################
|
||||
|
||||
use scwrypts/cache-output
|
||||
use scwrypts/environment/common
|
||||
|
||||
use zsh-cache
|
||||
|
||||
#####################################################################
|
||||
|
||||
${scwryptsmodule}() {
|
||||
eval "$(usage.reset)"
|
||||
local USAGE__description="
|
||||
local DESCRIPTION="
|
||||
Provies the combined YAML of all available scwrypts group 'template.yaml' files.
|
||||
|
||||
Template is cached after first generation in a given scwrypts runtime.
|
||||
"
|
||||
|
||||
local \
|
||||
CACHE_ARGS=() \
|
||||
PARSERS=(
|
||||
scwrypts.cache-output.zshparse.args
|
||||
)
|
||||
eval "$(utils.parse.autosetup)"
|
||||
|
||||
eval "$ZSHPARSEARGS"
|
||||
##########################################
|
||||
|
||||
scwrypts.cache-output ${CACHE_ARGS[@]} \
|
||||
--cache-file environment.template.yaml \
|
||||
-- \
|
||||
scwrypts.environment.get-full-template.helper \
|
||||
zsh-cache --auto
|
||||
}
|
||||
|
||||
${scwryptsmodule}.zsh-cache() {
|
||||
local DEBUG_ARGS=() TRACE_ARGS=()
|
||||
local template_file group _
|
||||
while IFS=: read -r group template_file _
|
||||
do
|
||||
[[ "$(head -n1 "${template_file}")" =~ ^---$ ]] || echo ---
|
||||
|
||||
cat -- "${template_file}" \
|
||||
| utils.yq "(.. | select(has(\".ENVIRONMENT\"))) += {\".GROUP\":\"${group}\"}"
|
||||
done < <(scwrypts.environment.common.get-all-template-files) \
|
||||
| scwrypts.environment.common.combine-template-files
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
${scwryptsmodule}.zsh-cache.get-hash() {
|
||||
{
|
||||
scwrypts.environment.common.get-environment-module-files
|
||||
scwrypts.environment.common.get-all-template-files.only-filenames
|
||||
} | utils.sha1sum.filelist
|
||||
}
|
||||
|
||||
${scwryptsmodule}.with-value-keys() {
|
||||
scwrypts.environment.get-full-template \
|
||||
| utils.yq '(.. | select(has(".ENVIRONMENT"))) += {
|
||||
"selection": [],
|
||||
"value": null
|
||||
}
|
||||
' \
|
||||
| sed 's/ ""$//' \
|
||||
;
|
||||
}
|
||||
|
||||
${scwryptsmodule}.helper() {
|
||||
local GROUP GROUP_ROOT GROUP_TEMPLATE_FILENAME
|
||||
{
|
||||
for GROUP in ${SCWRYPTS_GROUPS[@]}
|
||||
do
|
||||
GROUP_ROOT="$(scwrypts.config.group ${GROUP} root)"
|
||||
|
||||
GROUP_TEMPLATE_FILENAME="${GROUP_ROOT}/.config/env.yaml"
|
||||
|
||||
[ -f "${GROUP_TEMPLATE_FILENAME}" ] && {
|
||||
[[ $(head -n1 "${GROUP_TEMPLATE_FILENAME}") =~ ^---$ ]] || echo ---
|
||||
cat "${GROUP_TEMPLATE_FILENAME}" \
|
||||
| utils.yq "(.. | select(has(\".ENVIRONMENT\"))) += {\".GROUP\":\"${GROUP}\"}"
|
||||
}
|
||||
done
|
||||
} | scwrypts.environment.common.combine-template-files
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
#####################################################################
|
||||
|
||||
use scwrypts/environment/common
|
||||
use scwrypts/environment/get-configuration-values
|
||||
use scwrypts/environment/get-envvar-lookup-map
|
||||
use scwrypts/environment/get-full-template
|
||||
|
||||
#####################################################################
|
||||
|
||||
${scwryptsmodule}() {
|
||||
local DESCRIPTION="
|
||||
returns the fully populated configuration object for the target
|
||||
environment; this should be used as the primary source of truth
|
||||
for scwrypts environment configuration
|
||||
|
||||
values must be retrieved from '.configuration' by their lookup key
|
||||
|
||||
for values with a configured environment variable, the lookup key
|
||||
can be found in '.lookup.ENVIRONMENT_VARIABLE'
|
||||
|
||||
\`\`\`yaml
|
||||
someGroup:
|
||||
myData:
|
||||
value: abcdefg 1234
|
||||
.ENVIRONMENT: MY_DATA_VARIABLE
|
||||
\`\`\`
|
||||
|
||||
utils.yq .lookup.MY_DATA_VARIABLE > '.someGroup.myData'
|
||||
utils.yq .configuration.someGroup.myData.value > 'abcdefgh 1234'
|
||||
"
|
||||
|
||||
local PARSERS=(scwrypts.zshparse.environment-name)
|
||||
|
||||
eval "$(utils.parse.autosetup)"
|
||||
|
||||
##########################################
|
||||
|
||||
zsh-cache --auto -- --environment-name "${environment_name}"
|
||||
}
|
||||
|
||||
${scwryptsmodule}.zsh-cache.get-hash() {
|
||||
local PARSERS=(scwrypts.zshparse.environment-name)
|
||||
eval "$(utils.parse.autosetup)"
|
||||
{
|
||||
scwrypts.environment.common.get-environment-module-files
|
||||
scwrypts.environment.common.get-all-template-files.only-filenames
|
||||
scwrypts.environment.common.get-all-configuration-files --environment-name "${environment_name}"
|
||||
} | utils.sha1sum.filelist
|
||||
}
|
||||
|
||||
${scwryptsmodule}.zsh-cache() {
|
||||
local PARSERS=(scwrypts.zshparse.environment-name)
|
||||
eval "$(utils.parse.autosetup)"
|
||||
##########################################
|
||||
local configuration="$(scwrypts.environment.get-configuration-values --environment-name "${environment_name}")"
|
||||
local lookup_map="$(scwrypts.environment.get-envvar-lookup-map)"
|
||||
|
||||
echo.trace 'generating user json' -v lookup_map
|
||||
{
|
||||
echo "configuration:"
|
||||
echo "${configuration}" | sed 's/^/ /'
|
||||
|
||||
echo "lookup:"
|
||||
echo "${lookup_map}" | sed 's/^/ /'
|
||||
} | utils.yq -oj | jq -c
|
||||
}
|
||||
@@ -39,7 +39,7 @@ MAIN() {
|
||||
utils.environment.check --print-value ${ARGS[@]}
|
||||
;;
|
||||
( all )
|
||||
scwrypts.environment.user.get-json
|
||||
scwrypts.environment.get-user-json
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ scwrypts.environment.legacy.convert() {
|
||||
echo.status "checking '${ENVIRONMENT_NAME}' configuration files"
|
||||
for GROUP in ${SCWRYPTS_GROUPS[@]}
|
||||
do
|
||||
GROUP_CONFIG_FILENAME="$(scwrypts.environment.common.get-env-filename "${ENVIRONMENT_NAME}" "${GROUP}")"
|
||||
GROUP_CONFIG_FILENAME="$(scwrypts.environment.common.get-env-file "${ENVIRONMENT_NAME}" "${GROUP}")"
|
||||
LEGACY_CONFIG_FILENAME="${SCWRYPTS_ENV_PATH}/${GROUP}/${ENVIRONMENT_NAME}"
|
||||
|
||||
[ ! -f "${GROUP_CONFIG_FILENAME}" ] && [ -f "${LEGACY_CONFIG_FILENAME}" ] && {
|
||||
@@ -120,7 +120,7 @@ scwrypts.environment.legacy.convert.v4-to-v5.config-file() {
|
||||
local ENV_VAR NEW_TEMPLATE_KEY NEW_TEMPLATE="$(scwrypts.environment.get-full-template --reset-cache)"
|
||||
for ENV_VAR in $(echo "${LEGACY_CONFIG_VALUES}" | utils.yq -r 'keys | .[]')
|
||||
do
|
||||
NEW_TEMPLATE_KEY=$(scwrypts.environment.template.get-envvar-lookup-map | utils.yq -r ".${ENV_VAR}")
|
||||
NEW_TEMPLATE_KEY="$(scwrypts.environment.template.get-envvar-lookup-map | utils.yq -r ".${ENV_VAR}").value"
|
||||
NEW_TEMPLATE_VALUE="$(echo "${LEGACY_CONFIG_VALUES}" | utils.yq -r ".${ENV_VAR}")"
|
||||
echo "${NEW_TEMPLATE_VALUE}" | grep -q '^[[].*[]]$' \
|
||||
|| NEW_TEMPLATE_VALUE="\"${NEW_TEMPLATE_VALUE}\""
|
||||
|
||||
@@ -73,20 +73,20 @@ MAIN() {
|
||||
;;
|
||||
( copy )
|
||||
local SOURCE_ENVIRONMENT="$(scwrypts.environment.common.get-env-names | utils.fzf 'select an environment to copy')"
|
||||
[ "$SOURCE_ENVIRONMENT" ] || user.abort
|
||||
[ "$SOURCE_ENVIRONMENT" ] || echo.error.user-abort || return 1
|
||||
|
||||
[ "$ENVIRONMENT_NAME" ] || ENVIRONMENT_NAME=$(echo | utils.fzf.user-input 'new environment name')
|
||||
[ "$ENVIRONMENT_NAME" ] || user.abort
|
||||
[ "$ENVIRONMENT_NAME" ] || echo.error.user-abort || return 1
|
||||
;;
|
||||
( delete )
|
||||
[ "$ENVIRONMENT_NAME" ] || ENVIRONMENT_NAME="$(scwrypts.environment.common.get-env-names | utils.fzf 'select an environment to delete')"
|
||||
[ "$ENVIRONMENT_NAME" ] || user.abort
|
||||
[ "$ENVIRONMENT_NAME" ] || echo.error.user-abort || return 1
|
||||
;;
|
||||
( advanced )
|
||||
[ "$ENVIRONMENT_NAME" ] || ENVIRONMENT_NAME="$SCWRYPTS_ENV"
|
||||
esac
|
||||
|
||||
[ -f "$(scwrypts.environment.common.get-env-filename "$ENVIRONMENT_NAME" scwrypts)" ] \
|
||||
[ -f "$(scwrypts.environment.common.get-env-file "$ENVIRONMENT_NAME" scwrypts)" ] \
|
||||
&& local ENVIRONMENT_EXISTS=true \
|
||||
|| local ENVIRONMENT_EXISTS=false \
|
||||
;
|
||||
@@ -97,7 +97,7 @@ MAIN() {
|
||||
( true ) ;;
|
||||
( false )
|
||||
echo.warning "environment $ENVIRONMENT_NAME does not exist"
|
||||
Yn "create new environment '$ENVIRONMENT_NAME'?" || user.abort
|
||||
Yn "create new environment '$ENVIRONMENT_NAME'?" || echo.error.user-abort || return 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
@@ -117,7 +117,7 @@ MAIN() {
|
||||
case $ENVIRONMENT_EXISTS in
|
||||
( true )
|
||||
echo.reminder 'deleting an environment is PERMANENT\nmake sure your credentials are backed up!'
|
||||
yN "are you sure you want to delete '$ENVIRONMENT_NAME'?" || user.abort
|
||||
yN "are you sure you want to delete '$ENVIRONMENT_NAME'?" || echo.error.user-abort || return 1
|
||||
;;
|
||||
( false )
|
||||
echo.error "environment '$ENVIRONMENT_NAME' does not exist (can't delete it if it's not there)"
|
||||
|
||||
@@ -1,119 +1,128 @@
|
||||
#####################################################################
|
||||
|
||||
use scwrypts/environment/common
|
||||
|
||||
use scwrypts/environment/user
|
||||
use scwrypts/environment/get-full-template
|
||||
use scwrypts/environment/select-env
|
||||
|
||||
use zsh-cache/reset
|
||||
|
||||
#####################################################################
|
||||
|
||||
[ $SCWRYPTS_ENVIRONMENT__PREFERRED_EDIT_MODE ] \
|
||||
[ "${SCWRYPTS_ENVIRONMENT__PREFERRED_EDIT_MODE}" ] \
|
||||
|| export SCWRYPTS_ENVIRONMENT__PREFERRED_EDIT_MODE=basic
|
||||
|
||||
scwrypts.environment.update() {
|
||||
local EDIT_MODE=$SCWRYPTS_ENVIRONMENT__PREFERRED_EDIT_MODE
|
||||
local ENVIRONMENT_NAME="$SCWRYPTS_ENV"
|
||||
local FROM_EXISTING
|
||||
${scwryptsmodule}() {
|
||||
local DESCRIPTION='Update a scwrypts environment configuration'
|
||||
local PARSERS=(scwrypts.zshparse.environment-name)
|
||||
eval "$(utils.parse.autosetup)"
|
||||
##########################################
|
||||
|
||||
local USAGE="
|
||||
usage: scwrypts.environment.update [...options...]
|
||||
local TEMP_CONFIG_FILE="${SCWRYPTS_TEMP_PATH_SOCKET}/environment.temp.yaml"
|
||||
|
||||
options:
|
||||
--mode <string> update execution mode (default: $SCWRYPTS_ENVIRONMENT__PREFERRED_EDIT_MODE)
|
||||
modes:
|
||||
basic : create or edit environment with all tooltips and metadata
|
||||
quiet : create or edit a compact environment with minimal metadata
|
||||
init : create a new environment with no edit prompt
|
||||
delete : delete target environment
|
||||
recursive : (advanced) edit all inherited environments, starting from deepest parent
|
||||
copy : (see --create-from-existing; most likely you don't need to set this flag)
|
||||
|
||||
--environment-name <string> name of the target environment (default is current: $SCWRYPTS_ENV)
|
||||
--create-from-existing <string> name of the environment to copy
|
||||
|
||||
-h, --help print this dialogue and exit
|
||||
"
|
||||
|
||||
local _S ERRORS=0
|
||||
while [[ $# -gt 0 ]]
|
||||
do
|
||||
_S=1
|
||||
case $1 in
|
||||
( -h | --help ) utils.io.usage; return 0 ;;
|
||||
|
||||
( --environment-name )
|
||||
[ $2 ] && ((_S+=1)) || echo.error "missing environment name" || break
|
||||
ENVIRONMENT_NAME="$2"
|
||||
;;
|
||||
|
||||
( --create-from-existing )
|
||||
[ $2 ] && ((_S+=1)) || echo.error "must provide environment name to copy" || break
|
||||
EDIT_MODE=copy
|
||||
FROM_EXISTING="$2"
|
||||
;;
|
||||
|
||||
( --mode )
|
||||
[ $2 ] && ((_S+=1)) || echo.error "missing mode" || break
|
||||
EDIT_MODE="$2"
|
||||
command -v scwrypts.environment.update.edit.$EDIT_MODE &>/dev/null \
|
||||
|| echo.error "invalid mode '$EDIT_MODE'"
|
||||
|
||||
;;
|
||||
|
||||
* ) echo.error "unknown argument '$1'" ;;
|
||||
esac
|
||||
shift $_S
|
||||
done
|
||||
|
||||
case $EDIT_MODE in
|
||||
( copy )
|
||||
[ $FROM_EXISTING ] || FROM_EXISTING=$(scwrypts.environment.select-env)
|
||||
[ $FROM_EXISTING ] || echo.error "cannot work in '$EDIT_MODE' without existing target"
|
||||
|
||||
[[ $(scwrypts.environment.common.find-env-files-by-name "$FROM_EXISTING" | wc -l) -gt 0 ]] \
|
||||
|| echo.error "no such environment '$FROM_EXISTING' exists"
|
||||
;;
|
||||
|
||||
( * )
|
||||
[ ! $FROM_EXISTING ] || echo.error "cannot work in '$EDIT_MODE' with --create-from-existing"
|
||||
;;
|
||||
esac
|
||||
|
||||
utils.check-errors || return $?
|
||||
|
||||
local TEMP_CONFIG_FILE="$SCWRYPTS_TEMP_PATH/environment.temp.yaml"
|
||||
|
||||
[ -f "$TEMP_CONFIG_FILE" ] && {
|
||||
[[ -f "${TEMP_CONFIG_FILE}" ]] && {
|
||||
echo.error "temp config file already exists at '$TEMP_CONFIG_FILE'\nis another environment update in-progress?"
|
||||
echo.reminder "if you are certain no other environment update is in progress, you can resolve with\n rm -- '$TEMP_CONFIG_FILE'"
|
||||
echo.reminder "
|
||||
if you are certain no other environment update is in progress, you can resolve with
|
||||
rm -- ${(q)TEMP_CONFIG_FILE}
|
||||
"
|
||||
return 1
|
||||
}
|
||||
|
||||
scwrypts.environment.update.edit.${EDIT_MODE}
|
||||
local EXIT_CODE=$?
|
||||
"${edit_mode_helper}"
|
||||
local exit_code="${?}"
|
||||
|
||||
rm "$TEMP_CONFIG_FILE" 2>/dev/null
|
||||
rm "${TEMP_CONFIG_FILE}" 2>/dev/null
|
||||
|
||||
return $EXIT_CODE
|
||||
return ${exit_code}
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
|
||||
${scwryptsmodule}.parse.locals() {
|
||||
local edit_mode="${SCWRYPTS_ENVIRONMENT__PREFERRED_EDIT_MODE}"
|
||||
local edit_mode_helper
|
||||
local edit_mode_args=()
|
||||
local from_existing
|
||||
}
|
||||
|
||||
${scwryptsmodule}.parse() {
|
||||
local parsed=0
|
||||
|
||||
case "${1}" in
|
||||
( --mode )
|
||||
parsed=2
|
||||
edit_mode_args+=("${1} ${2}")
|
||||
edit_mode="${2}"
|
||||
;;
|
||||
|
||||
( --create-from-existing )
|
||||
parsed=2
|
||||
edit_mode_args+=("${1}")
|
||||
edit_mode=copy
|
||||
from_existing="${2}"
|
||||
;;
|
||||
esac
|
||||
|
||||
return "${parsed}"
|
||||
}
|
||||
|
||||
${scwryptsmodule}.parse.usage() {
|
||||
USAGE__options+="
|
||||
--mode <string> update execution mode (default: ${SCWRYPTS_ENVIRONMENT__PREFERRED_EDIT_MODE})
|
||||
modes:
|
||||
basic : create or edit environment with all tooltips and metadata
|
||||
quiet : create or edit a compact environment with minimal metadata
|
||||
init : create a new environment with no edit prompt
|
||||
delete : delete target environment
|
||||
recursive : (advanced) edit all inherited environments, starting from deepest parent
|
||||
copy : (see --create-from-existing; most likely you don't need to set this flag)
|
||||
|
||||
--create-from-existing <string> name of the environment to copy
|
||||
"
|
||||
}
|
||||
|
||||
${scwryptsmodule}.parse.validate() {
|
||||
[[ "${#edit_mode_args[@]}" -le 1 ]] \
|
||||
|| echo.error "incompatible arguments ${(q)edit_mode_args[@]}" \
|
||||
|| return
|
||||
|
||||
|
||||
[[ "${edit_mode}" ]] \
|
||||
&& edit_mode_helper="scwrypts.environment.update.edit.${edit_mode}" \
|
||||
&& command -v "${edit_mode_helper}" &>/dev/null \
|
||||
|| echo.error "invalid edit mode '${edit_mode}'" \
|
||||
;
|
||||
|
||||
case "${edit_mode}" in
|
||||
( copy )
|
||||
[[ "${from_existing}" ]] || from_existing="$(scwrypts.environment.select-env)"
|
||||
[[ "${from_existing}" ]] || echo.error "cannot work in '${edit_mode}' without existing target"
|
||||
;
|
||||
esac
|
||||
|
||||
if [[ "${from_existing}" ]]
|
||||
then
|
||||
[[ "$(scwrypts.environment.common.find-env-files-by-name "${from_existing}" | wc -l)" -gt 0 ]] \
|
||||
|| echo.error "no such environment '${from_existing}' exists"
|
||||
fi
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
|
||||
${scwryptsmodule}.edit.basic() {
|
||||
scwrypts.environment.user.get \
|
||||
--environment-name $ENVIRONMENT_NAME \
|
||||
scwrypts.environment.get-enriched-user-yaml \
|
||||
--environment-name $environment_name \
|
||||
> "$TEMP_CONFIG_FILE"
|
||||
|
||||
utils.io.edit "$TEMP_CONFIG_FILE"
|
||||
|
||||
scwrypts.environment.update.update-user-configs "$(cat "$TEMP_CONFIG_FILE")" "$ENVIRONMENT_NAME"
|
||||
scwrypts.environment.update.update-user-configs "${environment_name}"
|
||||
}
|
||||
|
||||
${scwryptsmodule}.edit.quiet() {
|
||||
echo "--- # $ENVIRONMENT_NAME" > "$TEMP_CONFIG_FILE"
|
||||
scwrypts.environment.user.get \
|
||||
--environment-name $ENVIRONMENT_NAME \
|
||||
echo "--- # $environment_name" > "$TEMP_CONFIG_FILE"
|
||||
scwrypts.environment.get-enriched-user-yaml \
|
||||
--environment-name $environment_name \
|
||||
| utils.yq '.
|
||||
| del(.. | select(has(".ENVIRONMENT")).".ENVIRONMENT")
|
||||
| del(.. | select(has(".GROUP")).".GROUP")
|
||||
@@ -126,75 +135,75 @@ ${scwryptsmodule}.edit.quiet() {
|
||||
|
||||
utils.io.edit "$TEMP_CONFIG_FILE"
|
||||
|
||||
scwrypts.environment.update.update-user-configs "$(cat "$TEMP_CONFIG_FILE")" "$ENVIRONMENT_NAME"
|
||||
scwrypts.environment.update.update-user-configs "${environment_name}"
|
||||
}
|
||||
|
||||
${scwryptsmodule}.edit.recursive() {
|
||||
local RECURSIVE_EDIT_MODE="$SCWRYPTS_ENVIRONMENT__PREFERRED_EDIT_MODE"
|
||||
[[ $RECURSIVE_EDIT_MODE =~ ^recursive$ ]] \
|
||||
&& RECURSIVE_EDIT_MODE=quiet
|
||||
local recursive_edit_mode="${SCWRYPTS_ENVIRONMENT__PREFERRED_EDIT_MODE}"
|
||||
[[ "${recursive_edit_mode}" =~ ^recursive$ ]] \
|
||||
&& recursive_edit_mode=quiet
|
||||
|
||||
local PARENT_ENVIRONMENT_NAME
|
||||
for PARENT_ENVIRONMENT_NAME in \
|
||||
$(scwrypts.environment.common.get-parent-env-names "$ENVIRONMENT_NAME") \
|
||||
$ENVIRONMENT_NAME
|
||||
local parent_environment_name
|
||||
for parent_environment_name in \
|
||||
$(scwrypts.environment.common.get-parent-env-names "$environment_name") \
|
||||
$environment_name
|
||||
;
|
||||
do
|
||||
echo.status "editing environment '$PARENT_ENVIRONMENT_NAME'"
|
||||
echo.status "editing environment '${parent_environment_name}'"
|
||||
scwrypts.environment.update \
|
||||
--environment-name $PARENT_ENVIRONMENT_NAME \
|
||||
--mode $RECURSIVE_EDIT_MODE \
|
||||
--environment-name "${parent_environment_name}" \
|
||||
--mode "${recursive_edit_mode}" \
|
||||
;
|
||||
done
|
||||
}
|
||||
|
||||
${scwryptsmodule}.edit.init() {
|
||||
[ -f "$(scwrypts.environment.common.get-env-filename)" ]
|
||||
[ -f "$(scwrypts.environment.common.get-env-file)" ]
|
||||
|
||||
scwrypts.environment.user.get \
|
||||
--environment-name $ENVIRONMENT_NAME \
|
||||
> "$TEMP_CONFIG_FILE"
|
||||
scwrypts.environment.get-enriched-user-yaml \
|
||||
--environment-name "${environment_name}" \
|
||||
> "${TEMP_CONFIG_FILE}"
|
||||
|
||||
scwrypts.environment.update.update-user-configs "$(cat "$TEMP_CONFIG_FILE")"
|
||||
scwrypts.environment.update.update-user-configs "${environment_name}"
|
||||
}
|
||||
|
||||
${scwryptsmodule}.edit.copy() {
|
||||
local GROUP_CONFIG_FILENAME SOURCE_CONFIG_FILENAME
|
||||
for GROUP in ${SCWRYPTS_GROUPS[@]}
|
||||
local group_config_filename SOURCE_CONFIG_FILENAME
|
||||
for group in ${SCWRYPTS_GROUPS[@]}
|
||||
do
|
||||
cp \
|
||||
"$(scwrypts.environment.common.get-env-filename "$FROM_EXISTING" "$GROUP")" \
|
||||
"$(scwrypts.environment.common.get-env-filename "$ENVIRONMENT_NAME" "$GROUP")" \
|
||||
"$(scwrypts.environment.common.get-env-file "${FROM_EXISTING}" "${group}")" \
|
||||
"$(scwrypts.environment.common.get-env-file "${environment_name}" "${group}")" \
|
||||
2>/dev/null \
|
||||
;
|
||||
done
|
||||
|
||||
scwrypts.environment.user.get \
|
||||
--environment-name $ENVIRONMENT_NAME \
|
||||
> "$TEMP_CONFIG_FILE"
|
||||
scwrypts.environment.get-enriched-user-yaml \
|
||||
--environment-name "${environment_name}" \
|
||||
> "${TEMP_CONFIG_FILE}"
|
||||
|
||||
scwrypts.environment.update.update-user-configs "$(cat "$TEMP_CONFIG_FILE")"
|
||||
scwrypts.environment.update.update-user-configs "${environment_name}"
|
||||
}
|
||||
|
||||
${scwryptsmodule}.edit.delete() {
|
||||
touch "$TEMP_CONFIG_FILE"
|
||||
|
||||
local ERRORS=0 GROUP GROUP_CONFIG_FILENAME
|
||||
for GROUP in ${SCWRYPTS_GROUPS[@]}
|
||||
local ERRORS=0 group group_config_filename
|
||||
for group in ${SCWRYPTS_GROUPS[@]}
|
||||
do
|
||||
local GROUP_CONFIG_FILENAME="$(scwrypts.environment.common.get-env-filename "$ENVIRONMENT_NAME" "$GROUP")"
|
||||
[ -f "$GROUP_CONFIG_FILENAME" ] || {
|
||||
echo.status "nothing to cleanup for $ENVIRONMENT_NAME/$GROUP"
|
||||
local group_config_filename="$(scwrypts.environment.common.get-env-file "${environment_name}" "${group}")"
|
||||
[ -f "${group_config_filename}" ] || {
|
||||
echo.status "nothing to cleanup for ${environment_name}/${group}"
|
||||
continue
|
||||
}
|
||||
|
||||
rm -- "$GROUP_CONFIG_FILENAME" \
|
||||
&& echo.success "deleted '$GROUP_CONFIG_FILENAME'" \
|
||||
|| echo.error "unable to delete '$GROUP_CONFIG_FILENAME'" \
|
||||
rm -- "${group_config_filename}" \
|
||||
&& echo.success "deleted '${group_config_filename}'" \
|
||||
|| echo.error "unable to delete '${group_config_filename}'" \
|
||||
;
|
||||
done
|
||||
|
||||
return $ERRORS
|
||||
return "${ERRORS}"
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
@@ -203,55 +212,54 @@ ${scwryptsmodule}.edit.delete() {
|
||||
|
||||
export __SCWRYPTS_ENVIRONMENT__WORKFLOW_IS_CHANGE_SAFE=false
|
||||
${scwryptsmodule}.update-user-configs() {
|
||||
local NEW_CONFIGURATION="$1"
|
||||
[ $NEW_CONFIGURATION ] || return 1
|
||||
|
||||
local ENVIRONMENT_NAME="$2"
|
||||
[ $ENVIRONMENT_NAME ] || return 2
|
||||
local environment_name="${1}"
|
||||
[[ "${environment_name}" ]] || return 2
|
||||
|
||||
# reinject all metadata, since the update function is allowed to strip it
|
||||
NEW_CONFIGURATION="$(
|
||||
{
|
||||
scwrypts.environment.user.get-full-template-with-value-keys --environment "$ENVIRONMENT_NAME"
|
||||
echo ---
|
||||
echo "$NEW_CONFIGURATION"
|
||||
} | scwrypts.environment.common.combine-template-files
|
||||
)"
|
||||
local new_configuration="$({
|
||||
scwrypts.environment.get-full-template.with-value-keys --environment "${environment_name}"
|
||||
echo ---
|
||||
cat -- "${TEMP_CONFIG_FILE}"
|
||||
} | scwrypts.environment.common.combine-template-files)"
|
||||
[[ "${new_configuration}" ]] || return 1
|
||||
|
||||
local METADATA_DELETE_QUERY="$(
|
||||
echo "$NEW_CONFIGURATION" \
|
||||
|
||||
local metadata_delete_query="$(
|
||||
echo "${new_configuration}" \
|
||||
| sed -n 's/^\s\+\(\.[-A-Za-z_:]\+\):.*$/ | del(.. | select(has("\1"))."\1")/p' \
|
||||
| sort --unique \
|
||||
)"
|
||||
|
||||
local GROUP GROUP_CONFIG_FILENAME GROUP_CONFIG
|
||||
for GROUP in ${SCWRYPTS_GROUPS[@]}
|
||||
local group group_config_filename group_config
|
||||
for group in ${SCWRYPTS_GROUPS[@]}
|
||||
do
|
||||
local GROUP_CONFIG="$(echo "$NEW_CONFIGURATION" \
|
||||
local group_config="$(echo "${new_configuration}" \
|
||||
| utils.yq ".
|
||||
| del(.. | select(has(\".PARENTVALUE\") and has(\"value\") and .\".PARENTVALUE\" == .value))
|
||||
| del(.. | select(has(\".PARENTSELECTION\") and has(\"selection\") and .\".PARENTSELECTION\" == .selection))
|
||||
| del(.. | select(has(\".GROUP\") and .\".GROUP\" != \"$GROUP\"))
|
||||
| del(.. | select(has(\".GROUP\") and .\".GROUP\" != \"${group}\"))
|
||||
| del(.. | select(has(\"selection\") and (.selection == null or (.selection | length) == 0)).selection)
|
||||
| del(.. | select(has(\"value\") and .value == null).value)
|
||||
$METADATA_DELETE_QUERY
|
||||
${metadata_delete_query}
|
||||
" \
|
||||
)"
|
||||
|
||||
while echo "$GROUP_CONFIG" | grep -q '{}'
|
||||
while echo "$group_config" | grep -q '{}'
|
||||
do
|
||||
GROUP_CONFIG="$(echo "$GROUP_CONFIG" | utils.yq 'del(.. | select(tag == "!!map" and length == 0))')"
|
||||
group_config="$(echo "${group_config}" | utils.yq 'del(.. | select(tag == "!!map" and length == 0))')"
|
||||
done
|
||||
|
||||
[ "$GROUP_CONFIG" ] || GROUP_CONFIG='# no configuration set'
|
||||
[ "${group_config}" ] || group_config='# no configuration set'
|
||||
|
||||
echo "--- # $ENVIRONMENT_NAME > $GROUP\n$GROUP_CONFIG" > "$(scwrypts.environment.common.get-env-filename "$ENVIRONMENT_NAME" "$GROUP")"
|
||||
echo "--- # ${environment_name} > ${group}\n${group_config}" > "$(scwrypts.environment.common.get-env-file "${environment_name}" "${group}")"
|
||||
done
|
||||
|
||||
[[ $ENVIRONMENT_NAME =~ ^$SCWRYPTS_ENV$ ]] && [[ $__SCWRYPTS_ENVIRONMENT__WORKFLOW_IS_CHANGE_SAFE =~ false ]] && {
|
||||
[[ "${environment_name}" == "${SCWRYPTS_ENV}" ]] && [[ "${__SCWRYPTS_ENVIRONMENT__WORKFLOW_IS_CHANGE_SAFE}" =~ false ]] && {
|
||||
echo.warning "current scwrypts environment has changed"
|
||||
export __SCWRYPTS_ENVIRONMENT__USER_ENVIRONMENT=
|
||||
}
|
||||
|
||||
zsh-cache.reset.all
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -1,276 +0,0 @@
|
||||
#####################################################################
|
||||
|
||||
use scwrypts/environment/common
|
||||
|
||||
use scwrypts/environment/get-full-template
|
||||
use scwrypts/environment/get-envvar-lookup-map
|
||||
|
||||
use scwrypts/cache-output
|
||||
|
||||
#####################################################################
|
||||
|
||||
${scwryptsmodule}.get() {
|
||||
eval "$(usage.reset)"
|
||||
local USAGE__description="
|
||||
Generates a metadata-enriched environment YAML for the target environment.
|
||||
" \
|
||||
CACHE_ARGS=() \
|
||||
ENVIRONMENT_NAME \
|
||||
PARSERS=(
|
||||
scwrypts.cache-output.zshparse.args
|
||||
scwrypts.environment.user.zshparse.env-name
|
||||
)
|
||||
|
||||
eval "$ZSHPARSEARGS"
|
||||
|
||||
##########################################
|
||||
|
||||
scwrypts.cache-output ${CACHE_ARGS[@]} \
|
||||
--cache-file environment.user.yaml \
|
||||
-- \
|
||||
scwrypts.environment.user.get.helper "$ENVIRONMENT_NAME" \
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
${scwryptsmodule}.get-shell-values() {
|
||||
eval "$(usage.reset)"
|
||||
local USAGE__description="
|
||||
used primarily by utils.environment.check in scwrypts environments
|
||||
|
||||
returns yaml which contains shell-compatible lookup values
|
||||
- moving inherited .PARENTVALUE to value (if value is empty)
|
||||
- moving inherited .PARENTSELECTION to selection (if selection is empty)
|
||||
- converting list 'values' to shell-arrays
|
||||
" \
|
||||
CACHE_ARGS=() \
|
||||
ENVIRONMENT_NAME \
|
||||
PARSERS=(
|
||||
scwrypts.cache-output.zshparse.args
|
||||
scwrypts.environment.user.zshparse.env-name
|
||||
)
|
||||
|
||||
eval "$ZSHPARSEARGS"
|
||||
|
||||
##########################################
|
||||
|
||||
scwrypts.cache-output ${CACHE_ARGS[@]} \
|
||||
--cache-file environment.shell.yaml \
|
||||
-- \
|
||||
_SCWRYPTS_ENVIRONMENT__CONVERT_SHELL_VALUES \
|
||||
--environment-name "$ENVIRONMENT_NAME" \
|
||||
;
|
||||
}
|
||||
|
||||
${scwryptsmodule}.get-json() {
|
||||
eval "$(usage.reset)"
|
||||
local USAGE__description="
|
||||
returns a JSON object containing live environment configurations
|
||||
for the target environment
|
||||
|
||||
contains both .path.style.lookup path and ENVIRONMENT_VARIABLE lookup keys
|
||||
" \
|
||||
CACHE_ARGS=() \
|
||||
ENVIRONMENT_NAME \
|
||||
PARSERS=(
|
||||
scwrypts.cache-output.zshparse.args
|
||||
scwrypts.environment.user.zshparse.env-name
|
||||
)
|
||||
|
||||
eval "$ZSHPARSEARGS"
|
||||
|
||||
##########################################
|
||||
|
||||
scwrypts.cache-output ${CACHE_ARGS[@]} \
|
||||
--cache-file environment.user.json \
|
||||
-- \
|
||||
scwrypts.environment.user.get-json.helper \
|
||||
--environment-name "$ENVIRONMENT_NAME" \
|
||||
;
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
|
||||
${scwryptsmodule}.zshparse.env-name() {
|
||||
# local ENVIRONMENT_NAME
|
||||
local PARSED=0
|
||||
case $1 in
|
||||
( --environment-name )
|
||||
PARSED=2
|
||||
ENVIRONMENT_NAME=$2
|
||||
|
||||
# bypass cache when specifying explicit environment name
|
||||
[[ ${(t)CACHE_ARGS} =~ array ]] && CACHE_ARGS+=(--use-cache bypass)
|
||||
;;
|
||||
esac
|
||||
return $PARSED
|
||||
}
|
||||
|
||||
${scwryptsmodule}.zshparse.env-name.usage() {
|
||||
USAGE__options+="\n
|
||||
--environment-name <string> name of a scwrypts environment (default: $SCWRYPTS_ENV)
|
||||
using this flag will bypass cache
|
||||
"
|
||||
}
|
||||
|
||||
${scwryptsmodule}.zshparse.env-name.validate() {
|
||||
[ "$ENVIRONMENT_NAME" ] || ENVIRONMENT_NAME="$SCWRYPTS_ENV"
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
|
||||
${scwryptsmodule}.get.helper() {
|
||||
local ENVIRONMENT_NAME LOADING_ORIGINAL_ENV=true
|
||||
local _S ERRORS=0
|
||||
while [[ $# -gt 0 ]]
|
||||
do
|
||||
_S=1
|
||||
case $1 in
|
||||
( --parent ) LOADING_ORIGINAL_ENV=false
|
||||
;;
|
||||
( * )
|
||||
: \
|
||||
&& [ ! $ENVIRONMENT_NAME ] \
|
||||
|| echo.error "unknown argument '$1'" \
|
||||
|| break
|
||||
|
||||
[ $ENVIRONMENT_NAME ] || { ENVIRONMENT_NAME="$1"; break; }
|
||||
esac
|
||||
shift $_S
|
||||
done
|
||||
|
||||
utils.check-errors || return $?
|
||||
|
||||
local ENVIRONMENT_NAME="$1"
|
||||
[ $ENVIRONMENT_NAME ] || ENVIRONMENT_NAME=$SCWRYPTS_ENV
|
||||
|
||||
[[ $LOADING_ORIGINAL_ENV =~ true ]] && {
|
||||
case $SCWRYPTS_ENVIRONMENT__SHOW_ENV_HELP in
|
||||
( true )
|
||||
echo "
|
||||
#
|
||||
# current scwrypts environment = $ENVIRONMENT_NAME
|
||||
#
|
||||
# - metadata tags are READONLY; changes to any key which starts with a
|
||||
# '.' followed by all-caps (e.g. '.DESCRIPTION') will be ignored on
|
||||
# save
|
||||
#
|
||||
# - value precedence is as follows (lower number is higher priority):
|
||||
# 0. runtime environment variable '__override' value
|
||||
# 1. the value from the 'value' key
|
||||
# 2. the value from the '.PARENTVALUE' (if 'value' key is null)
|
||||
# 3. a user-selected value from the 'selection' list
|
||||
# 4. a user-selected value from the '.PARENTSELECTION' list
|
||||
#
|
||||
# - values are used like environment variables, although most scalar
|
||||
# values are permitted, they are converted to strings or string arrays
|
||||
# before use; string 'null' is OK, but null-type means not configured
|
||||
#
|
||||
# - for 'value' null-type / empty = not configured
|
||||
# - for 'selection' null-type / empty / empty-list = not configured
|
||||
#
|
||||
---
|
||||
"
|
||||
;;
|
||||
( false )
|
||||
echo "--- # current scwrypts environment = $ENVIRONMENT_NAME"
|
||||
;;
|
||||
esac | sed 's/\(^\s\+\|\s\+$\)//g; /^$/d'
|
||||
}
|
||||
|
||||
local GROUP GROUP_CONFIG_FILENAME
|
||||
{
|
||||
scwrypts.environment.user.get-full-template-with-value-keys
|
||||
[[ $LOADING_ORIGINAL_ENV =~ true ]] && {
|
||||
for PARENT in $(scwrypts.environment.common.get-parent-env-names "$ENVIRONMENT_NAME")
|
||||
do
|
||||
echo ---
|
||||
scwrypts.environment.user.get.helper --parent "$PARENT" \
|
||||
| sed '
|
||||
s/^\(\s\+\)\(value\|selection\):/\1.PARENT\U\2:/
|
||||
' \
|
||||
| utils.yq '.
|
||||
| del(.. | select(has(".PARENTVALUE") and .".PARENTVALUE" == null).".PARENTVALUE")
|
||||
| del(.. | select(has(".PARENTSELECTION") and (.".PARENTSELECTION" | length) == 0).".PARENTSELECTION")
|
||||
| del(.. | select(has(".PARENTSELECTION") and has(".PARENTVALUE")).".PARENTSELECTION")
|
||||
' \
|
||||
;
|
||||
done
|
||||
}
|
||||
for GROUP in ${SCWRYPTS_GROUPS[@]}
|
||||
do
|
||||
GROUP_CONFIG_FILENAME="$(scwrypts.environment.common.get-env-filename "$ENVIRONMENT_NAME" "$GROUP")"
|
||||
|
||||
[ -f "${GROUP_CONFIG_FILENAME}" ] || touch "${GROUP_CONFIG_FILENAME}"
|
||||
|
||||
[[ $(head -n1 "$GROUP_CONFIG_FILENAME") =~ ^---$ ]] || echo ---
|
||||
cat "$GROUP_CONFIG_FILENAME"
|
||||
done
|
||||
} \
|
||||
| scwrypts.environment.common.combine-template-files \
|
||||
| utils.yq -P \
|
||||
| sed -z 's/\n[a-z]/\n&/g' \
|
||||
| sed 's/value: null$/value:/; /\svalue:/G' \
|
||||
;
|
||||
}
|
||||
|
||||
${scwryptsmodule}.get-full-template-with-value-keys() {
|
||||
scwrypts.environment.get-full-template \
|
||||
| utils.yq '(.. | select(has(".ENVIRONMENT"))) += {
|
||||
"selection": [],
|
||||
"value": null
|
||||
}
|
||||
' \
|
||||
| sed 's/ ""$//'
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
|
||||
_SCWRYPTS_ENVIRONMENT__CONVERT_SHELL_VALUES() {
|
||||
scwrypts.environment.user.get $@ \
|
||||
| utils.yq '..
|
||||
|= select(
|
||||
((has ("value") | not) or .value == null) and has (".PARENTVALUE")
|
||||
).value = .".PARENTVALUE"
|
||||
' \
|
||||
| utils.yq '..
|
||||
|= select(
|
||||
((has ("selection") | not) or .selection == null or .selection | length == 0) and has (".PARENTVALUE")
|
||||
).value = .".PARENTVALUE"
|
||||
' \
|
||||
| utils.yq '..
|
||||
|= select(
|
||||
has("value") and .value | type == "!!seq" and .value | length != 0
|
||||
).value = "(" + (.value | join " ") + ")"
|
||||
' \
|
||||
| utils.yq '.
|
||||
| del(.. | select(has(".PARENTVALUE")).".PARENTVALUE")
|
||||
| del(.. | select(has(".ENVIRONMENT")).".ENVIRONMENT")
|
||||
| del(.. | select(has(".GROUP")).".GROUP")
|
||||
| del(.. | select(has(".DESCRIPTION")).".DESCRIPTION")
|
||||
| del(.. | select(has("value") and .value == null).value)
|
||||
| del(.. | select(has("selection") and (.selection == null or .selection | length == 0)).selection)
|
||||
| del(.. | select(has("value") and has("selection")).selection)
|
||||
' \
|
||||
;
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
|
||||
${scwryptsmodule}.get-json.helper() {
|
||||
local SHELL_VALUES="$(_SCWRYPTS_ENVIRONMENT__CONVERT_SHELL_VALUES $@)"
|
||||
local LOOKUP_MAP="$(scwrypts.environment.get-envvar-lookup-map)"
|
||||
{
|
||||
echo "configuration:"
|
||||
echo "${SHELL_VALUES}" | sed 's/^/ /'
|
||||
local ENVIRONMENT_VARIABLE
|
||||
echo "environment:"
|
||||
for ENVIRONMENT_VARIABLE in $(\
|
||||
scwrypts.environment.get-full-template \
|
||||
| utils.yq '.. | select(has(".ENVIRONMENT")) | .".ENVIRONMENT"' \
|
||||
)
|
||||
do
|
||||
echo " ${ENVIRONMENT_VARIABLE}: $(echo "${SHELL_VALUES}" | utils.yq -r "$(echo "${LOOKUP_MAP}" | utils.yq -r ".${ENVIRONMENT_VARIABLE}")")"
|
||||
done
|
||||
} | utils.yq -oj
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
DEPENDENCIES+=(readlink envsubst)
|
||||
|
||||
${scwryptsmodule}() {
|
||||
#
|
||||
# returns the fully-qualified path from a user-specified, relative reference
|
||||
@@ -5,13 +7,20 @@ ${scwryptsmodule}() {
|
||||
#
|
||||
# also uses 'readlink --canonicalize' to read through symlinks to the realpath
|
||||
#
|
||||
local DEBUG_ARGS=() TRACE_ARGS=(-v path_0_raw -v path_1_evaluated -v path_2_target)
|
||||
|
||||
[ "$1" ] || return 1
|
||||
local path_0_raw="${1}"
|
||||
local path_1_evaluated="$(echo "${1}" | envsubst)"
|
||||
local path_2_target="$(
|
||||
case "${path_1_evaluated}" in
|
||||
( /* | ~/* ) echo "${path_1_evaluated}" ;;
|
||||
( * ) echo "${EXECUTION_DIR}/${path_1_evaluated}" ;;
|
||||
esac
|
||||
)"
|
||||
|
||||
[[ ! $1 =~ ^[/~] ]] \
|
||||
&& echo $(readlink --canonicalize -- "${EXECUTION_DIR}/$1") \
|
||||
|| echo "$1" \
|
||||
;
|
||||
echo.trace
|
||||
|
||||
return 0
|
||||
[[ "${path_2_target}" ]] || return 1
|
||||
|
||||
readlink --canonicalize -- "${path_2_target}"
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ ${scwryptsmodule}() {
|
||||
GROUP_TYPE=$(scwrypts.config.group ${GROUP} type)
|
||||
[ ${GROUP_TYPE} ] && MINDEPTH=1 && GROUP_TYPE="${GROUP_TYPE}\\/" || MINDEPTH=2
|
||||
|
||||
command -v SCWRYPTS_GROUP_CONFIGURATION__${GROUP}.list-available >/dev/null 2>&1 \
|
||||
command -v SCWRYPTS_GROUP_CONFIGURATION__${GROUP}.list-available &>/dev/null \
|
||||
&& LOOKUP=SCWRYPTS_GROUP_CONFIGURATION__${GROUP}.list-available \
|
||||
|| LOOKUP=scwrypts.list-available.default \
|
||||
;
|
||||
|
||||
@@ -12,7 +12,7 @@ MAIN() {
|
||||
echo.status "found ${LOGFILE_COUNT} log files"
|
||||
echo.warning "this will permanently delete everything in\n${SCWRYPTS_LOG_PATH}"
|
||||
|
||||
utils.yN 'continue?' || utils.abort
|
||||
user.yN 'continue?' || utils.abort
|
||||
|
||||
rm -rf "${SCWRYPTS_LOG_PATH}" \
|
||||
&& echo.success 'all logfiles removed' \
|
||||
|
||||
@@ -30,7 +30,7 @@ MAIN() {
|
||||
( 0 ) ARGS=(${SCWRYPTS_GROUPS[@]}) ;;
|
||||
esac
|
||||
|
||||
local GROUP GROUP_ROOT GROUP_TYPE GROUP_MODULES GROUP_MODULE CACHE_DIR
|
||||
local GROUP GROUP_ROOT GROUP_TYPE GROUP_MODULES GROUP_MODULE CACHE_DIR
|
||||
for GROUP in ${ARGS[@]}
|
||||
do
|
||||
GROUP_ROOT="$(scwrypts.config.group ${GROUP} zshlibrary)"
|
||||
@@ -83,7 +83,7 @@ MAIN.parse() {}
|
||||
MAIN.parse.validate() {
|
||||
[[ ${ZSHIMPORT_USE_CACHE} =~ true ]] \
|
||||
|| echo.error "ZSHIMPORT_USE_CACHE is set to 'false' so I cannot rebuild the cache"
|
||||
|
||||
|
||||
[ "${ZSHIMPORT_CACHE_DIR}" ] && [ -d "${ZSHIMPORT_CACHE_DIR}" ] \
|
||||
|| echo.error "I cannot find the ZSHIMPORT_CACHE_DIR '${ZSHIMPORT_CACHE_DIR}'"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
${scwryptsmodule}.environment-name.locals() {
|
||||
local environment_name="${SCWRYPTS_ENV}"
|
||||
}
|
||||
|
||||
${scwryptsmodule}.environment-name() {
|
||||
local p=0
|
||||
|
||||
case "${1}" in
|
||||
( --environment-name ) p=2; environment_name="${2}" ;;
|
||||
esac
|
||||
|
||||
return ${p}
|
||||
}
|
||||
|
||||
${scwryptsmodule}.environment-name.usage() {
|
||||
USAGE__options+="\n
|
||||
--environment-name <string> name of a scwrypts environment; when unspecified, uses current
|
||||
(current: ${SCWRYPTS_ENV})
|
||||
"
|
||||
}
|
||||
@@ -6,7 +6,7 @@ use unittest/operations
|
||||
|
||||
${scwryptsmodule}() {
|
||||
local ERRORS=0 TEST_ERRORS=0
|
||||
[ "$SCWRYPTS_LOG_LEVEL" ] || SCWRYPTS_LOG_LEVEL=4
|
||||
[[ "$SCWRYPTS_LOG_LEVEL" ]] || local SCWRYPTS_LOG_LEVEL=4
|
||||
|
||||
local UNITTESTS=($(echo "${(k)functions}" | sed 's/ /\n/g' | grep '^test\.' | sort))
|
||||
[[ ${#UNITTESTS[@]} -gt 0 ]] \
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
zsh.is-in-array() {
|
||||
# works like the terse "${a[(re)v]}" syntax;
|
||||
# checks the array returns either
|
||||
# '' (blank) when no match is found
|
||||
# ${value} when an exact match is found
|
||||
local array_variable="${1}"
|
||||
local value="${2}"
|
||||
|
||||
[[ "${(Pt)array_variable}" =~ array ]] \
|
||||
|| echo.error "${array_variable} is not an array" \
|
||||
|| return 1
|
||||
|
||||
local zsh_output="${${(P@)array_variable}[(re)${value}]}"
|
||||
|
||||
echo "${zsh_output}"
|
||||
[[ "${zsh_output}" ]]
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
+177
-29
@@ -1,43 +1,191 @@
|
||||
utils.fzf() {
|
||||
[ ${CI} ] && utils.fail 1 'currently in CI, but utils.fzf requires user input'
|
||||
case "$(normalize.boolean CI)" in
|
||||
( true )
|
||||
echo.error "currently in CI, but utils.fzf requires user input"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
##########################################
|
||||
|
||||
local FZF_ARGS=(
|
||||
-i
|
||||
--ansi
|
||||
--bind=ctrl-c:cancel
|
||||
--height=50%
|
||||
--layout=reverse
|
||||
)
|
||||
local PARSERS=() DESCRIPTION='
|
||||
provides a consistent interface to fzf by unifying display settings
|
||||
and assigning sensible defaults
|
||||
|
||||
returns 0 only when a non-empty selection is made by the user
|
||||
'
|
||||
|
||||
local SELECTION=$(fzf ${FZF_ARGS[@]} --prompt "$1 : " ${@:2} 2>/dev/tty)
|
||||
echo.prompt "$1"
|
||||
eval "$(utils.parse.autosetup)"
|
||||
|
||||
[ ${BE_QUIET} ] || {
|
||||
[[ ${SCWRYPTS_LOG_LEVEL} -ge 1 ]] && echo ${SELECTION} >&2
|
||||
}
|
||||
echo ${SELECTION}
|
||||
[ ${SELECTION} ]
|
||||
local DEBUG_ARGS=(-v user_prompt -v quiet -v selection) TRACE_ARGS=(-v fzf_args)
|
||||
|
||||
[[ "${BE_QUIET}" ]] && echo.warning.deprecation v5.3 'use of BE_QUIET= will be removed'
|
||||
|
||||
##########################################
|
||||
|
||||
local selection="$(fzf "${fzf_args[@]}" 2>/dev/tty)"
|
||||
|
||||
echo.trace
|
||||
|
||||
case "${quiet}" in
|
||||
( true ) ;;
|
||||
( false ) echo.prompt "${user_prompt}" --response "${(q)selection}" ;;
|
||||
esac
|
||||
|
||||
echo "${selection}"
|
||||
[[ "${selection}" ]]
|
||||
}
|
||||
|
||||
utils.fzf.user-input() { # allow user to type custom answers; reconfirm if ambiguous with select
|
||||
local FZF_OUTPUT=$(BE_QUIET=1 utils.fzf $@ --print-query | sed '/^$/d' | sort -u)
|
||||
[[ ${SCWRYPTS_LOG_LEVEL} -ge 1 ]] && echo ${FZF_OUTPUT} | head -n1 >&2
|
||||
[ ! ${FZF_OUTPUT} ] && return 1
|
||||
utils.fzf.parse.locals() {
|
||||
local fzf_args=(-i --ansi --bind=ctrl-c:cancel --height=50% --layout=reverse)
|
||||
local user_prompt
|
||||
local user_prompt_delimiter=' : '
|
||||
local quiet="${BE_QUIET:-false}"
|
||||
}
|
||||
|
||||
[[ $(echo "${FZF_OUTPUT}" | wc -l) -eq 1 ]] \
|
||||
&& { echo "${FZF_OUTPUT}"; return 0; }
|
||||
utils.fzf.parse() {
|
||||
local parsed=0
|
||||
|
||||
local FZF_OUTPUT=$(
|
||||
echo "${FZF_OUTPUT}" \
|
||||
case "${1}" in
|
||||
( --prompt ) parsed=2
|
||||
# add the user prompt back in as args to fzf
|
||||
[[ "${user_prompt}" ]] && fzf_args+=("${user_prompt}")
|
||||
|
||||
user_prompt="${2}"
|
||||
;;
|
||||
|
||||
( --quiet ) parsed=1
|
||||
quiet=true
|
||||
;;
|
||||
|
||||
( * ) parsed=1
|
||||
case "$((POSITIONAL_ARGS+=1))" in
|
||||
( 1 ) [[ "${user_prompt}" ]] && fzf_args+=("${1}") || user_prompt="${1}" ;;
|
||||
( * ) fzf_args+=("${1}") ;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
|
||||
return "${parsed}"
|
||||
}
|
||||
|
||||
utils.fzf.parse.validate() {
|
||||
[[ "${user_prompt}" ]] || user_prompt='make a selection'
|
||||
fzf_args+=(--prompt "${user_prompt}${user_prompt_delimiter}")
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
|
||||
utils.fzf.user-input() { # allow user to type custom answers; reconfirm if ambiguous with select
|
||||
local fzf_output="$(utils.fzf "${@}" --quiet --print-query | grep . | sort -u)"
|
||||
[[ "${fzf_output}" ]] || return 1
|
||||
|
||||
# only 1 line = non-ambiguous
|
||||
[[ $(echo "${fzf_output}" | wc -l) -eq 1 ]] \
|
||||
&& echo "${fzf_output}" \
|
||||
&& return 0
|
||||
|
||||
local fzf_output="$(
|
||||
echo "${fzf_output}" \
|
||||
| sed "1s/\$/^$(utils.colors.print light-gray '<- what you typed')/" \
|
||||
| sed "2s/\$/^$(utils.colors.print light-gray '<- what you selected')/" \
|
||||
| column -ts '^' \
|
||||
| BE_QUIET=1 utils.fzf "$@ (clarify)" \
|
||||
)
|
||||
| utils.fzf --quiet "${@}" '(clarify)' \
|
||||
| sed 's/\s\+<- what you .*$//' \
|
||||
)"
|
||||
|
||||
[[ ${SCWRYPTS_LOG_LEVEL} -ge 1 ]] && echo ${FZF_OUTPUT} >&2
|
||||
FZF_OUTPUT=$(echo ${FZF_OUTPUT} | sed 's/\s\+<- what you .*$//')
|
||||
echo ${FZF_OUTPUT}
|
||||
[ ${FZF_OUTPUT} ]
|
||||
echo "${fzf_output}"
|
||||
[[ "${fzf_output}" ]]
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
|
||||
utils.fzf.file-select() {
|
||||
local PARSERS=() DESCRIPTION='
|
||||
given a directory, provides a user-friendly file selection interface
|
||||
'
|
||||
|
||||
eval "$(utils.parse.autosetup)"
|
||||
|
||||
##########################################
|
||||
|
||||
local fzf_output="$(
|
||||
cd -- "${directory}"
|
||||
find . "${find_args[@]}" \
|
||||
| sed 's|^\./||' \
|
||||
| utils.fzf "${fzf_args[@]}" \
|
||||
)"
|
||||
|
||||
[[ "${fzf_output}" ]] && fzf_output="${directory}/${fzf_output}"
|
||||
|
||||
echo "${fzf_output}"
|
||||
[[ "${fzf_output}" ]]
|
||||
}
|
||||
|
||||
utils.fzf.file-select.parse.locals() {
|
||||
local ARGS=()
|
||||
local directory
|
||||
local user_prompt
|
||||
local fzf_args=()
|
||||
local find_args=()
|
||||
local filetypes=()
|
||||
local mindepth=1
|
||||
local maxdepth=1
|
||||
}
|
||||
|
||||
utils.fzf.file-select.parse() {
|
||||
local parsed=0
|
||||
|
||||
case "${1}" in
|
||||
( -t | --filetype ) parsed=2; filetypes+=("${2}") ;;
|
||||
( -p | --prompt ) parsed=2; user_prompt="${2}" ;;
|
||||
|
||||
( --mindepth ) parsed=2; mindepth="${2}" ;;
|
||||
( --maxdepth ) parsed=2; maxdepth="${2}" ;;
|
||||
esac
|
||||
|
||||
return "${parsed}"
|
||||
}
|
||||
|
||||
utils.fzf.file-select.parse.usage() {
|
||||
USAGE__options+='
|
||||
-t, --filetype <ext> file extension to include; multiple can be specified
|
||||
-p, --prompt <string> fzf prompt to the user (default: "select a file from ${directory}")
|
||||
|
||||
--mindepth <number> minimum find depth; unset with 0 (default: 1)
|
||||
--maxdepth <number> maximum find depth; unset with 0 (default: 1)
|
||||
'
|
||||
|
||||
USAGE__args+='
|
||||
$1 the directory to target
|
||||
$2..$N remaining arguments are forwarded to utils.fzf
|
||||
'
|
||||
}
|
||||
|
||||
utils.fzf.file-select.parse.validate() {
|
||||
directory="$(readlink --canonicalize -- "${ARGS[1]}")"
|
||||
[[ "${directory}" ]] && [[ -d "${directory}" ]] \
|
||||
|| echo.error "missing or invalid directory '${ARGS[1]}'" -v directory
|
||||
|
||||
[[ "${user_prompt}" ]] || user_prompt="select a file from ${directory}"
|
||||
fzf_args=(--prompt "${user_prompt}" "${ARGS[@]:1}")
|
||||
|
||||
[[ "${mindepth}" -le "${maxdepth}" ]] \
|
||||
|| echo.error "invalid min/max depth setting: mindepth=${(q)mindepth} maxdepth=${(q)maxdepth}"
|
||||
|
||||
local depth_flag
|
||||
for depth_flag in mindepth maxdepth
|
||||
do
|
||||
case "${(P)depth_flag}" in
|
||||
( 0 ) ;;
|
||||
( * ) find_args+=("-${depth_flag}" "${(P)depth_flag}")
|
||||
esac
|
||||
done
|
||||
|
||||
find_args+=(-type f)
|
||||
|
||||
local filetype
|
||||
for filetype in "${filetypes[@]}"
|
||||
do
|
||||
find_args+=(-o -name "\*.${filetype}" -o -name "\*.${filetype:u}")
|
||||
done
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
utils.notify-send() {
|
||||
utils.dependencies.check notify-send &>/dev/null || {
|
||||
echo.warning "missing 'notify-send'; cannot send desktop notifications"
|
||||
utils.dependencies.check notify-send --optional &>/dev/null || {
|
||||
echo.warning "cannot send desktop notifications"
|
||||
utils.notify-send() { true; }
|
||||
return 0
|
||||
}
|
||||
|
||||
[ "${SCWRYPT_GROUP}" ] && [ "${SCWRYPT_NAME}" ] \
|
||||
&& local TITLE="scwrypts/${SCWRYPT_GROUP} ${SCWRYPT_NAME}" \
|
||||
|| local TITLE="zsh"
|
||||
&& local title="scwrypts/${SCWRYPT_GROUP} ${SCWRYPT_NAME}" \
|
||||
|| local title="zsh"
|
||||
|
||||
notify-send "${TITLE}" $@
|
||||
notify-send "${title}" "${@}"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
utils.sha1sum() {
|
||||
# only output the sha1 hash (normally output includes filename too)
|
||||
sha1sum | awk '{print $1}'
|
||||
}
|
||||
|
||||
utils.sha1sum.filelist() {
|
||||
# provides a single sha1 hash for a list of input files
|
||||
sort | xargs sha1sum | utils.sha1sum
|
||||
}
|
||||
@@ -1,8 +1,15 @@
|
||||
utils.yq() {
|
||||
yq --version | grep -q mikefarah || {
|
||||
yq $@ # this is a different version from the preferred but throwing this in for compatibility
|
||||
return $?
|
||||
}
|
||||
yq --version | grep -q mikefarah && {
|
||||
utils.yq() { yq eval '... comments=""' | yq "${@}"; }
|
||||
} || {
|
||||
echo.warning '
|
||||
The utils.yq helper expects mikefarah/yq, but you appear to have kislyuk/yq or another flavor.
|
||||
Compatibility may vary.
|
||||
'
|
||||
|
||||
yq eval '... comments=""' | yq $@
|
||||
utils.yq() { yq "${@}"; }
|
||||
}
|
||||
|
||||
utils.yq.combine-files() { utils.yq \
|
||||
| utils.yq eval-all '. as $item ireduce ({}; . * $item)' \
|
||||
| utils.yq 'sort_keys(..)' \
|
||||
; }
|
||||
|
||||
+109
-43
@@ -1,64 +1,130 @@
|
||||
utils.dependencies.check-all() {
|
||||
local DEP ERRORS=0
|
||||
local SCWRYPTS_LOG_LEVEL=1
|
||||
[ ! ${E} ] && E=echo.error
|
||||
local ERRORS=0
|
||||
[[ "${SCWRYPTS_LOG_LEVEL}" ]] || local SCWRYPTS_LOG_LEVEL=1
|
||||
|
||||
DEPENDENCIES=($(echo ${DEPENDENCIES} | sed 's/ \+/\n/g' | sort -u))
|
||||
DEPENDENCIES+=(sed awk grep find readlink)
|
||||
DEPENDENCIES=($(echo ${DEPENDENCIES[@]} | sed 's/ \+/\n/g' | sort -u))
|
||||
|
||||
for DEP in ${DEPENDENCIES[@]}; do utils.dependencies.check ${DEP} || ((ERRORS+=1)); done
|
||||
utils.dependencies.check-coreutils || ((ERRORS+=$?))
|
||||
local dependency
|
||||
for dependency in ${DEPENDENCIES[@]}
|
||||
do
|
||||
utils.dependencies.check "${dependency}" || ((ERRORS+=1))
|
||||
done
|
||||
|
||||
return ${ERRORS}
|
||||
}
|
||||
|
||||
utils.dependencies.check() {
|
||||
local DEPENDENCY="$1"
|
||||
[ ! ${DEPENDENCY} ] && return 1
|
||||
command -v ${DEPENDENCY} >/dev/null 2>&1 || {
|
||||
[[ ${OPTIONAL} -eq 1 ]] \
|
||||
&& echo.warning "application '$1' preferred but not available on PATH $(utils.dependencies.credits $1)" \
|
||||
|| echo.error "application '$1' required but not available on PATH $(utils.dependencies.credits $1)" \
|
||||
;
|
||||
local ERRORS=0
|
||||
|
||||
local dependency
|
||||
local dependency_default_args=()
|
||||
local dependency_required=true
|
||||
|
||||
local _s _p
|
||||
while [[ "${#}" -gt 0 ]]
|
||||
do
|
||||
_s=1
|
||||
case "${1}" in
|
||||
( --optional ) dependency_required=false ;;
|
||||
( * )
|
||||
case $((_p+=1)) in
|
||||
( 1 ) dependency="${1}" ;;
|
||||
( * ) dependency_default_args+=("${1}") ;;
|
||||
esac
|
||||
esac
|
||||
shift ${_s} || echo.error "missing required argument for '${1}'" || shift "${#}"
|
||||
done
|
||||
unset _s _p
|
||||
|
||||
[[ "${dependency}" ]] || echo.error 'no dependency specified'
|
||||
|
||||
[[ "${ERRORS}" -eq 0 ]] || return "${ERRORS}"
|
||||
|
||||
##########################################
|
||||
|
||||
local executable_path="$(command -v "${dependency}" 2>/dev/null)"
|
||||
[[ "${executable_path}" ]] || {
|
||||
case "${dependency_required}" in
|
||||
( true ) echo.error "application '${dependency}' required but not available on PATH" ;;
|
||||
( false ) echo.warning "application '${dependency}' preferred but not available on PATH" ;;
|
||||
esac
|
||||
|
||||
local credits="$(utils.dependencies.credits "${dependency}")"
|
||||
[[ "${credits}" ]] && echo.reminder "${credits}"
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
command -v utils.wrapper.${DEPENDENCY} &>/dev/null && {
|
||||
utils.wrapper.${DEPENDENCY} \
|
||||
|| echo.error "failed to define required context wrapper for '$1'" \
|
||||
|| return 1 \
|
||||
;
|
||||
[[ "${dependency}" =~ "^${executable_path}$" ]] && {
|
||||
# echo "built-in '${dependency}' cannot be wrapped for safety" >&2
|
||||
return 0
|
||||
}
|
||||
|
||||
[[ ${DEPENDENCY} =~ ^yq$ ]] && {
|
||||
yq --version | grep -q mikefarah \
|
||||
|| echo.warning 'detected kislyuk/yq but mikefarah/yq is preferred (compatibility may vary)'
|
||||
}
|
||||
local automatic_trace=true
|
||||
local extra_eval_statements=''
|
||||
|
||||
return 0
|
||||
}
|
||||
case "${dependency}" in
|
||||
( yq )
|
||||
yq --version | grep -q mikefarah \
|
||||
|| echo.warning 'detected kislyuk/yq but mikefarah/yq is required'
|
||||
;;
|
||||
|
||||
utils.dependencies.check-coreutils() {
|
||||
local COREUTILS=(awk find grep sed readlink)
|
||||
local MISSING_DEPENDENCY_COUNT=0
|
||||
local NON_GNU_DEPENDENCY_COUNT=0
|
||||
( awk | sed | grep | find | readlink )
|
||||
automatic_trace=false
|
||||
"${executable_path}" --version 2>&1 | grep 'GNU' | grep -qv 'BSD' || {
|
||||
executable_path="$(command -v g${dependency})"
|
||||
[[ "${executable_path}" ]] \
|
||||
|| echo.error "unable to locate GNU ${dependency}; if you're on MacOS you may need to install the appropriate gnu-${dependency} package"
|
||||
}
|
||||
;;
|
||||
|
||||
local UTIL
|
||||
for UTIL in $(echo ${COREUTILS})
|
||||
do
|
||||
utils.dependencies.check ${UTIL} || { ((MISSING_DEPENDENCY_COUNT+=1)); continue; }
|
||||
( gawk | gsed | ggrep | gfind | greadlink )
|
||||
echo.error "do not use MacOS / BSD shadows for utilities which require GNU (use ${dependency/g/} instead)"
|
||||
;;
|
||||
|
||||
${UTIL} --version 2>&1 | grep 'GNU' | grep -qv 'BSD' || {
|
||||
echo.warning "non-GNU version of ${UTIL} detected"
|
||||
((NON_GNU_DEPENDENCY_COUNT+=1))
|
||||
}
|
||||
done
|
||||
( aws | eksctl )
|
||||
extra_eval_statements+="
|
||||
echo.warning 'use of ${dependency} directly is highly discouraged; utilize appropriate module instead'
|
||||
"
|
||||
case "${dependency}" in
|
||||
( aws ) extra_eval_statements+="echo.reminder 'use cloud/aws/cli : cloud.aws.cli'" ;;
|
||||
( * ) extra_eval_statements+="echo.reminder 'use cloud/aws/${dependency} : cloud.aws.${dependency}'" ;;
|
||||
esac
|
||||
;;
|
||||
esac || return "${ERRORS}"
|
||||
|
||||
[[ ${NON_GNU_DEPENDENCY_COUNT} -gt 0 ]] && {
|
||||
echo.warning 'scripts rely on GNU coreutils; compatibility may vary'
|
||||
utils.os.is-macos && echo.reminder 'GNU coreutils can be installed and linked through Homebrew'
|
||||
}
|
||||
eval "${dependency}() {
|
||||
local DEBUG_ARGS=() TRACE_ARGS=() ERRORS=0 WARNINGS=0
|
||||
local default_args=(${(q)dependency_default_args[@]})
|
||||
local print_trace=${automatic_trace}
|
||||
local args=()
|
||||
|
||||
return ${MISSING_DEPENDENCY_COUNT}
|
||||
while [[ \${#} -gt 0 ]]
|
||||
do
|
||||
case \${1} in
|
||||
( --omit-default-args )
|
||||
default_args=()
|
||||
;;
|
||||
|
||||
( --no-scwrypts-trace )
|
||||
print_trace=false
|
||||
;;
|
||||
|
||||
( --force-scwrypts-trace )
|
||||
print_trace=true
|
||||
;;
|
||||
|
||||
( * ) args+=(\"\${1}\") ;;
|
||||
esac
|
||||
shift 1
|
||||
done
|
||||
|
||||
[[ \"\${print_trace}\" =~ true ]] && echo.trace \"${(q-)executable_path} \${(q-)default_args[@]} \${(q-)args[@]}\"
|
||||
|
||||
${extra_eval_statements}
|
||||
|
||||
'${executable_path}' \"\${default_args[@]}\" \"\${args[@]}\"
|
||||
}"
|
||||
}
|
||||
|
||||
utils.dependencies.credits() {
|
||||
|
||||
+222
-153
@@ -1,19 +1,32 @@
|
||||
utils.environment.check-all() {
|
||||
# checks all environment variables in REQUIRED_ENV=()
|
||||
local SCWRYPTS_LOG_LEVEL=4
|
||||
local VAR ERRORS=0
|
||||
[[ "${SCWRYPTS_LOG_LEVEL}" ]] || local SCWRYPTS_LOG_LEVEL=4
|
||||
local ERRORS=0 WARNINGS=0
|
||||
|
||||
REQUIRED_ENV=($(echo ${REQUIRED_ENV} | sed 's/\s\+/\n/g' | sort --unique))
|
||||
REQUIRED_ENV=($(printf '%s\n' "${REQUIRED_ENV[@]}" | sort --unique))
|
||||
|
||||
for VAR in ${REQUIRED_ENV[@]}; do utils.environment.check ${VAR} || ((ERRORS+=1)); done
|
||||
local configuration_variable_name
|
||||
for configuration_variable_name in ${REQUIRED_ENV[@]}
|
||||
do
|
||||
utils.environment.check ${configuration_variable_name} || ((ERRORS+=1))
|
||||
done
|
||||
|
||||
return ${ERRORS}
|
||||
}
|
||||
|
||||
utils.environment.check() {
|
||||
local NAME OPTIONAL DEFAULT_VALUE LOOKUP_PATH
|
||||
local NAME_IS=environment-variable
|
||||
local PRINT_VALUE=false
|
||||
local ERRORS=0 WARNINGS=0
|
||||
local DEBUG_ARGS=() TRACE_ARGS=()
|
||||
|
||||
local configuration_variable_name optional default_value user_json_query
|
||||
DEBUG_ARGS+=(-v configuration_variable_name -v optional -v default_value)
|
||||
TRACE_ARGS+=(-v user_json_query)
|
||||
|
||||
local lookup_type
|
||||
DEBUG_ARGS+=(-v lookup_type)
|
||||
|
||||
local print_value_to_stdout=false
|
||||
TRACE_ARGS+=(-v print_value_to_stdout)
|
||||
|
||||
local USAGE="
|
||||
usage: utils.environment.check <environment variable> [...options...]
|
||||
@@ -34,217 +47,273 @@ utils.environment.check() {
|
||||
configuration env var (even when lookup is a config lookup path)
|
||||
"
|
||||
|
||||
local _S ERRORS=0 POSITIONAL_ARGS=0
|
||||
while [[ $# -gt 0 ]]
|
||||
local _s _p=0
|
||||
while [[ ${#} -gt 0 ]]
|
||||
do
|
||||
_S=1
|
||||
case $1 in
|
||||
_s=1
|
||||
case ${1} in
|
||||
( -h | --help )
|
||||
utils.io.usage
|
||||
return 0
|
||||
;;
|
||||
|
||||
( --optional )
|
||||
OPTIONAL=true
|
||||
optional=true
|
||||
;;
|
||||
|
||||
( --print-value )
|
||||
PRINT_VALUE=true
|
||||
print_value_to_stdout=true
|
||||
;;
|
||||
|
||||
( --default )
|
||||
[ $2 ] && ((_S+=1)) \
|
||||
[[ ${2} ]] && ((_S+=1)) \
|
||||
|| echo.error "missing env var default value" \
|
||||
|| break
|
||||
|
||||
[ ! "${DEFAULT_VALUE}" ] \
|
||||
[[ ! "${default_value}" ]] \
|
||||
|| echo.error "only one default value is supported" \
|
||||
|| break
|
||||
|
||||
[[ ${OPTIONAL} =~ true ]] \
|
||||
[[ ${optional} == true ]] \
|
||||
&& echo.warning "--optional and --default flags are redundant; remove '--optional' flag"
|
||||
|
||||
DEFAULT_VALUE="$2"
|
||||
OPTIONAL=true
|
||||
default_value="${2}"
|
||||
optional=true
|
||||
;;
|
||||
|
||||
( * )
|
||||
((POSITIONAL_ARGS+=1))
|
||||
case ${POSITIONAL_ARGS} in
|
||||
( 1 ) NAME="$1"
|
||||
case "$((_p+=1))" in
|
||||
( 1 ) configuration_variable_name="${1}"
|
||||
;;
|
||||
|
||||
( 2 ) DEFAULT_VALUE="$1"
|
||||
( 2 ) default_value="${1}"
|
||||
echo.warning "use of positional argument for default value is DEPRECATED\nplease use --default <value> flag"
|
||||
;;
|
||||
|
||||
( * ) echo.error "unknown argument '$1'"
|
||||
( * ) echo.error "unknown argument '${1}'"
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
shift ${_S}
|
||||
shift "${_s}"
|
||||
done
|
||||
unset _s _p
|
||||
|
||||
[ "${NAME}" ] \
|
||||
|| echo.error "must provide environment variable name"
|
||||
[[ "${configuration_variable_name}" ]] \
|
||||
|| echo.error "must provide configuration variable name"
|
||||
|
||||
[ "${OPTIONAL}" ] || OPTIONAL=false
|
||||
[[ "${optional}" ]] || optional=false
|
||||
|
||||
echo "${NAME}" | grep -q '^\.' \
|
||||
&& NAME_IS=lookup-path
|
||||
|
||||
case ${NAME_IS} in
|
||||
( lookup-path )
|
||||
[ ${__SCWRYPT} ] || echo.error "lookup paths cannot be used outside of scwrypts (${NAME})"
|
||||
[ ${__SCWRYPT} ] && {
|
||||
use scwrypts/environment \
|
||||
|| echo.error "unable to load lookup path"
|
||||
|
||||
LOOKUP_PATH="${NAME}"
|
||||
NAME=$(\
|
||||
scwrypts.environment.get-full-template \
|
||||
| utils.yq -r "${LOOKUP_PATH}.\".ENVIRONMENT\"" \
|
||||
| grep -v ^null$\
|
||||
)
|
||||
|
||||
[ ${NAME} ] || echo.error "no .ENVIRONMENT key is configured for '${LOOKUP_PATH}'"
|
||||
}
|
||||
case "${configuration_variable_name}" in
|
||||
( .* )
|
||||
lookup_type=query-path
|
||||
[[ "${__SCWRYPT}" ]] \
|
||||
|| echo.error "lookup paths cannot be used outside of scwrypts (${configuration_variable_name})"
|
||||
;;
|
||||
( environment-variable )
|
||||
|
||||
( * )
|
||||
lookup_type=environment-variable
|
||||
;;
|
||||
esac
|
||||
|
||||
utils.check-errors --no-usage || return $?
|
||||
utils.check-errors --no-usage || return "${?}"
|
||||
|
||||
##########################################
|
||||
|
||||
# only check env vars once
|
||||
local ALREADY_CHECKED="$(eval echo '$'${NAME}'__checked')"
|
||||
[ "${ALREADY_CHECKED}" ] && return ${ALREADY_CHECKED}
|
||||
# only check env vars once; this is a headache, but serves a substantial performance purpose
|
||||
local already_checked_status_suffix=scwrypts_checked_status
|
||||
local already_checked_status_variable
|
||||
TRACE_ARGS+=(-v already_checked_status_variable)
|
||||
|
||||
[ ${__SCWRYPT} ] || local CI=true # outside of scwrypts, environment must load like CI runtime
|
||||
[ ${CI} ] && {
|
||||
local VALUE="$(eval echo '$'${NAME})"
|
||||
[ "${VALUE}" ] && {
|
||||
export ${NAME}__checked=0
|
||||
[[ ${PRINT_VALUE} =~ true ]] && echo "${VALUE}"
|
||||
return 0
|
||||
}
|
||||
local already_checked_status
|
||||
DEBUG_ARGS+=(-v already_checked_status)
|
||||
case "${lookup_type}" in
|
||||
( query-path ) ;; # can't check early return status just yet
|
||||
( * )
|
||||
already_checked_status_variable="${configuration_variable_name}__${already_checked_status_suffix}"
|
||||
already_checked_status="${(P)already_checked_status_variable}"
|
||||
[[ "${already_checked_status}" ]] && {
|
||||
# if the previous check failed, we can escape early
|
||||
[[ "${already_checked_status}" -ne 0 ]] && echo.trace && return "${already_checked_status}"
|
||||
|
||||
case ${OPTIONAL} in
|
||||
( true )
|
||||
[ "${DEFAULT_VALUE}" ] \
|
||||
&& export ${NAME}=${DEFAULT_VALUE} \
|
||||
|| echo.warning "environment variable '${NAME}' is not set" \
|
||||
;
|
||||
# if we don't need to print, we can escape early
|
||||
[[ "${print_value_to_stdout}" == false ]] && echo.trace && return "${already_checked_status}"
|
||||
}
|
||||
;;
|
||||
esac
|
||||
|
||||
export ${NAME}__checked=0
|
||||
return 0
|
||||
;;
|
||||
( false )
|
||||
local ERROR_MESSAGE="missing required environment variable '${NAME}'"
|
||||
[ "${LOOKUP_PATH}" ] && ERROR_MESSAGE+=" (config path '${LOOKUP_PATH}')"
|
||||
# outside of scwrypts, environment must load like CI runtime
|
||||
[[ "${__SCWRYPT}" ]] || local CI=true
|
||||
DEBUG_ARGS+=(-v CI)
|
||||
|
||||
echo.error "${ERROR_MESSAGE}"
|
||||
export ${NAME}__checked=1
|
||||
return 1
|
||||
local configuration_value configuration_values=() check_status=1
|
||||
TRACE_ARGS+=(-v configuration_value -v configuration_values)
|
||||
case "$(normalize.boolean --variable CI --default false --mode echo)" in
|
||||
( true ) # in CI, all environment configuration MUST come from environment
|
||||
case "${lookup_type}" in
|
||||
( query-path )
|
||||
use scwrypts/environment/get-user-json 2>/dev/null
|
||||
|
||||
user_json_query=".configuration${configuration_variable_name}"
|
||||
configuration_variable_name="$(scwrypts.environment.get-user-json 2>/dev/null | utils.yq -r "${user_json_query}.\".ENVIRONMENT\"" | grep -v ^null$)"
|
||||
|
||||
[[ "${configuration_variable_name}" ]] \
|
||||
|| configuration_variable_name="SCWRYPTS_CONFIG_$(echo "${user_json_query}" | sed 's/[-. ]/_/g' | tr '[:upper:]' '[:lower:]')"
|
||||
|
||||
[[ "${configuration_variable_name}" ]] \
|
||||
|| echo.error "no .ENVIRONMENT key is configured for ${user_json_query}, and I failed to autogenerate one" \
|
||||
|| return 1
|
||||
;;
|
||||
esac
|
||||
utils.environment.check.environment "${configuration_variable_name}"
|
||||
;;
|
||||
|
||||
( false )
|
||||
use scwrypts/environment/get-user-json \
|
||||
|| echo.error "unable to load required environment library" \
|
||||
|| return 1
|
||||
|
||||
case "${lookup_type}" in
|
||||
( environment-variable )
|
||||
user_json_query=".configuration$(scwrypts.environment.get-user-json | utils.yq -r ".lookup.${configuration_variable_name}" | grep -v ^null$ | sed 's/\.value$//')"
|
||||
[[ "${user_json_query}" != '.configuration' ]] \
|
||||
|| echo.error "unable to determine lookup value from variable '${configuration_variable_name}'" \
|
||||
|| return 1
|
||||
;;
|
||||
|
||||
( query-path )
|
||||
user_json_query=".configuration${configuration_variable_name}"
|
||||
configuration_variable_name="$(scwrypts.environment.get-user-json | utils.yq -r "${user_json_query}.\".ENVIRONMENT\"" | grep -v ^null$)"
|
||||
|
||||
[[ "${configuration_variable_name}" ]] \
|
||||
|| configuration_variable_name="SCWRYPTS_CONFIG_$(echo "${user_json_query}" | sed 's/[-. ]/_/g' | tr '[:upper:]' '[:lower:]')"
|
||||
|
||||
[[ "${configuration_variable_name}" ]] \
|
||||
|| echo.error "no .ENVIRONMENT key is configured for ${user_json_query}, and I failed to autogenerate one" \
|
||||
|| return 1
|
||||
|
||||
# now that we know the name of the variable, we can check early return status
|
||||
already_checked_status_variable="${configuration_variable_name}__${already_checked_status_suffix}"
|
||||
already_checked_status="${(P)already_checked_status_variable}"
|
||||
[[ "${already_checked_status}" ]] && {
|
||||
# if the previous check failed, we can escape early
|
||||
[[ "${already_checked_status}" -ne 0 ]] && return "${already_checked_status}"
|
||||
|
||||
# if we don't need to print, we can escape early
|
||||
[[ "${print_value_to_stdout}" == false ]] && return "${already_checked_status}"
|
||||
}
|
||||
;;
|
||||
esac
|
||||
|
||||
# ensure environment safety; prevent bleed in from user's runtime
|
||||
[[ "${already_checked_status}" ]] || unset -- "${configuration_variable_name}"
|
||||
|
||||
local configuration_json="$(scwrypts.environment.get-user-json | utils.yq "${user_json_query}")"
|
||||
local configuration_value_type
|
||||
DEBUG_ARGS+=(-v configuration_value_type)
|
||||
|
||||
local get_method
|
||||
for get_method in \
|
||||
environment \
|
||||
runtime-override \
|
||||
value \
|
||||
selection \
|
||||
;
|
||||
do
|
||||
utils.environment.check.${get_method} "${configuration_variable_name}" "${configuration_json}"
|
||||
[[ "${configuration_value}" ]] && break
|
||||
[[ "${#configuration_values[@]}" -gt 0 ]] && break
|
||||
done
|
||||
;;
|
||||
esac
|
||||
|
||||
[[ "${configuration_value}" ]] && check_status=0 && configuration_value_type=scalar
|
||||
[[ "${#configuration_values[@]}" -gt 0 ]] && check_status=0 && configuration_value_type=array
|
||||
|
||||
[[ "${check_status}" -eq 0 ]] || {
|
||||
case "${optional}" in
|
||||
( false ) check_status=1 ;;
|
||||
( true ) check_status=0
|
||||
[[ "${default_value}" ]] \
|
||||
&& configuration_value="${default_value}" \
|
||||
|| echo.warning "environment variable '${configuration_variable_name}' is not set"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
[ ! ${LOOKUP_PATH} ] && {
|
||||
LOOKUP_PATH="$(scwrypts.environment.get-envvar-lookup-map | utils.yq -r ".${NAME}" | sed 's/\.value$//')"
|
||||
}
|
||||
export ${already_checked_status_variable}="${check_status}"
|
||||
|
||||
# ensure environment safety; prevent bleed in from user's runtime
|
||||
unset ${NAME}
|
||||
local VALUE
|
||||
for GET_VALUE_METHOD in \
|
||||
runtimeoverride \
|
||||
value \
|
||||
selection \
|
||||
select \
|
||||
;
|
||||
do
|
||||
VALUE=$(utils.environment.check.${GET_VALUE_METHOD} "${NAME}" "${LOOKUP_PATH}" 2>/dev/null)
|
||||
[ "${VALUE}" ] && eval "export ${NAME}=${VALUE}" && break
|
||||
done
|
||||
|
||||
[ "${VALUE}" ] && {
|
||||
export ${NAME}__checked=0
|
||||
[[ ${PRINT_VALUE} =~ true ]] && echo "${VALUE}"
|
||||
return 0
|
||||
}
|
||||
|
||||
case ${OPTIONAL} in
|
||||
( true )
|
||||
[ "${DEFAULT_VALUE}" ] \
|
||||
&& export ${NAME}=${DEFAULT_VALUE} \
|
||||
|| echo.warning "environment variable '${NAME}' is not set" \
|
||||
;
|
||||
|
||||
export ${NAME}__checked=0
|
||||
[[ ${PRINT_VALUE} =~ true ]] && echo "${DEFAULT_VALUE}"
|
||||
return 0
|
||||
case "${configuration_value_type}" in
|
||||
( array )
|
||||
[[ "${print_value_to_stdout}" == true ]] && printf '%s\n' "${configuration_values[@]}"
|
||||
eval "export ${configuration_variable_name}=(${(q)configuration_values[@]})"
|
||||
;;
|
||||
( false )
|
||||
local ERROR_MESSAGE="missing required environment variable '${NAME}'"
|
||||
[ "${LOOKUP_PATH}" ] && ERROR_MESSAGE+=" (config path '${LOOKUP_PATH}')"
|
||||
|
||||
echo.error "${ERROR_MESSAGE}"
|
||||
export ${NAME}__checked=1
|
||||
return 1
|
||||
( * )
|
||||
[[ "${print_value_to_stdout}" == true ]] && echo "${configuration_value}"
|
||||
export ${configuration_variable_name}="${configuration_value}"
|
||||
;;
|
||||
esac
|
||||
|
||||
echo.trace
|
||||
return "${check_status}"
|
||||
}
|
||||
|
||||
utils.environment.check.environment() { # pulls directly from environment : used in CI and after first check
|
||||
local configuration_variable_name="${1}"
|
||||
#local configuration_json="${2}"
|
||||
|
||||
# used when we need to print the variable after it was already checked once
|
||||
case "${(tP)configuration_variable_name}" in
|
||||
( '' ) ;; # no value set
|
||||
|
||||
( *array* ) configuration_values=("${(P@)configuration_variable_name}") ;;
|
||||
( * ) configuration_value="${(P)configuration_variable_name}" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
utils.environment.check.runtimeoverride() {
|
||||
local NAME="$1"
|
||||
local LOOKUP_PATH="$2"
|
||||
eval echo '$'${NAME}'__override'
|
||||
utils.environment.check.runtime-override() { # support for ENV_VAR__override= : scalar only
|
||||
local configuration_variable_name="${1}"
|
||||
#local configuration_json="${2}"
|
||||
|
||||
local environment_override_configuration_variable_name="${configuration_variable_name}__override"
|
||||
|
||||
configuration_value="${(P)environment_override_configuration_variable_name}"
|
||||
}
|
||||
|
||||
utils.environment.check.value() {
|
||||
local NAME="$1"
|
||||
local LOOKUP_PATH="$2"
|
||||
utils.environment.check.value() { # support for '.value' : splits between array and scalar
|
||||
local configuration_variable_name="${1}"
|
||||
local configuration_json="${2}"
|
||||
|
||||
scwrypts.environment.user.get-shell-values \
|
||||
| utils.yq -r "${LOOKUP_PATH}.value" \
|
||||
| grep -v '^null$' \
|
||||
;
|
||||
case "$(echo "${configuration_json}" | utils.yq -r '.value | type')" in
|
||||
( !!null ) ;; # no value set
|
||||
( !!map ) ;; # unsupported type(s)
|
||||
( !!seq )
|
||||
local _v
|
||||
while IFS= read -r _v
|
||||
do
|
||||
configuration_values+=("${_v}")
|
||||
done < <(echo "${configuration_json}" | utils.yq -r '.value[]')
|
||||
;;
|
||||
|
||||
( * ) configuration_value="$(echo "${configuration_json}" | utils.yq -r '.value')" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
utils.environment.check.selection() {
|
||||
local NAME="$1"
|
||||
local LOOKUP_PATH="$2"
|
||||
utils.environment.check.selection() { # support for '.selection' : configured list prompts user to select a scalar value
|
||||
local configuration_variable_name="${1}"
|
||||
local configuration_json="${2}"
|
||||
|
||||
local SELECTION_VALUES=($(
|
||||
scwrypts.environment.user.get-shell-values \
|
||||
| utils.yq -r "${LOOKUP_PATH}.selection[]" \
|
||||
| grep -v '^null$' \
|
||||
))
|
||||
local selection_value selection_values=()
|
||||
while IFS= read -r selection_value
|
||||
do
|
||||
selection_values+=("${selection_value}")
|
||||
done < <(echo "${configuration_json}" | utils.yq -r ".selection[]" | grep -v '^null$')
|
||||
|
||||
[[ ${#SELECTION_VALUES[@]} -gt 0 ]] || return
|
||||
[[ "${#selection_values[@]}" -gt 0 ]] || return
|
||||
|
||||
echo "${SELECTION_VALUES}" \
|
||||
| sed 's/\s\+/\n/g' \
|
||||
| utils.fzf "select a value for '${NAME}'" \
|
||||
;
|
||||
}
|
||||
|
||||
utils.environment.check.select() { # support for ENV_VAR__select=()
|
||||
local NAME="$1"
|
||||
local LOOKUP_PATH="$2"
|
||||
|
||||
local SELECTION_VALUES=($(eval echo '$'${NAME}'__select'))
|
||||
|
||||
[[ ${#SELECTION_VALUES[@]} -gt 0 ]] || return
|
||||
|
||||
echo.warning "support for ENV_VAR__select syntax is deprecated;\nplease use the .selection[] array in the yaml configuration for user-selectable options"
|
||||
|
||||
echo "${SELECTION_VALUES}" \
|
||||
| sed 's/\s\+/\n/g' \
|
||||
| utils.fzf "select a value for '${NAME}'" \
|
||||
;
|
||||
configuration_value="$(
|
||||
printf '%s\n' "${selection_values[@]}" \
|
||||
| utils.fzf "select a value for '${configuration_variable_name}'" \
|
||||
)"
|
||||
}
|
||||
|
||||
@@ -22,15 +22,10 @@ utils.colors.remove() { sed 's/\x1b\[[0-9;]*m//g'; }
|
||||
#####################################################################
|
||||
|
||||
utils.colors.print() { # print color + message + color-reset sequence
|
||||
local COLOR="$1"
|
||||
local MESSAGE="${@:2}"
|
||||
local color="${1}"
|
||||
local message="${@:2}"
|
||||
|
||||
command -v utils.colors.${COLOR} &>/dev/null || {
|
||||
printf "${MESSAGE}"
|
||||
return
|
||||
}
|
||||
|
||||
printf "$(utils.colors.${COLOR})${MESSAGE}$(utils.colors.reset)"
|
||||
printf "%s" "$(utils.colors.${color} 2>/dev/null)${message}$(utils.colors.reset)"
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
|
||||
+81
-60
@@ -2,103 +2,124 @@ DEPENDENCIES+=(jo jq printf)
|
||||
|
||||
utils.io.print() {
|
||||
local \
|
||||
MESSAGE \
|
||||
PREFIX COLOR \
|
||||
MINIMUM_LOG_LEVEL IGNORE_MINIMUM_LOG_LEVEL=false \
|
||||
PRINT_TO_STDERR=true \
|
||||
PRINT_TO_STDOUT=false \
|
||||
LAST_LINE_END='\n' \
|
||||
message \
|
||||
prefix prefix_delimiter=' : ' color \
|
||||
minimum_log_level ignore_minimum_log_level=false \
|
||||
print_to_stderr=true \
|
||||
print_to_stdout=false \
|
||||
last_line_end=$'\n' \
|
||||
variables=() \
|
||||
;
|
||||
|
||||
[ ${SCWRYPTS_LOG_LEVEL} ] || local SCWRYPTS_LOG_LEVEL=4
|
||||
[ ${SCWRYPTS_OUTPUT_FORMAT} ] || local SCWRYPTS_OUTPUT_FORMAT=pretty
|
||||
# since this function can be used outside of scwrypts, we bump log
|
||||
# level to maximum to avoid confusion of "why isn't my print
|
||||
# statment working"
|
||||
[[ "${SCWRYPTS_LOG_LEVEL:=${LOG_LEVEL}}" ]] \
|
||||
|| local SCWRYPTS_LOG_LEVEL=4
|
||||
|
||||
local _S
|
||||
while [[ $# -gt 0 ]]
|
||||
[[ "${SCWRYPTS_OUTPUT_FORMAT}" ]] \
|
||||
|| local SCWRYPTS_OUTPUT_FORMAT=pretty
|
||||
|
||||
local _s
|
||||
while [[ "${#}" -gt 0 ]]
|
||||
do
|
||||
_S=1
|
||||
case $1 in
|
||||
( --prefix )
|
||||
_S=2
|
||||
PREFIX="$2"
|
||||
;;
|
||||
_s=1
|
||||
case "${1}" in
|
||||
( --prefix ) _s=2; prefix="${2}" ;;
|
||||
( --prefix-delimiter ) _s=2; prefix_delimiter="${2}" ;;
|
||||
( --color ) _s=2; color="${2}" ;;
|
||||
( --minimum-log-level ) _s=2; minimum_log_level="${2}" ;;
|
||||
( --format ) _s=2; local SCWRYPTS_OUTPUT_FORMAT="${2}" ;;
|
||||
|
||||
( --color )
|
||||
_S=2
|
||||
COLOR="$2"
|
||||
;;
|
||||
|
||||
( --minimum-log-level )
|
||||
_S=2
|
||||
MINIMUM_LOG_LEVEL=$2
|
||||
( -v ) _s=2 # appends 'name=value' to the end of the message
|
||||
variables+=("${2}")
|
||||
;;
|
||||
|
||||
( --force-print )
|
||||
IGNORE_MINIMUM_LOG_LEVEL=true
|
||||
ignore_minimum_log_level=true
|
||||
;;
|
||||
|
||||
( --stdout )
|
||||
PRINT_TO_STDOUT=true
|
||||
PRINT_TO_STDERR=false
|
||||
print_to_stdout=true
|
||||
print_to_stderr=false
|
||||
;;
|
||||
|
||||
( --no-line-end )
|
||||
LAST_LINE_END=''
|
||||
;;
|
||||
|
||||
( --format )
|
||||
_S=2
|
||||
local SCWRYPTS_OUTPUT_FORMAT=$2
|
||||
( --no-line-end ) # only applicable to some formats (see below)
|
||||
last_line_end=''
|
||||
;;
|
||||
|
||||
( * )
|
||||
[ "${MESSAGE}" ] && MESSAGE+=" $1" || MESSAGE="$1"
|
||||
[[ "${message}" ]] && message+=" ${1}" || message="${1}"
|
||||
;;
|
||||
esac
|
||||
|
||||
[[ ${_S} -le $# ]] && shift ${_S} || { echo "echo.error : missing argument for '$1'" >&2; return 1; }
|
||||
shift "${_s}" || { echo "ERROR : missing argument for '${1}'" >&2; return 1; }
|
||||
done
|
||||
|
||||
: \
|
||||
&& [ "${MINIMUM_LOG_LEVEL}" ] \
|
||||
&& [[ "${IGNORE_MINIMUM_LOG_LEVEL}" =~ false ]] \
|
||||
&& [[ "${SCWRYPTS_LOG_LEVEL}" -lt "${MINIMUM_LOG_LEVEL}" ]] \
|
||||
&& [[ "${minimum_log_level}" ]] \
|
||||
&& [[ "${ignore_minimum_log_level}" =~ false ]] \
|
||||
&& [[ "${SCWRYPTS_LOG_LEVEL}" -lt "${minimum_log_level}" ]] \
|
||||
&& return 0
|
||||
|
||||
local variable
|
||||
for variable in "${variables[@]}"
|
||||
do
|
||||
message+=$'\n'" - ${variable}=${(Pq)variable}"
|
||||
done
|
||||
|
||||
case ${SCWRYPTS_OUTPUT_FORMAT:l} in
|
||||
( raw ) message+="${last_line_end}" ;;
|
||||
|
||||
MESSAGE="$(echo "${MESSAGE}" | sed 's/^ \+//; s/%/%%/g')"
|
||||
case ${SCWRYPTS_OUTPUT_FORMAT} in
|
||||
( raw ) MESSAGE+="${LAST_LINE_END}" ;;
|
||||
( pretty )
|
||||
MESSAGE="${COLOR}$({
|
||||
while IFS='' read line
|
||||
message="${color}$({
|
||||
while IFS='' read -r line
|
||||
do
|
||||
[[ ${PREFIX} =~ ^[[:space:]]\+$ ]] && printf '\n'
|
||||
|
||||
printf "${PREFIX} : $(echo "${line}" | sed 's/^ \+//; s/ \+$//')"
|
||||
|
||||
PREFIX=' '
|
||||
done <<< $(echo "${MESSAGE}" | sed 's/%/%%/g')
|
||||
})${LAST_LINE_END}$(utils.colors.reset)"
|
||||
[[ ${prefix} =~ ^[[:space:]]\+$ ]] && printf '%s' $'\n'
|
||||
printf "%s" "${prefix}${prefix_delimiter}$(echo "${line}" | sed 's/^ \+//; s/ \+$//')"
|
||||
prefix="$(printf '%*s' ${#prefix} '')"
|
||||
done <<< "${message}"
|
||||
})${last_line_end}$(utils.colors.reset)"
|
||||
;;
|
||||
|
||||
( json )
|
||||
MESSAGE="$(jo \
|
||||
message="$(jo \
|
||||
timestamp=$(date +%s) \
|
||||
runtime=${SCWRYPTS_RUNTIME_ID} \
|
||||
status="$(echo "${PREFIX}" | sed 's/ .*//')" \
|
||||
message="$(echo -n "${MESSAGE}" | sed 's/^\t\+//' | jq -Rs)" \
|
||||
2>/dev/null || echo "{\"error\":\"your message was too long so I encoded it manually\",\"messageB64\":\"$(echo "${MESSAGE}" | base64 | tr -d '\n')\"}"
|
||||
)\n"
|
||||
status="$(echo "${prefix}" | sed 's/ .*//')" \
|
||||
message="$(echo -n "${message}" | sed 's/^\t\+//')" \
|
||||
2>/dev/null || echo "{\"error\":\"your message was too long so I encoded it\",\"messageB64\":\"$(echo "${message}" | base64 | tr -d '\n')\"}"
|
||||
)"$'\n'
|
||||
;;
|
||||
|
||||
( logfmt )
|
||||
local ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
|
||||
local level
|
||||
case "${prefix}" in
|
||||
( ERROR* ) level=error ;;
|
||||
( WARNING* ) level=warn ;;
|
||||
( DEBUG* ) level=debug ;;
|
||||
( TRACE* ) level=trace ;;
|
||||
( * ) level=info ;;
|
||||
esac
|
||||
|
||||
local msg="${message}"
|
||||
msg="${msg//\\/\\\\}"
|
||||
msg="${msg//\"/\\\"}"
|
||||
msg="${msg//$'\n'/\\n}"
|
||||
|
||||
message="ts=${ts} level=${level} runtime=${SCWRYPTS_RUNTIME_ID} msg=\"${msg}\""$'\n'
|
||||
;;
|
||||
|
||||
( * )
|
||||
echo "echo.error : unsupported format '${SCWRYPTS_OUTPUT_FORMAT}'" >&2
|
||||
echo "ERROR : unsupported format '${SCWRYPTS_OUTPUT_FORMAT}'" >&2
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
|
||||
[[ ${PRINT_TO_STDERR} =~ true ]] && printf -- "${MESSAGE}" >&2
|
||||
[[ ${PRINT_TO_STDOUT} =~ true ]] && printf -- "${MESSAGE}"
|
||||
[[ "${print_to_stderr}" =~ true ]] && printf "%s" "${message}" >&2
|
||||
[[ "${print_to_stdout}" =~ true ]] && printf "%s" "${message}"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
+190
-26
@@ -1,57 +1,221 @@
|
||||
echo.success.color() { utils.colors.green; }
|
||||
echo.success() { # command completed successfully
|
||||
utils.io.print $@ --minimum-log-level 1 --prefix "SUCCESS ✔" --color $(echo.success.color)
|
||||
return 0
|
||||
}
|
||||
export _SCWRYPTS_DEFAULT_LOG_LEVEL=3
|
||||
typeset -gA SCWRYPTS_LOG_LEVELS
|
||||
|
||||
### level 0 : only print forced messages ############################
|
||||
SCWRYPTS_LOG_LEVELS[0]=fatal
|
||||
|
||||
### level 1 : only print errors #####################################
|
||||
SCWRYPTS_LOG_LEVELS[1]=error
|
||||
|
||||
echo.error.color() { utils.colors.red; }
|
||||
echo.error() { # command encountered an error
|
||||
utils.io.print $@ --minimum-log-level 1 --prefix "ERROR ✖" --color $(echo.error.color)
|
||||
utils.io.print "${@}" --minimum-log-level 1 --prefix "ERROR ✖" --color "$(echo.error.color)"
|
||||
((ERRORS+=1))
|
||||
return ${ERRORS}
|
||||
}
|
||||
|
||||
echo.reminder.color() { utils.colors.bright-magenta; }
|
||||
echo.reminder() { # sysadmin reminder or important notice to users
|
||||
utils.io.print $@ --minimum-log-level 1 --prefix "REMINDER " --color $(echo.reminder.color)
|
||||
echo.error.user-abort() { # consistent verbiage for user-aborted workflows
|
||||
echo.error 'user abort' "${@}"
|
||||
}
|
||||
|
||||
### level 2 : print errors & warnings ###############################
|
||||
SCWRYPTS_LOG_LEVELS[2]=warning
|
||||
|
||||
echo.warning.color() { utils.colors.yellow; }
|
||||
echo.warning() { # warning-level messages; not errors
|
||||
utils.io.print "${@}" --minimum-log-level 2 --prefix "WARNING " --color "$(echo.warning.color)"
|
||||
((WARNINGS+=1))
|
||||
return 0
|
||||
}
|
||||
|
||||
echo.warning.deprecation() { # emit a deprecation warning
|
||||
local removed_in_version="${1}"
|
||||
local message="${2:-this function will be removed}"
|
||||
|
||||
# "scwrypts/vX.X" is the default (specifies only vX.X), but custom version allowed
|
||||
[[ "${removed_in_version}" =~ ^v[0-9.]+$ ]] \
|
||||
&& removed_in_version="scwrypts/${removed_in_version}"
|
||||
|
||||
echo.warning "DEPRECATED : ${funcstack[2]} : ${message} in ${removed_in_version}"
|
||||
}
|
||||
|
||||
### level 3 : print status messages & user information ##############
|
||||
SCWRYPTS_LOG_LEVELS[3]=status
|
||||
|
||||
echo.success.color() { utils.colors.green; }
|
||||
echo.success() { # command completed successfully
|
||||
utils.io.print "${@}" --minimum-log-level 3 --prefix "SUCCESS ✔" --color "$(echo.success.color)"
|
||||
return 0
|
||||
}
|
||||
|
||||
echo.status.color() { utils.colors.blue; }
|
||||
echo.status() { # general status updates (prefer this to generic 'echo')
|
||||
utils.io.print $@ --minimum-log-level 2 --prefix "STATUS " --color $(echo.status.color)
|
||||
utils.io.print "${@}" --minimum-log-level 3 --prefix "STATUS " --color "$(echo.status.color)"
|
||||
return 0
|
||||
}
|
||||
|
||||
echo.warning.color() { utils.colors.yellow; }
|
||||
echo.warning() { # warning-level messages; not errors
|
||||
utils.io.print $@ --minimum-log-level 3 --prefix "WARNING " --color $(echo.warning.color)
|
||||
echo.reminder.color() { utils.colors.bright-magenta; }
|
||||
echo.reminder() { # sysadmin reminder or important notice to users
|
||||
utils.io.print "${@}" --minimum-log-level 3 --prefix "REMINDER " --color "$(echo.reminder.color)"
|
||||
return 0
|
||||
}
|
||||
|
||||
### level 4 : include debug messages ################################
|
||||
SCWRYPTS_LOG_LEVELS[4]=debug
|
||||
|
||||
echo.debug.color() { utils.colors.white; }
|
||||
echo.debug() { # helpful during development or (sparingly) to help others' development
|
||||
# early exit since debug injects state information
|
||||
[ ${SCWRYPTS_LOG_LEVEL} ] && [[ ${SCWRYPTS_LOG_LEVEL} -lt 4 ]] && [[ ! $@ =~ --force-print ]] && return 0
|
||||
set -- "${DEBUG_ARGS[@]}" "${@}"
|
||||
case "${@[(re)--force-print]}" in
|
||||
( --force-print ) ;; # if --force-print is present; always print
|
||||
( * ) # early exit since trace injects state information which is slow
|
||||
[[ "${SCWRYPTS_LOG_LEVEL:=${LOG_LEVEL}}" ]] || return 0
|
||||
[[ "${SCWRYPTS_LOG_LEVEL}" -lt 4 ]] && return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
local args=() extra_print_lines=() variable_name message_count=0
|
||||
local _s
|
||||
while [[ "${#}" -gt 0 ]]
|
||||
do
|
||||
_s=1
|
||||
case "${1}" in
|
||||
( -v ) _s=2
|
||||
variable_name="${2}"
|
||||
extra_print_lines+="\n> DEBUG::var${variable_name}${(t)${(P)variable_name}}${(P)variable_name}$(echo "${(P)variable_name}" | grep -q . || echo "$(echo.warning.color)<variable empty>$(echo.debug.color)")"
|
||||
;;
|
||||
|
||||
( --eval ) _s=2
|
||||
args+=("$(eval "${2}" 2>&1 | grep . || echo "failed to run ${2}")")
|
||||
((message_count+=1))
|
||||
;;
|
||||
|
||||
( -* ) args+=("${1}") ;;
|
||||
( '' ) ;; # a '' is inserted from empty TRACE_ARGS
|
||||
( * ) args+=("${1}") ; ((message_count+=1)) ;;
|
||||
esac
|
||||
shift "${_s}" || shift "${#}"
|
||||
done
|
||||
unset _s
|
||||
|
||||
[[ "${message_count}" -eq 0 ]] && args+=("<debug called by $(utils.colors.cyan)${funcstack[2]:-<unknown>~}$(echo.debug.color)>")
|
||||
|
||||
utils.io.print "${args[@]}" --minimum-log-level 4 --prefix "DEBUG ℹ" --color "$(echo.debug.color)" \
|
||||
$'\n'"$(
|
||||
[[ "${extra_print_lines[@]}" ]] && {
|
||||
echo "> DEBUG::var$(utils.colors.bright-yellow)variabletypevalue$(echo.debug.color)"
|
||||
echo "> DEBUG::var---------$(echo.debug.color)"
|
||||
echo "${extra_print_lines[@]}" | sort -u
|
||||
echo "> DEBUG::var---------$(echo.debug.color)"
|
||||
} | column -ts ''
|
||||
echo "> DEBUG::called-by : ${funcstack[2]}"
|
||||
echo "> DEBUG::funcstack : $(echo "${funcstack[@]:1}" | sed 's/ (anon) (eval) (anon)$/ scwrypts/')"
|
||||
echo "> DEBUG::timestamp : $(date +%s)"
|
||||
)"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
### level 5 : include full trace (may be sensitive) #################
|
||||
SCWRYPTS_LOG_LEVELS[5]=trace
|
||||
|
||||
echo.trace.color() { utils.colors.reset; }
|
||||
echo.trace() {
|
||||
(( ${funcstack[(Ie)echo.trace]} > 1 )) && return 0 # escape early if echo.trace is recursively nesting
|
||||
set -- "${TRACE_ARGS[@]}" "${DEBUG_ARGS[@]}" "${@}"
|
||||
case "${@[(re)--force-print]}" in
|
||||
( --force-print ) ;; # if --force-print is present; always print
|
||||
( * ) # early exit since trace injects state information which is slow
|
||||
[[ "${SCWRYPTS_LOG_LEVEL:=${LOG_LEVEL}}" ]] || return 0
|
||||
[[ "${SCWRYPTS_LOG_LEVEL}" -lt 5 ]] && return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
local args=() extra_print_lines=() variable_name message_count=0
|
||||
local _s
|
||||
while [[ "${#}" -gt 0 ]]
|
||||
do
|
||||
_s=1
|
||||
case "${1}" in
|
||||
( -v ) _s=2
|
||||
variable_name="${2}"
|
||||
extra_print_lines+="\n> TRACE::var${variable_name}${(t)${(P)variable_name}}${(P)variable_name}$(echo "${(P)variable_name}" | grep -q . || echo "$(echo.warning.color)<variable empty>$(echo.trace.color)")"
|
||||
;;
|
||||
|
||||
( --eval ) _s=2
|
||||
args+=("$(eval "${2}" 2>&1 | grep . || echo "failed to run ${2}")")
|
||||
((message_count+=1))
|
||||
;;
|
||||
|
||||
( -* ) args+=("${1}") ;;
|
||||
( '' ) ;; # a '' is inserted from empty TRACE_ARGS / DEBUG_ARGS
|
||||
( * ) args+=("${1}") ; ((message_count+=1)) ;;
|
||||
esac
|
||||
shift "${_s}" 2>/dev/null || shift "${#}"
|
||||
done
|
||||
unset _s
|
||||
|
||||
[[ "${message_count}" -eq 0 ]] && args+=("<trace called by ${funcstack[2]:-<unknown>~}>")
|
||||
|
||||
utils.io.print "${args[@]}" --minimum-log-level 5 --prefix "TRACE " --color "$(echo.trace.color)" \
|
||||
$'\n'"$(
|
||||
[[ "${extra_print_lines[@]}" ]] && {
|
||||
echo "> TRACE::var$(utils.colors.bright-yellow)variabletypevalue$(echo.trace.color)"
|
||||
echo "> TRACE::var---------$(echo.trace.color)"
|
||||
echo "${extra_print_lines[@]}" | sort -u
|
||||
echo "> TRACE::var---------$(echo.trace.color)"
|
||||
} | column -ts ''
|
||||
echo "> TRACE::called-by : ${funcstack[2]}"
|
||||
echo "> TRACE::funcstack : ${funcstack[@]}"
|
||||
echo "> TRACE::timestamp : $(date +%s)"
|
||||
)"
|
||||
|
||||
|
||||
utils.io.print $@ --minimum-log-level 4 --prefix "DEBUG ℹ" --color $(echo.debug.color) \
|
||||
"\n> DEBUG::funcstack : $(echo "${funcstack[@]:1}" | sed 's/ (anon) (eval) (anon)$/ scwrypts/')" \
|
||||
"\n> DEBUG::timestamp : $(date +%s)" \
|
||||
;
|
||||
return 0
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
|
||||
echo.prompt() {
|
||||
[ ! "${SCWRYPTS_LOG_LEVEL}" ] && local SCWRYPTS_LOG_LEVEL=4
|
||||
local prompt_print_args=()
|
||||
local response response_set=false only_response=false
|
||||
local response_print_args=()
|
||||
|
||||
[[ "${SCWRYPTS_LOG_LEVEL}" -eq 0 ]] && {
|
||||
utils.io.print --format raw $@ ": " --no-line-end
|
||||
return 0
|
||||
local _s
|
||||
while [[ "${#}" -gt 0 ]]
|
||||
do
|
||||
_s=1
|
||||
case "${1}" in
|
||||
( --response ) _s=2; response_set=true; response="${2}" ;;
|
||||
( --only-response ) only_response=true ;;
|
||||
|
||||
( * ) prompt_print_args+=("${1}") ;;
|
||||
esac
|
||||
shift "${_s}" || shift "${#}"
|
||||
done
|
||||
unset _s
|
||||
|
||||
[[ "${response_set}" == true ]] \
|
||||
&& response_print_args+=(':' "${response}") \
|
||||
|| response_print_args+=(': ' --no-line-end) \
|
||||
;
|
||||
|
||||
[[ "${only_response}" == true ]] && {
|
||||
utils.io.print "${response}" --prefix-delimiter ''
|
||||
return "${?}"
|
||||
}
|
||||
|
||||
utils.io.print $@ --prefix "PROMPT " --color $(utils.colors.cyan)
|
||||
utils.io.print '' --prefix "USER ⌨" --color $(utils.colors.bright-cyan) --no-line-end
|
||||
return 0
|
||||
case "${SCWRYPTS_LOG_LEVEL:-${LOG_LEVEL:-${_SCWRYPTS_DEFAULT_LOG_LEVEL}}}" in
|
||||
( 0 )
|
||||
case "${SCWRYPTS_OUTPUT_FORMAT:-pretty}" in
|
||||
( pretty ) utils.io.print "${prompt_print_args[@]}" "${response_print_args[@]}" --format raw ;;
|
||||
( * ) utils.io.print "${prompt_print_args[@]}" "${response_print_args[@]}" ;;
|
||||
esac
|
||||
;;
|
||||
|
||||
( * )
|
||||
utils.io.print "${prompt_print_args[@]}" --prefix "PROMPT " --color $(utils.colors.cyan)
|
||||
utils.io.print "${response_print_args[2]}" --prefix "USER ⌨" --color $(utils.colors.bright-cyan)
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
+47
-40
@@ -9,8 +9,8 @@ utils.io.input() { # read a single line of user input
|
||||
|
||||
# yes/no prompts && = yes (exit code 0)
|
||||
# || = no (exit code 1)
|
||||
utils.Yn() { [[ ! $(utils.io.read-yn $@ '[Yn]') =~ [nN] ]]; } # default 'yes'
|
||||
utils.yN() { [[ $(utils.io.read-yn $@ '[yN]') =~ [yY] ]]; } # default 'no'
|
||||
user.Yn() { [[ ! $(utils.io.read-yn $@ '[Yn]') =~ [nN] ]]; } # default 'yes'
|
||||
user.yN() { [[ $(utils.io.read-yn $@ '[yN]') =~ [yY] ]]; } # default 'no'
|
||||
|
||||
utils.io.edit() { # edit a file in user's preferred editor
|
||||
[ ${CI} ] && {
|
||||
@@ -25,7 +25,7 @@ utils.io.edit() { # edit a file in user's preferred editor
|
||||
|
||||
utils.io.getsudo() { # ensure a user has sudo permissions
|
||||
echo.prompt 'checking sudo password' --stdout | head -n1 >&2
|
||||
sudo echo hi >/dev/null 2>&1 </dev/tty \
|
||||
sudo echo hi &>/dev/null </dev/tty \
|
||||
&& echo.success '...authenticated!' \
|
||||
|| echo.error 'failed :c' \
|
||||
|| return 1
|
||||
@@ -35,51 +35,56 @@ utils.io.getsudo() { # ensure a user has sudo permissions
|
||||
#####################################################################
|
||||
|
||||
utils.io.read() {
|
||||
[ ${CI} ] && [ -t 0 ] \
|
||||
&& utils.fail 42 'currently in CI, but attempting interactive read; aborting'
|
||||
local stdin_is_a_terminal \
|
||||
&& [[ -t 0 ]] \
|
||||
&& stdin_is_a_terminal=true \
|
||||
|| stdin_is_a_terminal=false \
|
||||
;
|
||||
|
||||
local FORCE_USER_INPUT=false
|
||||
[[ "$(normalize.boolean CI)" == true ]] && [[ "${stdin_is_a_terminal}" == true ]] && {
|
||||
echo.error 'currently in CI, but attempting interactive read; aborting'
|
||||
return 42
|
||||
}
|
||||
|
||||
local force_user_input=false
|
||||
local ARGS=()
|
||||
|
||||
while [[ $# -gt 0 ]]
|
||||
while [[ "${#}" -gt 0 ]]
|
||||
do
|
||||
case $1 in
|
||||
--force-user-input ) FORCE_USER_INPUT=true ;;
|
||||
-k )
|
||||
ARGS+=($1)
|
||||
;;
|
||||
* ) ARGS+=($1) ;;
|
||||
case "${1}" in
|
||||
( --force-user-input ) force_user_input=true ;;
|
||||
( * ) ARGS+=("${1}") ;;
|
||||
esac
|
||||
shift 1
|
||||
done
|
||||
|
||||
while read -k -t 0; do :; done; # flush user stdin
|
||||
|
||||
case ${FORCE_USER_INPUT} in
|
||||
true )
|
||||
read ${PREARGS[@]} ${ARGS[@]} $@ </dev/tty
|
||||
case "${force_user_input}" in
|
||||
( true )
|
||||
read "${PREARGS[@]}" "${ARGS[@]}" "${@}" </dev/tty
|
||||
;;
|
||||
false )
|
||||
[ -t 0 ] || ARGS=(-u 0 ${ARGS[@]})
|
||||
read ${ARGS[@]} $@
|
||||
( false )
|
||||
[[ "${stdin_is_a_terminal}" == false ]] && ARGS=(-u 0 ${ARGS[@]})
|
||||
read "${ARGS[@]}" "${@}"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
utils.io.read-yn() { # yes/no read is suprisingly tricky
|
||||
local FORCE_USER_INPUT=false
|
||||
local force_user_input=false
|
||||
local USERPROMPT=()
|
||||
local READ_ARGS=()
|
||||
local read_args=()
|
||||
|
||||
[ "${SCWRYPTS_LOG_LEVEL}" ] || local SCWRYPTS_LOG_LEVEL=4
|
||||
[[ "${SCWRYPTS_LOG_LEVEL}" ]] || local SCWRYPTS_LOG_LEVEL=4
|
||||
|
||||
while [[ $# -gt 0 ]]
|
||||
while [[ "${#}" -gt 0 ]]
|
||||
do
|
||||
case $1 in
|
||||
--force-user-input )
|
||||
case "${1}" in
|
||||
( --force-user-input )
|
||||
# overrides 'scwrypts -y' and stdin pipe but not CI
|
||||
FORCE_USER_INPUT=true
|
||||
READ_ARGS+=($1)
|
||||
force_user_input=true
|
||||
read_args+=($1)
|
||||
;;
|
||||
* ) USERPROMPT+=($1) ;;
|
||||
esac
|
||||
@@ -88,27 +93,29 @@ utils.io.read-yn() { # yes/no read is suprisingly tricky
|
||||
|
||||
##########################################
|
||||
|
||||
local SKIP_USER_INPUT=false
|
||||
local skip_user_input=false
|
||||
case "$(normalize.boolean CI)" in
|
||||
( true ) # always skip input in CI
|
||||
skip_user_input=true
|
||||
;;
|
||||
|
||||
[ ${CI} ] \
|
||||
&& SKIP_USER_INPUT=true
|
||||
|
||||
[ ${__SCWRYPTS_YES} ] && [[ ${__SCWRYPTS_YES} -eq 1 ]] && [[ ${FORCE_USER_INPUT} =~ false ]] \
|
||||
&& SKIP_USER_INPUT=true
|
||||
( false )
|
||||
[[ "${force_user_input}" == false ]] && [[ "$(normalize.boolean __SCWRYPTS_YES)" == true ]] \
|
||||
&& skip_user_input=true
|
||||
;;
|
||||
esac
|
||||
|
||||
##########################################
|
||||
|
||||
local yn
|
||||
echo.prompt "${USERPROMPT[@]}"
|
||||
|
||||
case ${SKIP_USER_INPUT} in
|
||||
true ) yn=y ;;
|
||||
false )
|
||||
utils.io.read ${READ_ARGS[@]} -s -k yn
|
||||
;;
|
||||
case "${skip_user_input}" in
|
||||
( true ) yn=y ;;
|
||||
( false ) utils.io.read "${read_args[@]}" -s -k yn ;;
|
||||
esac
|
||||
|
||||
[[ ${SCWRYPTS_LOG_LEVEL} -ge 1 ]] && echo ${yn} >&2
|
||||
echo.prompt --only-response --response "${yn}"
|
||||
|
||||
echo ${yn}
|
||||
echo "${yn}"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
normalize.boolean() {
|
||||
local DESCRIPTION='
|
||||
normalizes common boolean values to exact values "true" and "false"
|
||||
|
||||
note: mutates the target variable based on operation mode (see below)
|
||||
'
|
||||
local PARSERS=()
|
||||
eval "$(utils.parse.autosetup)"
|
||||
##########################################
|
||||
|
||||
local variable_value="${(P)variable}"
|
||||
|
||||
case "${variable_value}" in
|
||||
( false | FALSE | False | no | NO | No | n | N | disable* | DISABLE* | Disable* | 0 )
|
||||
variable_value=false
|
||||
;;
|
||||
( true | TRUE | True | yes | YES | Yes | y | Y | enable* | ENABLE* | Enable* | 1 )
|
||||
variable_value=true
|
||||
;;
|
||||
( * )
|
||||
case "${default}" in
|
||||
( '' )
|
||||
echo.error "invalid boolean value ${variable}='${variable_value}'"
|
||||
return 1
|
||||
;;
|
||||
( * )
|
||||
[[ "${variable_value}" == '' ]] \
|
||||
|| echo.warning "unknown boolean value ${variable}='${variable_value}' (using default '${default}')"
|
||||
|
||||
variable_value="${default}"
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
|
||||
case "${mode}" in
|
||||
( export ) export ${variable}="${variable_value}" ;;
|
||||
( set ) ${variable}="${variable_value}" ;;
|
||||
( echo ) ;;
|
||||
esac
|
||||
echo "${variable_value}"
|
||||
}
|
||||
|
||||
normalize.boolean.parse.locals() {
|
||||
local ARGS=()
|
||||
local variable
|
||||
local default
|
||||
local mode=export
|
||||
}
|
||||
|
||||
normalize.boolean.parse() {
|
||||
local parsed=0
|
||||
|
||||
case "${1}" in
|
||||
( --variable ) parsed=2; variable="${2}" ;;
|
||||
( --default ) parsed=2; default="${2}" ;;
|
||||
( --mode ) parsed=2; mode="${2}" ;;
|
||||
esac
|
||||
|
||||
return "${parsed}"
|
||||
}
|
||||
|
||||
normalize.boolean.parse.usage() {
|
||||
USAGE__options+='
|
||||
--variable <string> (required) the environment variable to check
|
||||
--default <value> whether a default value is enabled (default: no default value allowed)
|
||||
|
||||
--mode <string> operation mode; one of the following:
|
||||
export : "export" the variable on success (default)
|
||||
set : execute "VARIABLE_NAME=<value>" and allow parent context to determine variable scope
|
||||
echo : only echo normalized value (do not mutate the variable)
|
||||
'
|
||||
|
||||
USAGE__args+='
|
||||
$1 variable name: when used instead of the above, uses --mode=echo --default=false
|
||||
'
|
||||
}
|
||||
|
||||
normalize.boolean.parse.validate() {
|
||||
case "${variable}" in
|
||||
( '' )
|
||||
variable="${ARGS[1]}"
|
||||
[[ "${default}" ]] || default=false
|
||||
[[ "${mode}" ]] || mode=echo
|
||||
;;
|
||||
esac
|
||||
|
||||
[[ "${variable}" ]] || echo.error "missing --variable-name"
|
||||
|
||||
case "${mode}" in
|
||||
( export | set | echo ) ;;
|
||||
( * ) echo.error "invalid mode '${mode}'" ;;
|
||||
esac
|
||||
}
|
||||
@@ -1,14 +1,21 @@
|
||||
#####################################################################
|
||||
|
||||
utils.fail() { # displays a crash error and exit
|
||||
echo.warning "utils.fail emits an 'exit' code which may unexpectedly terminate the entire scwrypts process"
|
||||
echo.warning "deprecation warning: ${0} will be removed in scwrypts v5.3+"
|
||||
echo.error --force-print ${@:2}
|
||||
exit $1
|
||||
}
|
||||
|
||||
utils.abort() { # for consistency; use after user aborts REQUIRED input
|
||||
echo.warning "deprecation warning: this will be removed in scwrypts v5.3+"
|
||||
utils.fail 69 'user abort'
|
||||
}
|
||||
|
||||
echo.user-abort() {
|
||||
echo.error 'user abort' "${@}"
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
|
||||
utils.check-errors() { # returns an error and reports usage if 'echo.error' was ever called
|
||||
|
||||
@@ -68,16 +68,16 @@ utils.parse.autosetup() {
|
||||
do
|
||||
command -v ${P}.locals &>/dev/null || continue
|
||||
which ${P}.locals | sed '1d; $d;'
|
||||
|
||||
[[ ${DEBUG} =~ true ]] \
|
||||
&& echo.debug --force-print "utils.autosetup : parser ${P} locals\n$(which ${P}.locals | sed 's/^/ /; 1d; $d;')"
|
||||
done | sed 's^\s*local \([A-Z0-9a-z_]*\)\(=\(.*\)\)*$[ "$\1" ] || local \1=\3'
|
||||
done | sed 's|$| >/dev/null|'
|
||||
|
||||
usage.reset
|
||||
|
||||
echo "local USAGE__description=$(printf "%q " "${DESCRIPTION}")"
|
||||
echo "local USAGE__usage=${funcstack[2]}"
|
||||
echo "local ERRORS=0"
|
||||
echo "local WARNINGS=0"
|
||||
echo "local DEBUG_ARGS=()"
|
||||
echo "local TRACE_ARGS=()"
|
||||
echo "local POSITIONAL_ARGS=0"
|
||||
|
||||
echo '
|
||||
|
||||
+1
-1
@@ -76,7 +76,7 @@ utils.parse() {
|
||||
|
||||
for PARSER in ${VALID_PARSERS[@]}
|
||||
do
|
||||
command -v ${PARSER}.usage &>/dev/null && ${PARSER}.usage
|
||||
command -v ${PARSER}.usage &>/dev/null && ${PARSER}.usage &>/dev/null
|
||||
done
|
||||
|
||||
local EARLY_ESCAPE_CODE _S ERRORS=0 POSITIONAL_ARGS=0
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
#####################################################################
|
||||
|
||||
zsh-cache() {
|
||||
local DESCRIPTION='
|
||||
Caches the output of any successful shell command for the current
|
||||
scwrypts runtime.
|
||||
|
||||
Keep in mind the following:
|
||||
- the --cache-file arg is trusted to be fully unique; running
|
||||
more than one command through the same file will resulted in
|
||||
unexpected behavior
|
||||
|
||||
- requires computation of a sha1 hash (two in persistent mode),
|
||||
and a write to file on disk/ram; this should only be used for
|
||||
commands which are expensive to compute
|
||||
'
|
||||
|
||||
eval "$(utils.parse.autosetup)"
|
||||
##########################################
|
||||
|
||||
local DEBUG_ARGS=(
|
||||
-v ARGS
|
||||
-v cache_file -v cache_file_exists
|
||||
-v persistent_cache_get_hash -v persistent_cache_get_hash_args
|
||||
-v persistent_cache_hash -v persistent_cache_file
|
||||
)
|
||||
local TRACE_ARGS=(${(q)DEBUG_ARGS[@]})
|
||||
|
||||
local cache_file_exists \
|
||||
&& [[ -f "${cache_file}" ]] \
|
||||
&& cache_file_exists=true \
|
||||
|| cache_file_exists=false \
|
||||
;
|
||||
|
||||
local cache_status
|
||||
case "${cache_file_exists}" in
|
||||
( true ) cache_status=hit ;;
|
||||
( false ) : \
|
||||
&& [[ "${persistent_cache_enabled}" == true ]] \
|
||||
&& [[ -f "${persistent_cache_file}" ]] \
|
||||
&& persistent_cache_hash="$("${persistent_cache_get_hash}" "${persistent_cache_get_hash_args[@]}")" \
|
||||
&& [[ "$(head -n1 -- "${persistent_cache_file}")" == "${persistent_cache_hash}" ]] \
|
||||
&& cache_status=persistent-hit \
|
||||
|| cache_status=miss \
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ "${persistent_cache_enabled}" == true ]] && [[ "${cache_status}" == persistent-hit ]]
|
||||
then
|
||||
[[ "${persistent_cache_hash}" ]] || {
|
||||
echo.warning "persistent cache is enabled, but hash compute failed! persistent cache disabled for this call"
|
||||
echo.debug 'hash compute failed'
|
||||
persistent_cache_enabled=false
|
||||
}
|
||||
fi
|
||||
|
||||
case "${cache_status}" in
|
||||
( hit )
|
||||
echo.trace "$(utils.colors.bright-green)runtime cache hit$(echo.trace.color)"
|
||||
;;
|
||||
|
||||
( persistent-hit )
|
||||
echo.trace "$(utils.colors.green)persistent cache hit; copying to runtime cache$(echo.trace.color)"
|
||||
sed 1d "${persistent_cache_file}" > "${cache_file}"
|
||||
;;
|
||||
|
||||
( miss )
|
||||
echo.trace "$(utils.colors.yellow)cache miss; running command$(echo.trace.color)"
|
||||
|
||||
local target_cache_file \
|
||||
&& [[ "${persistent_cache_enabled}" == true ]] \
|
||||
&& target_cache_file="${persistent_cache_file}" \
|
||||
|| target_cache_file="${cache_file}" \
|
||||
;
|
||||
|
||||
: > "${target_cache_file}"
|
||||
|
||||
[[ "${persistent_cache_enabled}" == true ]] \
|
||||
&& echo "${persistent_cache_hash}" >> "${target_cache_file}"
|
||||
|
||||
${ARGS[@]} | grep '.' >> "${target_cache_file}"
|
||||
local exit_code="${pipestatus[1]}"
|
||||
|
||||
case "${exit_code}" in
|
||||
( 0 )
|
||||
[[ "${persistent_cache_enabled}" == true ]] \
|
||||
&& sed 1d "${persistent_cache_file}" > "${cache_file}"
|
||||
;;
|
||||
|
||||
( * )
|
||||
echo.error "command '${ARGS[@]}' failed (or failed to produce output) with exit code ${exit_code}"
|
||||
echo.debug "command failed:\n$(cat -- "${target_cache_file}")"
|
||||
rm -- "${target_cache_file}"
|
||||
return "${exit_code}"
|
||||
;;
|
||||
esac
|
||||
esac
|
||||
|
||||
cat -- "${cache_file}"
|
||||
}
|
||||
|
||||
zsh-cache.parse.locals() {
|
||||
local ARGS=()
|
||||
local cache_file
|
||||
local CACHE_MODE_ARGS_COUNT=0
|
||||
local persistent_cache_file
|
||||
local persistent_cache_enabled=false
|
||||
local persistent_cache_get_hash
|
||||
local persistent_cache_get_hash_args=()
|
||||
local persistent_cache_hash
|
||||
local auto=false
|
||||
}
|
||||
|
||||
zsh-cache.parse.usage() {
|
||||
USAGE__options+='
|
||||
--cache-file <string> runtime-unique filename key for cached data (default: sha1sum of ARGS)
|
||||
--persistent <hash-func> enables persistence by providing a zsh function to compute the hash
|
||||
-p <argument> passthrough argument to the get-hash function
|
||||
--persistent-file <string> overwrite the default persistent filename (default: matches --cache-file)
|
||||
|
||||
--auto looks for caller.zsh-cache and, optionally, a caller.zsh-cache.get-hash
|
||||
- -p and args still accepted
|
||||
- when no -p is passed, get-hash will use the same args passed to the zsh-cache function
|
||||
'
|
||||
|
||||
USAGE__args+='
|
||||
$1 the command to be cached
|
||||
$2..$N the arguments for the command to be cached
|
||||
'
|
||||
}
|
||||
|
||||
zsh-cache.parse() {
|
||||
local parsed=0
|
||||
|
||||
case "${1}" in
|
||||
( --cache-file ) parsed=2
|
||||
cache_file="${2}"
|
||||
;;
|
||||
|
||||
( --persistent-file ) parsed=2
|
||||
persistent_cache_file="${2}"
|
||||
;;
|
||||
|
||||
( --persistent ) parsed=2
|
||||
persistent_cache_enabled=true
|
||||
persistent_cache_get_hash="${2}"
|
||||
;;
|
||||
|
||||
( -p ) parsed=2
|
||||
persistent_cache_get_hash_args+=("${2}")
|
||||
;;
|
||||
|
||||
( --auto ) parsed=1; auto=true ;;
|
||||
esac
|
||||
|
||||
return "${parsed}"
|
||||
}
|
||||
|
||||
zsh-cache.parse.validate() {
|
||||
[[ "${auto}" == true ]] && {
|
||||
local caller="${funcstack[5]}"
|
||||
|
||||
command -v "${caller}.zsh-cache" &>/dev/null \
|
||||
|| echo.error "--auto mode specified, but couldn't find ${caller}.zsh-cache function"
|
||||
|
||||
[[ "${#ARGS[@]}" -gt 0 ]] \
|
||||
&& ARGS=("${caller}.zsh-cache" "${ARGS[@]}") \
|
||||
|| ARGS=("${caller}.zsh-cache") \
|
||||
;
|
||||
|
||||
command -v "${caller}.zsh-cache.get-hash" &>/dev/null && {
|
||||
persistent_cache_enabled=true
|
||||
persistent_cache_get_hash="${caller}.zsh-cache.get-hash"
|
||||
|
||||
[[ "${#persistent_cache_get_hash_args[@]}" -eq 0 ]] \
|
||||
&& persistent_cache_get_hash_args=("${ARGS[@]:1}")
|
||||
}
|
||||
}
|
||||
|
||||
[[ "${#ARGS[@]}" -gt 0 ]] \
|
||||
|| echo.error "did not specify a command to cache" \
|
||||
|| return
|
||||
|
||||
[[ "${cache_file}" ]] || cache_file="zsh-cache.$(echo "${ARGS[@]}" | utils.sha1sum)"
|
||||
[[ "${persistent_cache_file}" ]] || persistent_cache_file="${cache_file}"
|
||||
|
||||
: \
|
||||
&& [[ "${cache_file}" ]] \
|
||||
&& cache_file="${SCWRYPTS_CACHE_PATH__runtime}/${cache_file}" \
|
||||
&& mkdir -p -- "$(dirname -- "${cache_file}")" \
|
||||
|| echo.error "must provide valid cache file; cache_file='${cache_file}'" \
|
||||
;
|
||||
|
||||
[[ "${CACHE_MODE_ARGS_COUNT}" -le 1 ]] \
|
||||
|| echo.error "too many mode arguments specified"
|
||||
|
||||
case "${persistent_cache_enabled}" in
|
||||
( false ) ;;
|
||||
( true )
|
||||
: \
|
||||
&& [[ "${persistent_cache_file}" ]] \
|
||||
&& persistent_cache_file="${SCWRYPTS_CACHE_PATH__persistent}/${persistent_cache_file}" \
|
||||
&& mkdir -p -- "$(dirname -- "${persistent_cache_file}")" \
|
||||
|| echo.error "must provide valid persistent cache file; persistent_cache_file='${persistent_cache_file}'" \
|
||||
;
|
||||
|
||||
command -v "${persistent_cache_get_hash}" &>/dev/null \
|
||||
|| echo.error "invalid hash function '${persistent_cache_get_hash}'" \
|
||||
|| return
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
|
||||
case "$(normalize.boolean --mode export --variable SCWRYPTS__ZSH_CACHE_ENABLED --default true)" in
|
||||
( true ) DEPENDENCIES+=(sha1sum) ;;
|
||||
( false )
|
||||
echo.trace "zsh-cache disabled; mocking module ${scwryptsmodule}" # see ./zsh-cache.module.zsh
|
||||
|
||||
zsh-cache() {
|
||||
local DESCRIPTION="
|
||||
cache bypassed with SCWRYPTS__ZSH_CACHE_ENABLED=${SCWRYPTS__ZSH_CACHE_ENABLED}
|
||||
|
||||
instead; this will always execute the forwarded command
|
||||
"
|
||||
|
||||
eval "$(zsh.parse.autosetup)"
|
||||
|
||||
##########################################
|
||||
|
||||
${ARGS[@]}
|
||||
}
|
||||
|
||||
zsh-cache.parse.validate() {
|
||||
case "${auto}" in
|
||||
( false ) ;; # do nothing; ARGS already setup correctly
|
||||
( true )
|
||||
local caller="${funcstack[5]}"
|
||||
|
||||
command -v "${caller}.zsh-cache" &>/dev/null \
|
||||
|| echo.error "--auto mode specified, but couldn't find ${caller}.zsh-cache function"
|
||||
|
||||
[[ "${#ARGS[@]}" -gt 0 ]] \
|
||||
&& ARGS=("${caller}.zsh-cache" "${ARGS[@]}") \
|
||||
|| ARGS=("${caller}.zsh-cache") \
|
||||
;
|
||||
;;
|
||||
esac
|
||||
}
|
||||
;;
|
||||
esac
|
||||
Executable
+63
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env zsh
|
||||
#####################################################################
|
||||
|
||||
USAGE__description='
|
||||
resets the persistent state cache
|
||||
'
|
||||
|
||||
USAGE__args='
|
||||
--dry-run preview the cache files which would be dropped
|
||||
'
|
||||
|
||||
#####################################################################
|
||||
|
||||
MAIN() {
|
||||
eval "$(utils.parse.autosetup)"
|
||||
|
||||
case "${mode}" in
|
||||
( default )
|
||||
zsh-cache.reset.persistent
|
||||
;;
|
||||
|
||||
( dry-run )
|
||||
[[ -d "${SCWRYPTS_CACHE_PATH__persistent}" ]] || {
|
||||
echo.reminder "[dry-run] cache folder does not exist: ${SCWRYPTS_CACHE_PATH__persistent}"
|
||||
return 0
|
||||
}
|
||||
|
||||
echo.reminder "[dry-run] would clear folder ${SCWRYPTS_CACHE_PATH__persistent}
|
||||
contents:
|
||||
$(
|
||||
cd -- "${SCWRYPTS_CACHE_PATH__persistent}"
|
||||
find . -type f | sed 's|^\./|- |' | grep . || echo "$(utils.colors.red)<folder is empty>$(echo.reminder.color)"
|
||||
)"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
|
||||
MAIN.parse.locals() {
|
||||
local mode=default
|
||||
local mode_args=()
|
||||
}
|
||||
|
||||
MAIN.parse() {
|
||||
local p=0
|
||||
|
||||
case "${1}" in
|
||||
( --dry-run ) p=1; mode="${1/--}"; mode_args+=("${1}") ;;
|
||||
esac
|
||||
|
||||
return "${p}"
|
||||
}
|
||||
|
||||
MAIN.parse.validate() {
|
||||
case "${mode}" in
|
||||
( default | dry-run ) ;;
|
||||
( * ) echo.error "invalid operation mode '${mode}'"
|
||||
esac
|
||||
|
||||
[[ "${#mode_args[@]}" -le 1 ]] \
|
||||
|| echo.error "incompatible arguments '${mode_args[@]}'"
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
#####################################################################
|
||||
|
||||
${scwryptsmodule}.all() {
|
||||
local ERRORS=0
|
||||
|
||||
local cache_type
|
||||
for cache_type in persistent runtime
|
||||
do
|
||||
zsh-cache.reset.${cache_type} || ((ERRORS+=1))
|
||||
done
|
||||
|
||||
return "${ERRORS}"
|
||||
}
|
||||
|
||||
${scwryptsmodule}.persistent() { zsh-cache.reset._ persistent; }
|
||||
${scwryptsmodule}.runtime() { zsh-cache.reset._ runtime; }
|
||||
|
||||
${scwryptsmodule}._() {
|
||||
local ERRORS=0
|
||||
local TRACE_ARGS=(-v cache_variable -v cache_path)
|
||||
|
||||
local cache_variable cache_path cache_name="${1}"
|
||||
case "${cache_name}" in
|
||||
( persistent | runtime )
|
||||
cache_variable="SCWRYPTS_CACHE_PATH__${cache_name}"
|
||||
cache_path="${(P)cache_variable}"
|
||||
|
||||
[[ "${cache_path}" ]] \
|
||||
|| echo.error "could not discover cache path; is ${cache_variable} set?" \
|
||||
|| return 1
|
||||
;;
|
||||
( * )
|
||||
echo.error "invalid cache '${cache_name}'"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ -d "${cache_path}" ]]
|
||||
then
|
||||
echo.trace "deleting cache directory"
|
||||
rm -rf -- "${cache_path}" \
|
||||
|| echo.error "failed to delete cache directory for ${cache_name}"
|
||||
fi
|
||||
|
||||
echo.trace "recreating cache directory"
|
||||
mkdir -p -- "${cache_path}" \
|
||||
|| echo.error "failed to recreate cache path for ${cache_name}"
|
||||
|
||||
[[ "${ERRORS}" -eq 0 ]] \
|
||||
&& echo.success "successfully cleared ${cache_name} cache"
|
||||
|
||||
return "${ERRORS}"
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
case "$(normalize.boolean --mode export --variable SCWRYPTS__ZSH_CACHE_ENABLED --default true)" in
|
||||
( true ) ;;
|
||||
( false )
|
||||
echo.trace "zsh-cache disabled; mocking module ${scwryptsmodule}" # see ./zsh-cache.module.zsh
|
||||
|
||||
for _PROVIDED_FUNCTION in all persistent runtime
|
||||
do
|
||||
${scwryptsmodule}.${_PROVIDED_FUNCTION}() {
|
||||
local WARNINGS=0
|
||||
echo.warning "cache is disabled; skipping reset request"
|
||||
return 0
|
||||
}
|
||||
done
|
||||
|
||||
unset _PROVIDED_FUNCTION
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,13 @@
|
||||
#
|
||||
# zsh-cache : cache tools for zsh commands
|
||||
#
|
||||
# this module can be disabled with 'export SCWRYPTS__ZSH_CACHE_ENABLED=false'
|
||||
# all library functions implement pass-through mocks so code does not break when disabled
|
||||
#
|
||||
normalize.boolean --mode export --variable SCWRYPTS__ZSH_CACHE_ENABLED --default true >/dev/null
|
||||
|
||||
# primary cache command 'zsh-cache()' which caches output for the current runtime
|
||||
use zsh-cache/command-output
|
||||
|
||||
# invoke to manually reset cache; provides { .all | .persistent | .runtime }
|
||||
use zsh-cache/reset
|
||||
Reference in New Issue
Block a user