forked from jakimfett/fsh
456 lines
12 KiB
Bash
Executable File
456 lines
12 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# # DESCRIPTION #
|
|
# functions & utilities for Debian-ish Linux shell.
|
|
#
|
|
# # LEGAL #
|
|
# This is a dangerous toy, read carefully before using in your shop.
|
|
# Test it in a copy of development, with your use case, first.
|
|
#
|
|
# And for Zod's unwholly snake,
|
|
### VERIFY YOUR BACKUPS BEFORE USING ###
|
|
# ...please.
|
|
|
|
#
|
|
# shellharden & version minimum boilerplate:
|
|
|
|
if test "$BASH" = "" || "$BASH" -uc "a=();true \"\${a[@]}\"" 2>/dev/null; then
|
|
# Bash 4.4, Zsh
|
|
set -Eeuox pipefail
|
|
else
|
|
echo "this script helper requires bash v4.4+, your version is insufficient"
|
|
bash --version
|
|
exit 13
|
|
fi
|
|
|
|
# ???
|
|
shopt -s nullglob globstar
|
|
|
|
# This allows aliases to be used inside the program.
|
|
shopt -s expand_aliases
|
|
|
|
#
|
|
# # METHODOLOGY #
|
|
# Write for the user, optimize for the program, develop for automagic deployment.
|
|
#
|
|
# To my future self,
|
|
# This code is an effort,
|
|
# dedicated to you,
|
|
# and to debugging the crufty parts one function at a time
|
|
#
|
|
# like real programmers do.
|
|
#
|
|
# # @TODO - generate automatically #
|
|
#
|
|
# @todo call, fork, disown, end?
|
|
|
|
# Variable Factory
|
|
## AUTOGENERATED "VFACTORY" SECTION, EDIT IN `compile.sh` ###
|
|
#
|
|
# If directly sourced, initialize variables locally and setup alias expansion.
|
|
if [ "${0}" == "${BASH_SOURCE}" ]; then
|
|
|
|
# This script uses three associative arrays (see #requirements section)
|
|
# to maintain isolation between conceptual paradigms.
|
|
# While combining these would be possible, it renders the resulting code
|
|
# less approachable, at least from this author's perspective.
|
|
#
|
|
# As with any object-oriented-adjacent programming, be mindful of
|
|
# the cascading effects of refactoring core code.
|
|
declare -A envConf commandRegistry activeParameters
|
|
|
|
|
|
fi
|
|
|
|
# Script version
|
|
envConf['version']='0.9.2' # <-- @todo - autopopulate this
|
|
|
|
# environment investigation preliminaries, configuration is the next block.
|
|
### AUTOGENERATED "ENVIRONMENT" SECTION, EDIT IN `compile.sh` ###
|
|
#
|
|
envConf['scriptName']="$(basename ${0})"
|
|
envConf['description']="Functions core program, enables alias usage, etc."
|
|
envConf['basePath']="$(dirname $(readlink --canonicalize ${BASH_SOURCE}))/"
|
|
envConf['corePath']="${envConf['basePath']}core/"
|
|
envConf['dateTimeFormat']='%Y.%m.%d_%H.%M'
|
|
envConf['secondsTimeFormat']="${envConf['dateTimeFormat']}.%S"
|
|
envConf['preciseTimeFormat']="${envConf['secondsTimeFormat']}.%N"
|
|
|
|
### END AUTOGEN "ENVIRONMENT" SECTION, "CONFIGURATION" IS NEXT ###
|
|
|
|
clear
|
|
|
|
# --- this is the section you're probably looking for ---#
|
|
|
|
# User-editable configuration variables
|
|
#
|
|
# Check for config usage in entire repository before removing anything.
|
|
# That's just good programming, yeah?
|
|
|
|
# Set the command character.
|
|
envConf['commandChar']='-'
|
|
|
|
# Commands help, short and long
|
|
commandRegistry['help']="command/usage info."
|
|
commandRegistry['version']="print contents of '\${envConf['version']}'"
|
|
commandRegistry['logLevel']="(0-9) error/debug/status text filtering level."
|
|
commandRegistry['file']="Pass in a file reference."
|
|
commandRegistry['persist']="Confirms write/install/execution, enables persistence."
|
|
|
|
# external capabilities
|
|
commandRegistry['depends']='Installs dependencies from <file/>.'
|
|
|
|
# this is the dangerous one, use carefully.
|
|
commandRegistry['compile']='Verifies, prepares, and rep-packages this script.'
|
|
|
|
# commandRegistry['']=''
|
|
|
|
# alias file, super important
|
|
envConf['aliases']="aliases.src"
|
|
|
|
# default of 2, 0 for quiet, 9 being 'all'
|
|
|
|
|
|
# Set verbosity to default if uninitialized
|
|
if [ -z "${envConf['logLevel']+set}" ];then
|
|
envConf['logLevel']=3
|
|
fi
|
|
|
|
### END AUTOGEN "ENVIRONMENT" SECTION, "POST-CONFIGURATION" IS NEXT ###
|
|
|
|
|
|
|
|
# --- configuration is just above here --- #
|
|
|
|
# environment setup post-config
|
|
### AUTOGENERATED "POST-CONFIGURATION" SECTION, EDIT IN `compile.sh` ###
|
|
#
|
|
#
|
|
envConf['aliases']="${envConf['corePath']}${envConf['aliases']}"
|
|
|
|
# make sure the command aliases file path leads to something useable...
|
|
if [ ! -f "${envConf[aliases]}" ]; then
|
|
echo "Debugging time..."
|
|
exit 13
|
|
fi
|
|
|
|
# Load the command aliases!
|
|
source "${envConf['aliases']}"
|
|
|
|
# Temporary directory location
|
|
envConf['tmpDir']="$(realpath ${envConf['corePath']}../tmp)/"
|
|
|
|
# Logging
|
|
envConf['logFile']="${envConf['tmpDir']}"
|
|
envConf['logFile']+="${envConf['scriptName']}."
|
|
envConf['logFile']+=$(date +"${envConf['dateTimeFormat']}")
|
|
envConf['logFile']+=".log"
|
|
|
|
# Ensure temporary directory exists
|
|
if [ ! -d "${envConf['tmpDir']}" ]; then
|
|
mkdir -p "${envConf['tmpDir']}"
|
|
fi
|
|
|
|
# Process ID file location
|
|
envConf['pidFile']="${envConf['tmpDir']}${envConf['scriptName']}.pid"
|
|
|
|
# touch all the things
|
|
touch "${envConf['logFile']}"
|
|
touch "${envConf['pidFile']}"
|
|
|
|
### END AUTOGEN "ENVIRONMENT" SECTION, "POST-CONFIGURATION" IS NEXT ###
|
|
|
|
|
|
### Functions ###
|
|
|
|
function logThis {
|
|
|
|
# the passed-in message, sanitize slightly
|
|
local message="${1/$(whoami)/'$(whoami)'}" # @todo probably need to sanitize the message somehow.
|
|
|
|
if [ -z "${message}" ];then
|
|
message="\t"
|
|
fi
|
|
|
|
# activeParameterseter 2 is optional...
|
|
local logging_level
|
|
|
|
# ...but we check if they're set, and if not, attempt to set from globals
|
|
if [ ! -z "${2+set}" ] ; then
|
|
# Set the log level (ideally) from the passed-in value...
|
|
logging_level="${2}"
|
|
|
|
elif [ ! -z "${envConf['logLevel']+set}" ] ; then # config is set, passin isn't.
|
|
|
|
# ...next from the script default...
|
|
logging_level="${envConf['logLevel']}"
|
|
|
|
else
|
|
# finally, if none of the default are set, manually set it to quietest
|
|
logging_level=9
|
|
# @todo eliminate this magic number
|
|
fi
|
|
|
|
local dateTimeNow="$(date +"${envConf['secondsTimeFormat']}")"
|
|
if [ "${logging_level}" -le "${envConf['logLevel']}" ] ; then
|
|
echo -e "${dateTimeNow}: ${message}" | tee -a "${envConf['logFile']}"
|
|
else
|
|
echo -e "${dateTimeNow}: ${message}" >> "${envConf['logFile']}"
|
|
fi
|
|
|
|
}
|
|
|
|
# Outputs a horizontal line to the logfile
|
|
# Syntax is logBreak <optional verbosity[0-9]/>
|
|
function logBreak {
|
|
local logging_level=2
|
|
if [ ! -z "${1+set}" ];then
|
|
logging_level="${1}"
|
|
fi
|
|
logThis "#######################################################################" "${logging_level}"
|
|
}
|
|
|
|
# Outputs a horizontal line to the logfile
|
|
# Syntax is logBreak <optional verbosity[0-9]/>
|
|
function logLine {
|
|
local logging_level=2
|
|
if [ ! -z "${1+set}" ];then
|
|
logging_level="${1}"
|
|
fi
|
|
logThis "----------------------------------------------------------------------" "${logging_level}"
|
|
}
|
|
|
|
function version {
|
|
logThis "\t'${envConf['scriptName']}' version is ${envConf['version']}" 1
|
|
}
|
|
|
|
# Display script usage information and flags.
|
|
function help {
|
|
logThis "" 1
|
|
logThis "Description:"
|
|
logThis "${envConf['description']}" 1
|
|
logThis "" 1
|
|
version
|
|
logThis "" 1
|
|
logThis "Commands available in '${envConf['scriptName']}':" 1
|
|
|
|
# iterate through command descriptions and display to the user
|
|
local cmdString
|
|
for cmd in "${!commandRegistry[@]}"; do
|
|
cmdString="\t${envConf['commandChar']}${envConf['commandChar']}${cmd}"
|
|
cmdString+="|${envConf['commandChar']}${cmd:0:1}"
|
|
cmdString+="\t\t"
|
|
cmdString+=${commandRegistry["${cmd}"]}
|
|
logThis "${cmdString}" 1
|
|
done
|
|
}
|
|
|
|
# @todo - actually implement this
|
|
function quiet {
|
|
envConf['logLevel']=0
|
|
export envConf
|
|
}
|
|
|
|
|
|
# sanitize and process short parameters
|
|
# short params are a single command character followed by one or more character(s)
|
|
# intended as shorthand and ease-of-prototyping flags
|
|
#
|
|
# function accepts a single input, but this is not enforced or used well
|
|
function processShortParams {
|
|
# skip if nothing was passed to the function
|
|
if [ ! -z "${@}" ]; then
|
|
|
|
# Local variable for manipulating/using input
|
|
local inputParamString="${@}"
|
|
|
|
#force this variable to re-initialize
|
|
activeParameters[0]=''
|
|
|
|
for key in ${!commandRegistry[@]}; do
|
|
|
|
if [ "${inputParamString:1:2}" == "${key:0:1}" ];then
|
|
|
|
# load directly from the registered commands list, both for reasons of
|
|
# single authoritative command source (eliminates broken chains) and
|
|
# to avoid the possibility of passed-in string shenanigan.
|
|
#
|
|
# use the current length of the param array to set the index for the
|
|
# new entry.
|
|
activeParameters["${#activeParameters[@]}"]="${key}"
|
|
|
|
logThis "...command '"${key}"' acknowledged." 1
|
|
|
|
fi
|
|
done
|
|
|
|
|
|
fi
|
|
}
|
|
|
|
function processLongParams {
|
|
# fail early if there's nothing passed to the function
|
|
if [ "${@}" ]; then
|
|
|
|
# Local variable for manipulating/using input
|
|
local inputParamString="${@}"
|
|
activeParameters[0]=''
|
|
# Remove the command sequence from the string
|
|
if [ "${envConf['commandChar']}${envConf['commandChar']}" == "${inputParamString:0:2}" ]; then
|
|
activeParameters[${#activeParameters[@]}]="${inputParamString:2:${#inputParamString}}"
|
|
fi
|
|
|
|
fi
|
|
}
|
|
|
|
# Waste management is pretty important.
|
|
function cleanup {
|
|
# signal that the exit trap was successful, for debugging
|
|
# use 'exit 13'
|
|
if [ "${?}" == 13 ];then
|
|
echo "failmuffins"
|
|
fi
|
|
|
|
# Ensure that child processes have finished before designating time of exit.
|
|
wait
|
|
|
|
# Make sure that the temp directory exists
|
|
if [ -d "${envConf['tmpDir']}" ]; then
|
|
|
|
# Check for process ID files.
|
|
if [ -f "${envConf['pidFile']}" ]; then
|
|
|
|
# If the PID file starts with 'LOGGING:', do an end-of-logging printout
|
|
if [ "$(cat ${envConf['pidFile']}|cut -d':' -f2)" == "LOGGING" ]; then
|
|
logThis "" 1
|
|
logThis "" 1
|
|
logThis "" 1
|
|
logThis "\t---" 1
|
|
logThis "\tEND" 1
|
|
logThis "\t---" 1
|
|
logBreak
|
|
fi
|
|
rm "${envConf['pidFile']}"
|
|
fi
|
|
fi
|
|
|
|
# Let the user know when the script ended.
|
|
echo
|
|
echo
|
|
echo
|
|
echo -e "\t\t\t--- cleanup ---"
|
|
echo
|
|
echo "Completed at:"
|
|
when
|
|
exit 0
|
|
}
|
|
|
|
function compile {
|
|
cd "${envConf['basePath']}"
|
|
logThis "" 0
|
|
logThis "begin live compile..." 1
|
|
./core/compile.sh "${envConf['aliases']}" && compile
|
|
logThis "...recompiling." 1 && exit 13
|
|
}
|
|
|
|
### END 'functions' ###
|
|
|
|
# Print out the date and time for the user
|
|
when;echo
|
|
|
|
# Inform the user of their environment
|
|
logBreak;logThis "\t\tExecuting via '$(which f)'" 1
|
|
|
|
# Timestamp descriptor, must match time/date format config & logThis
|
|
echo -e "YYYY.MM.DD_HH.MM.SS\t\t(chronostamp format via 'date +\"${envConf['secondsTimeFormat']}\"')"
|
|
|
|
# Visual break. set logging status in PID file
|
|
logBreak && echo "$$:LOGGING:${envConf['logFile']}" > "${envConf['pidFile']}"
|
|
|
|
# Inform the user of file locations
|
|
logThis "logging to: '${envConf['logFile']}'" 1
|
|
logThis "process ID file at: '${envConf['pidFile']}'" 1
|
|
logBreak
|
|
logThis "" 1
|
|
|
|
# On exit, do cleanup
|
|
#trap "cleanup ${@}" EXIT # <-- attempt to pass arguments to the exit function.
|
|
trap cleanup EXIT
|
|
|
|
# Pass input to the command parser if command character is found
|
|
# if no parameters have been passed, display the help
|
|
logThis "Processing parameters..." 1
|
|
logThis "" 1
|
|
|
|
for key in ${!commandRegistry[@]}; do
|
|
logThis "--> parameter '${key}' registered." 3
|
|
done
|
|
|
|
logThis "" 1
|
|
logThis "" 1
|
|
|
|
logThis "Processed parameter execution..."
|
|
# checking command line inputs, display usage help if none.
|
|
if [ -z "${@+set}" ]; then
|
|
logThis "...none found, displaying usage information." 1
|
|
logThis "" 1
|
|
help
|
|
else
|
|
# @todo - convert to use getopts, see http://wiki.bash-hackers.org/howto/getopts_tutorial
|
|
for flag in "${@}"; do
|
|
case "${flag}" in
|
|
# parse any short flags (single command char)
|
|
${envConf['commandChar']}[a-zA-Z0-9_]*)
|
|
logThis "...short form parameter '${flag}' found..." 1
|
|
processShortParams "${flag}"
|
|
;;
|
|
# parse long params (double command char)
|
|
${envConf['commandChar']}${envConf['commandChar']}[a-zA-Z0-9_]*)
|
|
logThis "...long form parameter '${flag}' found..." 1
|
|
processLongParams "${flag}"
|
|
;;
|
|
|
|
*)
|
|
logThis "command line input '${flag}' not registered." 1
|
|
echo
|
|
;;
|
|
esac
|
|
done
|
|
fi
|
|
|
|
logThis "" 1
|
|
logThis "Input processing complete."
|
|
|
|
if [ ! -z "${activeParameters[@]+set}" ]; then
|
|
logThis "" 1
|
|
logThis "" 1
|
|
logBreak
|
|
logThis "\t Begin parameter processing:"
|
|
logBreak
|
|
logThis "" 1
|
|
logThis "found '$(echo ${activeParameters[@]})', will attempt to process in order." 1
|
|
logThis "" 1
|
|
for parameter in ${activeParameters[@]}; do
|
|
logThis "Directly executing '${parameter}' function..."
|
|
logLine
|
|
eval "${parameter}"
|
|
logThis "" 1
|
|
logLine
|
|
logThis "done with direct '${parameter}' execution." 1
|
|
logThis "" 1
|
|
logThis "Core execution attempt..."
|
|
if [ -f "core/${parameter}.sh" ]; then
|
|
eval "core/${parameter}.sh" && logThis "...success."
|
|
else
|
|
logThis "...no core file found."
|
|
fi
|
|
logThis "" 1
|
|
logThis "...done with '${parameter}' flag execution." 1
|
|
logBreak
|
|
logThis "" 1
|
|
logThis "" 1
|
|
done
|
|
logThis "" 1
|
|
logBreak
|
|
fi
|