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

Subscwrypts + Environment Inheritance

--- Release Notes ------------------------

- added support for environment inheritance
- added support for arbitrarily nested scripts (subscwrypts)
- added support for CI mode

- improved modularity of zsh/utils module

- refactored to move some data from ~/.config/scwrypts to ~/.local/share/scwrypts

- refactored various scripts to use new subscwrypt api

--- New Scripts --------------------------

zsh )
  - db/interactive/postgres
  - aws/rds/interactive-login
This commit is contained in:
Wryn (yage) Wagner 2022-06-22 12:17:19 -06:00
parent 2dcf94199b
commit eaefc99774
34 changed files with 770 additions and 251 deletions

38
.config
View File

@ -1,14 +1,32 @@
# #####################################################################
# scwrypts config
#
# resource paths
SCWRYPTS_CONFIG_PATH="$HOME/.config/scwrypts" SCWRYPTS_CONFIG_PATH="$HOME/.config/scwrypts"
SCWRYPTS_ENV_PATH="$SCWRYPTS_CONFIG_PATH/env" SCWRYPTS_DATA_PATH="$HOME/.local/share/scwrypts"
SCWRYPTS_LOG_PATH="$SCWRYPTS_CONFIG_PATH/logs"
SCWRYPTS_OUTPUT_PATH="$HOME/SCWRYPTS" SCWRYPTS_SHORTCUT='' # CTRL + SPACE
# ZLE hotkeys
SCWRYPTS_SHORTCUT='' # CTRL + W
SCWRYPTS_ENV_SHORTCUT='' # CTRL + / SCWRYPTS_ENV_SHORTCUT='' # CTRL + /
#####################################################################
SCWRYPTS_ENV_PATH="$SCWRYPTS_CONFIG_PATH/env"
SCWRYPTS_LOG_PATH="$SCWRYPTS_DATA_PATH/logs"
SCWRYPTS_OUTPUT_PATH="$SCWRYPTS_DATA_PATH/output"
SCWRYPTS_VIRTUALENV_PATH="$SCWRYPTS_DATA_PATH/virtualenv"
[ -f $SCWRYPTS_CONFIG_PATH/config ] && source $SCWRYPTS_CONFIG_PATH/config
#####################################################################
[ ! -d $SCWRYPTS_CONFIG_PATH ] && mkdir -p $SCWRYPTS_CONFIG_PATH
[ ! -d $SCWRYPTS_DATA_PATH ] && mkdir -p $SCWRYPTS_DATA_PATH
[ ! -d $SCWRYPTS_ENV_PATH ] && mkdir -p $SCWRYPTS_ENV_PATH
[ ! -d $SCWRYPTS_LOG_PATH ] && mkdir -p $SCWRYPTS_LOG_PATH
[ ! -d $SCWRYPTS_OUTPUT_PATH ] && mkdir -p $SCWRYPTS_OUTPUT_PATH
[ ! -d $SCWRYPTS_VIRTUALENV_PATH ] && mkdir -p $SCWRYPTS_VIRTUALENV_PATH
#####################################################################
true

1
.gitattributes vendored
View File

@ -1 +1,2 @@
*.zsh diff *.zsh diff
.config diff

View File

@ -28,12 +28,9 @@ Check out [Meta Scwrypts](./zsh/scwrypts) to quickly set up environments and adj
### No Install / API Usage ### No Install / API Usage
Alternatively, the `scwrypts` API can be used directly: Alternatively, the `scwrypts` API can be used directly:
```zsh ```zsh
./scwrypts (environment-name) (...script-patterns) ./scwrypts [--env environment-name] (...script-name-patterns...) [-- ...passthrough arguments... ]
``` ```
If not already set with `$SCWRYPTS_ENV`, Scwrypts will try to load `$1` as an environment.
If no environment with the name `$1` is found, `$1` is assumed to be a script pattern.
Given one or more script patterns, Scwrypts will filter the commands by pattern conjunction. Given one or more script patterns, Scwrypts will filter the commands by pattern conjunction.
If only one command is found which matches the pattern(s), it will immediately begin execution. If only one command is found which matches the pattern(s), it will immediately begin execution.
If multiple commands match, the user will be prompted to select from the filtered list. If multiple commands match, the user will be prompted to select from the filtered list.
@ -44,6 +41,16 @@ Given no script patterns, Scwrypts becomes an interactive CLI, prompting the use
After determining which script to run, if no environment has been specified, Scwrypts prompts the user to choose one. After determining which script to run, if no environment has been specified, Scwrypts prompts the user to choose one.
### Using in CI/CD or Automated Workflows
Set environment variable `CI=true` (and use the no install method) to run in an automated pipeline.
There are a few notable changes to this runtime:
- **The Scwrypts sandbox environment will not load.** All variables will be read from context.
- The underscore-prefixed `_AWS_(PROFILE|REGION|ACCOUNT)` variables will be read from the standard `AWS_` variables
- User yes/no prompts will **always be YES**
- Other user input will default to an empty string
- Logs will not be captured
## Contributing ## Contributing
Before contributing an issue, idea, or pull request, check out the [super-brief contributing guide](./docs/CONTRIBUTING.md) Before contributing an issue, idea, or pull request, check out the [super-brief contributing guide](./docs/CONTRIBUTING.md)

1
py/.gitignore vendored
View File

@ -1,4 +1,3 @@
__pycache__/ __pycache__/
*.py[cod] *.py[cod]
*.so *.so
.env/

View File

@ -1,16 +1,15 @@
#!/usr/bin/env python #!/usr/bin/env python
from os import getenv
from py.redis.client import Client from py.redis.client import Client
from py.scwrypts import interactive from py.scwrypts import interactive, getenv
@interactive @interactive
def main(): def main():
r = Client r = Client
print(''' print(f'''
r = StrictRedis("{getenv("REDIS_HOST")}") >>> r = StrictRedis({getenv("REDIS_HOST")}:{getenv("REDIS_PORT")})
''') ''')
return locals() return locals()

View File

@ -1,2 +1,3 @@
from py.scwrypts.getenv import getenv from py.scwrypts.getenv import getenv
from py.scwrypts.interactive import interactive from py.scwrypts.interactive import interactive
from py.scwrypts.run import run

View File

@ -1,23 +1,16 @@
from os import getenv as os_getenv from os import getenv as os_getenv
from pathlib import Path
from subprocess import run
from py.scwrypts.exceptions import MissingVariableError from py.scwrypts.exceptions import MissingVariableError
from py.scwrypts.run import run
def getenv(name, required=True): def getenv(name, required=True):
value = os_getenv(name, None) value = os_getenv(name, None)
if value == None: if value == None:
ZSH_COMMAND = Path(__file__).parents[2] / 'zsh/scwrypts/environment/stage-variables' run('zsh/scwrypts/environment/stage-variables', name)
run( if required and not value:
f'{ZSH_COMMAND} {name}', raise MissingVariableError(name)
shell=True,
executable='/bin/zsh',
)
if required:
raise MissingVariableError(name)
return value return value

View File

@ -3,7 +3,9 @@ from bpython import embed
def interactive(function): def interactive(function):
def main(*args, **kwargs): def main(*args, **kwargs):
print('preparing interactive environment...')
local_vars = function(*args, **kwargs) local_vars = function(*args, **kwargs)
print('environment ready; user, GO! :)')
embed(local_vars) embed(local_vars)
return main return main

17
py/scwrypts/run.py Normal file
View File

@ -0,0 +1,17 @@
from os import getenv
from pathlib import Path
from subprocess import run as subprocess_run
def run(scwrypt_name, *args):
DEPTH = int(getenv('SUBSCWRYPT', '0'))
DEPTH += 1
print(f'\n {"--"*DEPTH} ({DEPTH}) BEGIN SUBSCWRYPT : {Path(scwrypt_name).name}')
subprocess_run(
f'SUBSCWRYPT={DEPTH} {Path(__file__).parents[2] / "scwrypts"} {scwrypt_name} -- {" ".join([str(x) for x in args])}',
shell=True,
executable='/bin/zsh',
)
print(f' {"--"*DEPTH} ({DEPTH}) END SUBSCWRYPT : {Path(scwrypt_name).name}\n')

244
scwrypts
View File

@ -1,32 +1,117 @@
#!/bin/zsh #!/bin/zsh
SCWRYPTS_ROOT="${0:a:h}" SCWRYPTS_ROOT="${0:a:h}"
source "$SCWRYPTS_ROOT/zsh/common.zsh" source "$SCWRYPTS_ROOT/zsh/common.zsh" || exit 42
##################################################################### #####################################################################
__RUN() { __RUN() {
cd "$SCWRYPTS_ROOT" cd "$SCWRYPTS_ROOT"
########################################## local ENV_NAME="$SCWRYPTS_ENV"
### parse arguments ###################### local SEARCH_PATTERNS=()
while [[ $# -gt 0 ]]
do
case $1 in
-e|--env )
[ $ENV_NAME ] && __WARNING 'overwriting session environment'
ENV_NAME="$2"
__STATUS "using CLI environment '$ENV_NAME'"
shift 2
;;
-- )
shift 1
break # pass arguments after '--' to the scwrypt
;;
* )
SEARCH_PATTERNS+=$1
shift 1
;;
esac
done
########################################## ##########################################
local ENV_NAME local SCRIPT=$(__SELECT_SCRIPT $SEARCH_PATTERNS)
[ $SCWRYPTS_ENV ] && ENV_NAME="$SCWRYPTS_ENV" || { [ ! $SCRIPT ] && exit 2
[ $1 ] && [ -f $(__GET_ENV_FILE $1) ] && {
ENV_NAME="$1" local ENV_REQUIRED=$(__CHECK_ENV_REQUIRED && echo 1 || echo 0)
shift 1
} [[ $ENV_REQUIRED -eq 1 ]] && {
[ ! $ENV_NAME ] && ENV_NAME=$(__SELECT_ENV)
local ENV_FILE=$(__GET_ENV_FILE $ENV_NAME)
[ -f "$ENV_FILE" ] && source "$ENV_FILE" \
|| __FAIL 5 "missing or invalid environment '$ENV_NAME'"
export ENV_NAME
} }
[ ! $SUBSCWRYPT ] \
&& [[ $ENV_NAME =~ prod ]] \
&& { __VALIDATE_UPSTREAM_TIMELINE || __ABORT; }
local RUN_STRING=$(__GET_RUN_STRING $SCRIPT $ENV_NAME)
[ ! $RUN_STRING ] && exit 3
[ -f $_VIRTUALENV ] && source $_VIRTUALENV
##########################################
local LOGFILE=$(__GET_LOGFILE $SCRIPT)
local HEADER=$(
[ $SUBSCWRYPT ] && return 0
echo '====================================================================='
echo "script : $SCRIPT"
echo "run at : $(date)"
echo "config : $ENV_NAME"
[ ! $LOGFILE ] && echo '\033[1;33m------------------------------------------\033[0m'
)
[ ! $LOGFILE ] && {
[ $HEADER ] && echo $HEADER
eval $RUN_STRING $@ </dev/tty >/dev/tty 2>&1
exit $?
}
{
[ $HEADER ] && echo $HEADER
echo '\033[1;33m--- BEGIN OUTPUT -------------------------\033[0m'
eval $RUN_STRING $@
EXIT_CODE=$?
echo '\033[1;33m--- END OUTPUT ---------------------------\033[0m'
[[ $EXIT_CODE -eq 0 ]] && EXIT_COLOR='32m' || EXIT_COLOR='31m'
echo "terminated with\\033[1;$EXIT_COLOR code $EXIT_CODE\\033[0m"
} 2>&1 | tee --append "$LOGFILE"
exit $(\
sed -n 's/^terminated with.*code \([0-9]*\).*$/\1/p' $LOGFILE \
| tail -n1
)
}
#####################################################################
__SELECT_SCRIPT() {
local SCRIPT local SCRIPT
local SCRIPTS=$(__GET_AVAILABLE_SCRIPTS) local SCRIPTS=$(__GET_AVAILABLE_SCRIPTS)
local SEARCH=($@)
[ $1 ] && { [[ ${#SEARCH[@]} -eq 0 ]] && {
for PATTERN in $* SCRIPT=$(echo $SCRIPTS | __FZF 'select a script')
}
[[ ${#SEARCH[@]} -eq 1 ]] && [ -f ./$SEARCH ] && {
SCRIPT=$SEARCH
}
[ ! $SCRIPT ] && [[ ${#SEARCH[@]} -gt 0 ]] && {
SCRIPT=$SCRIPTS
for PATTERN in $SEARCH
do do
shift 1 SCRIPT=$(echo $SCRIPT | grep $PATTERN)
[[ $PATTERN =~ ^--$ ]] && break
SCRIPT=$(echo $SCRIPTS | grep $PATTERN)
done done
[ ! $SCRIPT ] && __FAIL 2 "no script found by name '$@'" [ ! $SCRIPT ] && __FAIL 2 "no script found by name '$@'"
@ -35,108 +120,91 @@ __RUN() {
__STATUS "more than one script matched '$@'" __STATUS "more than one script matched '$@'"
SCRIPT=$(echo $SCRIPT | __FZF 'select a script') SCRIPT=$(echo $SCRIPT | __FZF 'select a script')
} }
true }
} || SCRIPT=$(echo $SCRIPTS | __FZF 'select a script')
[ ! $SCRIPT ] && exit 2 echo $SCRIPT
}
########################################## __GET_RUN_STRING() {
### check type and min dependencies ###### local SCRIPT="$1"
########################################## local ENV_NAME="$2"
local ENV_REQUIRED=1
local RUN_STRING="./$SCRIPT"
local TYPE=$(echo $SCRIPT | sed 's/\/.*$//') local TYPE=$(echo $SCRIPT | sed 's/\/.*$//')
local VIRTUALENV="$SCWRYPTS_ROOT/$TYPE/.env/bin/activate" local RUN_STRING
[ -f $VIRTUALENV ] && source $VIRTUALENV
local _VIRTUALENV="$SCWRYPTS_VIRTUALENV_PATH/$TYPE/bin/activate"
[ -f $_VIRTUALENV ] && source $_VIRTUALENV
case $TYPE in case $TYPE in
py ) __CHECK_DEPENDENCY python || exit 3 py ) __CHECK_DEPENDENCY python || return 1
RUN_STRING="python -m $(echo $SCRIPT | sed 's/\//./g; s/\.py$//; s/\.\.//')"
python --version | grep -q '3.[91]' || { CURRENT_PYTHON_VERSION=$(python --version | sed 's/^[^0-9]*\(3\.[^.]*\).*$/\1/')
__WARNING 'only tested on python>=3.9'
echo $__PREFERRED_PYTHON_VERSIONS | grep -q $CURRENT_PYTHON_VERSION || {
__WARNING "only tested on the following python versions: $(printf ', %s.x' ${__PREFERRED_PYTHON_VERSIONS[@]} | sed 's/^, //')"
__WARNING 'compatibility may vary' __WARNING 'compatibility may vary'
} }
RUN_STRING="python -m $(echo $SCRIPT | sed 's/\//./g; s/\.py$//; s/\.\.//')"
;; ;;
zsh ) __CHECK_DEPENDENCY zsh || exit 3 zsh ) __CHECK_DEPENDENCY zsh || return 1
echo $SCRIPT | grep -q 'scwrypts' && ENV_REQUIRED=0
RUN_STRING="./$SCRIPT" RUN_STRING="./$SCRIPT"
;; ;;
zx ) __CHECK_DEPENDENCY zx || exit 3 zx ) __CHECK_DEPENDENCY zx || return 1
RUN_STRING="FORCE_COLOR=3 ./$SCRIPT.mjs" RUN_STRING="FORCE_COLOR=3 ./$SCRIPT.mjs"
;; ;;
* ) __FAIL 4 "unsupported script type '$SCRIPT_TYPE'" ;; * ) __ERROR "unsupported script type '$SCRIPT_TYPE'"
return 2
;;
esac esac
########################################## RUN_STRING="SCWRYPTS_ENV='$ENV_NAME' $RUN_STRING"
### load scwrypts env and virtualenv ##### [ -f $_VIRTUALENV ] && RUN_STRING="source '$_VIRTUALENV'; $RUN_STRING"
##########################################
[[ $ENV_REQUIRED -eq 1 ]] && { echo $RUN_STRING
[ ! $ENV_NAME ] && ENV_NAME=$(__SELECT_ENV) }
[ ! $ENV_NAME ] && __ABORT
local ENV_FILE=$(__GET_ENV_FILE $ENV_NAME) __CHECK_ENV_REQUIRED() {
[ $CI ] && return 1
[ -f "$ENV_FILE" ] \ echo $SCRIPT | grep -q 'zsh/scwrypts/logs' && return 1
&& source "$ENV_FILE" \
&& export ENV_NAME \
|| __FAIL 5 "missing or invalid environment '$ENV_NAME'"
[[ $ENV_NAME =~ prod ]] && { return 0
__STATUS "on '$ENV_NAME'; checking diff against origin/main" }
git fetch --quiet origin main \ __VALIDATE_UPSTREAM_TIMELINE() {
&& git diff --exit-code origin/main -- . >&2 \ __STATUS "on '$ENV_NAME'; checking diff against origin/main"
&& __SUCCESS 'up-to-date with main!' \
|| { git fetch --quiet origin main
__WARNING local SYNC_STATUS=$?
__WARNING 'your branch differs from origin/main'
__WARNING 'in '$ENV_NAME', being out-of-sync with main may have BAD CONSEQUENCES' git diff --exit-code origin/main -- . >&2
__WARNING local DIFF_STATUS=$?
__yN 'continue?' || __ABORT
} [[ $SYNC_STATUS -eq 0 ]] && [[ $DIFF_STATUS -eq 0 ]] && {
} __SUCCESS 'up-to-date with origin/main'
} || {
__WARNING
[[ $SYNC_STATUS -ne 0 ]] && __WARNING 'unable to synchronize with origin/main'
[[ $DIFF_STATUS -ne 0 ]] && __WARNING 'your branch differs from origin/main (diff listed above)'
__WARNING
__yN 'continue?' || return 1
} }
}
########################################## __GET_LOGFILE() {
### run the scwrypt ###################### local SCRIPT="$1"
##########################################
local HEADER=$( [ $CI ] \
echo '=====================================================================' || [ $SUBSCWRYPT ] \
echo "script : $SCRIPT" || [[ $SCRIPT =~ scwrypts/logs ]] \
echo "run at : $(date)" || [[ $SCRIPT =~ interactive ]] \
echo "config : $ENV_NAME" && return 0
echo '------------------------------------------'
)
echo $SCRIPT | grep -q 'interactive' && { echo "$SCWRYPTS_LOG_PATH/$(echo $SCRIPT | sed 's/^\.\///; s/\//\%/g').log"
echo $HEADER
eval $RUN_STRING $@ </dev/tty >/dev/tty 2>&1; exit $?
}
local LOGFILE="$SCWRYPTS_LOG_PATH/$(echo $SCRIPT | sed 's/^\.\///; s/\//\%/g').log"
[[ $SCRIPT =~ scwrypts/logs ]] && LOGFILE=/dev/null
{
echo $HEADER
echo '--- BEGIN OUTPUT--------------------------'
eval $RUN_STRING $@; local EXIT_CODE="$?"
echo '--- END OUTPUT ---------------------------'
local C
[[ $EXIT_CODE -eq 0 ]] && C='32m' || C='31m';
echo "terminated with\\033[1;$C code $EXIT_CODE\\033[0m"
} 2>&1 | tee --append "$LOGFILE"
} }
##################################################################### #####################################################################

View File

@ -1,11 +1,6 @@
source ${0:a:h}/zsh/common.zsh DONT_EXIT=1 source ${0:a:h}/zsh/common.zsh
##################################################################### #####################################################################
[ ! $SCWRYPTS_SHORTCUT ] && {
export SCWRYPTS_SHORTCUT='' # CTRL + SPACE
}
__SCWRYPTS() { __SCWRYPTS() {
local SCRIPT=$(__GET_AVAILABLE_SCRIPTS | __FZF 'select a script') local SCRIPT=$(__GET_AVAILABLE_SCRIPTS | __FZF 'select a script')
zle clear-command-line zle clear-command-line
[ ! $SCRIPT ] && { zle accept-line; return 0; } [ ! $SCRIPT ] && { zle accept-line; return 0; }
@ -21,10 +16,6 @@ zle -N scwrypts __SCWRYPTS
bindkey $SCWRYPTS_SHORTCUT scwrypts bindkey $SCWRYPTS_SHORTCUT scwrypts
##################################################################### #####################################################################
[ ! $SCWRYPTS_ENV_SHORTCUT ] && {
export SCWRYPTS_ENV_SHORTCUT='' # CTRL + /
}
__SCWRYPTS_ENV() { __SCWRYPTS_ENV() {
local RESET='reset' local RESET='reset'
local SELECTED=$(\ local SELECTED=$(\

View File

@ -4,6 +4,7 @@
[![Generic Badge](https://img.shields.io/badge/junegunn-fzf-informational.svg)](https://github.com/junegunn/fzf) [![Generic Badge](https://img.shields.io/badge/junegunn-fzf-informational.svg)](https://github.com/junegunn/fzf)
[![Generic Badge](https://img.shields.io/badge/mikefarah-yq-informational.svg)](https://github.com/mikefarah/yq) [![Generic Badge](https://img.shields.io/badge/mikefarah-yq-informational.svg)](https://github.com/mikefarah/yq)
[![Generic Badge](https://img.shields.io/badge/stedolan-jq-informational.svg)](https://github.com/stedolan/jq) [![Generic Badge](https://img.shields.io/badge/stedolan-jq-informational.svg)](https://github.com/stedolan/jq)
[![Generic Badge](https://img.shields.io/badge/dbcli-pgcli-informational.svg)](https://github.com/dbcli/pgcli)
<br> <br>
Since they emulate direct user interaction, shell scripts are often the straightforward choice for task automation. Since they emulate direct user interaction, shell scripts are often the straightforward choice for task automation.

View File

@ -1,6 +1,22 @@
_DEPENDENCIES+=( _DEPENDENCIES+=()
psql
)
_REQUIRED_ENV+=() _REQUIRED_ENV+=()
source ${0:a:h}/../common.zsh source ${0:a:h}/../common.zsh
##################################################################### #####################################################################
__SELECT_CONNECTOR() {
local DB_TYPE="$1"
CLIENTS_postgresql=(pgcli psql)
local C CLIENT=none
for C in $(eval 'echo $CLIENTS_'$DB_TYPE)
do
__CHECK_DEPENDENCY $C >/dev/null 2>&1 && {
CLIENT=$C
__STATUS "detected '$CLIENT' for $DB_TYPE"
break
}
done
echo $CLIENT
}

121
zsh/aws/rds/interactive-login Executable file
View File

@ -0,0 +1,121 @@
#!/bin/zsh
_DEPENDENCIES+=()
_REQUIRED_ENV+=()
source ${0:a:h}/common.zsh
#####################################################################
__CONNECT_TO_RDS() {
local DATABASE=$(__SELECT_DATABASE)
[ ! $DATABASE ] && __ABORT
local DB_HOST DB_USER DB_PORT DB_NAME DB_AUTH DB_TYPE
DB_HOST=$(echo $DATABASE | jq -r '.host')
DB_USER=$(echo $DATABASE | jq -r '.user')
DB_PORT=$(echo $DATABASE | jq -r '.port')
DB_TYPE=$(echo $DATABASE | jq -r '.type')
[[ $DB_PORT =~ null ]] && DB_PORT=5432
DB_NAME=postgres
local AUTH_METHODS=(iam secretsmanager user-input)
local AUTH_METHOD=$(\
echo $AUTH_METHODS | sed 's/\s\+/\n/g' \
| __FZF 'select an authentication method' \
)
[ ! $AUTH_METHOD ] && __ABORT
case $AUTH_METHOD in
iam )
DB_AUTH=$(\
_AWS rds generate-db-auth-token \
--hostname $DB_HOST \
--port $DB_PORT \
--username $DB_USER \
)
;;
secretsmanager )
CREDENTIALS=$(__GET_SECRETSMANAGER_CREDENTIALS)
echo $CREDENTIALS | jq -e '.pass' >/dev/null 2>&1 \
&& DB_AUTH=$(echo $CREDENTIALS | jq -r '.pass')
echo $CREDENTIALS | jq -e '.password' >/dev/null 2>&1 \
&& DB_AUTH=$(echo $CREDENTIALS | jq -r '.password')
echo $CREDENTIALS | jq -e '.user' >/dev/null 2>&1 \
&& DB_USER=$(echo $CREDENTIALS | jq -r '.user')
echo $CREDENTIALS | jq -e '.username' >/dev/null 2>&1 \
&& DB_USER=$(echo $CREDENTIALS | jq -r '.username')
echo $CREDENTIALS | jq -e '.name' >/dev/null 2>&1 \
&& DB_NAME=$(echo $CREDENTIALS | jq -r '.name')
echo $CREDENTIALS | jq -e '.dbname' >/dev/null 2>&1 \
&& DB_NAME=$(echo $CREDENTIALS | jq -r '.dbname')
;;
user-input )
;;
esac
__STATUS
__STATUS "host : $DB_HOST"
__STATUS "type : $DB_TYPE"
__STATUS "port : $DB_PORT"
__STATUS "database : $DB_NAME"
__STATUS "username : $DB_USER"
__STATUS
__RUN_SCWRYPT 'zsh/db/interactive/postgres' -- \
--host $DB_HOST \
--port $DB_PORT \
--name $DB_NAME \
--user $DB_USER \
--pass $DB_AUTH \
;
}
__SELECT_DATABASE() {
local DATABASES=$(__GET_AVAILABLE_DATABASES)
[ ! $DATABASES ] && __FAIL 1 'no databases available'
local ID=$(\
echo $DATABASES | jq -r '.instance + " @ " + .cluster' \
| __FZF 'select a database (instance@cluster)' \
)
[ ! $ID ] && __ABORT
local INSTANCE=$(echo $ID | sed 's/ @ .*$//')
local CLUSTER=$(echo $ID | sed 's/^.* @ //')
echo $DATABASES | jq "select (.instance == \"$INSTANCE\" and .cluster == \"$CLUSTER\")"
}
__GET_AVAILABLE_DATABASES() {
_AWS rds describe-db-instances \
| jq -r '.[] | .[] | {
instance: .DBInstanceIdentifier,
cluster: .DBClusterIdentifier,
type: .Engine,
host: .Endpoint.Address,
port: .Endpoint.Port,
user: .MasterUsername,
database: .DBName
}'
}
__GET_SECRETSMANAGER_CREDENTIALS() {
local ID=$(\
_AWS secretsmanager list-secrets \
| jq -r '.[] | .[] | .Name' \
| __FZF 'select a secret' \
)
[ ! $ID ] && return 1
_AWS secretsmanager get-secret-value --secret-id "$ID" \
| jq -r '.SecretString' | jq
}
#####################################################################
__CONNECT_TO_RDS

View File

@ -2,25 +2,20 @@
[ ! $SCWRYPTS_ROOT ] && SCWRYPTS_ROOT="$(dirname ${0:a:h})" [ ! $SCWRYPTS_ROOT ] && SCWRYPTS_ROOT="$(dirname ${0:a:h})"
source $SCWRYPTS_ROOT/.config
[ -f $SCWRYPTS_CONFIG_PATH/config ] && source $SCWRYPTS_CONFIG_PATH/config
[ ! -d $SCWRYPTS_CONFIG_PATH ] && mkdir -p $SCWRYPTS_CONFIG_PATH
[ ! -d $SCWRYPTS_ENV_PATH ] && mkdir -p $SCWRYPTS_ENV_PATH
[ ! -d $SCWRYPTS_LOG_PATH ] && mkdir -p $SCWRYPTS_LOG_PATH
__PREFERRED_PYTHON_VERSIONS=(3.10 3.9) __PREFERRED_PYTHON_VERSIONS=(3.10 3.9)
__NODE_VERSION=18.0.0 __NODE_VERSION=18.0.0
__ENV_TEMPLATE=$SCWRYPTS_ROOT/.env.template
__SCWRYPT=1
source $SCWRYPTS_ROOT/.config
source ${0:a:h}/utils/utils.module.zsh || {
[ $DONT_EXIT ] && return 1 || exit 1
}
##################################################################### #####################################################################
source ${0:a:h}/utils/utils.zsh __GET_ENV_FILES() { find $SCWRYPTS_CONFIG_PATH/env -maxdepth 1 -type f | sort -r }
#####################################################################
__ENV_TEMPLATE=$SCWRYPTS_ROOT/.template.env
__GET_ENV_FILES() { find $SCWRYPTS_CONFIG_PATH/env -maxdepth 1 -type f; }
[ ! "$(__GET_ENV_FILES)" ] && { [ ! "$(__GET_ENV_FILES)" ] && {
cp $__ENV_TEMPLATE "$SCWRYPTS_CONFIG_PATH/env/dev" cp $__ENV_TEMPLATE "$SCWRYPTS_CONFIG_PATH/env/dev"
cp $__ENV_TEMPLATE "$SCWRYPTS_CONFIG_PATH/env/local" cp $__ENV_TEMPLATE "$SCWRYPTS_CONFIG_PATH/env/local"
@ -42,3 +37,22 @@ __GET_AVAILABLE_SCRIPTS() {
| sed 's/^\.\///; s/\.[^.]*$//' \ | sed 's/^\.\///; s/\.[^.]*$//' \
; ;
} }
#####################################################################
__RUN_SCWRYPT() {
# run a scwrypt inside a scwrypt w/stack-depth indicators
((SUBSCWRYPT+=1))
printf ' '; printf '--%.0s' {1..$SUBSCWRYPT}; printf " ($SUBSCWRYPT) "
echo " BEGIN SUBSCWRYPT : $(basename $1)"
SUBSCWRYPT=$SUBSCWRYPT SCWRYPTS_ENV=$ENV_NAME \
"$SCWRYPTS_ROOT/scwrypts" $@
EXIT_CODE=$?
printf ' '; printf '--%.0s' {1..$SUBSCWRYPT}; printf " ($SUBSCWRYPT) "
echo " END SUBSCWRYPT : $(basename $1)"
((SUBSCWRYPT-=1))
return $EXIT_CODE
}

4
zsh/db/common.zsh Normal file
View File

@ -0,0 +1,4 @@
_DEPENDENCIES+=()
_REQUIRED_ENV+=()
source ${0:a:h}/../common.zsh
#####################################################################

View File

@ -0,0 +1,4 @@
_DEPENDENCIES+=()
_REQUIRED_ENV+=()
source ${0:a:h}/../common.zsh
#####################################################################

45
zsh/db/interactive/postgres Executable file
View File

@ -0,0 +1,45 @@
#!/bin/zsh
_DEPENDENCIES+=(
pgcli
)
_REQUIRED_ENV+=()
source ${0:a:h}/common.zsh
#####################################################################
_LOGIN_POSTGRES() {
local _HOST _NAME _PASS _PORT _USER
while [[ $# -gt 0 ]]
do
case $1 in
--host | -h ) _HOST="$2"; shift 2 ;;
--name | -d ) _NAME="$2"; shift 2 ;;
--pass | -w ) _PASS="$2"; shift 2 ;;
--port | -p ) _PORT="$2"; shift 2 ;;
--user | -U ) _USER="$2"; shift 2 ;;
* ) shift 1 ;;
esac
done
[ ! $_HOST ] && _HOST=127.0.0.1
[ ! $_NAME ] && _NAME=postgres
[ ! $_PORT ] && _PORT=5432
[ ! $_USER ] && _USER=postgres
local DATA_DIR="$SCWRYPTS_DATA_PATH/db/$_HOST"
[ ! -d $DATA_DIR ] && mkdir -p $DATA_DIR
cd $DATA_DIR
__STATUS "performing login : $_USER@$_HOST:$_PORT/$_NAME"
__STATUS "working directory : $DATA_DIR"
PGPASSWORD="$_PASS" pgcli \
--host $_HOST \
--port $_PORT \
--user $_USER \
--dbname $_NAME \
;
}
#####################################################################
_LOGIN_POSTGRES $@

View File

@ -15,12 +15,21 @@ This will immediately open your custom configuration file and reload any necessa
If you use Scwrypts, **you should use these commands all the time**. If you use Scwrypts, **you should use these commands all the time**.
This is your gateway to managing scwrypts sandboxed environments. This is your gateway to managing scwrypts sandboxed environments.
Command | Description Command | Description
------------- | --------------------------------------------------------------------------------------- ----------------- | ---------------------------------------------------------------------------------------
`edit` | edit an existing environment; synchronizes environments if new variables are added `edit` | edit an existing environment
`copy` | copy an existing environment to a new one `copy` | create and edit a new environment from an existing one
`delete` | permanently delete an environment by name `delete` | permanently delete an environment by name
`synchronize` | uses [template](../../.template.env) to add missing and remove extemporaneous variables `stage-variables` | stage missing variables; [helpful for non-ZSH scwrypts](../../py/scwrypts/getenv.py)
`synchronize` | uses [template](../../.env.template) to add missing and remove extemporaneous variables
### Environment Inheritance
You can make a child environment by naming an environment `<parent-name>.<child-name>`.
Children inherit all parent-set values, and **parent-set values overwrite child-set values**.
Remember that synchronize runs *every time you edit an environment*, so changes propagate to children immediately.
Inherited values are denoted by `# inherited from <parent-name>` in the environment file.
Nested children will inherit values from all parents.
## Logs ## Logs
Quickly view or clear Scwrypts logs. Quickly view or clear Scwrypts logs.

View File

@ -6,12 +6,20 @@ source ${0:a:h}/common.zsh
[ ! -f $SCWRYPTS_CONFIG_PATH/config ] && { [ ! -f $SCWRYPTS_CONFIG_PATH/config ] && {
__STATUS 'first-time setup detected; creating local configuration override...' __STATUS 'first-time setup detected; creating local configuration override...'
cp $SCWRYPTS_ROOT/.config $SCWRYPTS_CONFIG_PATH/config \ touch $SCWRYPTS_CONFIG_PATH/config \
&& __SUCCESS 'created!' \ && __SUCCESS 'created!' \
|| __FAIL 1 "unable to create config at '$SCWRYPTS_CONFIG_PATH/config'" || __FAIL 1 "unable to create config at '$SCWRYPTS_CONFIG_PATH/config'"
{
echo '#'
echo '# configuration for scwrypts'
echo '#'
sed -n '1d; /^###/q; p' $SCWRYPTS_ROOT/.config | sed '$d'
} > $SCWRYPTS_CONFIG_PATH/config
__EDIT $SCWRYPTS_CONFIG_PATH/config
__STATUS 'attempting to build virtual environments' __STATUS 'attempting to build virtual environments'
$SCWRYPTS_ROOT/zsh/scwrypts/virutalenv/update-all \ __RUN_SCWRYPT zsh/scwrypts/virtualenv/update-all \
&& __SUCCESS 'finished updating virtualenvs' \ && __SUCCESS 'finished updating virtualenvs' \
|| __WARNING 'unable to create one or more virtualenv (see above)' \ || __WARNING 'unable to create one or more virtualenv (see above)' \
; ;
@ -20,16 +28,12 @@ source ${0:a:h}/common.zsh
__REMINDER 'use "zsh/scwrypts/virtualenv/update-all" to update environments' __REMINDER 'use "zsh/scwrypts/virtualenv/update-all" to update environments'
__REMINDER '(equivalent to "npm install" or "pip install -r requirements.txt")' __REMINDER '(equivalent to "npm install" or "pip install -r requirements.txt")'
__REMINDER __REMINDER
} || {
__STATUS 'opening local config for editing'
__EDIT $SCWRYPTS_CONFIG_PATH/config
__STATUS 'finished editing!'
} }
__STATUS 'opening local config for editing'
__EDIT $SCWRYPTS_CONFIG_PATH/config
__STATUS 'finished editing!'
which __SCWRYPTS >/dev/null 2>&1 && {
__STATUS 'reloading configuration for current session'
source $SCWRYPTS_ROOT/zsh/common.zsh \
|| __FAIL 2 'unable to reload configuration :c'
}
__SUCCESS 'saved new configuration' __SUCCESS 'saved new configuration'
__REMINDER 'changes which affect the hot-key plugin will require a ZSHRC reload'

View File

@ -2,3 +2,10 @@ _DEPENDENCIES+=()
_REQUIRED_ENV+=() _REQUIRED_ENV+=()
source ${0:a:h}/../common.zsh source ${0:a:h}/../common.zsh
##################################################################### #####################################################################
_SORT_ENV() {
local ENV_FILE="$1"
sed -i "s/^[A-Z]/export &/; s/^[^#=]\\+$/&=/" "$ENV_FILE"
LC_COLLATE=C sort -uo "$ENV_FILE" "$ENV_FILE"
}

View File

@ -11,7 +11,7 @@ TEMPLATE_ENV_NAME=$(__SELECT_ENV)
__STATUS "selected '$TEMPLATE_ENV_NAME'" __STATUS "selected '$TEMPLATE_ENV_NAME'"
__PROMPT 'enter new environment name' __PROMPT 'enter new environment name'
ENV_NAME=$(__FZF_HEAD 'new environment') ENV_NAME=$(echo '' | __FZF_HEAD 'new environment')
[ ! $ENV_NAME ] && __ABORT [ ! $ENV_NAME ] && __ABORT
TEMPLATE_ENV_FILE=$(__GET_ENV_FILE $TEMPLATE_ENV_NAME) TEMPLATE_ENV_FILE=$(__GET_ENV_FILE $TEMPLATE_ENV_NAME)
@ -19,7 +19,19 @@ ENV_FILE=$(__GET_ENV_FILE $ENV_NAME)
[ -f "$ENV_FILE" ] && __FAIL 2 "'$ENV_NAME' already exists" [ -f "$ENV_FILE" ] && __FAIL 2 "'$ENV_NAME' already exists"
__STATUS "creating environment" __STATUS "creating environment '$ENV_NAME'"
cp "$TEMPLATE_ENV_FILE" "$ENV_FILE" \ cp "$TEMPLATE_ENV_FILE" "$ENV_FILE" \
&& __SUCCESS "created '$ENV_NAME'" \ && __SUCCESS "created '$ENV_NAME'" \
|| __FAIL 3 "unable to create '$ENV_NAME'" || __FAIL 3 "unable to create '$ENV_NAME'"
__STATUS 'stripping inherited values'
sed -i 's/ # inherited from.*$//' "$ENV_FILE" 2>/dev/null
__RUN_SCWRYPT zsh/scwrypts/environment/synchronize -- --no-prompt \
|| __FAIL 4 'failed to run environment sync'
__RUN_SCWRYPT zsh/scwrypts/environment/edit -- $ENV_NAME \
|| __FAIL 4 'failed to edit new environment'
;
__SUCCESS "finished copy environment '$TEMPLATE_ENV_NAME > $ENV_NAME'"

View File

@ -4,9 +4,13 @@ _REQUIRED_ENV+=()
source ${0:a:h}/common.zsh source ${0:a:h}/common.zsh
##################################################################### #####################################################################
[ $SCWRYPTS_ENV ] \ [ $1 ] && ENV_NAME="$1"
&& ENV_NAME=$SCWRYPTS_ENV \
|| ENV_NAME=$(__SELECT_OR_CREATE_ENV) [ ! $1 ] && {
[ $SCWRYPTS_ENV ] \
&& ENV_NAME=$SCWRYPTS_ENV \
|| ENV_NAME=$(__SELECT_OR_CREATE_ENV)
}
[ ! $ENV_NAME ] && __ABORT [ ! $ENV_NAME ] && __ABORT
ENV_FILE=$(__GET_ENV_FILE $ENV_NAME) ENV_FILE=$(__GET_ENV_FILE $ENV_NAME)
@ -33,18 +37,8 @@ do
} }
done < $ENV_FILE done < $ENV_FILE
[ $NEW_VAR ] && { __RUN_SCWRYPT zsh/scwrypts/environment/synchronize -- --no-prompt \
LC_COLLATE=C sort -uo $__ENV_TEMPLATE $__ENV_TEMPLATE || __FAIL 4 'failed to run environment sync' \
NOPROMPT=1 ${0:a:h}/synchronize ;
git add $__ENV_TEMPLATE \
&& __SUCCESS "auto-staged the $(basename $__ENV_TEMPLATE) changes" \
|| {
__WARNING "unable to stage $(basename $__ENV_TEMPLATE) changes"
__REMINDER "don't forget to commit changes to $(basename $__ENV_TEMPLATE)"
}
true
} || {
__STATUS 'no new environment variables'
}
__SUCCESS "environment '$ENV_NAME' successfully modified" __SUCCESS "environment '$ENV_NAME' successfully modified"

View File

@ -4,4 +4,4 @@ _REQUIRED_ENV+=()
source ${0:a:h}/common.zsh source ${0:a:h}/common.zsh
##################################################################### #####################################################################
__CHECK_ENV_VARS $@ || NOPROMPT=1 $SCWRYPTS_ROOT/zsh/scwrypts/environment/synchronize __CHECK_REQUIRED_ENV $@

View File

@ -1,47 +1,130 @@
#!/bin/zsh #!/bin/zsh
_DEPENDENCIES+=() _DEPENDENCIES+=()
_REQUIRED_ENV+=() _REQUIRED_ENV+=()
source ${0:a:h}/common.zsh source ${0:a:h}/common.zsh
##################################################################### #####################################################################
[ ! $NOPROMPT ] && { _SYNCHRONIZE() {
__yN 'change the template before sync?' && __EDIT $__ENV_TEMPLATE while [[ $# -gt 0 ]]
sed -i "s/^[A-Z]/export &/; s/^[^#=]\\+$/&=/; s/=.*$/=/" $__ENV_TEMPLATE do
LC_COLLATE=C sort -uo $__ENV_TEMPLATE $__ENV_TEMPLATE case $1 in
git add $__ENV_TEMPLATE >/dev/null 2>&1 --no-prompt ) SLIENT=1; shift 1 ;;
* ) __WARNING "argument '$1' not recognized"
shift 1 ;;
esac
done
[ ! $SLIENT ] && {
__yN 'change the template before sync?' && __EDIT $__ENV_TEMPLATE
_SORT_ENV "$__ENV_TEMPLATE"
git add $__ENV_TEMPLATE >/dev/null 2>&1
}
ENVIRONMENTS=$(__GET_ENV_NAMES | sort -r)
_CLEAR_INHERITED_VARIABLES
_INSERT_NEW_VARIABLES
_REMOVE_OLD_VARIABLES
_SORT_AND_CASCADE
__SUCCESS 'finished sync!'
} }
ENVIRONMENTS=$(__GET_ENV_FILES) #####################################################################
__STATUS 'inserting new environment variables...' _CLEAR_INHERITED_VARIABLES() {
while read line for ENV_NAME in $(echo $ENVIRONMENTS)
do do
for ENV_FILE in $(echo $ENVIRONMENTS) ENV_FILE=$(__GET_ENV_FILE $ENV_NAME)
do sed -i 's/ # inherited from.*//' "$ENV_FILE"
grep -q "^$line" $ENV_FILE || { done
echo $line >> $ENV_FILE && __STATUS "added '$line' to '$ENV_FILE'" }
}
done
done < <(sed -n '/^./p' $__ENV_TEMPLATE)
__STATUS 'removing old environment variables...' _INSERT_NEW_VARIABLES() {
for ENV_FILE in $(echo $ENVIRONMENTS) __STATUS 'inserting new environment variables...'
do
while read line
do
ENV_VAR=$(echo "$line" | sed 's/=.*/=/')
grep -q "$ENV_VAR" $__ENV_TEMPLATE || {
sed -i "\\%$ENV_VAR%d" $ENV_FILE
__WARNING "removed unwanted '$ENV_VAR' from '$ENV_FILE'"
}
done < $ENV_FILE
done
for ENV_FILE in $(echo $ENVIRONMENTS) local ENV_NAME ENV_FILE line
do while read line
sed -i "s/^[A-Z]/export &/; s/^[^#=]\\+$/&=/" $ENV_FILE do
LC_COLLATE=C sort -uo $ENV_FILE $ENV_FILE for ENV_NAME in $(echo $ENVIRONMENTS)
done do
LC_COLLATE=C sort -uo $__ENV_TEMPLATE $__ENV_TEMPLATE ENV_FILE=$(__GET_ENV_FILE $ENV_NAME)
grep -q "$line" $ENV_FILE || {
echo $line >> $ENV_FILE && __STATUS "added '$line' to '$ENV_NAME'"
}
done
done < <(sed -n '/^./p' "$__ENV_TEMPLATE")
}
__SUCCESS 'finished sync!' _REMOVE_OLD_VARIABLES() {
__STATUS 'removing old environment variables...'
local ENV_NAME ENV_FILE line
for ENV_NAME in $(echo $ENVIRONMENTS)
do
ENV_FILE=$(__GET_ENV_FILE $ENV_NAME)
while read line
do
ENV_VAR=$(echo "$line" | sed 's/=.*/=/')
grep -q "$ENV_VAR" "$__ENV_TEMPLATE" || {
sed -i "\\%$ENV_VAR%d" "$ENV_FILE"
echo "$ENV_VAR" | grep -qv '^#' \
&& __WARNING "removed unwanted '$ENV_VAR' from '$ENV_NAME'"
}
done < $ENV_FILE
done
}
_SORT_AND_CASCADE() {
local ENV_NAM ENV_FILE
for ENV_NAME in $(echo $ENVIRONMENTS)
do
ENV_FILE=$(__GET_ENV_FILE $ENV_NAME)
_CASCADE_ENVIRONMENT $ENV_NAME
done
for ENV_NAME in $(echo $ENVIRONMENTS)
do
ENV_FILE=$(__GET_ENV_FILE $ENV_NAME)
_SORT_ENV "$ENV_FILE"
done
}
_CASCADE_ENVIRONMENT() {
local PARENT_NAME="$1"
local PARENT_FILE=$(__GET_ENV_FILE $PARENT_NAME)
local CHILD_NAMES=$(echo $ENVIRONMENTS | grep "^$PARENT_NAME\\.")
[ ! $CHILD_NAMES ] && return 0
__STATUS "cascading '$PARENT_NAME' to children"
for CHILD_NAME in $(echo $CHILD_NAMES)
do
__SUCCESS "detected child '$CHILD_NAME'"
done
local PARENT_VAR VAR_PATTERN CHILD_NAME CHILD_FILE
while read PARENT_VAR
do
VAR_PATTERN=$(echo "$PARENT_VAR" | sed 's/=.*/=/; s/\//\/\//g')
__STATUS "propagating '$(echo $VAR_PATTERN | sed 's/^export \([^=]*\)=/\1/')' to children"
PARENT_VAR+=" # inherited from $PARENT_NAME"
for CHILD_NAME in $(echo $CHILD_NAMES)
do
CHILD_FILE=$(__GET_ENV_FILE $CHILD_NAME)
sed -i "/^$VAR_PATTERN/d" "$CHILD_FILE"
echo $PARENT_VAR >> "$CHILD_FILE"
done
done < <(sed -n '/^[^#][^=]*=[^#]\+$/p' "$PARENT_FILE")
__SUCCESS "finished '$PARENT_NAME' propagation"
}
#####################################################################
_SYNCHRONIZE $@

View File

@ -6,7 +6,7 @@ source ${0:a:h}/common.zsh
cd $SCWRYPTS_ROOT cd $SCWRYPTS_ROOT
__PROMPT 'select a script log' __PROMPT 'select a script log'
LOG_FILE=$(ls $SCWRYPTS_LOG_PATH | __FZF 'logfile') LOG_FILE=$(ls -t $SCWRYPTS_LOG_PATH | __FZF 'logfile')
[ ! $LOG_FILE ] && { __ERROR 'user abort'; exit 1; } [ ! $LOG_FILE ] && { __ERROR 'user abort'; exit 1; }
__STATUS 'opening logfile' __STATUS 'opening logfile'

View File

@ -42,11 +42,11 @@ __UPDATE_VIRTUALENV() {
return 1 return 1
} }
cd $VIRTUALENV_PATH/../ cd $SCWRYPTS_ROOT
local UPDATE_CODE=0 local UPDATE_CODE=0
case $TYPE in case $TYPE in
python ) pip install -r requirements.txt; UPDATE_CODE=$? ;; python ) cd py; pip install -r requirements.txt; UPDATE_CODE=$? ;;
node ) npm install ; node ) cd zx; npm install ;;
esac esac
UPDATE_CODE=$? UPDATE_CODE=$?
[[ $UPDATE_CODE -eq 0 ]] \ [[ $UPDATE_CODE -eq 0 ]] \
@ -78,8 +78,8 @@ __DELETE_VIRTUALENV() {
__GET_VIRTUALENV_PATH() { __GET_VIRTUALENV_PATH() {
local TYPE="$1" local TYPE="$1"
case $TYPE in case $TYPE in
python ) echo "$SCWRYPTS_ROOT/py/.env" ;; python ) echo "$SCWRYPTS_VIRTUALENV_PATH/py" ;;
node ) echo "$SCWRYPTS_ROOT/zx/.env" ;; node ) echo "$SCWRYPTS_VIRTUALENV_PATH/zx" ;;
esac esac
} }

71
zsh/utils/README.md Normal file
View File

@ -0,0 +1,71 @@
# ZSH Utilities
A shell-scripting utilities module made for ZSH.
This module is definitely a major component of Scwrypts, but is also standalone and can be sourced by any ZSH script to utilize (almost) all of the features.
## Usage
Import `utils.module.zsh` to activate all of the features.
Doing so will *also* check for path dependencies and required environment variables (see [Dependencies](#dependencies) and [Environment](#environment) below).
```shell
#!/bin/zsh
source ./path/to/utils.plugin.zsh
__SUCCESS 'ZSH utilities online!'
```
Checkout [io](./io.zsh) and [os](./os.zsh) for available simple functions.
### Dependencies
Ensures dependent programs are available for execution.
Specify a simple name to check the current `PATH`, or give a fully-qualified path for arbitrary dependency inclusion.
Include a dependency by adding to the `_DEPENDENCIES` array.
*Always using `+=` makes your dependencies extensible to other scripts :)*
If any dependencies are missing, `source utils.module.zsh` will return an error code and count the number of missing dependencies in the variable `DEP_ERROR_COUNT`.
```shell
#!/bin/zsh
_DEPENDENCIES+=(
path-executable-1
path-executable-2
/path/to/arbitrary/program
)
source ./path/to/utils.plugin.zsh
echo "missing $DEP_ERROR required dependencies"
```
### Environment
Similar to [Dependencies](#dependencies), `environment.zsh` ensures a list of environment variables are *set to non-empty values*.
Include an environment variable by adding to the `_REQUIRED_ENV` array.
*Something something use `+=` here too ;)*
If any environment variables are missing, `source utils.module.zsh` will return an error code and count the number of missing variables in `ENV_ERROR_COUNT`.
Missing environment variables will be added to the environment template (*exclusive to Scwrypts*).
```shell
#!/bin/zsh
_REQUIRED_ENV+=(
AWS_PROFILE
AWS_REGION
)
source ./path/to/utils.plugin.zsh
echo "missing $ENV_ERROR_COUNT required environment variables"
```
io.zsh
os.zsh
## Basic Utilities
One of my biggest pet-peeves with scripting is when every line of a *(insert-language-here)* program is escaped to shell.
This kind of program, which doesn't use language features, should be a shell script.
While there are definitely unavoidable limitations to shell scripting, we can minimize a variety of problems with a modern shell and shared utilities library.
Loaded by `common.zsh`, the [`utils/` library](./utils) provides:
- common function wrappers to unify flags and context
- lazy dependency and environment variable validation
- consistent (and pretty) user input / output

View File

@ -1,5 +1,5 @@
__CREDITS() { __CREDITS() {
# only applicable within scwrypts ("credits" pulled from README files) # scwrypts exclusive ("credits" pulled from README files)
[ ! $SCWRYPTS_ROOT ] && return 0 [ ! $SCWRYPTS_ROOT ] && return 0
local COMMAND="$1" local COMMAND="$1"

View File

@ -1,38 +1,37 @@
__CHECK_REQUIRED_ENV() { __CHECK_REQUIRED_ENV() {
local VAR ERROR=0 local VAR ERROR=0
for VAR in $*; do __CHECK_ENV_VAR $VAR_NAME || ((ERROR+=1)); done for VAR in $*; do __CHECK_ENV_VAR $VAR || ((ERROR+=1)); done
return $ERROR return $ERROR
} }
__CHECK_ENV_VAR() { __CHECK_ENV_VAR() {
local NAME="$1" local NAME="$1"
[ ! $NAME ] && return 1
local OPTIONAL="$2" local OPTIONAL="$2"
local DEFAULT_VALUE="$3" local DEFAULT_VALUE="$3"
local VALUE=$(eval echo '$'$NAME) local VALUE=$(eval echo '$'$NAME)
[ $VALUE ] && return 0 [ $VALUE ] && return 0
local LINE="export $NAME="
local TEMPLATE="$SCWRYPTS_ROOT/.template.env"
grep -q -- "^$LINE" "$TEMPLATE" || { [ $__SCWRYPT ] && {
__STATUS 'staging new variable in template' # scwrypts exclusive (missing vars staged in env.template)
local LINE="export $NAME="
echo "$LINE" >> "$TEMPLATE" \ grep -q -- "^$LINE" "$__ENV_TEMPLATE" || {
&& NOPROMPT=1 $SCWRYPTS_ROOT/zsh/scwrypts/environment/synchronize \ __STATUS 'staging new variable in template'
&& git add $TEMPLATE >/dev/null 2>&1 \
&& __SUCCESS "staged '$NAME'" \ echo "$LINE" >> "$__ENV_TEMPLATE" \
|| { && __RUN_SCWRYPT zsh/scwrypts/environment/synchronize -- --no-prompt
__WARNING "failed to stage '$NAME'" }
__REMINDER "add/commit '$NAME' to template manually"
}
} }
[ $OPTIONAL ] && { [ $OPTIONAL ] && {
__ERROR "'$NAME' required"
return 1
} || {
[ $DEFAULT_VALUE ] && $NAME="$DEFAULT_VALUE" [ $DEFAULT_VALUE ] && $NAME="$DEFAULT_VALUE"
return 0 return 0
} || {
__ERROR "'$NAME' required"
return 1
} }
} }

View File

@ -3,6 +3,7 @@ __SUCCESS() { echo "\\033[1;32mSUCCESS ✔ : $@\\033[0m" >&2; }
__WARNING() { echo "\\033[1;33mWARNING  : $@\\033[0m" >&2; } __WARNING() { echo "\\033[1;33mWARNING  : $@\\033[0m" >&2; }
__STATUS() { echo "\\033[1;34mSTATUS : $@\\033[0m" >&2; } __STATUS() { echo "\\033[1;34mSTATUS : $@\\033[0m" >&2; }
__REMINDER() { echo "\\033[1;35mREMINDER  : $@\\033[0m" >&2; } __REMINDER() { echo "\\033[1;35mREMINDER  : $@\\033[0m" >&2; }
__PROMPT() { __PROMPT() {
echo "\\033[1;36mPROMPT  : $@\\033[0m" >&2 echo "\\033[1;36mPROMPT  : $@\\033[0m" >&2
printf "\\033[1;36mUSER  : \\033[0m" >&2 printf "\\033[1;36mUSER  : \\033[0m" >&2
@ -10,12 +11,16 @@ __PROMPT() {
__Yn() { __Yn() {
__PROMPT "$@ [Yn]" __PROMPT "$@ [Yn]"
[ $CI ] && { echo y; return 0; }
local Yn; __READ -k Yn; echo local Yn; __READ -k Yn; echo
[[ $Yn =~ [nN] ]] && return 1 || return 0 [[ $Yn =~ [nN] ]] && return 1 || return 0
} }
__yN() { __yN() {
__PROMPT "$@ [yN]" __PROMPT "$@ [yN]"
[ $CI ] && { echo y; return 0; }
local yN; __READ -k yN; echo local yN; __READ -k yN; echo
[[ $yN =~ [yY] ]] && return 0 || return 1 [[ $yN =~ [yY] ]] && return 0 || return 1
} }
@ -35,10 +40,29 @@ __GETSUDO() {
__LESS() { less -R $@ </dev/tty >/dev/tty; } __LESS() { less -R $@ </dev/tty >/dev/tty; }
__FZF() { fzf -i --height=30% --layout=reverse --prompt "$@ : "; } __FZF() {
__FZF_HEAD() { fzf -i --height=30% --layout=reverse --print-query --prompt "$@ : " | head -n1; } [ $CI ] && {
__FZF_TAIL() { fzf -i --height=30% --layout=reverse --print-query --prompt "$@ : " | tail -n1; } __ERROR 'currently in CI, but __FZF requires user input'
exit 1
}
__READ() { read $@ </dev/tty; } fzf -i --height=30% --layout=reverse --prompt "$1 : " ${@:2}
}
__FZF_HEAD() { __FZF $@ --print-query | sed '/^$/d' | head -n1; } # prefer user input over selected
__FZF_TAIL() { __FZF $@ --print-query | sed '/^$/d' | tail -n1; } # prefer selected over user input
__EDIT() { $EDITOR $@ </dev/tty >/dev/tty; } __READ() {
[ $CI ] && {
__ERROR 'currently in CI, but __READ explicitly requires terminal input'
return 1
}
read $@ </dev/tty
}
__EDIT() {
[ $CI ] && {
__ERROR 'currently in CI, but __EDIT explicitly requires terminal input'
return 1
}
$EDITOR $@ </dev/tty >/dev/tty
}

View File

@ -13,17 +13,33 @@ source ${0:a:h}/credits.zsh
IMPORT_ERROR=0 IMPORT_ERROR=0
[ $CI ] && {
export _AWS_PROFILE="$AWS_PROFILE"
export _AWS_ACCOUNT="$AWS_ACCOUNT"
export _AWS_REGION="$AWS_REGION"
}
source ${0:a:h}/dependencies.zsh source ${0:a:h}/dependencies.zsh
_DEP_ERROR=0
_DEPENDENCIES=($(echo $_DEPENDENCIES | sort -u)) _DEPENDENCIES=($(echo $_DEPENDENCIES | sort -u))
__CHECK_DEPENDENCIES $_DEPENDENCIES || ((IMPORT_ERROR+=$?)) __CHECK_DEPENDENCIES $_DEPENDENCIES || _DEP_ERROR=$?
source ${0:a:h}/environment.zsh source ${0:a:h}/environment.zsh
_REQUIRED_ENV=($(echo $__CHECK_REQUIRED_ENV | sort -u)) _ENV_ERROR=0
__CHECK_REQUIRED_ENV $_REQUIRED_ENV || ((IMPORT_ERROR+=$?)) _REQUIRED_ENV=($(echo $_REQUIRED_ENV | sort -u))
__CHECK_REQUIRED_ENV $_REQUIRED_ENV || _ENV_ERROR=$?
[[ $IMPORT_ERROR -eq 0 ]] || { [[ $_ENV_ERROR -ne 0 ]] && {
__REMINDER 'to update missing environment variables, run:'
__REMINDER "'scwrypts zsh/scwrypts/environment/edit'"
}
((IMPORT_ERROR+=$_DEP_ERROR))
((IMPORT_ERROR+=$_ENV_ERROR))
[[ $IMPORT_ERROR -ne 0 ]] && {
__ERROR "encountered $IMPORT_ERROR import error(s)" __ERROR "encountered $IMPORT_ERROR import error(s)"
return 1
} }
##################################################################### #####################################################################
[[ $IMPORT_ERROR -eq 0 ]]

1
zx/.gitignore vendored
View File

@ -1,2 +1 @@
node_modules/ node_modules/
.env/