=====================================================================

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:
2026-05-05 15:31:50 -06:00
parent 7f14edd039
commit e7484103b9
56 changed files with 2392 additions and 1424 deletions
+2 -2
View File
@@ -28,8 +28,8 @@ ABORT >> utils.abort
CHECK_ERRORS >> utils.check-errors CHECK_ERRORS >> utils.check-errors
Yn >> utils.Yn Yn >> user.Yn
yN >> utils.yN yN >> user.yN
EDIT >> utils.io.edit EDIT >> utils.io.edit
+1 -1
View File
@@ -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 for CLI in kubectl helm flux
+2 -2
View File
@@ -1,6 +1,6 @@
[[ $SCWRYPTS_KUBECTL_DRIVER_READY -eq 1 ]] && return 0 [[ $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 $@; } k() { _SCWRYPTS_KUBECTL_DRIVER kubectl $@; }
h() { _SCWRYPTS_KUBECTL_DRIVER helm $@; } h() { _SCWRYPTS_KUBECTL_DRIVER helm $@; }
f() { _SCWRYPTS_KUBECTL_DRIVER flux $@; } f() { _SCWRYPTS_KUBECTL_DRIVER flux $@; }
@@ -11,7 +11,7 @@ _SCWRYPTS_KUBECTL_DRIVER() {
return 1 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)" || eval "$(scwrypts -n --name meta/get-static-redis-definition --type zsh --group kube)"
local CLI="$1"; shift 1 local CLI="$1"; shift 1
+1 -1
View File
@@ -87,7 +87,7 @@ kube.redis ping 2>/dev/null | grep -qi pong || {
--detach \ --detach \
--name scwrypts-kubectl-redis \ --name scwrypts-kubectl-redis \
--publish $SCWRYPTS_KUBECTL_REDIS_PORT__managed:6379 \ --publish $SCWRYPTS_KUBECTL_REDIS_PORT__managed:6379 \
redis >/dev/null 2>&1 redis &>/dev/null
echo.status 'awaiting redis connection' echo.status 'awaiting redis connection'
until kube.redis ping 2>/dev/null | grep -qi pong; do sleep 0.5; done until kube.redis ping 2>/dev/null | grep -qi pong; do sleep 0.5; done
+325 -274
View File
@@ -7,13 +7,18 @@ use scwrypts/environment
use scwrypts/list-available use scwrypts/list-available
use scwrypts/get-runstring 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)" 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 ERRORS=0
local USAGE=' local USAGE="
usage: scwrypts [...options...] [...patterns...] -- [...script options...] usage: scwrypts [...options...] [...patterns...] -- [...script options...]
options: options:
@@ -27,22 +32,23 @@ use scwrypts/get-runstring
-y, --yes auto-accept all [yn] prompts through current scwrypt -y, --yes auto-accept all [yn] prompts through current scwrypt
-e, --env <env-name> set environment; overwrites SCWRYPTS_ENV -e, --env <env-name> set environment; overwrites SCWRYPTS_ENV
-n shorthand for "--log-level 0" -n shorthand for \"--log-level 0\"
-v, --log-level <0-4> set incremental scwrypts log level to one of the following: -v, --log-level <0-5> set incremental scwrypts log level to one of the following:
0 : only command output and critical failures; skips logfile 0 : only command output and critical failures; skips logfile
1 : include success / failure messages 1 : include error messages
2 : include status update messages 2 : include warnings
3 : (default) include warning messages 3 : (default) include success, status, and user information
4 : include debug messages 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 alternate commands
-h, --help display this message and exit -h, --help display this message and exit
-l, --list print out command list and exit -l, --list print out command list and exit
--list-envs print out environment list and exit --list-envs print out environment list and exit
--list-groups print out configured scwrypts groups 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 --root print out scwrypts.config.group.scwrypts.root and exit
--update update scwrypts library to latest version --update update scwrypts library to latest version
--version print out scwrypts version and exit --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 - a list of glob patterns to loose-match a scwrypt by name
script options: script options:
- everything after "--" is forwarded to the scwrypt you run - everything after \"--\" is forwarded to the scwrypt you run
("-- --help" will provide more information) (\"-- --help\" will provide more information)
' "
##################################################################### #####################################################################
### cli argument parsing and global configuration ################### ### cli argument parsing and global configuration ###################
##################################################################### #####################################################################
local ENV_NAME="${SCWRYPTS_ENV}" local target_scwrypts_env="${SCWRYPTS_ENV}"
local SEARCH_PATTERNS=() 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 ]] while [[ $# -gt 0 ]]
do do
SHIFT_COUNT=1 _s=1
case $1 in case $1 in
( -[a-z][a-z]* ) ( -[a-z][a-z]* )
VARSPLIT=$(echo "$1 " | sed 's/^\(-.\)\(.*\) /\1 -\2/') local varsplit_first="${1:0:2}" varsplit_remaining="-${1:2}"
set -- throw-away $(echo " ${VARSPLIT} ") ${@: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 ################### ### alternate commands ###################
@@ -99,8 +116,8 @@ use scwrypts/get-runstring
;; ;;
( --version ) ( --version )
case ${SCWRYPTS_INSTALLATION_TYPE} in case "${SCWRYPTS_INSTALLATION_TYPE}" in
( manual ) echo "scwrypts $(GIT_SCWRYPTS describe --tags) (via GIT)" ;; ( manual ) echo "scwrypts $(git.scwrypts describe --tags) (via GIT)" ;;
( * ) echo "scwrypts $(cat "$(scwrypts.config.group scwrypts root)/VERSION")" ;; ( * ) echo "scwrypts $(cat "$(scwrypts.config.group scwrypts root)/VERSION")" ;;
esac esac
return 0 return 0
@@ -119,34 +136,34 @@ use scwrypts/get-runstring
;; ;;
( --update ) ( --update )
case ${SCWRYPTS_INSTALLATION_TYPE} in case "${SCWRYPTS_INSTALLATION_TYPE}" in
aur ) ( aur )
echo.reminder --force-print " echo.reminder --force-print "
This installation is built from the AUR. Update through 'makepkg' or use This installation is built from the AUR. Update through 'makepkg' or use
your preferred AUR package management tool (e.g. 'yay -Syu scwrypts') 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'" echo.reminder --force-print "This installation is managed by homebrew. Update me with 'brew update'"
;; ;;
manual ) ( manual )
GIT_SCWRYPTS fetch --quiet origin main git.scwrypts fetch --quiet origin main
GIT_SCWRYPTS fetch --quiet origin main --tags git.scwrypts fetch --quiet origin main --tags
local SYNC_STATUS=$? local sync_status="${?}"
GIT_SCWRYPTS diff --exit-code origin/main -- . >/dev/null 2>&1 git.scwrypts diff --exit-code origin/main -- . &>/dev/null
local DIFF_STATUS=$? 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' 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' \ && 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.error 'unable to update scwrypts; please try manual upgrade'
echo.reminder "installation in '$(scwrypts.config.group scwrypts root)'" echo.reminder "installation in '$(scwrypts.config.group scwrypts root)'"
} }
@@ -165,25 +182,9 @@ use scwrypts/get-runstring
### scwrypts filters ##################### ### scwrypts filters #####################
( -m | --name ) ( -m | --name ) _s=2; search_name=$2 ;;
((SHIFT_COUNT+=1)) ( -g | --group ) _s=2; search_group=$2; GROUP=$2 ;;
[ $2 ] || { echo.error "missing value for argument $1"; break; } ( -t | --type ) _s=2; search_type=$2; TYPE=$2 ;;
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
;;
### runtime settings ##################### ### runtime settings #####################
@@ -191,49 +192,46 @@ use scwrypts/get-runstring
( -n ) SCWRYPTS_LOG_LEVEL=0 ;; ( -n ) SCWRYPTS_LOG_LEVEL=0 ;;
( -v | --log-level ) ( -v | --log-level ) _s=2
((SHIFT_COUNT+=1)) SCWRYPTS_LOG_LEVEL="${2}"
[[ $2 =~ ^[0-4]$ ]] || echo.error "invalid setting for log-level '$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 ) ( -o | --output ) _s=2
((SHIFT_COUNT+=1)) export SCWRYPTS_OUTPUT_FORMAT="${2:l}"
export SCWRYPTS_OUTPUT_FORMAT=$2 utils.io.print 'test' --force-print &>/dev/null \
case ${SCWRYPTS_OUTPUT_FORMAT} in || echo.error "unsupported format '${SCWRYPTS_OUTPUT_FORMAT}'"
( pretty | json ) ;;
* ) echo.error "unsupported format '${SCWRYPTS_OUTPUT_FORMAT}'" ;;
esac
;; ;;
( -e | --env ) ( -e | --env ) _s=2
((SHIFT_COUNT+=1)) [[ "${target_scwrypts_env}" ]] \
[ $2 ] || { echo.error "missing value for argument $1"; break; } && echo.debug "overwriting session environment to ${2}" -v target_scwrypts_env
[ ${ENV_NAME} ] && echo.debug 'overwriting session environment' target_scwrypts_env="${2}"
ENV_NAME="$2"
echo.status "using CLI environment '${ENV_NAME}'"
;; ;;
########################################## ##########################################
( -- ) shift 1; break ;; # pass arguments after '--' to the scwrypt ( -- ) shift 1; break ;; # pass arguments after '--' to the scwrypt
( --* ) echo.error "unrecognized argument '$1'" ;; ( --* ) echo.error "unrecognized argument '${1}'" ;;
( * ) SEARCH_PATTERNS+=($1) ;; ( * ) search_patterns+=(${1}) ;;
esac esac
[[ ${SHIFT_COUNT} -le $# ]] \
&& shift ${SHIFT_COUNT} \ shift "${_s}" 2>/dev/null || echo.error "missing argument for '${1}'" || shift "${#}"
|| echo.error "missing argument for '$1'" \
|| shift $# \
;
done done
[ ${SCWRYPTS_OUTPUT_FORMAT} ] || export SCWRYPTS_OUTPUT_FORMAT=pretty [[ "${SCWRYPTS_OUTPUT_FORMAT}" ]] || export SCWRYPTS_OUTPUT_FORMAT=pretty
[ ${SEARCH_NAME} ] && { [[ "${search_name}" ]] && {
[ ${SEARCH_TYPE} ] || echo.error '--name requires --type argument' [[ "${search_type}" ]] || echo.error '--name requires --type argument'
[ ${SEARCH_GROUP} ] || echo.error '--name requires --group argument' [[ "${search_group}" ]] || echo.error '--name requires --group argument'
} }
utils.check-errors --fail utils.check-errors --fail
@@ -243,283 +241,336 @@ use scwrypts/get-runstring
### scwrypts selection / filtering ################################## ### 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=$({ [[ "${search_name}" ]] && scwrypts_available="$({
echo ${SCWRYPTS_AVAILABLE} | head -n1 echo "${scwrypts_available}" | utils.colors.remove | grep -- "^${search_name} *${search_type} *${search_group}\$"
echo ${SCWRYPTS_AVAILABLE} | utils.colors.remove | grep "^${SEARCH_NAME} *${SEARCH_TYPE} *${SEARCH_GROUP}\$" })" || {
}) || { [[ "${search_type}" ]] && scwrypts_available="$(\
[ ${SEARCH_TYPE} ] && { echo "${scwrypts_available}" \
SCWRYPTS_AVAILABLE=$(\ | grep -- ' [^/]*'${search_type}'[^/]* ' \
{
echo ${SCWRYPTS_AVAILABLE} | head -n1
echo ${SCWRYPTS_AVAILABLE} | grep ' [^/]*'${SEARCH_TYPE}'[^/]* '
} \
| sed 's/ \+$/'$(utils.colors.reset)'/; s/ \+/^/g' \ | sed 's/ \+$/'$(utils.colors.reset)'/; s/ \+/^/g' \
| column -ts '^' | column -ts '^' \
) )"
}
[ ${SEARCH_GROUP} ] && { [[ "${search_group}" ]] && scwrypts_available="$(
SCWRYPTS_AVAILABLE=$( echo "${scwrypts_available}" \
{ | grep -- "${search_group}"'[^/ ]*$' \
echo ${SCWRYPTS_AVAILABLE} | head -n1
echo ${SCWRYPTS_AVAILABLE} | grep "${SEARCH_GROUP}"'[^/ ]*$'
} \
| sed 's/ \+$/'$(utils.colors.reset)'/; s/ \+/^/g' \ | sed 's/ \+$/'$(utils.colors.reset)'/; s/ \+/^/g' \
| column -ts '^' | column -ts '^' \
) )"
}
[[ ${#SEARCH_PATTERNS[@]} -gt 0 ]] && { [[ "${#search_patterns[@]}" -gt 0 ]] && {
POTENTIAL_ERROR+="\n PATTERNS : ${SEARCH_PATTERNS}" local search_pattern
local P for search_pattern in "${search_patterns[@]}"
for P in ${SEARCH_PATTERNS[@]}
do do
SCWRYPTS_AVAILABLE=$( scwrypts_available="$(echo "${scwrypts_available}" | grep -- "${search_pattern}")"
{
echo ${SCWRYPTS_AVAILABLE} | head -n1
echo ${SCWRYPTS_AVAILABLE} | grep ${P}
}
)
done done
unset search_pattern
} }
} }
[[ $(echo ${SCWRYPTS_AVAILABLE} | wc -l) -lt 2 ]] && { [[ $(echo "${scwrypts_available}" | wc -l) -gt 0 ]] \
utils.fail 1 "$(echo " || echo.error "$(echo "
no such scwrypt exists no such scwrypt exists
NAME : '${SEARCH_NAME}' NAME : '${search_name}'
TYPE : '${SEARCH_TYPE}' TYPE : '${search_type}'
GROUP : '${SEARCH_GROUP}' GROUP : '${search_group}'
PATTERNS : '${SEARCH_PATTERNS}' PATTERNS : '${search_patterns[@]}'
" | sed "1d; \$d; /''$/d")" " | sed "1d; \$d; /''$/d")" \
} || return 1
########################################## ##########################################
[[ $(echo ${SCWRYPTS_AVAILABLE} | wc -l) -eq 2 ]] \ [[ "$(echo "${scwrypts_available}" | wc -l)" -eq 1 ]] \
&& SCWRYPT_SELECTION=$(echo ${SCWRYPTS_AVAILABLE} | tail -n1) \ && scwrypt_selection="$(echo "${scwrypts_available}" | tail -n1)" \
|| SCWRYPT_SELECTION=$(echo ${SCWRYPTS_AVAILABLE} | utils.fzf "select a script to run" --header-lines 1) \ || 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
########################################## ##########################################
() { echo "${scwrypt_selection}" | utils.colors.remove | read -r SCWRYPT_NAME SCWRYPT_TYPE SCWRYPT_GROUP
set -- $(echo $@ | utils.colors.remove) export SCWRYPT_NAME SCWRYPT_TYPE SCWRYPT_GROUP
export SCWRYPT_NAME=$1
export SCWRYPT_TYPE=$2
export SCWRYPT_GROUP=$3
} ${SCWRYPT_SELECTION}
##################################################################### #####################################################################
### environment variables and configuration validation ############## ### environment variables and configuration validation ##############
##################################################################### #####################################################################
local ENV_REQUIRED=true \ local env_required \
&& [ ! ${CI} ] \ && [[ ! "${CI}" ]] \
&& [[ ! ${SCWRYPT_NAME} =~ scwrypts/logs ]] \ && [[ ! "${SCWRYPT_NAME}" =~ scwrypts/logs ]] \
&& [[ ! ${SCWRYPT_NAME} =~ scwrypts/environment ]] \ && [[ ! "${SCWRYPT_NAME}" =~ scwrypts/environment ]] \
|| ENV_REQUIRED=false && 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} ] && { [[ "${target_scwrypts_env}" ]] && [[ "${required_environment_regex}" ]] && {
[[ ${ENV_NAME} =~ ${REQUIRED_ENVIRONMENT_REGEX} ]] \ [[ "${target_scwrypts_env}" =~ "${required_environment_regex}" ]] \
|| utils.fail 5 "group '${SCWRYPT_GROUP}' requires current environment name to match '${REQUIRED_ENVIRONMENT_REGEX}' (currently ${ENV_NAME})" || echo.error "group '${SCWRYPT_GROUP}' requires current environment name to match '${required_environment_regex}' (currently ${target_scwrypts_env})" \
|| return 5
} }
[[ ${ENV_REQUIRED} =~ true ]] && { case "${env_required}" in
[ ! ${ENV_NAME} ] && { ( false ) ;;
scwrypts.environment.init \ ( true )
|| echo.error "failed to initialize scwrypts environments (see above)" \ [[ "${target_scwrypts_env}" ]] || {
|| return 1 \ 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) case "${sync_with_main_required}" in
[ "${ENV_NAME}" ] || user.abort ( false ) ;;
} ( true )
echo.status "on '${SCWRYPTS_ENV}'; checking diff against origin/main"
for GROUP in ${SCWRYPTS_GROUPS[@]} [[ ! "${scwrypts_execution_warning_message}" ]] && {
do git.scwrypts fetch --quiet origin main &>/dev/null \
local REQUIRED_REGEX="$(scwrypts.config.group ${GROUP} required_environment_regex)" || scwrypts_execution_warning_message='I am unable to verify your scwrypts version'
[ ${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 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}) local scwrypts_run_string="$(scwrypts.get-runstring "${SCWRYPT_NAME}" "${SCWRYPT_TYPE}" "${SCWRYPT_GROUP}")"
[ "${RUN_STRING}" ] || return 42 [[ "${scwrypts_run_string}" ]] || return 42
##################################################################### #####################################################################
### logging and pretty header/footer setup ########################## ### logging and pretty header/footer setup ##########################
##################################################################### #####################################################################
local RUN_MODE=normal local scwrypts_execution_mode=normal
[[ ${SCWRYPT_NAME} =~ interactive ]] && RUN_MODE=interactive [[ "${SCWRYPT_NAME}" =~ interactive ]] && scwrypts_execution_mode=interactive
local LOGFILE \ local LOGFILE \
&& [[ ${RUN_MODE} =~ normal ]] \ && [[ "${scwrypts_execution_mode}" =~ normal ]] \
&& [[ ${SCWRYPTS_LOG_LEVEL} -gt 0 ]] \ && [[ "${SCWRYPTS_LOG_LEVEL}" -gt 0 ]] \
&& [[ ${SUBSCWRYPT} -eq 0 ]] \ && [[ "${SUBSCWRYPT}" -eq 0 ]] \
&& [[ ! ${SCWRYPT_NAME} =~ scwrypts/logs ]] \ && [[ ! "${SCWRYPT_NAME}" =~ scwrypts/logs ]] \
&& LOGFILE="${SCWRYPTS_LOG_PATH}/$(echo ${GROUP}/${TYPE}/${NAME} | sed 's/^\.\///; s/\//\%/g').log" \ && LOGFILE="${SCWRYPTS_LOG_PATH}/$(echo ${SCWRYPT_GROUP}/${SCWRYPT_TYPE}/${SCWRYPT_NAME} | sed 's/^\.\///; s/\//\%/g').log" \
|| LOGFILE='/dev/null' \ || LOGFILE='/dev/null' \
; ;
local HEADER FOOTER scwrypts.runner.header-and-footer() {
[[ ${SCWRYPTS_LOG_LEVEL} -ge 2 ]] && { local output_type="${1}"
case ${SCWRYPTS_OUTPUT_FORMAT} in case "${output_type}" in
( header | footer ) ;;
( * ) return 1 ;;
esac
local header footer
[[ "${SCWRYPTS_LOG_LEVEL}" -ge 3 ]] && case "${SCWRYPTS_OUTPUT_FORMAT}" in
( raw ) ( raw )
HEADER="--- start scwrypt ${SCWRYPT_GROUP}/${SCWRYPT_TYPE} ${SCWRYPT_NAME} in ${SCWRYPTS_ENV} ---" header="--- start scwrypt ${SCWRYPT_GROUP}/${SCWRYPT_TYPE} ${SCWRYPT_NAME} in ${SCWRYPTS_ENV} ---"
FOOTER="--- end scwrypt ---" footer="--- end scwrypt ---"
;; ;;
( pretty ) ( pretty )
HEADER=$( header=$(
echo " echo "
===================================================================== =====================================================================
scwrypt : ${SCWRYPT_GROUP} ${SCWRYPT_TYPE} ${SCWRYPT_NAME} scwrypt : ${SCWRYPT_GROUP} ${SCWRYPT_TYPE} ${SCWRYPT_NAME}
run at : $(date) run at : $(date)
config : ${SCWRYPTS_ENV} 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 ---------------------------------------------------') $(utils.colors.print bright-yellow '--- SCWRYPT BEGIN ---------------------------------------------------')
" | sed 's/^\s\+//; 1d' " | sed 's/^\s\+//; 1d'
) )
FOOTER="$(utils.colors.print bright-yellow '--- SCWRYPT END ---------------------------------------------------')" footer="$(utils.colors.print bright-yellow '--- SCWRYPT END ---------------------------------------------------')"
;; ;;
( json ) ( json )
HEADER=$(echo '{}' | jq -c ". header=$(jo \
| .timestamp = \"$(date +%s)\" timestamp="$(date +%s)" \
| .runtime = \"${SCWRYPTS_RUNTIME_ID}\" config="${SCWRYPTS_ENV}" \
| .scwrypt = \"start of ${SCWRYPT_NAME} ${SCWRYPT_GROUP} ${SCWRYPT_TYPE}\" logLevel="${SCWRYPTS_LOG_LEVEL}" \
| .config = \"${SCWRYPTS_ENV}\" subscwrypt="${SUBSCWRYPT}" \
| .logLevel = \"${SCWRYPTS_LOG_LEVEL}\" runtime="${SCWRYPTS_RUNTIME_ID}" \
| .subscwrypt = ${SUBSCWRYPT} 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 esac
}
[[ ${SUBSCWRYPT} -eq 0 ]] || { [[ "${SUBSCWRYPT}" -ne 0 ]] && case "${SCWRYPTS_OUTPUT_FORMAT}" in
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 ) ( pretty )
HEADER="$(utils.colors.print yellow "--- (${SUBSCWRYPT}) BEGIN ${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} ---")" 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 esac
}
[[ "${(P)output_type}" ]] && echo "${(P)output_type}"
return 0
}
##################################################################### #####################################################################
### run the scwrypt ################################################# ### 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 "${scwrypts_execution_mode}" in
case ${RUN_MODE} in ( normal ) eval "${scwrypts_run_string} ${(q)@}" ;;
( normal ) ( interactive ) eval "${scwrypts_run_string} ${(q)@}" </dev/tty &>/dev/tty ;;
eval "${RUN_STRING} $(printf "%q " "$@")" esac
;;
( interactive )
eval "${RUN_STRING} $(printf "%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 ]] && { [[ ${print_exit_code} =~ true ]] && {
EXIT_COLOR=$( [[ ${EXIT_CODE} -eq 0 ]] && utils.colors.bright-green || utils.colors.bright-red ) local exit_color="$([[ ${exit_code} -eq 0 ]] && utils.colors.bright-green || utils.colors.bright-red)"
case ${SCWRYPTS_OUTPUT_FORMAT} in case "${SCWRYPTS_OUTPUT_FORMAT}" in
( raw )
echo "terminated with code ${EXIT_CODE}" >&2
;;
( pretty ) ( 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}" \ [[ "${exit_code}" -eq 0 ]] \
|| echo.error --force-print "terminated with code ${EXID_CODE}" \ && echo.success --force-print "terminated with code ${exit_code}" \
|| echo.error --force-print "terminated with code ${exit_code}" \
; ;
;; ;;
esac esac
} }
} > >(tee --append "${LOGFILE}") 2> >(tee --append "${LOGFILE}" >&2)
} "${@}"
return ${EXIT_CODE} EXIT_CODE="${?}"
} | tee --append "${LOGFILE}" [[ -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}" ] \ [[ -f "${EXIT_CODE_FILE}" ]] && rm "${EXIT_CODE_FILE}"
&& {
rm -- $(find "${SCWRYPTS_TEMP_PATH}" -mindepth 1 -maxdepth 1 -type f)
rmdir "${SCWRYPTS_TEMP_PATH}"
} &>/dev/null
return ${EXIT_CODE} return "${EXIT_CODE}"
+1 -1
View File
@@ -84,7 +84,7 @@ readonly ${scwryptsgroup}__color=$(utils.colors.green)
# #
readonly ${scwryptsgroup}__zshlibrary= #readonly ${scwryptsgroup}__zshlibrary=
# #
# ${scwryptsgroup}__zshlibrary (optional) (default = *see below*) # ${scwryptsgroup}__zshlibrary (optional) (default = *see below*)
# #
+3 -3
View File
@@ -66,15 +66,15 @@ Of the `echo` family, there are two unique functions:
#### Yes / No Prompts #### 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 "yes", the command returns 0 / success / `&&`
- when the user responds "no", the command returns 1 / error / `||` - 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 - 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: The two commands work identically; however, the capitalization denotes the default:
- `utils.Yn` = default "yes" - `user.Yn` = default "yes"
- `utils.yN` = default "no" - `user.yN` = default "no"
#### Select from a List Prompt #### Select from a List Prompt
+6 -6
View File
@@ -14,13 +14,13 @@ MAIN() {
eval "$(utils.parse.autosetup)" eval "$(utils.parse.autosetup)"
########################################## ##########################################
[ ! -d "${AWS__EFS__LOCAL_MOUNT_POINT}" ] && { [[ -d "${AWS__EFS__LOCAL_MOUNT_POINT}" ]] || {
echo.status 'no efs currently mounted' echo.status 'no efs currently mounted'
return 0 return 0
} }
local MOUNTED=$(cd -- "${AWS__EFS__LOCAL_MOUNT_POINT}" | find . -type -f | sed 's|^\./.||') local MOUNTED=$(cd -- "${AWS__EFS__LOCAL_MOUNT_POINT}" && find . -type f | sed 's|^\./||')
[ "${MOUNTED}" ] && { [[ "${MOUNTED}" ]] || {
echo.status 'no efs currently mounted' echo.status 'no efs currently mounted'
return 0 return 0
} }
@@ -28,12 +28,12 @@ MAIN() {
utils.io.getsudo || return 1 utils.io.getsudo || return 1
local SELECTED=$(echo ${MOUNTED} | utils.fzf 'select a file system to unmount') 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}" local EFS="${AWS__EFS__LOCAL_MOUNT_POINT}/${SELECTED}"
echo.status "unmounting '${SELECTED}'" echo.status "unmounting '${SELECTED}'"
sudo umount "${EFS}" >/dev/null 2>&1 sudo umount "${EFS}" &>/dev/null
sudo rmdir -- "${EFS}" \ sudo rmdir -- "${EFS}" \
&& echo.success "done" \ && echo.success "done" \
|| utils.fail 2 "failed to unmount '${EFS}'" || echo.error "failed to unmount '${EFS}'"
} }
+35 -34
View File
@@ -8,19 +8,22 @@ use cloud/aws/cli
##################################################################### #####################################################################
RDS__SELECT_DATABASE() { RDS__SELECT_DATABASE() {
local DATABASES=$(_RDS__GET_AVAILABLE_DATABASES) local DATABASES="$(_RDS__GET_AVAILABLE_DATABASES)"
[ ! $DATABASES ] && utils.fail 1 'no databases available' [[ "${DATABASES}" ]] \
|| echo.error 'no databases available' \
|| return 1
local ID=$(\ local ID="$(\
echo $DATABASES | jq -r '.instance + " @ " + .cluster' \ echo "${DATABASES}" | jq -r '.instance + " @ " + .cluster' \
| utils.fzf 'select a database (instance@cluster)' \ | utils.fzf 'select a database (instance@cluster)' \
) )"
[ ! $ID ] && user.abort
local INSTANCE=$(echo $ID | sed 's/ @ .*$//') [[ "${ID}" ]] || echo.user-abort || return 1
local CLUSTER=$(echo $ID | sed 's/^.* @ //')
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() { _RDS__GET_AVAILABLE_DATABASES() {
@@ -52,19 +55,18 @@ RDS__GET_DATABASE_CREDENTIALS() {
shift 1 shift 1
done done
utils.check-errors --fail utils.check-errors || return "${ERRORS}"
########################################## ##########################################
local DATABASE=$(RDS__SELECT_DATABASE) local DATABASE="$(RDS__SELECT_DATABASE)"
[ ! $DATABASE ] && user.abort [[ "${DATABASE}" ]] || echo.user-abort || return 1
DB_HOST="$(echo $DATABASE | jq -r '.host')" DB_HOST="$(echo "${DATABASE}" | jq -r '.host')"
[ ! $DB_HOST ] && { echo.error 'unable to find host'; return 2; } [[ "${DB_HOST}" ]] || echo.error 'unable to find host' || return 2
DB_PORT="$(echo $DATABASE | jq -r '.port')" DB_PORT="$(echo "${DATABASE}" | jq -r '.port' | grep -v ^null$)"
[ ! $DB_PORT ] && DB_PORT=5432 [[ "${DB_PORT}" ]] || DB_PORT=5432
[[ $DB_PORT =~ ^null$ ]] && DB_PORT=5432
########################################## ##########################################
@@ -72,46 +74,46 @@ RDS__GET_DATABASE_CREDENTIALS() {
echo "iam\nsecretsmanager\nuser-input" \ echo "iam\nsecretsmanager\nuser-input" \
| utils.fzf 'select an authentication method' \ | 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 ;; ( iam ) _RDS_AUTH__iam ;;
( secretsmanager ) _RDS_AUTH__secretsmanager ;; ( secretsmanager ) _RDS_AUTH__secretsmanager ;;
( user-input ) _RDS_AUTH__userinput ;; ( user-input ) _RDS_AUTH__userinput ;;
esac esac
[[ $PRINT_PASSWORD -eq 1 ]] && echo.debug "password : $DB_PASS" [[ "${PRINT_PASSWORD}" -eq 1 ]] && echo.debug "password : ${DB_PASS}"
return 0 return 0
} }
_RDS_AUTH__iam() { _RDS_AUTH__iam() {
DB_PASS=$(\ DB_PASS="$(\
AWS rds generate-db-auth-token \ AWS rds generate-db-auth-token \
--hostname $DB_HOST \ --hostname "${DB_HOST}" \
--port $DB_PORT \ --port "${DB_PORT}" \
--username $DB_USER \ --username "${DB_USER}" \
) )"
} }
_RDS_AUTH__secretsmanager() { _RDS_AUTH__secretsmanager() {
local CREDENTIALS=$(_RDS__GET_SECRETSMANAGER_CREDENTIALS) 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')" && 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')" && 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') && 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') && 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') && 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') && DB_NAME=$(echo $CREDENTIALS | jq -r '.dbname')
} }
@@ -121,9 +123,8 @@ _RDS__GET_SECRETSMANAGER_CREDENTIALS() {
| jq -r '.[] | .[] | .Name' \ | jq -r '.[] | .[] | .Name' \
| utils.fzf 'select a secret' \ | 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 | jq -r '.SecretString' | jq
} }
+1 -1
View File
@@ -2,7 +2,7 @@ scwrypts.config() {
[ $1 ] || return 1 [ $1 ] || return 1
case $1 in 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" ;; ( nodejs.version ) echo "22.0.0" ;;
( * ) return 1 ;; ( * ) return 1 ;;
esac esac
+23 -10
View File
@@ -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_STATE_PATH="${XDG_STATE_HOME:-${HOME}/.local/state}/scwrypts"
readonly SCWRYPTS_LOG_PATH="${SCWRYPTS_STATE_PATH}/logs" readonly SCWRYPTS_LOG_PATH="${SCWRYPTS_STATE_PATH}/logs"
[ -d /tmp ] \ if [[ -d /tmp ]]
&& readonly SCWRYPTS_TEMP_PATH="/tmp/scwrypts/${SCWRYPTS_RUNTIME_ID}" \ then
|| readonly SCWRYPTS_TEMP_PATH="${XDG_RUNTIME_DIR:-/run/user/${UID}}/scwrypts/${SCWRYPTS_RUNTIME_ID}" \ 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_ENV_PATH}" \
"${SCWRYPTS_DATA_PATH}" \
"${SCWRYPTS_STATE_PATH}" \
"${SCWRYPTS_LOG_PATH}" \ "${SCWRYPTS_LOG_PATH}" \
"${SCWRYPTS_TEMP_PATH}" \ "${SCWRYPTS_TEMP_PATH}" \
"${SCWRYPTS_TEMP_PATH_SOCKET}" \
"${SCWRYPTS_CACHE_PATH__runtime}" \
"${SCWRYPTS_CACHE_PATH__persistent}" \
; ;
DEFAULT_CONFIG="${__SCWRYPTS_ROOT}/zsh/config.user.zsh" DEFAULT_CONFIG="${__SCWRYPTS_ROOT}/zsh/config.user.zsh"
@@ -80,9 +93,8 @@ source "${__SCWRYPTS_ROOT}/zsh/config.global.zsh"
SCWRYPTS_GROUPS=() SCWRYPTS_GROUPS=()
command -v echo.warning &>/dev/null || WARNING() { echo "echo.warning : $@" >&2; } command -v echo.warning &>/dev/null || echo.warning() { echo "echo.warning : $@" >&2; }
command -v echo.error &>/dev/null || ERROR() { echo "echo.error : $@" >&2; return 1; } command -v echo.error &>/dev/null || echo.error() { echo "echo.error : $@" >&2; return 1; }
command -v utils.fail &>/dev/null || FAIL() { echo.error "${@:2}"; exit $1; }
__SCWRYPTS_GROUP_LOADERS=( __SCWRYPTS_GROUP_LOADERS=(
"${__SCWRYPTS_ROOT}/scwrypts.scwrypts.zsh" "${__SCWRYPTS_ROOT}/scwrypts.scwrypts.zsh"
@@ -146,7 +158,7 @@ do
|| echo.warning "error encountered when loading group '${__SCWRYPTS_GROUP_NAME}'" \ || 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 case $(scwrypts.config.group ${__SCWRYPTS_GROUP_NAME} type) in
( zsh ) ( zsh )
[ -d "${scwryptsgrouproot}/lib" ] \ [ -d "${scwryptsgrouproot}/lib" ] \
@@ -165,7 +177,8 @@ do
done done
[[ ${SCWRYPTS_GROUPS[1]} =~ ^scwrypts$ ]] \ [[ ${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 ######################################################### ### cleanup #########################################################
+1 -1
View File
@@ -94,7 +94,7 @@ PG_RESTORE() {
make sure your database is ready to accept the database file! 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" PSQL < "$INPUT_FILE"
EXIT_CODE=$? EXIT_CODE=$?
+23 -20
View File
@@ -3,39 +3,42 @@ use db/postgres
##################################################################### #####################################################################
MAIN() { MAIN() {
echo.warning " \nthis function is in a beta state\n " echo.warning "---\nthis function is in a beta state\n---"
local _PASS _ARGS=() 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" local sql_path="${SCWRYPTS_DATA_PATH}/sql"
[ ! -d $SQL_DIR ] && mkdir -p $SQL_DIR 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.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 return 1
} }
[ ! $INPUT_FILE ] && INPUT_FILE=$(utils.fzf 'select a sql file to run') [[ "${input_file}" ]] || input_file="$(utils.fzf.file-select --prompt 'select a sql file to run' "${sql_path}")"
[ ! $INPUT_FILE ] && user.abort [[ "${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..." echo.status "loading '${input_file}' preview..."
LESS "$INPUT_FILE" utils.less "${input_file}"
echo.status "login : $_USER@$_HOST:$_PORT/$_NAME" echo.status "login : ${_USER}@${_HOST}:${_PORT}/${_NAME}"
echo.status "command : '$INPUT_FILE'" 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 \ PSQL < "${input_file}" \
&& echo.success "finished running '$INPUT_FILE'" \ && echo.success "finished running '${input_file}'" \
|| utils.fail 3 "something went wrong running '$INPUT_FILE' (see above)" || echo.error "something went wrong running '${input_file}' (see above)"
} }
+1 -1
View File
@@ -18,7 +18,7 @@ MAIN() {
pruned resources are PERMANENTLY DELETED pruned resources are PERMANENTLY DELETED
" "
utils.yN 'continue?' || utils.abort user.yN 'continue?' || utils.abort
echo.success "$( echo.success "$(
for RESOURCE in ${RESOURCES[@]} for RESOURCE in ${RESOURCES[@]}
+1 -1
View File
@@ -2,5 +2,5 @@
##################################################################### #####################################################################
MAIN() { MAIN() {
echo.success 'Hello, World!' echo.success 'Hello, World!' --force-print
} }
+1 -2
View File
@@ -251,8 +251,7 @@ use.compute-scwrypts-library-hash() {
LC_ALL=POSIX find "${SCWRYPTS_LIBRARY_ROOT}" -type f -name \*.module.zsh -print0 \ LC_ALL=POSIX find "${SCWRYPTS_LIBRARY_ROOT}" -type f -name \*.module.zsh -print0 \
| sort -z \ | sort -z \
| xargs -0 sha1sum \ | xargs -0 sha1sum \
| sha1sum \ | utils.sha1sum \
| awk '{print $1;}' \
; ;
} }
+15 -21
View File
@@ -15,39 +15,33 @@ USAGE__description='
##################################################################### #####################################################################
MAIN() { MAIN() {
local OUTPUT_FUNCTION=success local output_function=success
local EXIT_CODE=0 local exit_code=0
local MESSAGE='Hello, world!' local message='Hello, world!'
eval "${ZSHPARSEARGS}" 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}" "echo.${output_function}" "${message}"
return ${EXIT_CODE} return ${exit_code}
} }
##################################################################### #####################################################################
MAIN.parse() { MAIN.parse() {
local PARSED=0 local parsed=0
case $1 in case "${1}" in
( -e | --exit-code ) ( -e | --exit-code ) parsed=2; exit_code="${2}" ;;
PARSED=2 ( -f | --output-function ) parsed=2; output_function="${2}" ;;
EXIT_CODE="$2" ( -m | --message ) parsed=2; message="${2}" ;;
;;
( -f | --output-function )
PARSED=2
OUTPUT_FUNCTION="$2"
;;
( -m | --message )
PARSED=2
MESSAGE="$2"
;;
esac esac
return ${PARSED} return "${parsed}"
} }
-143
View File
@@ -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)
"
}
#####################################################################
+76 -29
View File
@@ -5,59 +5,106 @@ DEPENDENCIES+=(grep jq sed sort yq)
##################################################################### #####################################################################
${scwryptsmodule}.get-env-names() { ${scwryptsmodule}.get-env-names() {
[ $REQUIRED_ENVIRONMENT_REGEX ] && { scwrypts.environment.common.find-env-names \
scwrypts.environment.common.find-env-names \ | grep "${REQUIRED_ENVIRONMENT_REGEX:-.}"
| grep "$REQUIRED_ENVIRONMENT_REGEX"
return $?
}
scwrypts.environment.common.find-env-names
} }
${scwryptsmodule}.get-env-filename() { # provides the fully qualified path to the group config file ${scwryptsmodule}.get-env-file() { # provides the fully qualified path to the group config file
local NAME="$1" local environment_name="${1}" group="${2}"
local GROUP="$2" [[ "${environment_name}" ]] && [[ "${group}" ]] \
[ $NAME ] && [ $GROUP ] \ || echo.error 'cannot determine environment file without name ($1) and group ($2)' \
|| echo.error 'cannot determine environment filename without name ($1) and group ($2)' \
|| return 1 || 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=() ${scwryptsmodule}.get-parent-env-names() { # highest-level parent first; e.g. for 'a.b.c.d', returns (a a.b a.b.c)
while [ $NAME ] local environment_name="${1}"
[[ "${environment_name}" ]] || return 0
local parent_environment_names=()
while [[ "${environment_name}" ]]
do do
NAME="$(echo $NAME | sed -n 's/\.[^.]\+$//p')" environment_name="$(echo "${environment_name}" | sed -n 's/\.[^.]\+$//p')"
[ $NAME ] && PARENT_ENV_NAMES+=($NAME) [[ "${environment_name}" ]] && parent_environment_names+=("${environment_name}")
done done
echo ${PARENT_ENV_NAMES[@]} | sed 's/\s\+/\n/g' | sort printf '%s\n' "${parent_environment_names[@]}" | sort
} }
#####################################################################
${scwryptsmodule}.find-env-files() { ${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() { ${scwryptsmodule}.find-env-names() {
scwrypts.environment.common.find-env-files \ 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 \ | sort --reverse --unique \
; ;
} }
${scwryptsmodule}.find-env-files-by-name() { ${scwryptsmodule}.find-env-files-by-name() {
local NAME="$1" local environment_name="${1}"
[ $NAME ] || return 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() { ${scwryptsmodule}.combine-template-files() {
utils.yq eval-all '. as $item ireduce ({}; . * $item)' \ # combine files & set 'somethingEmpty: {}' to 'somethingEmpty:'
| sed 's/: {}$/:/' \ utils.yq.combine-files | sed 's/: {}$/:/'
| utils.yq 'sort_keys(...)' \ }
#####################################################################
${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 # common lookups used by all environment logic
use scwrypts/environment/common use scwrypts/environment/common
# primary source of truth for configuration values in the environment
# allows utils.fzf selection of environments use scwrypts/environment/get-user-json
use scwrypts/environment/select-env
# initialize environments, or skip if already initialized # initialize environments, or skip if already initialized
use scwrypts/environment/init 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 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 # create/edit/delete operations on local configurations
use scwrypts/environment/update 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 use scwrypts/environment/get-full-template
##################################################################### #####################################################################
${scwryptsmodule}() { ${scwryptsmodule}() {
eval "$(usage.reset)" local ARGS=() ARGS_FORCE=true
local USAGE__description=" local DESCRIPTION="
outputs a JSON map which can be used to lookup config-file query outputs a JSON map which can be used to lookup config-file query
paths from environment variable names; GET_FULL_TEMPLATE flags OK paths from environment variable names; GET_FULL_TEMPLATE flags OK
@@ -15,20 +14,15 @@ ${scwryptsmodule}() {
value : jq-style query path value : jq-style query path
" "
local \ eval "$(utils.parse.autosetup)"
PARSERS=(
scwrypts.cache-output.zshparse.args # passthrough
)
eval "$ZSHPARSEARGS"
########################################## ##########################################
scwrypts.environment.get-full-template $@ \ scwrypts.environment.get-full-template "${ARGS[@]}" \
| utils.yq -P ' | utils.yq -P '
.. ..
| select(. == "*") | select(. == "*")
| {(.): "." + (path | join(".") + ".value")} | {(.): "." + (path | join("."))}
'\ '\
| sed -n 's/\.\.ENVIRONMENT//p' \ | sed -n 's/\.\.ENVIRONMENT//p' \
; ;
@@ -1,48 +1,54 @@
##################################################################### #####################################################################
use scwrypts/cache-output
use scwrypts/environment/common use scwrypts/environment/common
use zsh-cache
##################################################################### #####################################################################
${scwryptsmodule}() { ${scwryptsmodule}() {
eval "$(usage.reset)" local DESCRIPTION="
local USAGE__description="
Provies the combined YAML of all available scwrypts group 'template.yaml' files. Provies the combined YAML of all available scwrypts group 'template.yaml' files.
Template is cached after first generation in a given scwrypts runtime. Template is cached after first generation in a given scwrypts runtime.
" "
local \ eval "$(utils.parse.autosetup)"
CACHE_ARGS=() \
PARSERS=(
scwrypts.cache-output.zshparse.args
)
eval "$ZSHPARSEARGS"
########################################## ##########################################
scwrypts.cache-output ${CACHE_ARGS[@]} \ zsh-cache --auto
--cache-file environment.template.yaml \ }
-- \
scwrypts.environment.get-full-template.helper \ ${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
}
+1 -1
View File
@@ -39,7 +39,7 @@ MAIN() {
utils.environment.check --print-value ${ARGS[@]} utils.environment.check --print-value ${ARGS[@]}
;; ;;
( all ) ( all )
scwrypts.environment.user.get-json scwrypts.environment.get-user-json
;; ;;
esac esac
} }
+2 -2
View File
@@ -70,7 +70,7 @@ scwrypts.environment.legacy.convert() {
echo.status "checking '${ENVIRONMENT_NAME}' configuration files" echo.status "checking '${ENVIRONMENT_NAME}' configuration files"
for GROUP in ${SCWRYPTS_GROUPS[@]} for GROUP in ${SCWRYPTS_GROUPS[@]}
do 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}" LEGACY_CONFIG_FILENAME="${SCWRYPTS_ENV_PATH}/${GROUP}/${ENVIRONMENT_NAME}"
[ ! -f "${GROUP_CONFIG_FILENAME}" ] && [ -f "${LEGACY_CONFIG_FILENAME}" ] && { [ ! -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)" 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 | .[]') for ENV_VAR in $(echo "${LEGACY_CONFIG_VALUES}" | utils.yq -r 'keys | .[]')
do 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}")" NEW_TEMPLATE_VALUE="$(echo "${LEGACY_CONFIG_VALUES}" | utils.yq -r ".${ENV_VAR}")"
echo "${NEW_TEMPLATE_VALUE}" | grep -q '^[[].*[]]$' \ echo "${NEW_TEMPLATE_VALUE}" | grep -q '^[[].*[]]$' \
|| NEW_TEMPLATE_VALUE="\"${NEW_TEMPLATE_VALUE}\"" || NEW_TEMPLATE_VALUE="\"${NEW_TEMPLATE_VALUE}\""
+6 -6
View File
@@ -73,20 +73,20 @@ MAIN() {
;; ;;
( copy ) ( copy )
local SOURCE_ENVIRONMENT="$(scwrypts.environment.common.get-env-names | utils.fzf 'select an environment to 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" ] || ENVIRONMENT_NAME=$(echo | utils.fzf.user-input 'new environment name')
[ "$ENVIRONMENT_NAME" ] || user.abort [ "$ENVIRONMENT_NAME" ] || echo.error.user-abort || return 1
;; ;;
( delete ) ( delete )
[ "$ENVIRONMENT_NAME" ] || ENVIRONMENT_NAME="$(scwrypts.environment.common.get-env-names | utils.fzf 'select an environment to 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 ) ( advanced )
[ "$ENVIRONMENT_NAME" ] || ENVIRONMENT_NAME="$SCWRYPTS_ENV" [ "$ENVIRONMENT_NAME" ] || ENVIRONMENT_NAME="$SCWRYPTS_ENV"
esac 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=true \
|| local ENVIRONMENT_EXISTS=false \ || local ENVIRONMENT_EXISTS=false \
; ;
@@ -97,7 +97,7 @@ MAIN() {
( true ) ;; ( true ) ;;
( false ) ( false )
echo.warning "environment $ENVIRONMENT_NAME does not exist" 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 esac
;; ;;
@@ -117,7 +117,7 @@ MAIN() {
case $ENVIRONMENT_EXISTS in case $ENVIRONMENT_EXISTS in
( true ) ( true )
echo.reminder 'deleting an environment is PERMANENT\nmake sure your credentials are backed up!' 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 ) ( false )
echo.error "environment '$ENVIRONMENT_NAME' does not exist (can't delete it if it's not there)" echo.error "environment '$ENVIRONMENT_NAME' does not exist (can't delete it if it's not there)"
+151 -143
View File
@@ -1,119 +1,128 @@
##################################################################### #####################################################################
use scwrypts/environment/common use scwrypts/environment/common
use scwrypts/environment/user
use scwrypts/environment/get-full-template use scwrypts/environment/get-full-template
use scwrypts/environment/select-env 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 || export SCWRYPTS_ENVIRONMENT__PREFERRED_EDIT_MODE=basic
scwrypts.environment.update() { ${scwryptsmodule}() {
local EDIT_MODE=$SCWRYPTS_ENVIRONMENT__PREFERRED_EDIT_MODE local DESCRIPTION='Update a scwrypts environment configuration'
local ENVIRONMENT_NAME="$SCWRYPTS_ENV" local PARSERS=(scwrypts.zshparse.environment-name)
local FROM_EXISTING eval "$(utils.parse.autosetup)"
##########################################
local USAGE=" local TEMP_CONFIG_FILE="${SCWRYPTS_TEMP_PATH_SOCKET}/environment.temp.yaml"
usage: scwrypts.environment.update [...options...]
options: [[ -f "${TEMP_CONFIG_FILE}" ]] && {
--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" ] && {
echo.error "temp config file already exists at '$TEMP_CONFIG_FILE'\nis another environment update in-progress?" 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 return 1
} }
scwrypts.environment.update.edit.${EDIT_MODE} "${edit_mode_helper}"
local EXIT_CODE=$? 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() { ${scwryptsmodule}.edit.basic() {
scwrypts.environment.user.get \ scwrypts.environment.get-enriched-user-yaml \
--environment-name $ENVIRONMENT_NAME \ --environment-name $environment_name \
> "$TEMP_CONFIG_FILE" > "$TEMP_CONFIG_FILE"
utils.io.edit "$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() { ${scwryptsmodule}.edit.quiet() {
echo "--- # $ENVIRONMENT_NAME" > "$TEMP_CONFIG_FILE" echo "--- # $environment_name" > "$TEMP_CONFIG_FILE"
scwrypts.environment.user.get \ scwrypts.environment.get-enriched-user-yaml \
--environment-name $ENVIRONMENT_NAME \ --environment-name $environment_name \
| utils.yq '. | utils.yq '.
| del(.. | select(has(".ENVIRONMENT")).".ENVIRONMENT") | del(.. | select(has(".ENVIRONMENT")).".ENVIRONMENT")
| del(.. | select(has(".GROUP")).".GROUP") | del(.. | select(has(".GROUP")).".GROUP")
@@ -126,75 +135,75 @@ ${scwryptsmodule}.edit.quiet() {
utils.io.edit "$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.recursive() { ${scwryptsmodule}.edit.recursive() {
local RECURSIVE_EDIT_MODE="$SCWRYPTS_ENVIRONMENT__PREFERRED_EDIT_MODE" local recursive_edit_mode="${SCWRYPTS_ENVIRONMENT__PREFERRED_EDIT_MODE}"
[[ $RECURSIVE_EDIT_MODE =~ ^recursive$ ]] \ [[ "${recursive_edit_mode}" =~ ^recursive$ ]] \
&& RECURSIVE_EDIT_MODE=quiet && recursive_edit_mode=quiet
local PARENT_ENVIRONMENT_NAME local parent_environment_name
for PARENT_ENVIRONMENT_NAME in \ for parent_environment_name in \
$(scwrypts.environment.common.get-parent-env-names "$ENVIRONMENT_NAME") \ $(scwrypts.environment.common.get-parent-env-names "$environment_name") \
$ENVIRONMENT_NAME $environment_name
; ;
do do
echo.status "editing environment '$PARENT_ENVIRONMENT_NAME'" echo.status "editing environment '${parent_environment_name}'"
scwrypts.environment.update \ scwrypts.environment.update \
--environment-name $PARENT_ENVIRONMENT_NAME \ --environment-name "${parent_environment_name}" \
--mode $RECURSIVE_EDIT_MODE \ --mode "${recursive_edit_mode}" \
; ;
done done
} }
${scwryptsmodule}.edit.init() { ${scwryptsmodule}.edit.init() {
[ -f "$(scwrypts.environment.common.get-env-filename)" ] [ -f "$(scwrypts.environment.common.get-env-file)" ]
scwrypts.environment.user.get \ scwrypts.environment.get-enriched-user-yaml \
--environment-name $ENVIRONMENT_NAME \ --environment-name "${environment_name}" \
> "$TEMP_CONFIG_FILE" > "${TEMP_CONFIG_FILE}"
scwrypts.environment.update.update-user-configs "$(cat "$TEMP_CONFIG_FILE")" scwrypts.environment.update.update-user-configs "${environment_name}"
} }
${scwryptsmodule}.edit.copy() { ${scwryptsmodule}.edit.copy() {
local GROUP_CONFIG_FILENAME SOURCE_CONFIG_FILENAME local group_config_filename SOURCE_CONFIG_FILENAME
for GROUP in ${SCWRYPTS_GROUPS[@]} for group in ${SCWRYPTS_GROUPS[@]}
do do
cp \ cp \
"$(scwrypts.environment.common.get-env-filename "$FROM_EXISTING" "$GROUP")" \ "$(scwrypts.environment.common.get-env-file "${FROM_EXISTING}" "${group}")" \
"$(scwrypts.environment.common.get-env-filename "$ENVIRONMENT_NAME" "$GROUP")" \ "$(scwrypts.environment.common.get-env-file "${environment_name}" "${group}")" \
2>/dev/null \ 2>/dev/null \
; ;
done done
scwrypts.environment.user.get \ scwrypts.environment.get-enriched-user-yaml \
--environment-name $ENVIRONMENT_NAME \ --environment-name "${environment_name}" \
> "$TEMP_CONFIG_FILE" > "${TEMP_CONFIG_FILE}"
scwrypts.environment.update.update-user-configs "$(cat "$TEMP_CONFIG_FILE")" scwrypts.environment.update.update-user-configs "${environment_name}"
} }
${scwryptsmodule}.edit.delete() { ${scwryptsmodule}.edit.delete() {
touch "$TEMP_CONFIG_FILE" touch "$TEMP_CONFIG_FILE"
local ERRORS=0 GROUP GROUP_CONFIG_FILENAME local ERRORS=0 group group_config_filename
for GROUP in ${SCWRYPTS_GROUPS[@]} for group in ${SCWRYPTS_GROUPS[@]}
do do
local GROUP_CONFIG_FILENAME="$(scwrypts.environment.common.get-env-filename "$ENVIRONMENT_NAME" "$GROUP")" local group_config_filename="$(scwrypts.environment.common.get-env-file "${environment_name}" "${group}")"
[ -f "$GROUP_CONFIG_FILENAME" ] || { [ -f "${group_config_filename}" ] || {
echo.status "nothing to cleanup for $ENVIRONMENT_NAME/$GROUP" echo.status "nothing to cleanup for ${environment_name}/${group}"
continue continue
} }
rm -- "$GROUP_CONFIG_FILENAME" \ rm -- "${group_config_filename}" \
&& echo.success "deleted '$GROUP_CONFIG_FILENAME'" \ && echo.success "deleted '${group_config_filename}'" \
|| echo.error "unable to delete '$GROUP_CONFIG_FILENAME'" \ || echo.error "unable to delete '${group_config_filename}'" \
; ;
done done
return $ERRORS return "${ERRORS}"
} }
##################################################################### #####################################################################
@@ -203,55 +212,54 @@ ${scwryptsmodule}.edit.delete() {
export __SCWRYPTS_ENVIRONMENT__WORKFLOW_IS_CHANGE_SAFE=false export __SCWRYPTS_ENVIRONMENT__WORKFLOW_IS_CHANGE_SAFE=false
${scwryptsmodule}.update-user-configs() { ${scwryptsmodule}.update-user-configs() {
local NEW_CONFIGURATION="$1" local environment_name="${1}"
[ $NEW_CONFIGURATION ] || return 1 [[ "${environment_name}" ]] || return 2
local ENVIRONMENT_NAME="$2"
[ $ENVIRONMENT_NAME ] || return 2
# reinject all metadata, since the update function is allowed to strip it # reinject all metadata, since the update function is allowed to strip it
NEW_CONFIGURATION="$( local new_configuration="$({
{ scwrypts.environment.get-full-template.with-value-keys --environment "${environment_name}"
scwrypts.environment.user.get-full-template-with-value-keys --environment "$ENVIRONMENT_NAME" echo ---
echo --- cat -- "${TEMP_CONFIG_FILE}"
echo "$NEW_CONFIGURATION" } | scwrypts.environment.common.combine-template-files)"
} | 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' \ | sed -n 's/^\s\+\(\.[-A-Za-z_:]\+\):.*$/ | del(.. | select(has("\1"))."\1")/p' \
| sort --unique \ | sort --unique \
)" )"
local GROUP GROUP_CONFIG_FILENAME GROUP_CONFIG local group group_config_filename group_config
for GROUP in ${SCWRYPTS_GROUPS[@]} for group in ${SCWRYPTS_GROUPS[@]}
do do
local GROUP_CONFIG="$(echo "$NEW_CONFIGURATION" \ local group_config="$(echo "${new_configuration}" \
| utils.yq ". | utils.yq ".
| del(.. | select(has(\".PARENTVALUE\") and has(\"value\") and .\".PARENTVALUE\" == .value)) | del(.. | select(has(\".PARENTVALUE\") and has(\"value\") and .\".PARENTVALUE\" == .value))
| del(.. | select(has(\".PARENTSELECTION\") and has(\"selection\") and .\".PARENTSELECTION\" == .selection)) | 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(\"selection\") and (.selection == null or (.selection | length) == 0)).selection)
| del(.. | select(has(\"value\") and .value == null).value) | 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 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 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 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" echo.warning "current scwrypts environment has changed"
export __SCWRYPTS_ENVIRONMENT__USER_ENVIRONMENT= export __SCWRYPTS_ENVIRONMENT__USER_ENVIRONMENT=
} }
zsh-cache.reset.all
return 0 return 0
} }
-276
View File
@@ -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
}
+15 -6
View File
@@ -1,3 +1,5 @@
DEPENDENCIES+=(readlink envsubst)
${scwryptsmodule}() { ${scwryptsmodule}() {
# #
# returns the fully-qualified path from a user-specified, relative reference # 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 # 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.trace
&& echo $(readlink --canonicalize -- "${EXECUTION_DIR}/$1") \
|| echo "$1" \
;
return 0 [[ "${path_2_target}" ]] || return 1
readlink --canonicalize -- "${path_2_target}"
} }
+1 -1
View File
@@ -12,7 +12,7 @@ ${scwryptsmodule}() {
GROUP_TYPE=$(scwrypts.config.group ${GROUP} type) GROUP_TYPE=$(scwrypts.config.group ${GROUP} type)
[ ${GROUP_TYPE} ] && MINDEPTH=1 && GROUP_TYPE="${GROUP_TYPE}\\/" || MINDEPTH=2 [ ${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_GROUP_CONFIGURATION__${GROUP}.list-available \
|| LOOKUP=scwrypts.list-available.default \ || LOOKUP=scwrypts.list-available.default \
; ;
+1 -1
View File
@@ -12,7 +12,7 @@ MAIN() {
echo.status "found ${LOGFILE_COUNT} log files" echo.status "found ${LOGFILE_COUNT} log files"
echo.warning "this will permanently delete everything in\n${SCWRYPTS_LOG_PATH}" 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}" \ rm -rf "${SCWRYPTS_LOG_PATH}" \
&& echo.success 'all logfiles removed' \ && echo.success 'all logfiles removed' \
+2 -2
View File
@@ -30,7 +30,7 @@ MAIN() {
( 0 ) ARGS=(${SCWRYPTS_GROUPS[@]}) ;; ( 0 ) ARGS=(${SCWRYPTS_GROUPS[@]}) ;;
esac 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[@]} for GROUP in ${ARGS[@]}
do do
GROUP_ROOT="$(scwrypts.config.group ${GROUP} zshlibrary)" GROUP_ROOT="$(scwrypts.config.group ${GROUP} zshlibrary)"
@@ -83,7 +83,7 @@ MAIN.parse() {}
MAIN.parse.validate() { MAIN.parse.validate() {
[[ ${ZSHIMPORT_USE_CACHE} =~ true ]] \ [[ ${ZSHIMPORT_USE_CACHE} =~ true ]] \
|| echo.error "ZSHIMPORT_USE_CACHE is set to 'false' so I cannot rebuild the cache" || echo.error "ZSHIMPORT_USE_CACHE is set to 'false' so I cannot rebuild the cache"
[ "${ZSHIMPORT_CACHE_DIR}" ] && [ -d "${ZSHIMPORT_CACHE_DIR}" ] \ [ "${ZSHIMPORT_CACHE_DIR}" ] && [ -d "${ZSHIMPORT_CACHE_DIR}" ] \
|| echo.error "I cannot find the ZSHIMPORT_CACHE_DIR '${ZSHIMPORT_CACHE_DIR}'" || echo.error "I cannot find the ZSHIMPORT_CACHE_DIR '${ZSHIMPORT_CACHE_DIR}'"
} }
+20
View File
@@ -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})
"
}
+1 -1
View File
@@ -6,7 +6,7 @@ use unittest/operations
${scwryptsmodule}() { ${scwryptsmodule}() {
local ERRORS=0 TEST_ERRORS=0 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)) local UNITTESTS=($(echo "${(k)functions}" | sed 's/ /\n/g' | grep '^test\.' | sort))
[[ ${#UNITTESTS[@]} -gt 0 ]] \ [[ ${#UNITTESTS[@]} -gt 0 ]] \
+17
View File
@@ -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
View File
@@ -1 +0,0 @@
+177 -29
View File
@@ -1,43 +1,191 @@
utils.fzf() { 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=( local PARSERS=() DESCRIPTION='
-i provides a consistent interface to fzf by unifying display settings
--ansi and assigning sensible defaults
--bind=ctrl-c:cancel
--height=50%
--layout=reverse
)
returns 0 only when a non-empty selection is made by the user
'
local SELECTION=$(fzf ${FZF_ARGS[@]} --prompt "$1 : " ${@:2} 2>/dev/tty) eval "$(utils.parse.autosetup)"
echo.prompt "$1"
[ ${BE_QUIET} ] || { local DEBUG_ARGS=(-v user_prompt -v quiet -v selection) TRACE_ARGS=(-v fzf_args)
[[ ${SCWRYPTS_LOG_LEVEL} -ge 1 ]] && echo ${SELECTION} >&2
} [[ "${BE_QUIET}" ]] && echo.warning.deprecation v5.3 'use of BE_QUIET= will be removed'
echo ${SELECTION}
[ ${SELECTION} ] ##########################################
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 utils.fzf.parse.locals() {
local FZF_OUTPUT=$(BE_QUIET=1 utils.fzf $@ --print-query | sed '/^$/d' | sort -u) local fzf_args=(-i --ansi --bind=ctrl-c:cancel --height=50% --layout=reverse)
[[ ${SCWRYPTS_LOG_LEVEL} -ge 1 ]] && echo ${FZF_OUTPUT} | head -n1 >&2 local user_prompt
[ ! ${FZF_OUTPUT} ] && return 1 local user_prompt_delimiter=' : '
local quiet="${BE_QUIET:-false}"
}
[[ $(echo "${FZF_OUTPUT}" | wc -l) -eq 1 ]] \ utils.fzf.parse() {
&& { echo "${FZF_OUTPUT}"; return 0; } local parsed=0
local FZF_OUTPUT=$( case "${1}" in
echo "${FZF_OUTPUT}" \ ( --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 "1s/\$/^$(utils.colors.print light-gray '<- what you typed')/" \
| sed "2s/\$/^$(utils.colors.print light-gray '<- what you selected')/" \ | sed "2s/\$/^$(utils.colors.print light-gray '<- what you selected')/" \
| column -ts '^' \ | 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 echo "${fzf_output}"
FZF_OUTPUT=$(echo ${FZF_OUTPUT} | sed 's/\s\+<- what you .*$//') [[ "${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
} }
+5 -5
View File
@@ -1,13 +1,13 @@
utils.notify-send() { utils.notify-send() {
utils.dependencies.check notify-send &>/dev/null || { utils.dependencies.check notify-send --optional &>/dev/null || {
echo.warning "missing 'notify-send'; cannot send desktop notifications" echo.warning "cannot send desktop notifications"
utils.notify-send() { true; } utils.notify-send() { true; }
return 0 return 0
} }
[ "${SCWRYPT_GROUP}" ] && [ "${SCWRYPT_NAME}" ] \ [ "${SCWRYPT_GROUP}" ] && [ "${SCWRYPT_NAME}" ] \
&& local TITLE="scwrypts/${SCWRYPT_GROUP} ${SCWRYPT_NAME}" \ && local title="scwrypts/${SCWRYPT_GROUP} ${SCWRYPT_NAME}" \
|| local TITLE="zsh" || local title="zsh"
notify-send "${TITLE}" $@ notify-send "${title}" "${@}"
} }
+9
View File
@@ -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
}
+13 -6
View File
@@ -1,8 +1,15 @@
utils.yq() { yq --version | grep -q mikefarah && {
yq --version | grep -q mikefarah || { utils.yq() { yq eval '... comments=""' | yq "${@}"; }
yq $@ # this is a different version from the preferred but throwing this in for compatibility } || {
return $? 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
View File
@@ -1,64 +1,130 @@
utils.dependencies.check-all() { utils.dependencies.check-all() {
local DEP ERRORS=0 local ERRORS=0
local SCWRYPTS_LOG_LEVEL=1 [[ "${SCWRYPTS_LOG_LEVEL}" ]] || local SCWRYPTS_LOG_LEVEL=1
[ ! ${E} ] && E=echo.error
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 local dependency
utils.dependencies.check-coreutils || ((ERRORS+=$?)) for dependency in ${DEPENDENCIES[@]}
do
utils.dependencies.check "${dependency}" || ((ERRORS+=1))
done
return ${ERRORS} return ${ERRORS}
} }
utils.dependencies.check() { utils.dependencies.check() {
local DEPENDENCY="$1" local ERRORS=0
[ ! ${DEPENDENCY} ] && return 1
command -v ${DEPENDENCY} >/dev/null 2>&1 || { local dependency
[[ ${OPTIONAL} -eq 1 ]] \ local dependency_default_args=()
&& echo.warning "application '$1' preferred but not available on PATH $(utils.dependencies.credits $1)" \ local dependency_required=true
|| echo.error "application '$1' required but not available on PATH $(utils.dependencies.credits $1)" \
; 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 return 1
} }
[[ "${dependency}" =~ "^${executable_path}$" ]] && {
command -v utils.wrapper.${DEPENDENCY} &>/dev/null && { # echo "built-in '${dependency}' cannot be wrapped for safety" >&2
utils.wrapper.${DEPENDENCY} \ return 0
|| echo.error "failed to define required context wrapper for '$1'" \
|| return 1 \
;
} }
[[ ${DEPENDENCY} =~ ^yq$ ]] && { local automatic_trace=true
yq --version | grep -q mikefarah \ local extra_eval_statements=''
|| echo.warning 'detected kislyuk/yq but mikefarah/yq is preferred (compatibility may vary)'
}
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() { ( awk | sed | grep | find | readlink )
local COREUTILS=(awk find grep sed readlink) automatic_trace=false
local MISSING_DEPENDENCY_COUNT=0 "${executable_path}" --version 2>&1 | grep 'GNU' | grep -qv 'BSD' || {
local NON_GNU_DEPENDENCY_COUNT=0 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 ( gawk | gsed | ggrep | gfind | greadlink )
for UTIL in $(echo ${COREUTILS}) echo.error "do not use MacOS / BSD shadows for utilities which require GNU (use ${dependency/g/} instead)"
do ;;
utils.dependencies.check ${UTIL} || { ((MISSING_DEPENDENCY_COUNT+=1)); continue; }
${UTIL} --version 2>&1 | grep 'GNU' | grep -qv 'BSD' || { ( aws | eksctl )
echo.warning "non-GNU version of ${UTIL} detected" extra_eval_statements+="
((NON_GNU_DEPENDENCY_COUNT+=1)) echo.warning 'use of ${dependency} directly is highly discouraged; utilize appropriate module instead'
} "
done 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 ]] && { eval "${dependency}() {
echo.warning 'scripts rely on GNU coreutils; compatibility may vary' local DEBUG_ARGS=() TRACE_ARGS=() ERRORS=0 WARNINGS=0
utils.os.is-macos && echo.reminder 'GNU coreutils can be installed and linked through Homebrew' 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() { utils.dependencies.credits() {
+222 -153
View File
@@ -1,19 +1,32 @@
utils.environment.check-all() { utils.environment.check-all() {
# checks all environment variables in REQUIRED_ENV=() # checks all environment variables in REQUIRED_ENV=()
local SCWRYPTS_LOG_LEVEL=4 [[ "${SCWRYPTS_LOG_LEVEL}" ]] || local SCWRYPTS_LOG_LEVEL=4
local VAR ERRORS=0 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} return ${ERRORS}
} }
utils.environment.check() { utils.environment.check() {
local NAME OPTIONAL DEFAULT_VALUE LOOKUP_PATH local ERRORS=0 WARNINGS=0
local NAME_IS=environment-variable local DEBUG_ARGS=() TRACE_ARGS=()
local PRINT_VALUE=false
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=" local USAGE="
usage: utils.environment.check <environment variable> [...options...] 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) configuration env var (even when lookup is a config lookup path)
" "
local _S ERRORS=0 POSITIONAL_ARGS=0 local _s _p=0
while [[ $# -gt 0 ]] while [[ ${#} -gt 0 ]]
do do
_S=1 _s=1
case $1 in case ${1} in
( -h | --help ) ( -h | --help )
utils.io.usage utils.io.usage
return 0 return 0
;; ;;
( --optional ) ( --optional )
OPTIONAL=true optional=true
;; ;;
( --print-value ) ( --print-value )
PRINT_VALUE=true print_value_to_stdout=true
;; ;;
( --default ) ( --default )
[ $2 ] && ((_S+=1)) \ [[ ${2} ]] && ((_S+=1)) \
|| echo.error "missing env var default value" \ || echo.error "missing env var default value" \
|| break || break
[ ! "${DEFAULT_VALUE}" ] \ [[ ! "${default_value}" ]] \
|| echo.error "only one default value is supported" \ || echo.error "only one default value is supported" \
|| break || break
[[ ${OPTIONAL} =~ true ]] \ [[ ${optional} == true ]] \
&& echo.warning "--optional and --default flags are redundant; remove '--optional' flag" && echo.warning "--optional and --default flags are redundant; remove '--optional' flag"
DEFAULT_VALUE="$2" default_value="${2}"
OPTIONAL=true optional=true
;; ;;
( * ) ( * )
((POSITIONAL_ARGS+=1)) case "$((_p+=1))" in
case ${POSITIONAL_ARGS} in ( 1 ) configuration_variable_name="${1}"
( 1 ) 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.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
;; ;;
esac esac
shift ${_S} shift "${_s}"
done done
unset _s _p
[ "${NAME}" ] \ [[ "${configuration_variable_name}" ]] \
|| echo.error "must provide environment variable name" || echo.error "must provide configuration variable name"
[ "${OPTIONAL}" ] || OPTIONAL=false [[ "${optional}" ]] || optional=false
echo "${NAME}" | grep -q '^\.' \ case "${configuration_variable_name}" in
&& NAME_IS=lookup-path ( .* )
lookup_type=query-path
case ${NAME_IS} in [[ "${__SCWRYPT}" ]] \
( lookup-path ) || echo.error "lookup paths cannot be used outside of scwrypts (${configuration_variable_name})"
[ ${__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}'"
}
;; ;;
( environment-variable )
( * )
lookup_type=environment-variable
;; ;;
esac esac
utils.check-errors --no-usage || return $? utils.check-errors --no-usage || return "${?}"
########################################## ##########################################
# only check env vars once # only check env vars once; this is a headache, but serves a substantial performance purpose
local ALREADY_CHECKED="$(eval echo '$'${NAME}'__checked')" local already_checked_status_suffix=scwrypts_checked_status
[ "${ALREADY_CHECKED}" ] && return ${ALREADY_CHECKED} 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 local already_checked_status
[ ${CI} ] && { DEBUG_ARGS+=(-v already_checked_status)
local VALUE="$(eval echo '$'${NAME})" case "${lookup_type}" in
[ "${VALUE}" ] && { ( query-path ) ;; # can't check early return status just yet
export ${NAME}__checked=0 ( * )
[[ ${PRINT_VALUE} =~ true ]] && echo "${VALUE}" already_checked_status_variable="${configuration_variable_name}__${already_checked_status_suffix}"
return 0 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 # if we don't need to print, we can escape early
( true ) [[ "${print_value_to_stdout}" == false ]] && echo.trace && return "${already_checked_status}"
[ "${DEFAULT_VALUE}" ] \ }
&& export ${NAME}=${DEFAULT_VALUE} \ ;;
|| echo.warning "environment variable '${NAME}' is not set" \ esac
;
export ${NAME}__checked=0 # outside of scwrypts, environment must load like CI runtime
return 0 [[ "${__SCWRYPT}" ]] || local CI=true
;; DEBUG_ARGS+=(-v CI)
( false )
local ERROR_MESSAGE="missing required environment variable '${NAME}'"
[ "${LOOKUP_PATH}" ] && ERROR_MESSAGE+=" (config path '${LOOKUP_PATH}')"
echo.error "${ERROR_MESSAGE}" local configuration_value configuration_values=() check_status=1
export ${NAME}__checked=1 TRACE_ARGS+=(-v configuration_value -v configuration_values)
return 1 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 esac
} }
[ ! ${LOOKUP_PATH} ] && { export ${already_checked_status_variable}="${check_status}"
LOOKUP_PATH="$(scwrypts.environment.get-envvar-lookup-map | utils.yq -r ".${NAME}" | sed 's/\.value$//')"
}
# ensure environment safety; prevent bleed in from user's runtime case "${configuration_value_type}" in
unset ${NAME} ( array )
local VALUE [[ "${print_value_to_stdout}" == true ]] && printf '%s\n' "${configuration_values[@]}"
for GET_VALUE_METHOD in \ eval "export ${configuration_variable_name}=(${(q)configuration_values[@]})"
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
;; ;;
( 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 [[ "${print_value_to_stdout}" == true ]] && echo "${configuration_value}"
return 1 export ${configuration_variable_name}="${configuration_value}"
;; ;;
esac 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() { utils.environment.check.runtime-override() { # support for ENV_VAR__override= : scalar only
local NAME="$1" local configuration_variable_name="${1}"
local LOOKUP_PATH="$2" #local configuration_json="${2}"
eval echo '$'${NAME}'__override'
local environment_override_configuration_variable_name="${configuration_variable_name}__override"
configuration_value="${(P)environment_override_configuration_variable_name}"
} }
utils.environment.check.value() { utils.environment.check.value() { # support for '.value' : splits between array and scalar
local NAME="$1" local configuration_variable_name="${1}"
local LOOKUP_PATH="$2" local configuration_json="${2}"
scwrypts.environment.user.get-shell-values \ case "$(echo "${configuration_json}" | utils.yq -r '.value | type')" in
| utils.yq -r "${LOOKUP_PATH}.value" \ ( !!null ) ;; # no value set
| grep -v '^null$' \ ( !!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() { utils.environment.check.selection() { # support for '.selection' : configured list prompts user to select a scalar value
local NAME="$1" local configuration_variable_name="${1}"
local LOOKUP_PATH="$2" local configuration_json="${2}"
local SELECTION_VALUES=($( local selection_value selection_values=()
scwrypts.environment.user.get-shell-values \ while IFS= read -r selection_value
| utils.yq -r "${LOOKUP_PATH}.selection[]" \ do
| grep -v '^null$' \ 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}" \ configuration_value="$(
| sed 's/\s\+/\n/g' \ printf '%s\n' "${selection_values[@]}" \
| utils.fzf "select a value for '${NAME}'" \ | utils.fzf "select a value for '${configuration_variable_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}'" \
;
} }
+3 -8
View File
@@ -22,15 +22,10 @@ utils.colors.remove() { sed 's/\x1b\[[0-9;]*m//g'; }
##################################################################### #####################################################################
utils.colors.print() { # print color + message + color-reset sequence utils.colors.print() { # print color + message + color-reset sequence
local COLOR="$1" local color="${1}"
local MESSAGE="${@:2}" local message="${@:2}"
command -v utils.colors.${COLOR} &>/dev/null || { printf "%s" "$(utils.colors.${color} 2>/dev/null)${message}$(utils.colors.reset)"
printf "${MESSAGE}"
return
}
printf "$(utils.colors.${COLOR})${MESSAGE}$(utils.colors.reset)"
} }
##################################################################### #####################################################################
+81 -60
View File
@@ -2,103 +2,124 @@ DEPENDENCIES+=(jo jq printf)
utils.io.print() { utils.io.print() {
local \ local \
MESSAGE \ message \
PREFIX COLOR \ prefix prefix_delimiter=' : ' color \
MINIMUM_LOG_LEVEL IGNORE_MINIMUM_LOG_LEVEL=false \ minimum_log_level ignore_minimum_log_level=false \
PRINT_TO_STDERR=true \ print_to_stderr=true \
PRINT_TO_STDOUT=false \ print_to_stdout=false \
LAST_LINE_END='\n' \ last_line_end=$'\n' \
variables=() \
; ;
[ ${SCWRYPTS_LOG_LEVEL} ] || local SCWRYPTS_LOG_LEVEL=4 # since this function can be used outside of scwrypts, we bump log
[ ${SCWRYPTS_OUTPUT_FORMAT} ] || local SCWRYPTS_OUTPUT_FORMAT=pretty # 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 [[ "${SCWRYPTS_OUTPUT_FORMAT}" ]] \
while [[ $# -gt 0 ]] || local SCWRYPTS_OUTPUT_FORMAT=pretty
local _s
while [[ "${#}" -gt 0 ]]
do do
_S=1 _s=1
case $1 in case "${1}" in
( --prefix ) ( --prefix ) _s=2; prefix="${2}" ;;
_S=2 ( --prefix-delimiter ) _s=2; prefix_delimiter="${2}" ;;
PREFIX="$2" ( --color ) _s=2; color="${2}" ;;
;; ( --minimum-log-level ) _s=2; minimum_log_level="${2}" ;;
( --format ) _s=2; local SCWRYPTS_OUTPUT_FORMAT="${2}" ;;
( --color ) ( -v ) _s=2 # appends 'name=value' to the end of the message
_S=2 variables+=("${2}")
COLOR="$2"
;;
( --minimum-log-level )
_S=2
MINIMUM_LOG_LEVEL=$2
;; ;;
( --force-print ) ( --force-print )
IGNORE_MINIMUM_LOG_LEVEL=true ignore_minimum_log_level=true
;; ;;
( --stdout ) ( --stdout )
PRINT_TO_STDOUT=true print_to_stdout=true
PRINT_TO_STDERR=false print_to_stderr=false
;; ;;
( --no-line-end ) ( --no-line-end ) # only applicable to some formats (see below)
LAST_LINE_END='' last_line_end=''
;;
( --format )
_S=2
local SCWRYPTS_OUTPUT_FORMAT=$2
;; ;;
( * ) ( * )
[ "${MESSAGE}" ] && MESSAGE+=" $1" || MESSAGE="$1" [[ "${message}" ]] && message+=" ${1}" || message="${1}"
;; ;;
esac 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 done
: \ : \
&& [ "${MINIMUM_LOG_LEVEL}" ] \ && [[ "${minimum_log_level}" ]] \
&& [[ "${IGNORE_MINIMUM_LOG_LEVEL}" =~ false ]] \ && [[ "${ignore_minimum_log_level}" =~ false ]] \
&& [[ "${SCWRYPTS_LOG_LEVEL}" -lt "${MINIMUM_LOG_LEVEL}" ]] \ && [[ "${SCWRYPTS_LOG_LEVEL}" -lt "${minimum_log_level}" ]] \
&& return 0 && 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 ) ( pretty )
MESSAGE="${COLOR}$({ message="${color}$({
while IFS='' read line while IFS='' read -r line
do do
[[ ${PREFIX} =~ ^[[:space:]]\+$ ]] && printf '\n' [[ ${prefix} =~ ^[[:space:]]\+$ ]] && printf '%s' $'\n'
printf "%s" "${prefix}${prefix_delimiter}$(echo "${line}" | sed 's/^ \+//; s/ \+$//')"
printf "${PREFIX} : $(echo "${line}" | sed 's/^ \+//; s/ \+$//')" prefix="$(printf '%*s' ${#prefix} '')"
done <<< "${message}"
PREFIX=' ' })${last_line_end}$(utils.colors.reset)"
done <<< $(echo "${MESSAGE}" | sed 's/%/%%/g')
})${LAST_LINE_END}$(utils.colors.reset)"
;; ;;
( json ) ( json )
MESSAGE="$(jo \ message="$(jo \
timestamp=$(date +%s) \ timestamp=$(date +%s) \
runtime=${SCWRYPTS_RUNTIME_ID} \ runtime=${SCWRYPTS_RUNTIME_ID} \
status="$(echo "${PREFIX}" | sed 's/ .*//')" \ status="$(echo "${prefix}" | sed 's/ .*//')" \
message="$(echo -n "${MESSAGE}" | sed 's/^\t\+//' | jq -Rs)" \ message="$(echo -n "${message}" | sed 's/^\t\+//')" \
2>/dev/null || echo "{\"error\":\"your message was too long so I encoded it manually\",\"messageB64\":\"$(echo "${MESSAGE}" | base64 | tr -d '\n')\"}" 2>/dev/null || echo "{\"error\":\"your message was too long so I encoded it\",\"messageB64\":\"$(echo "${message}" | base64 | tr -d '\n')\"}"
)\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 return 1
;; ;;
esac esac
[[ "${print_to_stderr}" =~ true ]] && printf "%s" "${message}" >&2
[[ ${PRINT_TO_STDERR} =~ true ]] && printf -- "${MESSAGE}" >&2 [[ "${print_to_stdout}" =~ true ]] && printf "%s" "${message}"
[[ ${PRINT_TO_STDOUT} =~ true ]] && printf -- "${MESSAGE}"
return 0 return 0
} }
+190 -26
View File
@@ -1,57 +1,221 @@
echo.success.color() { utils.colors.green; } export _SCWRYPTS_DEFAULT_LOG_LEVEL=3
echo.success() { # command completed successfully typeset -gA SCWRYPTS_LOG_LEVELS
utils.io.print $@ --minimum-log-level 1 --prefix "SUCCESS ✔" --color $(echo.success.color)
return 0 ### 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.color() { utils.colors.red; }
echo.error() { # command encountered an error 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)) ((ERRORS+=1))
return ${ERRORS} return ${ERRORS}
} }
echo.reminder.color() { utils.colors.bright-magenta; } echo.error.user-abort() { # consistent verbiage for user-aborted workflows
echo.reminder() { # sysadmin reminder or important notice to users echo.error 'user abort' "${@}"
utils.io.print $@ --minimum-log-level 1 --prefix "REMINDER " --color $(echo.reminder.color) }
### 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 return 0
} }
echo.status.color() { utils.colors.blue; } echo.status.color() { utils.colors.blue; }
echo.status() { # general status updates (prefer this to generic 'echo') 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 return 0
} }
echo.warning.color() { utils.colors.yellow; } echo.reminder.color() { utils.colors.bright-magenta; }
echo.warning() { # warning-level messages; not errors echo.reminder() { # sysadmin reminder or important notice to users
utils.io.print $@ --minimum-log-level 3 --prefix "WARNING " --color $(echo.warning.color) utils.io.print "${@}" --minimum-log-level 3 --prefix "REMINDER " --color "$(echo.reminder.color)"
return 0 return 0
} }
### level 4 : include debug messages ################################
SCWRYPTS_LOG_LEVELS[4]=debug
echo.debug.color() { utils.colors.white; } echo.debug.color() { utils.colors.white; }
echo.debug() { # helpful during development or (sparingly) to help others' development echo.debug() { # helpful during development or (sparingly) to help others' development
# early exit since debug injects state information set -- "${DEBUG_ARGS[@]}" "${@}"
[ ${SCWRYPTS_LOG_LEVEL} ] && [[ ${SCWRYPTS_LOG_LEVEL} -lt 4 ]] && [[ ! $@ =~ --force-print ]] && return 0 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 return 0
} }
##################################################################### #####################################################################
echo.prompt() { 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 ]] && { local _s
utils.io.print --format raw $@ ": " --no-line-end while [[ "${#}" -gt 0 ]]
return 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) case "${SCWRYPTS_LOG_LEVEL:-${LOG_LEVEL:-${_SCWRYPTS_DEFAULT_LOG_LEVEL}}}" in
utils.io.print '' --prefix "USER ⌨" --color $(utils.colors.bright-cyan) --no-line-end ( 0 )
return 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
View File
@@ -9,8 +9,8 @@ utils.io.input() { # read a single line of user input
# yes/no prompts && = yes (exit code 0) # yes/no prompts && = yes (exit code 0)
# || = no (exit code 1) # || = no (exit code 1)
utils.Yn() { [[ ! $(utils.io.read-yn $@ '[Yn]') =~ [nN] ]]; } # default 'yes' user.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]') =~ [yY] ]]; } # default 'no'
utils.io.edit() { # edit a file in user's preferred editor utils.io.edit() { # edit a file in user's preferred editor
[ ${CI} ] && { [ ${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 utils.io.getsudo() { # ensure a user has sudo permissions
echo.prompt 'checking sudo password' --stdout | head -n1 >&2 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.success '...authenticated!' \
|| echo.error 'failed :c' \ || echo.error 'failed :c' \
|| return 1 || return 1
@@ -35,51 +35,56 @@ utils.io.getsudo() { # ensure a user has sudo permissions
##################################################################### #####################################################################
utils.io.read() { utils.io.read() {
[ ${CI} ] && [ -t 0 ] \ local stdin_is_a_terminal \
&& utils.fail 42 'currently in CI, but attempting interactive read; aborting' && [[ -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=() local ARGS=()
while [[ $# -gt 0 ]] while [[ "${#}" -gt 0 ]]
do do
case $1 in case "${1}" in
--force-user-input ) FORCE_USER_INPUT=true ;; ( --force-user-input ) force_user_input=true ;;
-k ) ( * ) ARGS+=("${1}") ;;
ARGS+=($1)
;;
* ) ARGS+=($1) ;;
esac esac
shift 1 shift 1
done done
while read -k -t 0; do :; done; # flush user stdin while read -k -t 0; do :; done; # flush user stdin
case ${FORCE_USER_INPUT} in case "${force_user_input}" in
true ) ( true )
read ${PREARGS[@]} ${ARGS[@]} $@ </dev/tty read "${PREARGS[@]}" "${ARGS[@]}" "${@}" </dev/tty
;; ;;
false ) ( false )
[ -t 0 ] || ARGS=(-u 0 ${ARGS[@]}) [[ "${stdin_is_a_terminal}" == false ]] && ARGS=(-u 0 ${ARGS[@]})
read ${ARGS[@]} $@ read "${ARGS[@]}" "${@}"
;; ;;
esac esac
} }
utils.io.read-yn() { # yes/no read is suprisingly tricky utils.io.read-yn() { # yes/no read is suprisingly tricky
local FORCE_USER_INPUT=false local force_user_input=false
local USERPROMPT=() 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 do
case $1 in case "${1}" in
--force-user-input ) ( --force-user-input )
# overrides 'scwrypts -y' and stdin pipe but not CI # overrides 'scwrypts -y' and stdin pipe but not CI
FORCE_USER_INPUT=true force_user_input=true
READ_ARGS+=($1) read_args+=($1)
;; ;;
* ) USERPROMPT+=($1) ;; * ) USERPROMPT+=($1) ;;
esac 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} ] \ ( false )
&& SKIP_USER_INPUT=true [[ "${force_user_input}" == false ]] && [[ "$(normalize.boolean __SCWRYPTS_YES)" == true ]] \
&& skip_user_input=true
[ ${__SCWRYPTS_YES} ] && [[ ${__SCWRYPTS_YES} -eq 1 ]] && [[ ${FORCE_USER_INPUT} =~ false ]] \ ;;
&& SKIP_USER_INPUT=true esac
########################################## ##########################################
local yn local yn
echo.prompt "${USERPROMPT[@]}" echo.prompt "${USERPROMPT[@]}"
case ${SKIP_USER_INPUT} in case "${skip_user_input}" in
true ) yn=y ;; ( true ) yn=y ;;
false ) ( false ) utils.io.read "${read_args[@]}" -s -k yn ;;
utils.io.read ${READ_ARGS[@]} -s -k yn
;;
esac esac
[[ ${SCWRYPTS_LOG_LEVEL} -ge 1 ]] && echo ${yn} >&2 echo.prompt --only-response --response "${yn}"
echo ${yn} echo "${yn}"
} }
+94
View File
@@ -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
}
+7
View File
@@ -1,14 +1,21 @@
##################################################################### #####################################################################
utils.fail() { # displays a crash error and exit 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} echo.error --force-print ${@:2}
exit $1 exit $1
} }
utils.abort() { # for consistency; use after user aborts REQUIRED input 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' 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 utils.check-errors() { # returns an error and reports usage if 'echo.error' was ever called
+4 -4
View File
@@ -68,16 +68,16 @@ utils.parse.autosetup() {
do do
command -v ${P}.locals &>/dev/null || continue command -v ${P}.locals &>/dev/null || continue
which ${P}.locals | sed '1d; $d;' which ${P}.locals | sed '1d; $d;'
done | sed 's|$| >/dev/null|'
[[ ${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'
usage.reset usage.reset
echo "local USAGE__description=$(printf "%q " "${DESCRIPTION}")" echo "local USAGE__description=$(printf "%q " "${DESCRIPTION}")"
echo "local USAGE__usage=${funcstack[2]}" echo "local USAGE__usage=${funcstack[2]}"
echo "local ERRORS=0" echo "local ERRORS=0"
echo "local WARNINGS=0"
echo "local DEBUG_ARGS=()"
echo "local TRACE_ARGS=()"
echo "local POSITIONAL_ARGS=0" echo "local POSITIONAL_ARGS=0"
echo ' echo '
+1 -1
View File
@@ -76,7 +76,7 @@ utils.parse() {
for PARSER in ${VALID_PARSERS[@]} for PARSER in ${VALID_PARSERS[@]}
do do
command -v ${PARSER}.usage &>/dev/null && ${PARSER}.usage command -v ${PARSER}.usage &>/dev/null && ${PARSER}.usage &>/dev/null
done done
local EARLY_ESCAPE_CODE _S ERRORS=0 POSITIONAL_ARGS=0 local EARLY_ESCAPE_CODE _S ERRORS=0 POSITIONAL_ARGS=0
+252
View File
@@ -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
+63
View File
@@ -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[@]}'"
}
+72
View File
@@ -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
+13
View File
@@ -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