From a529db28bd682d0513a08d32c9f80585e1acecea Mon Sep 17 00:00:00 2001 From: Nathaniel Landau Date: Mon, 1 Nov 2021 16:24:25 -0400 Subject: [PATCH] prepare for hooks directory --- .hooks/README.md | 9 - .hooks/pre-commit.sh | 994 ------------------------------------------- 2 files changed, 1003 deletions(-) delete mode 100644 .hooks/README.md delete mode 100755 .hooks/pre-commit.sh diff --git a/.hooks/README.md b/.hooks/README.md deleted file mode 100644 index 2f904f5..0000000 --- a/.hooks/README.md +++ /dev/null @@ -1,9 +0,0 @@ -This folder contains a git pre-commit script which will be executed on the client side before files are committed to the repository. This script provides automated linting and testing of multiple file types. - -## Usage - -To install the hook, create a symlink into the `/path/to/repo/.git/hooks` directory. - -```bash -ln -s "$(git rev-parse --show-toplevel)/.hooks/pre-commit.sh" "$(git rev-parse --show-toplevel)/.git/hooks/pre-commit" -``` diff --git a/.hooks/pre-commit.sh b/.hooks/pre-commit.sh deleted file mode 100755 index 448ace9..0000000 --- a/.hooks/pre-commit.sh +++ /dev/null @@ -1,994 +0,0 @@ -#!/usr/bin/env bash - -_mainScript_() { - - GITROOT=$(git rev-parse --show-toplevel 2>/dev/null) - _setPATH_ "/usr/local/bin" "${HOME}/bin" - LOGFILE="${HOME}/logs/$(_fileName_ "${GITROOT}")-$(basename "$0").log" - - _gitStopWords_() { - # DESC: - # Check if any specified stop words are in the commit diff. If found, the pre-commit hook will exit with a non-zero exit code. - # ARGS: - # $1 (Required): Path to file - # OUTS: - # 0: Success - # 1: Failure - # USAGE: - # _gitStopWords_ "/path/to/file.sh" - # NOTE: - # Requires a file located at `~/.git_stop_words` containing one stopword per line. - - [[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}" - - local _gitDiffTmp - _gitDiffTmp="${TMP_DIR}/diff.txt" - - if [ -f "${STOP_WORD_FILE}" ]; then - - if [[ $(basename "${STOP_WORD_FILE}") == "$(basename "${1}")" ]]; then - debug "$(basename "${1}"): Don't check stop words file for stop words." - return 0 - fi - debug "$(basename "${1}"): Checking for stop words..." - - # remove blank lines from stopwords file - sed '/^$/d' "${STOP_WORD_FILE}" >"${TMP_DIR}/pattern_file.txt" - - # Add diff to a temporary file - git diff --cached -- "${1}" | grep '^+' >"${_gitDiffTmp}" - - if grep --file="${TMP_DIR}/pattern_file.txt" "${_gitDiffTmp}"; then - return 1 - else - return 0 - fi - else - notice "Could not find git stopwords file expected at '${STOP_WORD_FILE}'. Continuing..." - return 0 - fi - } - - _ignoreSymlinks_() { - # DESC: - # Ensures that no symlinks have been committed to the repository. If the symlink - # has not yet been staged, it will be added to the .gitignore file. - # ARGS: - # NONE - # OUTS: - # 0: Success - # 1: Failure - # USAGE: - # _ignoreSymlinks_ - - local _gitIgnore - local _haveSymlink - local _f - _gitIgnore="${GITROOT}/.gitignore" - _haveSymlink=false - - debug "Checking for symlinks..." - - # Work on files not yet staged - for _f in $(git status --porcelain | grep '^??' | sed 's/^?? //'); do - if [ -L "${_f}" ]; then - if ! grep "${_f}" "${_gitIgnore}"; then - if printf "\n%s" "${_f}" >>"${_gitIgnore}"; then - notice "Added unstaged symlink '${_f}' to .gitignore" - else - fatal "Could not add symlink '${_f}' to .gitignore" - fi - fi - _haveSymlink=true - fi - done - - # Work on files that were mistakenly staged - for _f in $(git status --porcelain | grep '^A' | sed 's/^A //'); do - if [ -L "${_f}" ]; then - if ! grep "${_f}" "${_gitIgnore}"; then - if printf "\n%s" "${_f}" >>"${_gitIgnore}"; then - notice "Added unstaged symlink '${_f}' to .gitignore" - else - fatal "Could not add symlink '${_f}' to .gitignore" - fi - fi - _haveSymlink=true - fi - done - - if [[ ${_haveSymlink} == true ]]; then - return 1 - else - return 0 - fi - } - - _lintYAML_() { - # DESC: - # Lint YAML files staged for commit. - # Requires either 'yaml-lint 'or 'yamllint' be installed. - # ARGS: - # $1 (Required): Path to file - # OUTS: - # 0: Success - # 1: Failure - # USAGE: - # _lintYAML_ "/path/to/file.yaml" - - [[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}" - - local _filename - _filename="$(_fileName_ "${1}")" - - if command -v yaml-lint >/dev/null; then - debug "${_filename}: Linting YAML..." - if yaml-lint "${1}"; then - return 0 - else - return 1 - fi - elif command -v yamllint >/dev/null; then - debug "${_filename}: Linting YAML..." - if [ -f "$(git rev-parse --show-toplevel)/.yamllint.yml" ]; then - if yamllint -c "$(git rev-parse --show-toplevel)/.yamllint.yml" "${1}"; then - return 0 - else - return 1 - fi - else - if yamllint "${1}"; then - return 0 - else - return 1 - fi - fi - else - notice "No YAML linter found. Continuing..." - return 0 - fi - } - - _lintShellscripts_() { - # DESC: - # Lint shell scripts staged for commit. - # ARGS: - # $1 (Required): Path to file - # OUTS: - # 0: Success - # 1: Failure - # stdout: - # USAGE: - # _lintShellscripts_ "/path/to/file.sh" - - [[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}" - - local _filename - _filename="$(_fileName_ "${1}")" - if command -v shellcheck >/dev/null; then - debug "${_filename}: Linting shellscript..." - if [[ ${_filename} == "*.j2" ]]; then - if shellcheck -x --exclude=1009,1054,1056,1072,1073,1083,2001,2148 "${1}"; then - return 0 - else - return 1 - fi - else - if shellcheck -x --exclude=2001,2148 "${1}"; then - return 0 - else - return 1 - fi - fi - else - notice "Shellcheck not installed. Continuing..." - return 0 - fi - } - - _BATS_() { - # DESC: - # Runs BATS unit tests on bash scripts. Requires bats to be installed. - # ARGS: - # $1 (Required): Path to bats test file - # OUTS: - # 0: Success - # 1: Failure - # USAGE: - # _BATS_ "/path/to/file.sh" - - [[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}" - - local _filename - _filename="$(_fileName_ "${1}")" - - debug "${_filename}: Runing bats tests..." - if bats -t "$1"; then - return 0 - else - return 1 - fi - } - - _lintAnsible_() { - # DESC: - # Lint Ansible YAML files staged for commit. Requires ansible-lint to be installed. - # ARGS: - # $1 (Required): Path to file - # OUTS: - # 0: Success - # 1: Failure - # USAGE: - # _lintAnsible_ "/path/to/file.yml" - - [[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}" - - local _filename - _filename="$(_fileName_ "${1}")" - - if ! command -v ansible-lint &>/dev/null; then - notice "ansible-lint not intstalled. Continuing..." - return 0 - fi - - ANSIBLE_COMMAND="ansible-lint -vv --parseable-severity ${1}" - if [ -f "$(git rev-parse --show-toplevel)/.ansible-lint.yml" ]; then - ANSIBLE_COMMAND="ansible-lint -p -c $(git rev-parse --show-toplevel)/.ansible-lint.yml ${1}" - fi - - debug "${_filename}: Linting ansible..." - if ${ANSIBLE_COMMAND}; then - return 0 - else - return 1 - fi - } - - # RUN SCRIPT LOGIC - - # Attempt to discern if we are working on a repo that contains ansible files - IS_ANSIBLE_REPO=false - if find "$(git rev-parse --show-toplevel)" -mindepth 1 -maxdepth 1 -type f \ - -name "inventory.yml" \ - -o -name "ansible.cfg" \ - -o -name ".ansible-lint.yml" &>/dev/null; then - - IS_ANSIBLE_REPO=true - fi - - if ! _ignoreSymlinks_; then - notice "Found symlink in repository. Exiting..." - _safeExit_ 1 - fi - - while read -r STAGED_FILE; do - debug "$(_fileName_ "${STAGED_FILE}"): Linting..." - - if [ -f "${STAGED_FILE}" ]; then - - if _gitStopWords_ "${STAGED_FILE}"; then - info "$(_fileName_ "${STAGED_FILE}"): Passed stopwords lint" - else - notice "$(_fileName_ "${STAGED_FILE}"): Failed stopwords lint" - _safeExit_ 1 - fi - - # YAML Linting - if [[ ${STAGED_FILE} =~ \.(yaml|yml)$ ]]; then - - if _lintYAML_ "${STAGED_FILE}"; then - info "$(_fileName_ "${STAGED_FILE}"): Passed yaml lint" - else - notice "$(_fileName_ "${STAGED_FILE}"): Failed yaml lint" - _safeExit_ 1 - fi - fi - - # Ansible Linting - # - Only run in Ansible repos - # - Only run on YAML files - # - Don't lint files that are not Ansible playbooks - # - Don't lint in directory names that are not likely to contain Ansible playbooks - if [[ ${IS_ANSIBLE_REPO} == true ]] \ - && [[ ${STAGED_FILE} =~ \.(yaml|yml)$ ]] \ - && [[ ! $(_fileName_ "${STAGED_FILE}") =~ (^\.|^requirements|j2|vault\.yml|variables|meta|defaults?|inventory) ]] \ - && [[ ! $(_filePath_ "${STAGED_FILE}") =~ /(handlers|vars/|defaults/|meta/|molecule/|templates/|files/)/ ]]; then - - if _lintAnsible_ "${STAGED_FILE}"; then - info "$(_fileName_ "${STAGED_FILE}"): Passed ansible-lint" - else - notice "$(_fileName_ "${STAGED_FILE}"): Failed ansible-lint" - _safeExit_ 1 - fi - fi - - # Shellscript Linting - if [[ ${STAGED_FILE} =~ \.(bash|sh)$ || "$(head -n 1 "${STAGED_FILE}")" =~ ^#!.*bash$ ]]; then - if _lintShellscripts_ "${STAGED_FILE}"; then - info "$(_fileName_ "${STAGED_FILE}"): Passed shellcheck" - else - notice "$(_fileName_ "${STAGED_FILE}"): Failed shellcheck" - _safeExit_ 1 - fi - fi - - # Run BATS unit tests on individual files if STAGED_FILE.bats exists in test/ directory - if [[ ${STAGED_FILE} =~ \.(sh|bash|bats|zsh)$ || "$(head -n 1 "${STAGED_FILE}")" =~ ^#!.*bash$ ]]; then - - if [ -f "${GITROOT}/test/$(_fileBasename_ "${STAGED_FILE}").bats" ]; then - if _BATS_ "${GITROOT}/test/$(_fileBasename_ "${STAGED_FILE}").bats"; then - info "$(_fileName_ "${STAGED_FILE}"): BATS passed" - else - notice "$(_fileName_ "${STAGED_FILE}"): BATS failed" - _safeExit_ 1 - fi - fi - fi - fi - - done < <(git diff --cached --name-only --line-prefix="$(git rev-parse --show-toplevel)/") - -} -# end _mainScript_ - -# ################################## Flags and defaults -# Script specific -STOP_WORD_FILE="${HOME}/.git_stop_words" -shopt -s nocasematch # Case insensitive matching - -# Required variables -LOGFILE="${HOME}/logs/$(basename "$0").log" -QUIET=false -LOGLEVEL=ERROR -VERBOSE=false -FORCE=false -DRYRUN=false -declare -a ARGS=() - -# ################################## Custom utility functions (Pasted from repository) -_fileName_() { - # DESC: - # Get only the filename from a string - # ARGS: - # $1 (Required) - Input string - # OUTS: - # 0 - Success - # 1 - Failure - # stdout: Filename with extension - # USAGE: - # _fileName_ "some/path/to/file.txt" --> "file.txt" - # _fileName_ "some/path/to/file" --> "file" - [[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}" - printf "%s\n" "${1##*/}" -} - -_filePath_() { - # DESC: - # Finds the directory name from a file path. If it exists on filesystem, print - # absolute path. If a string, remove the filename and return the path - # ARGS: - # $1 (Required) - Input string path - # OUTS: - # 0 - Success - # 1 - Failure - # stdout: Directory path - # USAGE: - # _fileDir_ "some/path/to/file.txt" --> "some/path/to" - # CREDIT: - # https://github.com/labbots/bash-utility/ - - [[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}" - - local _tmp=${1} - - if [ -e "${_tmp}" ]; then - _tmp="$(dirname "$(realpath "${_tmp}")")" - else - [[ ${_tmp} != *[!/]* ]] && { printf '/\n' && return; } - _tmp="${_tmp%%"${_tmp##*[!/]}"}" - - [[ ${_tmp} != */* ]] && { printf '.\n' && return; } - _tmp=${_tmp%/*} && _tmp="${_tmp%%"${_tmp##*[!/]}"}" - fi - printf '%s' "${_tmp:-/}" -} - -_fileBasename_() { - # DESC: - # Gets the basename of a file from a file name - # ARGS: - # $1 (Required) - Input string path - # OUTS: - # 0 - Success - # 1 - Failure - # stdout: Filename basename (no extension or path) - # USAGE: - # _fileBasename_ "some/path/to/file.txt" --> "file" - - [[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}" - - local _file - local _basename - _file="${1##*/}" - _basename="${_file%.*}" - - printf "%s" "${_basename}" -} -# ################################## Functions required for this template to work - -_setColors_() { - # DESC: - # Sets colors use for alerts. - # ARGS: - # None - # OUTS: - # None - # USAGE: - # printf "%s\n" "${blue}Some text${reset}" - - if tput setaf 1 >/dev/null 2>&1; then - bold=$(tput bold) - underline=$(tput smul) - reverse=$(tput rev) - reset=$(tput sgr0) - - if [[ $(tput colors) -ge 256 ]] >/dev/null 2>&1; then - white=$(tput setaf 231) - blue=$(tput setaf 38) - yellow=$(tput setaf 11) - green=$(tput setaf 82) - red=$(tput setaf 1) - purple=$(tput setaf 171) - gray=$(tput setaf 250) - else - white=$(tput setaf 7) - blue=$(tput setaf 38) - yellow=$(tput setaf 3) - green=$(tput setaf 2) - red=$(tput setaf 1) - purple=$(tput setaf 13) - gray=$(tput setaf 7) - fi - else - bold="\033[4;37m" - reset="\033[0m" - underline="\033[4;37m" - # shellcheck disable=SC2034 - reverse="" - white="\033[0;37m" - blue="\033[0;34m" - yellow="\033[0;33m" - green="\033[1;32m" - red="\033[0;31m" - purple="\033[0;35m" - gray="\033[0;37m" - fi -} - -_alert_() { - # DESC: - # Controls all printing of messages to log files and stdout. - # ARGS: - # $1 (required) - The type of alert to print - # (success, header, notice, dryrun, debug, warning, error, - # fatal, info, input) - # $2 (required) - The message to be printed to stdout and/or a log file - # $3 (optional) - Pass '${LINENO}' to print the line number where the _alert_ was triggered - # OUTS: - # stdout: The message is printed to stdout - # log file: The message is printed to a log file - # USAGE: - # [_alertType] "[MESSAGE]" "${LINENO}" - # NOTES: - # - The colors of each alert type are set in this function - # - For specified alert types, the funcstac will be printed - - local _color - local _alertType="${1}" - local _message="${2}" - local _line="${3:-}" # Optional line number - - [[ $# -lt 2 ]] && fatal 'Missing required argument to _alert_' - - if [[ -n ${_line} && ${_alertType} =~ ^(fatal|error) && ${FUNCNAME[2]} != "_trapCleanup_" ]]; then - _message="${_message} ${gray}(line: ${_line}) $(_printFuncStack_)" - elif [[ -n ${_line} && ${FUNCNAME[2]} != "_trapCleanup_" ]]; then - _message="${_message} ${gray}(line: ${_line})" - elif [[ -z ${_line} && ${_alertType} =~ ^(fatal|error) && ${FUNCNAME[2]} != "_trapCleanup_" ]]; then - _message="${_message} ${gray}$(_printFuncStack_)" - fi - - if [[ ${_alertType} =~ ^(error|fatal) ]]; then - _color="${bold}${red}" - elif [ "${_alertType}" == "info" ]; then - _color="${gray}" - elif [ "${_alertType}" == "warning" ]; then - _color="${red}" - elif [ "${_alertType}" == "success" ]; then - _color="${green}" - elif [ "${_alertType}" == "debug" ]; then - _color="${purple}" - elif [ "${_alertType}" == "header" ]; then - _color="${bold}${white}${underline}" - elif [ "${_alertType}" == "notice" ]; then - _color="${bold}" - elif [ "${_alertType}" == "input" ]; then - _color="${bold}${underline}" - elif [ "${_alertType}" = "dryrun" ]; then - _color="${blue}" - else - _color="" - fi - - _writeToScreen_() { - ("${QUIET}") && return 0 # Print to console when script is not 'quiet' - [[ ${VERBOSE} == false && ${_alertType} =~ ^(debug|verbose) ]] && return 0 - - if ! [[ -t 1 || -z ${TERM:-} ]]; then # Don't use colors on non-recognized terminals - _color="" - reset="" - fi - - if [[ ${_alertType} == header ]]; then - printf "${_color}%s${reset}\n" "${_message}" - else - printf "${_color}[%7s] %s${reset}\n" "${_alertType}" "${_message}" - fi - } - _writeToScreen_ - - _writeToLog_() { - [[ ${_alertType} == "input" ]] && return 0 - [[ ${LOGLEVEL} =~ (off|OFF|Off) ]] && return 0 - if [ -z "${LOGFILE:-}" ]; then - LOGFILE="$(pwd)/$(basename "$0").log" - fi - [ ! -d "$(dirname "${LOGFILE}")" ] && mkdir -p "$(dirname "${LOGFILE}")" - [[ ! -f ${LOGFILE} ]] && touch "${LOGFILE}" - - # Don't use colors in logs - local _cleanmessage - _cleanmessage="$(printf "%s" "${_message}" | sed -E 's/(\x1b)?\[(([0-9]{1,2})(;[0-9]{1,3}){0,2})?[mGK]//g')" - # Print message to log file - printf "%s [%7s] %s %s\n" "$(date +"%b %d %R:%S")" "${_alertType}" "[$(/bin/hostname)]" "${_cleanmessage}" >>"${LOGFILE}" - } - - # Write specified log level data to logfile - case "${LOGLEVEL:-ERROR}" in - ALL | all | All) - _writeToLog_ - ;; - DEBUG | debug | Debug) - _writeToLog_ - ;; - INFO | info | Info) - if [[ ${_alertType} =~ ^(error|fatal|warning|info|notice|success) ]]; then - _writeToLog_ - fi - ;; - NOTICE | notice | Notice) - if [[ ${_alertType} =~ ^(error|fatal|warning|notice|success) ]]; then - _writeToLog_ - fi - ;; - WARN | warn | Warn) - if [[ ${_alertType} =~ ^(error|fatal|warning) ]]; then - _writeToLog_ - fi - ;; - ERROR | error | Error) - if [[ ${_alertType} =~ ^(error|fatal) ]]; then - _writeToLog_ - fi - ;; - FATAL | fatal | Fatal) - if [[ ${_alertType} =~ ^fatal ]]; then - _writeToLog_ - fi - ;; - OFF | off) - return 0 - ;; - *) - if [[ ${_alertType} =~ ^(error|fatal) ]]; then - _writeToLog_ - fi - ;; - esac - -} # /_alert_ - -error() { _alert_ error "${1}" "${2:-}"; } -warning() { _alert_ warning "${1}" "${2:-}"; } -notice() { _alert_ notice "${1}" "${2:-}"; } -info() { _alert_ info "${1}" "${2:-}"; } -success() { _alert_ success "${1}" "${2:-}"; } -dryrun() { _alert_ dryrun "${1}" "${2:-}"; } -input() { _alert_ input "${1}" "${2:-}"; } -header() { _alert_ header "${1}" "${2:-}"; } -debug() { _alert_ debug "${1}" "${2:-}"; } -fatal() { - _alert_ fatal "${1}" "${2:-}" - _safeExit_ "1" -} - -_printFuncStack_() { - # DESC: - # Prints the function stack in use. Used for debugging, and error reporting. - # ARGS: - # None - # OUTS: - # stdout: Prints [function]:[file]:[line] - # NOTE: - # Does not print functions from the alert class - local _i - _funcStackResponse=() - for ((_i = 1; _i < ${#BASH_SOURCE[@]}; _i++)); do - case "${FUNCNAME[$_i]}" in "_alert_" | "_trapCleanup_" | fatal | error | warning | notice | info | debug | dryrun | header | success) continue ;; esac - _funcStackResponse+=("${FUNCNAME[$_i]}:$(basename "${BASH_SOURCE[$_i]}"):${BASH_LINENO[_i - 1]}") - done - printf "( " - printf %s "${_funcStackResponse[0]}" - printf ' < %s' "${_funcStackResponse[@]:1}" - printf ' )\n' -} - -_safeExit_() { - # DESC: - # Cleanup and exit from a script - # ARGS: - # $1 (optional) - Exit code (defaults to 0) - # OUTS: - # None - - if [[ -d ${SCRIPT_LOCK:-} ]]; then - if command rm -rf "${SCRIPT_LOCK}"; then - debug "Removing script lock" - else - warning "Script lock could not be removed. Try manually deleting ${yellow}'${SCRIPT_LOCK}'" - fi - fi - - if [[ -n ${TMP_DIR:-} && -d ${TMP_DIR:-} ]]; then - if [[ ${1:-} == 1 && -n "$(ls "${TMP_DIR}")" ]]; then - command rm -r "${TMP_DIR}" - else - command rm -r "${TMP_DIR}" - debug "Removing temp directory" - fi - fi - - trap - INT TERM EXIT - exit "${1:-0}" -} - -_trapCleanup_() { - # DESC: - # Log errors and cleanup from script when an error is trapped. Called by 'trap' - # ARGS: - # $1: Line number where error was trapped - # $2: Line number in function - # $3: Command executing at the time of the trap - # $4: Names of all shell functions currently in the execution call stack - # $5: Scriptname - # $6: $BASH_SOURCE - # USAGE: - # trap '_trapCleanup_ ${LINENO} ${BASH_LINENO} "${BASH_COMMAND}" "${FUNCNAME[*]}" "${0}" "${BASH_SOURCE[0]}"' EXIT INT TERM SIGINT SIGQUIT SIGTERM ERR - # OUTS: - # Exits script with error code 1 - - local _line=${1:-} # LINENO - local _linecallfunc=${2:-} - local _command="${3:-}" - local _funcstack="${4:-}" - local _script="${5:-}" - local _sourced="${6:-}" - - if [[ "$(declare -f "fatal")" && "$(declare -f "_printFuncStack_")" ]]; then - - _funcstack="'$(printf "%s" "${_funcstack}" | sed -E 's/ / < /g')'" - - if [[ ${_script##*/} == "${_sourced##*/}" ]]; then - fatal "${7:-} command: '${_command}' (line: ${_line}) [func: $(_printFuncStack_)]" - else - fatal "${7:-} command: '${_command}' (func: ${_funcstack} called at line ${_linecallfunc} of '${_script##*/}') (line: ${_line} of '${_sourced##*/}') " - fi - else - printf "%s\n" "Fatal error trapped. Exiting..." - fi - - if [ "$(declare -f "_safeExit_")" ]; then - _safeExit_ 1 - else - exit 1 - fi -} - -_makeTempDir_() { - # DESC: - # Creates a temp directory to house temporary files - # ARGS: - # $1 (Optional) - First characters/word of directory name - # OUTS: - # Sets $TMP_DIR variable to the path of the temp directory - # USAGE: - # _makeTempDir_ "$(basename "$0")" - - [ -d "${TMP_DIR:-}" ] && return 0 - - if [ -n "${1:-}" ]; then - TMP_DIR="${TMPDIR:-/tmp/}${1}.${RANDOM}.${RANDOM}.$$" - else - TMP_DIR="${TMPDIR:-/tmp/}$(basename "$0").${RANDOM}.${RANDOM}.${RANDOM}.$$" - fi - (umask 077 && mkdir "${TMP_DIR}") || { - fatal "Could not create temporary directory! Exiting." - } - debug "\$TMP_DIR=${TMP_DIR}" -} - -# shellcheck disable=SC2120 -_acquireScriptLock_() { - # DESC: - # Acquire script lock to prevent running the same script a second time before the - # first instance exits - # ARGS: - # $1 (optional) - Scope of script execution lock (system or user) - # OUTS: - # exports $SCRIPT_LOCK - Path to the directory indicating we have the script lock - # Exits script if lock cannot be acquired - # NOTE: - # If the lock was acquired it's automatically released in _safeExit_() - - local _lockDir - if [[ ${1:-} == 'system' ]]; then - _lockDir="${TMPDIR:-/tmp/}$(basename "$0").lock" - else - _lockDir="${TMPDIR:-/tmp/}$(basename "$0").${UID}.lock" - fi - - if command mkdir "${_lockDir}" 2>/dev/null; then - readonly SCRIPT_LOCK="${_lockDir}" - debug "Acquired script lock: ${yellow}${SCRIPT_LOCK}${purple}" - else - if declare -f "_safeExit_" &>/dev/null; then - error "Unable to acquire script lock: ${yellow}${_lockDir}${red}" - fatal "If you trust the script isn't running, delete the lock dir" - else - printf "%s\n" "ERROR: Could not acquire script lock. If you trust the script isn't running, delete: ${_lockDir}" - exit 1 - fi - - fi -} - -_setPATH_() { - # DESC: - # Add directories to $PATH so script can find executables - # ARGS: - # $@ - One or more paths - # OUTS: Adds items to $PATH - # USAGE: - # _setPATH_ "/usr/local/bin" "${HOME}/bin" "$(npm bin)" - - [[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}" - - local _newPath - - for _newPath in "$@"; do - if [ -d "${_newPath}" ]; then - if ! printf "%s" "${PATH}" | grep -Eq "(^|:)${_newPath}($|:)"; then - if PATH="${_newPath}:${PATH}"; then - debug "Added '${_newPath}' to PATH" - else - return 1 - fi - else - debug "_setPATH_: '${_newPath}' already exists in PATH" - fi - else - debug "_setPATH_: can not find: ${_newPath}" - return 0 - fi - done - return 0 -} - -_useGNUutils_() { - # DESC: - # Add GNU utilities to PATH to allow consistent use of sed/grep/tar/etc. on MacOS - # ARGS: - # None - # OUTS: - # 0 if successful - # 1 if unsuccessful - # PATH: Adds GNU utilities to the path - # USAGE: - # # if ! _useGNUUtils_; then exit 1; fi - # NOTES: - # GNU utilities can be added to MacOS using Homebrew - - [ ! "$(declare -f "_setPATH_")" ] && fatal "${FUNCNAME[0]} needs function _setPATH_" - - if _setPATH_ \ - "/usr/local/opt/gnu-tar/libexec/gnubin" \ - "/usr/local/opt/coreutils/libexec/gnubin" \ - "/usr/local/opt/gnu-sed/libexec/gnubin" \ - "/usr/local/opt/grep/libexec/gnubin"; then - return 0 - else - return 1 - fi - -} - -_parseOptions_() { - # DESC: - # Iterates through options passed to script and sets variables. Will break -ab into -a -b - # when needed and --foo=bar into --foo bar - # ARGS: - # $@ from command line - # OUTS: - # Sets array 'ARGS' containing all arguments passed to script that were not parsed as options - # USAGE: - # _parseOptions_ "$@" - - # Iterate over options - local _optstring=h - declare -a _options - local _c - local i - while (($#)); do - case $1 in - # If option is of type -ab - -[!-]?*) - # Loop over each character starting with the second - for ((i = 1; i < ${#1}; i++)); do - _c=${1:i:1} - _options+=("-${_c}") # Add current char to options - # If option takes a required argument, and it's not the last char make - # the rest of the string its argument - if [[ ${_optstring} == *"${_c}:"* && ${1:i+1} ]]; then - _options+=("${1:i+1}") - break - fi - done - ;; - # If option is of type --foo=bar - --?*=*) _options+=("${1%%=*}" "${1#*=}") ;; - # add --endopts for -- - --) _options+=(--endopts) ;; - # Otherwise, nothing special - *) _options+=("$1") ;; - esac - shift - done - set -- "${_options[@]:-}" - unset _options - - # Read the options and set stuff - # shellcheck disable=SC2034 - while [[ ${1:-} == -?* ]]; do - case $1 in - # Custom options - - # Common options - -h | --help) - _usage_ - _safeExit_ - ;; - --loglevel) - shift - LOGLEVEL=${1} - ;; - --logfile) - shift - LOGFILE="${1}" - ;; - -n | --dryrun) DRYRUN=true ;; - -v | --verbose) VERBOSE=true ;; - -q | --quiet) QUIET=true ;; - --force) FORCE=true ;; - --endopts) - shift - break - ;; - *) - if [ "$(declare -f "_safeExit_")" ]; then - fatal "invalid option: $1" - else - printf "%s\n" "Invalid option: $1" - exit 1 - fi - ;; - esac - shift - done - - if [[ -z ${*} || ${*} == null ]]; then - ARGS=() - else - ARGS+=("$@") # Store the remaining user input as arguments. - fi -} - -_usage_() { - cat <