diff --git a/sequencer.sh b/sequencer.sh index a73b6e5..1275f18 100755 --- a/sequencer.sh +++ b/sequencer.sh @@ -1,5 +1,6 @@ #!/usr/bin/env bash # shellcheck disable=SC2034 # variable not used +# shellcheck disable=SC1090 # follow non constant source # Exit on error. Append "|| true" if you expect an error. set -o errexit @@ -28,9 +29,13 @@ set -o pipefail # shellcheck disable=SC2015 # && || is not if else readonly seq_invocation="$(printf '%q' "${0}")$( (($#)) && printf ' %q' "$@" || true)" + seq_args= + seq_configFile= # Filled by initSeqConfig # May be overwritten by seq - seq_configFile="${seq_fileName}.cfg" + seq_configName="${seq_fileName}.cfg" seq_configTemplate="${0%.*}.cfg.example" + seq_profileName= + _seq_profileList= ## Sequencer readonly sqr_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" @@ -38,17 +43,21 @@ set -o pipefail readonly sqr_name="${sqr_file%%.*}" readonly sqr_origin="$(cd -- "$(dirname -- \ "$(readlink -f -- "${BASH_SOURCE[0]}")")" && pwd)" + sqr_args= - _sqr_stepMax=512 - _sqr_interactive=1 - _sqr_debug=0 - _sqr_dry=0 - _sqr_verbose=0 - _sqr_errno=0 + _sqr_configEdit=0 + readonly _sqr_configDirName=".seqs" + _sqr_configRoot="${HOME}/${_sqr_configDirName}" _sqr_contextHelp=0 _sqr_contextExe=0 + _sqr_debug=0 + _sqr_dry=0 _sqr_editor= - + _sqr_errno=0 + _sqr_interactive=1 + readonly _sqr_stepMax=512 + _sqr_verbose=0 + ## Terminal position _sqr_savePosAlias= ;[ -t 1 ] && _sqr_savePosAlias='\033[1A\033[1C\033[s\033[1B\033[1C' _sqr_savePosExe= ;[ -t 1 ] && _sqr_savePosExe='\033[s' @@ -251,7 +260,7 @@ USAGE_EOF readonly _sqr_indentHelp=' : ' readonly _sqr_indentExe=' ' readonly _sqr_indentAppendHelp=' ' - echoinfo() { + echoinfo() { if [ $_sqr_contextHelp -ne 0 ] ; then printf '%s' "$_sqr_indentAppendHelp"; echo "$@" else @@ -303,7 +312,6 @@ running() { # : either a variable name or # -f : a function exists() { - sqr::debugPause local func= local arg= @@ -315,7 +323,6 @@ exists() { func="${2:-}" esac done - sqr::debugContinue if [[ -n "${func}" ]] ; then declare -F "${func}" &>>/dev/null else @@ -494,7 +501,7 @@ endReturn() { if [[ "${1:?}" =~ ${rex} ]] ; then errorCode="${1:?}" else - warning " [W] Ignoring invalid error code: $1" + warning "Ignoring invalid error code: $1" fi shift ;; @@ -539,6 +546,23 @@ endReturn() { fi } +# endIfEmpty [DESCRIPTION] +# DESCRIPTION : Optional text for error +endIfEmpty() { + eval 'local ref=$'"${1:-}" + + if [ -z "${ref:-}" ] ; then + if [ -n "${2:-}" ] ; then + error -- "$2" + error -a "Sequence stopped." + else + error -- "\${$1} must not be empty." + error -a "Sequence stopped." + fi + exit 6 + fi +} + # checkStep # return 0 - for invalid step # return Step Number @@ -594,6 +618,7 @@ step() { # back reference variable of schema: # alias_[ALIAS]=[STEP NUMBER] parseAlias() { + sqr::debugPause local i for ((i=1; i<=_sqr_stepMax; i++)); do # Check for alias definition @@ -602,6 +627,7 @@ parseAlias() { # Function returns step alias eval '_sqr_alias_'"$("step_${i}_alias")"'='"$i" done + sqr::debugContinue } # displayHelp [NO TEMPLATE] [STEP NUMBER OR ALIAS] @@ -745,12 +771,164 @@ listSteps() { [ ${#aList[@]} -ne 0 ] && printf '%s\n' "${aList[*]}" } +# listProfiles [OPTION] [SEARCH] +# List all available profiles for current user +# [OPTION] +# -q : only check for profile support +listProfiles() { + local file= + local profiles=() + if [[ ${_sqr_configDirName} == $(basename "${_sqr_configRoot}") ]] ; then + error "${seq_name} does not have configuration profiles" + return 1 + fi + [[ "${1:-}" == "-q" ]] && return 0 + #for file in $(ls "${_sqr_configRoot}" 2>/dev/null); do + for file in "${_sqr_configRoot}"/* ; do + file="$(basename -- "${file}")" + [[ ${file%.*} =~ ^${1:-".*"} ]] && profiles+=("${file%.*}") + done + printf '%s\n' "${profiles[*]}" +} + # showVersion showVersion() { printf 'Sequencer %s\n' "${_sqr_versionString}" printf 'Seq Revision %s\n' "${VERSION_SEQREV:-"-"}" } +# initSeqConfig [OPTION] [TEMPLATE] +# Create a configuration file in the users' home. +# Source it if already existent +# [OPTION] +# -p : is subfolder used for profiles +# -t : Source config also if created from template +# -e : Create empty configuration if no template is found +# Return +# 0 : Sourced configuration or +# (-t) : created and sourced configuration from template +# 1 : Created configuration from template but not sourced +# 2 : Created empty configuration +# 3 : No configuration created +initSeqConfig() { + local arg + local answer=n + local retVal=255 + local sourceAlways=0 + local createEmpty=0 + local seqProfiles=0 + local configExists= + local configDir= + for arg in "$@" ; do + case "$1" in + -e) + createEmpty=1 + shift ;; + -p) + seqProfiles=1 + shift ;; + -t) + sourceAlways=1 + shift ;; + esac + done + + local configLoc="$_sqr_configRoot/$1" + if [[ $seqProfiles -ne 0 ]] ; then + [ -z "$seq_profileName" ] && seq_profileName=default + configLoc="$_sqr_configRoot/$1/${seq_profileName}.cfg" + fi + configDir="$(dirname -- "$configLoc")" + local configTemplate="$2" + + # Don't create anything if only profiles should be listed + if [ -n "${_seq_profileList}" ] ; then + _sqr_configRoot="$configDir" + return 0 + fi + + _sqr_configRoot="$configDir" + + if [ -s "$configLoc" ] ; then + info "Using configuration file: $configLoc" + seq_configFile="$configLoc" + . "$configLoc" + return 0 + fi + + # Ask for config creation if not existent + if ! quiet && ! dry ; then + sqr::echo " [i] Configuration $configLoc missing" + confirm "Create it now?" || return 3 + fi + + # Create config subdir in users home + if [ ! -e "$configDir/" ] ; then + sqr::echo -n "Creating $(realpath -- "$configDir")..." + # shellcheck disable=SC2015 # && || is not if else + exe install -m 700 -d "$configDir" && sqr::echo "Ok" || sqr::echo "Nok" + fi + + # Config does not exist, check for template + if [ -s "$configTemplate" ] ; then + + # Check first if there is an existing configuration at the templates position + configExists="$(dirname -- "$configTemplate")/$1" + if [ -s "$configExists" ] ; then + exe mv "$configExists" "$configLoc" + endReturn -o $? "Unable to use existing configuration: $configExists" + + sqr::echo " [i] Using existing configuration: $configExists" + sqr::echo " (Moved to $configDir)" + . "$configLoc" + retVal=0 + else + # Install new template to the final location + exe install -m 600 -T "$configTemplate" "$configLoc" + endReturn -o $? "Failed to create configuration" + + if [ $sourceAlways -eq 0 ] ; then + if [ $_sqr_configEdit -eq 0 ] ; then + warning "Seq configuration created from template but not used" + warning -a "Please modify \"$configLoc\" first" + fi + retVal=1 + else + warning "Using seq configuration from template $configTemplate" + warning -a " (Copied to $configDir)" + . "$configTemplate" + retVal=0 + fi + fi + seq_configFile="$configLoc" + else + warning "Seq configuration template not found" + fi + + if [ $createEmpty -ne 0 ] ; then + # Create empty config file + warning "Created empty configuration file $configLoc" + exe touch "$configLoc" + exe chmod 600 "$configLoc" + seq_configFile="$configLoc" + retVal=2 + fi + + # Give the user a chance to edit the create config file + if [ $retVal -eq 255 ]; then + error "No seq configuration created" + retVal=3 + elif ! quiet && ! dry ; then + if confirm "Edit configuration file now?" ; then + exe editor "$configLoc" + . "$configLoc" + retVal=0 + fi + fi + + return $retVal +} + # exe # Handle dry run and verbose output for commands without pipe and/or redirects exe() { @@ -785,11 +963,17 @@ sqr::noEditor() { } sqr::main() { + local emptyCall=1 local quickStartOne=0 # options check for arg in "$@" ; do - case "$1" in + case "${1:-}" in + --) # end parameter + shift && break;; + --config|-c) # open sequence configuration file + _sqr_configEdit=1 + shift;; --debug) _sqr_debug="1" shift ;; @@ -798,24 +982,42 @@ sqr::main() { shift ;; --help|-h) # show only help displayHelp 1 "${2:-}" - exit 0;; + exit 0 ;; --liststeps|-ls) shift listSteps "${1:-}" - exit 0;; + exit 0 ;; + --profile|-p) # seq profile name + sqr_args+=" $1 ${2:-}" + shift + # Cover the case when only -p is given without profile name + if [ -z "${1:-}" ] ; then + seq_profileName=default + else + seq_profileName="$1" && shift + fi ;; + -pl) # List available profiles with search + shift + [[ ${LOG_LEVEL} -gt 1 ]] && LOG_LEVEL=1 # "only show errors" + _seq_profileList="${1:-".*"}" + [ -n "${1:-}" ] && shift + ;; --quiet|-q) + sqr_args+=" $1" _sqr_interactive=0 shift ;; --silent|-qq) + sqr_args+=" $1" _sqr_interactive=0 LOG_LEVEL=0 shift ;; --verbose|-v) + sqr_args+=" $1" _sqr_verbose=1 shift ;; --version) # version request showVersion - exit 0;; + exit 0 ;; esac done @@ -826,9 +1028,9 @@ sqr::main() { # Enable error backtracing trap 'sqr::error_report "${FUNCNAME:-.}" ${LINENO}' ERR fi - + # Don't show help if only configuration should be edited - # TODO [ $SEQ_CONFIG_EDIT -ne 0 ] && [ -z "$1" ] && QUIET=2 + [ ${_sqr_configEdit} -ne 0 ] && [ -z "${1:-}" ] && LOG_LEVEL=1 if [ -z "${1:-}" ] && [ $quickStartOne -eq 0 ] ; then if ! quiet ; then @@ -838,14 +1040,14 @@ sqr::main() { # Assume starting at one for interactive mode START=1 elif [ $quickStartOne -ne 0 ] ; then - EMPTYCALL=0 + emptyCall=0 START=1 - STEP_ARGS=( "$@" ) + seq_args=( "$@" ) else - EMPTYCALL=0 + emptyCall=0 read -r -a START <<< "$1" shift - STEP_ARGS=( "$@" ) + seq_args=( "$@" ) fi # Determine system default editor @@ -858,8 +1060,48 @@ sqr::main() { ## Fall back to error message [ -z "${_sqr_editor}" ] && _sqr_editor="sqr::noEditor" + parseAlias + + # run configuration for sequence only if available and if first step is valid + if exists -f seq_config ; then + + # Create/edit configuration file + if [ $_sqr_configEdit -ne 0 ] ; then + # Suppress seq_config output for editing + quietSave=${LOG_LEVEL} + LOG_LEVEL=0 + seq_config "${seq_args[@]}" + LOG_LEVEL=${quietSave} + if [ -w "$seq_configFile" ]; then + exe editor "$seq_configFile" + else + error "No configuration file available" + fi + [ ${emptyCall} -ne 0 ] && exit 0 + fi + + if checkStep "${START[0]}" >/dev/null ; then + if ! seq_config "${seq_args[@]}" ; then + error "Configuring sequence failed" + exit 1 + fi + else + return 1 + fi + elif [ ${_sqr_configEdit} -ne 0 ] ; then + error "Sequence does not have a configuration file" + return 1 + fi + + # Check for profile support + if [ -n "${_seq_profileList}" ]; then + listProfiles "${_seq_profileList}"; exit $? + elif [ -n "${seq_profileName}" ]; then + listProfiles -q || exit 1 + fi + sqr::printf 'Running...\n' - seq_config 2>/dev/null || true + step 1 "$@" }