diff --git a/clean_files.txt b/clean_files.txt index a5959798f9..28e3d379f2 100644 --- a/clean_files.txt +++ b/clean_files.txt @@ -78,6 +78,8 @@ completion/available/vuejs.completion.bash completion/available/wpscan.completion.bash # libraries +lib/helpers.bash +lib/search.bash lib/utilities.bash # plugins diff --git a/lib/helpers.bash b/lib/helpers.bash old mode 100755 new mode 100644 index 01211079a1..5bd88f546d --- a/lib/helpers.bash +++ b/lib/helpers.bash @@ -1,8 +1,11 @@ -#!/usr/bin/env bash - -BASH_IT_LOAD_PRIORITY_DEFAULT_ALIAS=${BASH_IT_LOAD_PRIORITY_DEFAULT_ALIAS:-150} -BASH_IT_LOAD_PRIORITY_DEFAULT_PLUGIN=${BASH_IT_LOAD_PRIORITY_DEFAULT_PLUGIN:-250} -BASH_IT_LOAD_PRIORITY_DEFAULT_COMPLETION=${BASH_IT_LOAD_PRIORITY_DEFAULT_COMPLETION:-350} +# shellcheck shell=bash +# shellcheck disable=SC2016 +# +# A collection of reusable functions. + +: "${BASH_IT_LOAD_PRIORITY_ALIAS:=150}" +: "${BASH_IT_LOAD_PRIORITY_PLUGIN:=250}" +: "${BASH_IT_LOAD_PRIORITY_COMPLETION:=350}" BASH_IT_LOAD_PRIORITY_SEPARATOR="---" # Handle the different ways of running `sed` without generating a backup file based on OS @@ -10,74 +13,75 @@ BASH_IT_LOAD_PRIORITY_SEPARATOR="---" # - BSD sed (macOS) uses `-i ''` # To use this in Bash-it for inline replacements with `sed`, use the following syntax: # sed "${BASH_IT_SED_I_PARAMETERS[@]}" -e "..." file -BASH_IT_SED_I_PARAMETERS=(-i) +BASH_IT_SED_I_PARAMETERS=('-i') +# shellcheck disable=SC2034 # expected for this case case "$OSTYPE" in - 'darwin'*) BASH_IT_SED_I_PARAMETERS=(-i "") + 'darwin'*) BASH_IT_SED_I_PARAMETERS=('-i' '') ;; esac -function _command_exists () -{ - _about 'checks for existence of a command' - _param '1: command to check' - _param '2: (optional) log message to include when command not found' - _example '$ _command_exists ls && echo exists' - _group 'lib' - local msg="${2:-Command '$1' does not exist!}" - if type -t "$1" &> /dev/null - then - return 0 - else - _log_warning "$msg" - return 1 - fi +function _command_exists() { + _about 'checks for existence of a command' + _param '1: command to check' + _param '2: (optional) log message to include when command not found' + _example '$ _command_exists ls && echo exists' + _group 'lib' + local msg="${2:-Command '$1' does not exist}" + if type -t "$1" > /dev/null; then + return 0 + else + _log_debug "$msg" + return 1 + fi } -function _binary_exists () -{ - _about 'checks for existence of a binary' - _param '1: binary to check' - _param '2: (optional) log message to include when binary not found' - _example '$ _binary_exists ls && echo exists' - _group 'lib' - local msg="${2:-Binary '$1' does not exist!}" - if type -P "$1" &> /dev/null - then - return 0 - else - _log_warning "$msg" - return 1 - fi +function _binary_exists() { + _about 'checks for existence of a binary' + _param '1: binary to check' + _param '2: (optional) log message to include when binary not found' + _example '$ _binary_exists ls && echo exists' + _group 'lib' + local msg="${2:-Binary '$1' does not exist}" + if type -P "$1" > /dev/null; then + return 0 + else + _log_debug "$msg" + return 1 + fi } -function _completion_exists () -{ - _about 'checks for existence of a completion' - _param '1: command to check' - _param '2: (optional) log message to include when completion is found' - _example '$ _completion_exists gh && echo exists' - _group 'lib' - local msg="${2:-Completion for '$1' already exists!}" - complete -p "$1" &> /dev/null && _log_warning "$msg" ; +function _completion_exists() { + _about 'checks for existence of a completion' + _param '1: command to check' + _param '2: (optional) log message to include when completion is found' + _example '$ _completion_exists gh && echo exists' + _group 'lib' + local msg="${2:-Completion for '$1' already exists}" + if complete -p "$1" &> /dev/null; then + _log_debug "$msg" + return 0 + else + return 1 + fi } -function _bash_it_homebrew_check() -{ - if _binary_exists 'brew' - then # Homebrew is installed - if [[ "${BASH_IT_HOMEBREW_PREFIX:-unset}" == 'unset' ]] - then # variable isn't set +function _bash_it_homebrew_check() { + if _binary_exists 'brew'; then + # Homebrew is installed + if [[ "${BASH_IT_HOMEBREW_PREFIX:-unset}" == 'unset' ]]; then + # variable isn't set BASH_IT_HOMEBREW_PREFIX="$(brew --prefix)" else true # Variable is set already, don't invoke `brew`. fi - else # Homebrew is not installed. - BASH_IT_HOMEBREW_PREFIX= # clear variable, if set to anything. + else + # Homebrew is not installed: clear variable. + BASH_IT_HOMEBREW_PREFIX= false # return failure if brew not installed. fi } function _make_reload_alias() { - echo "source \${BASH_IT}/scripts/reloader.bash ${1} ${2}" + echo "source '${BASH_IT?}/scripts/reloader.bash' '${1?}' '${2?}'" } # Alias for reloading aliases @@ -92,980 +96,933 @@ alias reload_completion="$(_make_reload_alias completion completion)" # shellcheck disable=SC2139 alias reload_plugins="$(_make_reload_alias plugin plugins)" -bash-it () -{ - about 'Bash-it help and maintenance' - param '1: verb [one of: help | show | enable | disable | migrate | update | search | version | reload | restart | doctor ] ' - param '2: component type [one of: alias(es) | completion(s) | plugin(s) ] or search term(s)' - param '3: specific component [optional]' - example '$ bash-it show plugins' - example '$ bash-it help aliases' - example '$ bash-it enable plugin git [tmux]...' - example '$ bash-it disable alias hg [tmux]...' - example '$ bash-it migrate' - example '$ bash-it update' - example '$ bash-it search [-|@]term1 [-|@]term2 ... [ -e/--enable ] [ -d/--disable ] [ -r/--refresh ] [ -c/--no-color ]' - example '$ bash-it version' - example '$ bash-it reload' - example '$ bash-it restart' - example '$ bash-it profile list|save|load|rm [profile_name]' - example '$ bash-it doctor errors|warnings|all' - typeset verb=${1:-} - shift - typeset component=${1:-} - shift - typeset func - - case $verb in - show) - func=_bash-it-$component;; - enable) - func=_enable-$component;; - disable) - func=_disable-$component;; - help) - func=_help-$component;; - doctor) - func=_bash-it-doctor-$component;; - profile) - func=_bash-it-profile-$component;; - search) - _bash-it-search $component "$@" - return;; - update) - func=_bash-it-update-$component;; - migrate) - func=_bash-it-migrate;; - version) - func=_bash-it-version;; - restart) - func=_bash-it-restart;; - reload) - func=_bash-it-reload;; - *) - reference bash-it - return;; - esac - - # pluralize component if necessary - if ! _is_function $func; then - if _is_function ${func}s; then - func=${func}s - else - if _is_function ${func}es; then - func=${func}es - else - echo "oops! $component is not a valid option!" - reference bash-it - return - fi - fi - fi - - if [ x"$verb" == x"enable" ] || [ x"$verb" == x"disable" ]; then - # Automatically run a migration if required - _bash-it-migrate - - for arg in "$@" - do - $func $arg - done - - if [ -n "${BASH_IT_AUTOMATIC_RELOAD_AFTER_CONFIG_CHANGE:-}" ]; then - _bash-it-reload - fi - else - $func "$@" - fi +function bash-it() { + about 'Bash-it help and maintenance' + param '1: verb [one of: help | show | enable | disable | migrate | update | search | version | reload | restart | doctor ] ' + param '2: component type [one of: alias(es) | completion(s) | plugin(s) ] or search term(s)' + param '3: specific component [optional]' + example '$ bash-it show plugins' + example '$ bash-it help aliases' + example '$ bash-it enable plugin git [tmux]...' + example '$ bash-it disable alias hg [tmux]...' + example '$ bash-it migrate' + example '$ bash-it update' + example '$ bash-it search [-|@]term1 [-|@]term2 ... [ -e/--enable ] [ -d/--disable ] [ -r/--refresh ] [ -c/--no-color ]' + example '$ bash-it version' + example '$ bash-it reload' + example '$ bash-it restart' + example '$ bash-it profile list|save|load|rm [profile_name]' + example '$ bash-it doctor errors|warnings|all' + local verb=${1:-} + shift + local component=${1:-} + shift + local func + + case "$verb" in + show) + func="_bash-it-$component" + ;; + enable) + func="_enable-$component" + ;; + disable) + func="_disable-$component" + ;; + help) + func="_help-$component" + ;; + doctor) + func="_bash-it-doctor-$component" + ;; + profile) + func=_bash-it-profile-$component + ;; + search) + _bash-it-search "$component" "$@" + return + ;; + update) + func="_bash-it-update-$component" + ;; + migrate) + func="_bash-it-migrate" + ;; + version) + func="_bash-it-version" + ;; + restart) + func="_bash-it-restart" + ;; + reload) + func="_bash-it-reload" + ;; + *) + reference "bash-it" + return + ;; + esac + + # pluralize component if necessary + if ! _is_function "$func"; then + if _is_function "${func}s"; then + func="${func}s" + else + if _is_function "${func}es"; then + func="${func}es" + else + echo "oops! $component is not a valid option!" + reference bash-it + return + fi + fi + fi + + if [[ "$verb" == "enable" || "$verb" == "disable" ]]; then + # Automatically run a migration if required + _bash-it-migrate + + for arg in "$@"; do + "$func" "$arg" + done + + if [[ -n "${BASH_IT_AUTOMATIC_RELOAD_AFTER_CONFIG_CHANGE:-}" ]]; then + _bash-it-reload + fi + else + "$func" "$@" + fi } -_is_function () -{ - _about 'sets $? to true if parameter is the name of a function' - _param '1: name of alleged function' - _group 'lib' - [ -n "$(LANG=C type -t $1 2>/dev/null | grep 'function')" ] +function _is_function() { + _about 'sets $? to true if parameter is the name of a function' + _param '1: name of alleged function' + _param '2: (optional) log message to include when function not found' + _group 'lib' + _example '$ _is_function ls && echo exists' + _group 'lib' + local msg="${2:-Function '$1' does not exist}" + if LC_ALL=C type -t "$1" | _bash-it-egrep -q 'function'; then + return 0 + else + _log_debug "$msg" + return 1 + fi } -_bash-it-aliases () -{ - _about 'summarizes available bash_it aliases' - _group 'lib' +function _bash-it-aliases() { + _about 'summarizes available bash_it aliases' + _group 'lib' - _bash-it-describe "aliases" "an" "alias" "Alias" + _bash-it-describe "aliases" "an" "alias" "Alias" } -_bash-it-completions () -{ - _about 'summarizes available bash_it completions' - _group 'lib' +function _bash-it-completions() { + _about 'summarizes available bash_it completions' + _group 'lib' - _bash-it-describe "completion" "a" "completion" "Completion" + _bash-it-describe "completion" "a" "completion" "Completion" } -_bash-it-plugins () -{ - _about 'summarizes available bash_it plugins' - _group 'lib' +function _bash-it-plugins() { + _about 'summarizes available bash_it plugins' + _group 'lib' - _bash-it-describe "plugins" "a" "plugin" "Plugin" + _bash-it-describe "plugins" "a" "plugin" "Plugin" } -_bash-it-update-dev() { - _about 'updates Bash-it to the latest master' - _group 'lib' +function _bash-it-update-dev() { + _about 'updates Bash-it to the latest master' + _group 'lib' - _bash-it-update- dev "$@" + _bash-it-update- dev "$@" } -_bash-it-update-stable() { - _about 'updates Bash-it to the latest tag' - _group 'lib' +function _bash-it-update-stable() { + _about 'updates Bash-it to the latest tag' + _group 'lib' - _bash-it-update- stable "$@" + _bash-it-update- stable "$@" } -_bash-it_update_migrate_and_restart() { +function _bash-it_update_migrate_and_restart() { _about 'Checks out the wanted version, pops directory and restart. Does not return (because of the restart!)' - _param '1: Which branch to checkout to' - _param '2: Which type of version we are using' - git checkout "$1" &> /dev/null - if [[ $? -eq 0 ]]; then - echo "Bash-it successfully updated." - echo "" - echo "Migrating your installation to the latest $2 version now..." - _bash-it-migrate - echo "" - echo "All done, enjoy!" - # Don't forget to restore the original pwd! - popd &> /dev/null - _bash-it-restart - else - echo "Error updating Bash-it, please, check if your Bash-it installation folder (${BASH_IT}) is clean." - fi + _param '1: Which branch to checkout to' + _param '2: Which type of version we are using' + if git checkout "$1" &> /dev/null; then + echo "Bash-it successfully updated." + echo "" + echo "Migrating your installation to the latest $2 version now..." + _bash-it-migrate + echo "" + echo "All done, enjoy!" + # Don't forget to restore the original pwd (called from `_bash-it-update-`)! + popd > /dev/null || return + _bash-it-restart + else + echo "Error updating Bash-it, please, check if your Bash-it installation folder (${BASH_IT}) is clean." + fi } -_bash-it-update-() { - _about 'updates Bash-it' - _param '1: What kind of update to do (stable|dev)' - _group 'lib' - - declare silent - for word in $@; do - if [[ ${word} == "--silent" || ${word} == "-s" ]]; then - silent=true - fi - done - - pushd "${BASH_IT}" &> /dev/null || return - - DIFF=$(git diff --name-status) - [ -n "$DIFF" ] && echo -e "Local changes detected in bash-it directory. Clean '$BASH_IT' directory to proceed.\n$DIFF" && return 1 - - if [ -z "$BASH_IT_REMOTE" ]; then - BASH_IT_REMOTE="origin" - fi - - git fetch $BASH_IT_REMOTE --tags &> /dev/null - - if [ -z "$BASH_IT_DEVELOPMENT_BRANCH" ]; then - BASH_IT_DEVELOPMENT_BRANCH="master" - fi - # Defaults to stable update - if [ -z "$1" ] || [ "$1" == "stable" ]; then - version="stable" - TARGET=$(git describe --tags "$(git rev-list --tags --max-count=1)" 2> /dev/null) - - if [[ -z "$TARGET" ]]; then - echo "Can not find tags, so can not update to latest stable version..." - popd &> /dev/null - return - fi - else - version="dev" - TARGET=${BASH_IT_REMOTE}/${BASH_IT_DEVELOPMENT_BRANCH} - fi - - declare revision - revision="HEAD..${TARGET}" - declare status - status="$(git rev-list ${revision} 2> /dev/null)" - declare revert - - if [[ -z "${status}" && ${version} == "stable" ]]; then - revision="${TARGET}..HEAD" - status="$(git rev-list ${revision} 2> /dev/null)" - revert=true - fi - - if [[ -n "${status}" ]]; then - if [[ $revert ]]; then - echo "Your version is a more recent development version ($(git log -1 --format=%h HEAD))" - echo "You can continue in order to revert and update to the latest stable version" - echo "" - log_color="%Cred" - fi - - for i in $(git rev-list --merges --first-parent ${revision}); do - num_of_lines=$(git log -1 --format=%B $i | awk 'NF' | wc -l) - if [ $num_of_lines -eq 1 ]; then - description="%s" - else - description="%b" - fi - git log --format="${log_color}%h: $description (%an)" -1 $i - done - echo "" - - if [[ $silent ]]; then - echo "Updating to ${TARGET}($(git log -1 --format=%h "${TARGET}"))..." - _bash-it_update_migrate_and_restart $TARGET $version - else - read -e -n 1 -p "Would you like to update to ${TARGET}($(git log -1 --format=%h "${TARGET}"))? [Y/n] " RESP - case $RESP in - [yY]|"") - _bash-it_update_migrate_and_restart $TARGET $version - ;; - [nN]) - echo "Not updating…" - ;; - *) - echo -e "\033[91mPlease choose y or n.\033[m" - ;; - esac - fi - else - if [[ ${version} == "stable" ]]; then - echo "You're on the latest stable version. If you want to check out the latest 'dev' version, please run \"bash-it update dev\"" - else - echo "Bash-it is up to date, nothing to do!" - fi - fi - popd &> /dev/null +function _bash-it-update-() { + _about 'updates Bash-it' + _param '1: What kind of update to do (stable|dev)' + _group 'lib' + + local silent word DIFF version TARGET revision status revert log_color num_of_lines description i RESP + for word in "$@"; do + if [[ "${word}" == "--silent" || "${word}" == "-s" ]]; then + silent=true + fi + done + + pushd "${BASH_IT?}" > /dev/null || return + + DIFF=$(git diff --name-status) + if [[ -n "$DIFF" ]]; then + echo -e "Local changes detected in bash-it directory. Clean '$BASH_IT' directory to proceed.\n$DIFF" + return 1 + fi + + if [[ -z "$BASH_IT_REMOTE" ]]; then + BASH_IT_REMOTE="origin" + fi + + git fetch "$BASH_IT_REMOTE" --tags &> /dev/null + + if [[ -z "$BASH_IT_DEVELOPMENT_BRANCH" ]]; then + BASH_IT_DEVELOPMENT_BRANCH="master" + fi + # Defaults to stable update + if [[ -z "$1" || "$1" == "stable" ]]; then + version="stable" + TARGET=$(git describe --tags "$(git rev-list --tags --max-count=1)" 2> /dev/null) + + if [[ -z "$TARGET" ]]; then + echo "Can not find tags, so can not update to latest stable version..." + popd > /dev/null || return + return + fi + else + version="dev" + TARGET="${BASH_IT_REMOTE}/${BASH_IT_DEVELOPMENT_BRANCH}" + fi + + revision="HEAD..${TARGET}" + status="$(git rev-list "${revision}" 2> /dev/null)" + + if [[ -z "${status}" && "${version}" == "stable" ]]; then + revision="${TARGET}..HEAD" + status="$(git rev-list "${revision}" 2> /dev/null)" + revert=true + fi + + if [[ -n "${status}" ]]; then + if [[ -n "${revert}" ]]; then + echo "Your version is a more recent development version ($(git log -1 --format=%h HEAD))" + echo "You can continue in order to revert and update to the latest stable version" + echo "" + log_color="%Cred" + fi + + for i in $(git rev-list --merges --first-parent "${revision}"); do + num_of_lines=$(git log -1 --format=%B "$i" | awk '!/^[[:space:]]*$ {++i} END{print i}') + if [[ "$num_of_lines" -eq 1 ]]; then + description="%s" + else + description="%b" + fi + git log --format="${log_color}%h: $description (%an)" -1 "$i" + done + echo "" + + if [[ -n "${silent}" ]]; then + echo "Updating to ${TARGET}($(git log -1 --format=%h "${TARGET}"))..." + _bash-it_update_migrate_and_restart "$TARGET" "$version" + else + read -r -e -n 1 -p "Would you like to update to ${TARGET}($(git log -1 --format=%h "${TARGET}"))? [Y/n] " RESP + case "$RESP" in + [yY] | "") + _bash-it_update_migrate_and_restart "$TARGET" "$version" + ;; + [nN]) + echo "Not updating…" + ;; + *) + echo -e "${echo_orange?}Please choose y or n.${echo_reset_color?}" + ;; + esac + fi + else + if [[ "${version}" == "stable" ]]; then + echo "You're on the latest stable version. If you want to check out the latest 'dev' version, please run \"bash-it update dev\"" + else + echo "Bash-it is up to date, nothing to do!" + fi + fi + popd > /dev/null || return } -_bash-it-migrate() { - _about 'migrates Bash-it configuration from a previous format to the current one' - _group 'lib' +function _bash-it-migrate() { + _about 'migrates Bash-it configuration from a previous format to the current one' + _group 'lib' - declare migrated_something - migrated_something=false + local migrated_something component_type component_name single_type file_type _bash_it_config_file disable_func enable_func + migrated_something=false - for file_type in "aliases" "plugins" "completion" - do - for f in `sort <(compgen -G "${BASH_IT}/$file_type/enabled/*.bash")` - do - typeset ff="${f##*/}" + for file_type in "aliases" "plugins" "completion"; do + for _bash_it_config_file in "${BASH_IT}/$file_type/enabled"/*.bash; do + [[ -f "$_bash_it_config_file" ]] || continue - # Get the type of component from the extension - typeset single_type=$(echo $ff | sed -e 's/.*\.\(.*\)\.bash/\1/g' | sed 's/aliases/alias/g') - # Cut off the optional "250---" prefix and the suffix - typeset component_name=$(echo $ff | sed -e 's/[0-9]*[-]*\(.*\)\..*\.bash/\1/g') + # Get the type of component from the extension + component_type="$(_bash-it-get-component-type-from-path "$_bash_it_config_file")" + # Cut off the optional "250---" prefix and the suffix + component_name="$(_bash-it-get-component-name-from-path "$_bash_it_config_file")" - migrated_something=true + migrated_something=true - echo "Migrating $single_type $component_name." + single_type="${component_type/aliases/aliass}" + echo "Migrating ${single_type%s} $component_name." - disable_func="_disable-$single_type" - enable_func="_enable-$single_type" + disable_func="_disable-${single_type%s}" + enable_func="_enable-${single_type%s}" - $disable_func "$component_name" - $enable_func "$component_name" - done - done + "$disable_func" "$component_name" + "$enable_func" "$component_name" + done + done - if [ -n "$BASH_IT_AUTOMATIC_RELOAD_AFTER_CONFIG_CHANGE" ]; then - _bash-it-reload - fi + if [[ -n "${BASH_IT_AUTOMATIC_RELOAD_AFTER_CONFIG_CHANGE:-}" ]]; then + _bash-it-reload + fi - if [ "$migrated_something" = "true" ]; then - echo "" - echo "If any migration errors were reported, please try the following: reload && bash-it migrate" - fi + if [[ "$migrated_something" == "true" ]]; then + echo "" + echo "If any migration errors were reported, please try the following: reload && bash-it migrate" + fi } -_bash-it-version() { - _about 'shows current Bash-it version' - _group 'lib' - - cd "${BASH_IT}" || return - - if [ -z "${BASH_IT_REMOTE:-}" ]; then - BASH_IT_REMOTE="origin" - fi - - BASH_IT_GIT_REMOTE=$(git remote get-url $BASH_IT_REMOTE) - BASH_IT_GIT_URL=${BASH_IT_GIT_REMOTE%.git} - if [[ "$BASH_IT_GIT_URL" == *"git@"* ]]; then - # Fix URL in case it is ssh based URL - BASH_IT_GIT_URL=${BASH_IT_GIT_URL/://} - BASH_IT_GIT_URL=${BASH_IT_GIT_URL/git@/https://} - fi - - current_tag=$(git describe --exact-match --tags 2> /dev/null) - - if [[ -z $current_tag ]]; then - BASH_IT_GIT_VERSION_INFO="$(git log --pretty=format:'%h on %aI' -n 1)" - TARGET=${BASH_IT_GIT_VERSION_INFO%% *} - echo "Version type: dev" - echo "Current git SHA: $BASH_IT_GIT_VERSION_INFO" - echo "Commit info: $BASH_IT_GIT_URL/commit/$TARGET" - else - TARGET=$current_tag - echo "Version type: stable" - echo "Current tag: $current_tag" - echo "Tag information: $BASH_IT_GIT_URL/releases/tag/$current_tag" - fi - - echo "Compare to latest: $BASH_IT_GIT_URL/compare/$TARGET...master" - - cd - &> /dev/null || return +function _bash-it-version() { + _about 'shows current Bash-it version' + _group 'lib' + + local BASH_IT_GIT_REMOTE BASH_IT_GIT_URL current_tag BASH_IT_GIT_VERSION_INFO TARGET + + pushd "${BASH_IT?}" > /dev/null || return + + if [[ -z "${BASH_IT_REMOTE:-}" ]]; then + BASH_IT_REMOTE="origin" + fi + + BASH_IT_GIT_REMOTE="$(git remote get-url "$BASH_IT_REMOTE")" + BASH_IT_GIT_URL="${BASH_IT_GIT_REMOTE%.git}" + if [[ "$BASH_IT_GIT_URL" == *"git@"* ]]; then + # Fix URL in case it is ssh based URL + BASH_IT_GIT_URL="${BASH_IT_GIT_URL/://}" + BASH_IT_GIT_URL="${BASH_IT_GIT_URL/git@/https://}" + fi + + current_tag="$(git describe --exact-match --tags 2> /dev/null)" + + if [[ -z "$current_tag" ]]; then + BASH_IT_GIT_VERSION_INFO="$(git log --pretty=format:'%h on %aI' -n 1)" + TARGET="${BASH_IT_GIT_VERSION_INFO%% *}" + echo "Version type: dev" + echo "Current git SHA: $BASH_IT_GIT_VERSION_INFO" + echo "Commit info: $BASH_IT_GIT_URL/commit/$TARGET" + else + TARGET="$current_tag" + echo "Version type: stable" + echo "Current tag: $current_tag" + echo "Tag information: $BASH_IT_GIT_URL/releases/tag/$current_tag" + fi + + echo "Compare to latest: $BASH_IT_GIT_URL/compare/$TARGET...master" + + popd > /dev/null || return } -_bash-it-doctor() { - _about 'reloads a profile file with a BASH_IT_LOG_LEVEL set' - _param '1: BASH_IT_LOG_LEVEL argument: "errors" "warnings" "all"' - _group 'lib' +function _bash-it-doctor() { + _about 'reloads a profile file with a BASH_IT_LOG_LEVEL set' + _param '1: BASH_IT_LOG_LEVEL argument: "errors" "warnings" "all"' + _group 'lib' - BASH_IT_LOG_LEVEL=$1 - _bash-it-reload - unset BASH_IT_LOG_LEVEL + # shellcheck disable=SC2034 # expected for this case + local BASH_IT_LOG_LEVEL="${1?}" + _bash-it-reload } -_bash-it-doctor-all() { - _about 'reloads a profile file with error, warning and debug logs' - _group 'lib' +function _bash-it-doctor-all() { + _about 'reloads a profile file with error, warning and debug logs' + _group 'lib' - _bash-it-doctor $BASH_IT_LOG_LEVEL_ALL + _bash-it-doctor "${BASH_IT_LOG_LEVEL_ALL?}" } -_bash-it-doctor-warnings() { - _about 'reloads a profile file with error and warning logs' - _group 'lib' +function _bash-it-doctor-warnings() { + _about 'reloads a profile file with error and warning logs' + _group 'lib' - _bash-it-doctor $BASH_IT_LOG_LEVEL_WARNING + _bash-it-doctor "${BASH_IT_LOG_LEVEL_WARNING?}" } -_bash-it-doctor-errors() { - _about 'reloads a profile file with error logs' - _group 'lib' +function _bash-it-doctor-errors() { + _about 'reloads a profile file with error logs' + _group 'lib' - _bash-it-doctor $BASH_IT_LOG_LEVEL_ERROR + _bash-it-doctor "${BASH_IT_LOG_LEVEL_ERROR?}" } -_bash-it-doctor-() { - _about 'default bash-it doctor behavior, behaves like bash-it doctor all' - _group 'lib' +function _bash-it-doctor-() { + _about 'default bash-it doctor behavior, behaves like bash-it doctor all' + _group 'lib' - _bash-it-doctor-all + _bash-it-doctor-all } -_bash-it-profile-save() { - _about 'saves the current configuration to the "profile" directory' - _group 'lib' - - local name=$1 - while [ -z "$1" ]; do - read -r -e -p "Please enter the name of the profile to save: " name - case $name in - "") - echo -e "\033[91mPlease choose a name.\033[m" - ;; - *) - break - ;; - esac - done - - local profile_path="${BASH_IT}/profiles/${name}.bash_it" - if [ -f "$profile_path" ]; then - echo -e "\033[0;33mProfile \"$name\" already exists.\033[m" - while true; do - read -r -e -n 1 -p "Would you like to overwrite existing profile? [y/N] " RESP - case $RESP in - [yY]) - echo -e "\033[0;32mOverwriting profile \"$name\"...\033[m" - rm "$profile_path" - break - ;; - [nN] | "") - echo -e "\033[91mAborting profile save...\033[m" - return 1 - ;; - *) - echo -e "\033[91mPlease choose y or n.\033[m" - ;; - esac - done - fi - - local something_exists - echo "# This file is auto generated by Bash-it. Do not edit manually!" > "$profile_path" - for subdirectory in "plugins" "completion" "aliases"; do - local component_exists="" - echo "Saving $subdirectory configuration..." - for f in "${BASH_IT}/$subdirectory/available/"*.bash; do - _bash-it-determine-component-status-from-path "$f" - if [ "$enabled" == "x" ]; then - if [ -z "$component_exists" ]; then - # This is the first component of this type, print the header - component_exists="yes" - something_exists="yes" - echo "" >> "$profile_path" - echo "# $subdirectory" >> "$profile_path" - fi - echo "$subdirectory $enabled_file_clean" >> "$profile_path" - fi - done - done - if [ -z "$something_exists" ]; then - echo "It seems like no configuration was enabled.." - echo "Make sure to double check that this is the wanted behavior." - fi - - echo "All done!" - echo "" - echo "Profile location: $profile_path" - echo "Load the profile by invoking \"bash-it profile load $name\"" +function _bash-it-profile-save() { + _about 'saves the current configuration to the "profile" directory' + _group 'lib' + + local name="${1:-}" + while [[ -z "$name" ]]; do + read -r -e -p "Please enter the name of the profile to save: " name + case "$name" in + "") + echo -e "${echo_orange?}Please choose a name.${echo_reset_color?}" + ;; + *) + break + ;; + esac + done + + local profile_path="${BASH_IT}/profiles/${name}.bash_it" RESP + if [[ -s "$profile_path" ]]; then + echo -e "${echo_yellow?}Profile '$name' already exists.${echo_reset_color?}" + while true; do + read -r -e -n 1 -p "Would you like to overwrite existing profile? [y/N] " RESP + case "$RESP" in + [yY]) + echo -e "${echo_green?}Overwriting profile '$name'...${echo_reset_color?}" + rm "$profile_path" + break + ;; + [nN] | "") + echo -e "${echo_orange?}Aborting profile save...${echo_reset_color?}" + return 1 + ;; + *) + echo -e "${echo_orange?}Please choose y or n.${echo_reset_color?}" + ;; + esac + done + fi + + local something_exists subdirectory component_exists f enabled_file + echo "# This file is auto generated by Bash-it. Do not edit manually!" > "$profile_path" + for subdirectory in "plugins" "completion" "aliases"; do + echo "Saving $subdirectory configuration..." + for f in "${BASH_IT}/$subdirectory/available"/*.bash; do + if _bash-it-component-item-is-enabled "$f"; then + if [[ -z "${component_exists:-}" ]]; then + # This is the first component of this type, print the header + component_exists="yes" + something_exists="yes" + echo "" >> "$profile_path" + echo "# $subdirectory" >> "$profile_path" + fi + enabled_file="$(_bash-it-get-component-name-from-path "$f")" + echo "$subdirectory $enabled_file" >> "$profile_path" + fi + done + done + if [[ -z "$something_exists" ]]; then + echo "It seems like no configuration was enabled.." + echo "Make sure to double check that this is the wanted behavior." + fi + + echo "All done!" + echo "" + echo "Profile location: $profile_path" + echo "Load the profile by invoking \"bash-it profile load $name\"" } _bash-it-profile-load-parse-profile() { - _about 'Internal function used to parse the profile file' - _param '1: path to the profile file' - _param '2: dry run- only check integrity of the profile file' - _example '$ _bash-it-profile-load-parse-profile "profile.bash_it" "dry"' - - local num=0 - while read -r -a line; do - num=$((num + 1)) - # Ignore comments and empty lines - [[ -z "${line[*]}" || "${line[*]}" =~ ^#.* ]] && continue - local enable_func="_enable-${line[0]}" - local subdirectory=${line[0]} - local component=${line[1]} - - typeset to_enable=$(command ls "${BASH_IT}/$subdirectory/available/$component".*bash 2>/dev/null | head -1) - # Ignore botched lines - if [[ -z "$to_enable" ]]; then - echo -e "\033[91mBad line(#$num) in profile, aborting load...\033[m" - local bad="bad line" - break - fi - # Do not actually modify config on dry run - [[ -z $2 ]] || continue - # Actually enable the component - $enable_func "$component" - done < "$1" - - # Make sure to propagate the error - [[ -z $bad ]] + _about 'Internal function used to parse the profile file' + _param '1: path to the profile file' + _param '2: dry run- only check integrity of the profile file' + _example '$ _bash-it-profile-load-parse-profile "profile.bash_it" "dry"' + + local -i num=0 + local line + while read -r -a line; do + ((++num)) + # Ignore comments and empty lines + [[ -z "${line[*]}" || "${line[*]}" =~ ^#.* ]] && continue + local enable_func="_enable-${line[0]}" + local subdirectory=${line[0]} + local component=${line[1]} + + local to_enable=("${BASH_IT}/$subdirectory/available/$component.${subdirectory%s}"*.bash) + # Ignore botched lines + if [[ ! -e "${to_enable[0]}" ]]; then + echo -e "${echo_orange?}Bad line(#$num) in profile, aborting load...${line[*]}${echo_reset_color?}" + local bad="bad line" + break + fi + # Do not actually modify config on dry run + [[ -z $2 ]] || continue + # Actually enable the component + $enable_func "$component" + done < "$1" + + # Make sure to propagate the error + [[ -z $bad ]] } _bash-it-profile-list() { - about 'lists all profiles from the "profiles" directory' - _group 'lib' - - echo "Available profiles:" - for profile in "${BASH_IT}/profiles"/*.bash_it; do - profile="${profile##*/}" - echo "${profile/.bash_it/}" - done + about 'lists all profiles from the "profiles" directory' + _group 'lib' + local profile + + echo "Available profiles:" + for profile in "${BASH_IT}/profiles"/*.bash_it; do + profile="${profile##*/}" + echo "${profile/.bash_it/}" + done } _bash-it-profile-rm() { - about 'Removes a profile from the "profiles" directory' - _group 'lib' - local name="$1" - if [[ -z $name ]]; then - echo -e "\033[91mPlease specify profile name to remove...\033[m" - return 1 - fi - - # Users should not be allowed to delete the default profile - if [[ $name == "default" ]]; then - echo -e "\033[91mCan not remove the default profile...\033[m" - return 1 - fi - - local profile_path="${BASH_IT}/profiles/$name.bash_it" - if [[ ! -f "$profile_path" ]]; then - echo -e "\033[91mCould not find profile \"$name\"...\033[m" - return 1 - fi - - command rm "$profile_path" - echo "Removed profile \"$name\" successfully!" + about 'Removes a profile from the "profiles" directory' + _group 'lib' + + local name="$1" + if [[ -z $name ]]; then + echo -e "${echo_orange?}Please specify profile name to remove...${echo_reset_color?}" + return 1 + fi + + # Users should not be allowed to delete the default profile + if [[ $name == "default" ]]; then + echo -e "${echo_orange?}Can not remove the default profile...${echo_reset_color?}" + return 1 + fi + + local profile_path="${BASH_IT}/profiles/$name.bash_it" + if [[ ! -f "$profile_path" ]]; then + echo -e "${echo_orange?}Could not find profile '$name'...${echo_reset_color?}" + return 1 + fi + + command rm "$profile_path" + echo "Removed profile '$name' successfully!" } _bash-it-profile-load() { - _about 'loads a configuration from the "profiles" directory' - _group 'lib' - - local name="$1" - if [[ -z $name ]]; then - echo -e "\033[91mPlease specify profile name to load, not changing configuration...\033[m" - return 1 - fi - - local profile_path="${BASH_IT}/profiles/$name.bash_it" - if [[ ! -f "$profile_path" ]]; then - echo -e "\033[91mCould not find profile \"$name\", not changing configuration...\033[m" - return 1 - fi - - echo "Trying to parse profile \"$name\"..." - if _bash-it-profile-load-parse-profile "$profile_path" "dry"; then - echo "Profile \"$name\" parsed successfully!" - echo "Disabling current configuration..." - _disable-all - echo "" - echo "Enabling configuration based on profile..." - _bash-it-profile-load-parse-profile "$profile_path" - echo "" - echo "Profile \"$name\" enabled!" - fi -} + _about 'loads a configuration from the "profiles" directory' + _group 'lib' -_bash-it-restart() { - _about 'restarts the shell in order to fully reload it' - _group 'lib' - - saved_pwd="${PWD}" - - case $OSTYPE in - darwin*) - init_file=.bash_profile - ;; - *) - init_file=.bashrc - ;; - esac - exec "${0/-/}" --rcfile <(echo "source \"$HOME/$init_file\"; cd \"$saved_pwd\"") -} + local name="$1" + if [[ -z $name ]]; then + echo -e "${echo_orange?}Please specify profile name to load, not changing configuration...${echo_reset_color?}" + return 1 + fi + + local profile_path="${BASH_IT}/profiles/$name.bash_it" + if [[ ! -f "$profile_path" ]]; then + echo -e "${echo_orange?}Could not find profile '$name', not changing configuration...${echo_reset_color?}" + return 1 + fi -_bash-it-reload() { - _about 'reloads a profile file' - _group 'lib' + echo "Trying to parse profile '$name'..." + if _bash-it-profile-load-parse-profile "$profile_path" "dry"; then + echo "Profile '$name' parsed successfully!" + echo "Disabling current configuration..." + _disable-all + echo "" + echo "Enabling configuration based on profile..." + _bash-it-profile-load-parse-profile "$profile_path" + echo "" + echo "Profile '$name' enabled!" + else + false # failure + fi +} - pushd "${BASH_IT}" &> /dev/null || return +function _bash-it-restart() { + _about 'restarts the shell in order to fully reload it' + _group 'lib' - case $OSTYPE in - darwin*) - source ~/.bash_profile - ;; - *) - source ~/.bashrc - ;; - esac + local saved_pwd="${PWD}" init_file - popd &> /dev/null || return + if shopt -q login_shell; then + init_file=.bash_profile + else + init_file=.bashrc + fi + exec "${0/-/}" --rcfile <(echo "source \"$HOME/$init_file\"; cd \"$saved_pwd\"") } -_bash-it-determine-component-status-from-path () -{ - _about 'internal function used to process component name and check if its enabled' - _param '1: full path to available component file' - _example '$ _bash-it-determine-component-status-from-path "${BASH_IT}/plugins/available/git.plugin.bash' - - # Check for both the old format without the load priority, and the extended format with the priority - declare enabled_files enabled_file - enabled_file="${f##*/}" - enabled_file_clean=$(echo "$enabled_file" | sed -e 's/\(.*\)\..*\.bash/\1/g') - enabled_files=$(sort <(compgen -G "${BASH_IT}/enabled/*$BASH_IT_LOAD_PRIORITY_SEPARATOR${enabled_file}") <(compgen -G "${BASH_IT}/$subdirectory/enabled/${enabled_file}") <(compgen -G "${BASH_IT}/$subdirectory/enabled/*$BASH_IT_LOAD_PRIORITY_SEPARATOR${enabled_file}") | wc -l) - - if [ "$enabled_files" -gt 0 ]; then - enabled='x' - else - enabled=' ' - fi +function _bash-it-reload() { + _about 'reloads a profile file' + _group 'lib' + + pushd "${BASH_IT?}" > /dev/null || return + + # shellcheck disable=SC1090 + if shopt -q login_shell; then + # shellcheck source-path=$HOME + source ~/.bash_profile + else + # shellcheck source-path=$HOME + source ~/.bashrc + fi + popd > /dev/null || return } -_bash-it-describe () -{ - _about 'summarizes available bash_it components' - _param '1: subdirectory' - _param '2: preposition' - _param '3: file_type' - _param '4: column_header' - _example '$ _bash-it-describe "plugins" "a" "plugin" "Plugin"' - - subdirectory="$1" - preposition="$2" - file_type="$3" - column_header="$4" - - typeset f - typeset enabled - printf "%-20s%-10s%s\n" "$column_header" 'Enabled?' 'Description' - for f in "${BASH_IT}/$subdirectory/available/"*.bash - do - _bash-it-determine-component-status-from-path "$f" - printf "%-20s%-10s%s\n" "$enabled_file_clean" " [$enabled]" "$(cat $f | metafor about-$file_type)" - done - printf '\n%s\n' "to enable $preposition $file_type, do:" - printf '%s\n' "$ bash-it enable $file_type <$file_type name> [$file_type name]... -or- $ bash-it enable $file_type all" - printf '\n%s\n' "to disable $preposition $file_type, do:" - printf '%s\n' "$ bash-it disable $file_type <$file_type name> [$file_type name]... -or- $ bash-it disable $file_type all" +function _bash-it-describe() { + _about 'summarizes available bash_it components' + _param '1: subdirectory' + _param '2: preposition' + _param '3: file_type' + _param '4: column_header' + _example '$ _bash-it-describe "plugins" "a" "plugin" "Plugin"' + + local subdirectory preposition file_type column_header f enabled enabled_file + subdirectory="$1" + preposition="$2" + file_type="$3" + column_header="$4" + + printf "%-20s %-10s %s\n" "$column_header" 'Enabled?' 'Description' + for f in "${BASH_IT?}/$subdirectory/available"/*.*.bash; do + enabled='' + enabled_file="${f##*/}" + enabled_file="${enabled_file%."${file_type}"*.bash}" + _bash-it-component-item-is-enabled "${file_type}" "${enabled_file}" && enabled='x' + printf "%-20s %-10s %s\n" "$enabled_file" "[${enabled:- }]" "$(metafor "about-$file_type" < "$f")" + done + printf '\n%s\n' "to enable $preposition $file_type, do:" + printf '%s\n' "$ bash-it enable $file_type <$file_type name> [$file_type name]... -or- $ bash-it enable $file_type all" + printf '\n%s\n' "to disable $preposition $file_type, do:" + printf '%s\n' "$ bash-it disable $file_type <$file_type name> [$file_type name]... -or- $ bash-it disable $file_type all" } -_on-disable-callback() -{ - _about 'Calls the disabled plugin destructor, if present' - _param '1: plugin name' - _example '$ _on-disable-callback gitstatus' - _group 'lib' +function _on-disable-callback() { + _about 'Calls the disabled plugin destructor, if present' + _param '1: plugin name' + _example '$ _on-disable-callback gitstatus' + _group 'lib' - callback=$1_on_disable - _command_exists $callback && $callback + local callback="${1}_on_disable" + _command_exists "$callback" && "$callback" } -_disable-all () -{ - _about 'disables all bash_it components' - _example '$ _disable-all' - _group 'lib' +function _disable-all() { + _about 'disables all bash_it components' + _example '$ _disable-all' + _group 'lib' - _disable-plugin "all" - _disable-alias "all" - _disable-completion "all" + _disable-plugin "all" + _disable-alias "all" + _disable-completion "all" } -_disable-plugin () -{ - _about 'disables bash_it plugin' - _param '1: plugin name' - _example '$ disable-plugin rvm' - _group 'lib' +function _disable-plugin() { + _about 'disables bash_it plugin' + _param '1: plugin name' + _example '$ disable-plugin rvm' + _group 'lib' - _disable-thing "plugins" "plugin" $1 - _on-disable-callback $1 + _disable-thing "plugins" "plugin" "$1" + _on-disable-callback "$1" } -_disable-alias () -{ - _about 'disables bash_it alias' - _param '1: alias name' - _example '$ disable-alias git' - _group 'lib' +function _disable-alias() { + _about 'disables bash_it alias' + _param '1: alias name' + _example '$ disable-alias git' + _group 'lib' - _disable-thing "aliases" "alias" $1 + _disable-thing "aliases" "alias" "$1" } -_disable-completion () -{ - _about 'disables bash_it completion' - _param '1: completion name' - _example '$ disable-completion git' - _group 'lib' +function _disable-completion() { + _about 'disables bash_it completion' + _param '1: completion name' + _example '$ disable-completion git' + _group 'lib' - _disable-thing "completion" "completion" $1 + _disable-thing "completion" "completion" "$1" } -_disable-thing () -{ - _about 'disables a bash_it component' - _param '1: subdirectory' - _param '2: file_type' - _param '3: file_entity' - _example '$ _disable-thing "plugins" "plugin" "ssh"' - - subdirectory="$1" - file_type="$2" - file_entity="$3" - - if [ -z "$file_entity" ]; then - reference "disable-$file_type" - return - fi - - typeset f suffix - suffix=$(echo "$subdirectory" | sed -e 's/plugins/plugin/g') - - if [ "$file_entity" = "all" ]; then - # Disable everything that's using the old structure - for f in `compgen -G "${BASH_IT}/$subdirectory/enabled/*.${suffix}.bash"` - do - rm "$f" - done - - # Disable everything in the global "enabled" directory - for f in `compgen -G "${BASH_IT}/enabled/*.${suffix}.bash"` - do - rm "$f" - done - else - typeset plugin_global=$(command ls $ "${BASH_IT}/enabled/"[0-9]*$BASH_IT_LOAD_PRIORITY_SEPARATOR$file_entity.$suffix.bash 2>/dev/null | head -1) - if [ -z "$plugin_global" ]; then - # Use a glob to search for both possible patterns - # 250---node.plugin.bash - # node.plugin.bash - # Either one will be matched by this glob - typeset plugin=$(command ls $ "${BASH_IT}/$subdirectory/enabled/"{[0-9]*$BASH_IT_LOAD_PRIORITY_SEPARATOR$file_entity.$suffix.bash,$file_entity.$suffix.bash} 2>/dev/null | head -1) - if [ -z "$plugin" ]; then - printf '%s\n' "sorry, $file_entity does not appear to be an enabled $file_type." - return - fi - rm "${BASH_IT}/$subdirectory/enabled/${plugin##*/}" - else - rm "${BASH_IT}/enabled/${plugin_global##*/}" - fi - fi - - _bash-it-clean-component-cache "${file_type}" - - if [ "$file_entity" = "all" ]; then - printf '%s\n' "$file_entity $(_bash-it-pluralize-component "$file_type") disabled." - else - printf '%s\n' "$file_entity disabled." - fi +function _disable-thing() { + _about 'disables a bash_it component' + _param '1: subdirectory' + _param '2: file_type' + _param '3: file_entity' + _example '$ _disable-thing "plugins" "plugin" "ssh"' + + local subdirectory="${1?}" + local file_type="${2?}" + local file_entity="${3:-}" + + if [[ -z "$file_entity" ]]; then + reference "disable-$file_type" + return + fi + + local f suffix _bash_it_config_file plugin + suffix="${subdirectory/plugins/plugin}" + + if [[ "$file_entity" == "all" ]]; then + # Disable everything that's using the old structure and everything in the global "enabled" directory. + for _bash_it_config_file in "${BASH_IT}/$subdirectory/enabled"/*."${suffix}.bash" "${BASH_IT}/enabled"/*".${suffix}.bash"; do + rm -f "$_bash_it_config_file" + done + else + # Use a glob to search for both possible patterns + # 250---node.plugin.bash + # node.plugin.bash + # Either one will be matched by this glob + for plugin in "${BASH_IT}/enabled"/[[:digit:]][[:digit:]][[:digit:]]"${BASH_IT_LOAD_PRIORITY_SEPARATOR}${file_entity}.${suffix}.bash" "${BASH_IT}/$subdirectory/enabled/"{[[:digit:]][[:digit:]][[:digit:]]"${BASH_IT_LOAD_PRIORITY_SEPARATOR}${file_entity}.${suffix}.bash","${file_entity}.${suffix}.bash"}; do + if [[ -e "${plugin}" ]]; then + rm "${plugin}" + plugin= + break + fi + done + if [[ -n "${plugin}" ]]; then + printf '%s\n' "sorry, $file_entity does not appear to be an enabled $file_type." + return + fi + fi + + _bash-it-clean-component-cache "${file_type}" + + if [[ "$file_entity" = "all" ]]; then + printf '%s\n' "$file_entity $(_bash-it-pluralize-component "$file_type") disabled." + else + printf '%s\n' "$file_entity disabled." + fi } -_enable-plugin () -{ - _about 'enables bash_it plugin' - _param '1: plugin name' - _example '$ enable-plugin rvm' - _group 'lib' +function _enable-plugin() { + _about 'enables bash_it plugin' + _param '1: plugin name' + _example '$ enable-plugin rvm' + _group 'lib' - _enable-thing "plugins" "plugin" $1 $BASH_IT_LOAD_PRIORITY_DEFAULT_PLUGIN + _enable-thing "plugins" "plugin" "$1" "$BASH_IT_LOAD_PRIORITY_PLUGIN" } -_enable-plugins () -{ - _about 'alias of _enable-plugin' - _enable-plugin "$@" +function _enable-plugins() { + _about 'alias of _enable-plugin' + _enable-plugin "$@" } -_enable-alias () -{ - _about 'enables bash_it alias' - _param '1: alias name' - _example '$ enable-alias git' - _group 'lib' +function _enable-alias() { + _about 'enables bash_it alias' + _param '1: alias name' + _example '$ enable-alias git' + _group 'lib' - _enable-thing "aliases" "alias" $1 $BASH_IT_LOAD_PRIORITY_DEFAULT_ALIAS + _enable-thing "aliases" "alias" "$1" "$BASH_IT_LOAD_PRIORITY_ALIAS" } -_enable-aliases () -{ - _about 'alias of _enable-alias' - _enable-alias "$@" +function _enable-aliases() { + _about 'alias of _enable-alias' + _enable-alias "$@" } -_enable-completion () -{ - _about 'enables bash_it completion' - _param '1: completion name' - _example '$ enable-completion git' - _group 'lib' +function _enable-completion() { + _about 'enables bash_it completion' + _param '1: completion name' + _example '$ enable-completion git' + _group 'lib' - _enable-thing "completion" "completion" $1 $BASH_IT_LOAD_PRIORITY_DEFAULT_COMPLETION + _enable-thing "completion" "completion" "$1" "$BASH_IT_LOAD_PRIORITY_COMPLETION" } -_enable-thing () -{ - cite _about _param _example - _about 'enables a bash_it component' - _param '1: subdirectory' - _param '2: file_type' - _param '3: file_entity' - _param '4: load priority' - _example '$ _enable-thing "plugins" "plugin" "ssh" "150"' - - subdirectory="$1" - file_type="$2" - file_entity="$3" - load_priority="$4" - - if [ -z "$file_entity" ]; then - reference "enable-$file_type" - return - fi - - if [ "$file_entity" = "all" ]; then - typeset f $file_type - for f in "${BASH_IT}/$subdirectory/available/"*.bash - do - to_enable=$(basename $f .$file_type.bash) - if [ "$file_type" = "alias" ]; then - to_enable=$(basename $f ".aliases.bash") - fi - _enable-thing $subdirectory $file_type $to_enable $load_priority - done - else - typeset to_enable=$(command ls "${BASH_IT}/$subdirectory/available/"$file_entity.*bash 2>/dev/null | head -1) - if [ -z "$to_enable" ]; then - printf '%s\n' "sorry, $file_entity does not appear to be an available $file_type." - return - fi - - to_enable="${to_enable##*/}" - # Check for existence of the file using a wildcard, since we don't know which priority might have been used when enabling it. - typeset enabled_plugin=$(command ls "${BASH_IT}/$subdirectory/enabled/"{[0-9][0-9][0-9]$BASH_IT_LOAD_PRIORITY_SEPARATOR$to_enable,$to_enable} 2>/dev/null | head -1) - if [ ! -z "$enabled_plugin" ] ; then - printf '%s\n' "$file_entity is already enabled." - return - fi - - typeset enabled_plugin_global=$(command compgen -G "${BASH_IT}/enabled/[0-9][0-9][0-9]$BASH_IT_LOAD_PRIORITY_SEPARATOR$to_enable" 2>/dev/null | head -1) - if [ ! -z "$enabled_plugin_global" ] ; then - printf '%s\n' "$file_entity is already enabled." - return - fi - - mkdir -p "${BASH_IT}/enabled" - - # Load the priority from the file if it present there - declare local_file_priority use_load_priority - local_file_priority="$(_bash-it-egrep "^# BASH_IT_LOAD_PRIORITY:" "${BASH_IT}/$subdirectory/available/$to_enable" | awk -F': ' '{ print $2 }')" +function _enable-thing() { + cite _about _param _example + _about 'enables a bash_it component' + _param '1: subdirectory' + _param '2: file_type' + _param '3: file_entity' + _param '4: load priority' + _example '$ _enable-thing "plugins" "plugin" "ssh" "150"' + + local subdirectory="${1?}" + local file_type="${2?}" + local file_entity="${3:-}" + local load_priority="${4:-500}" + + if [[ -z "$file_entity" ]]; then + reference "enable-$file_type" + return + fi + + local _bash_it_config_file to_enable to_enables enabled_plugin local_file_priority use_load_priority + local suffix="${subdirectory/plugins/plugin}" + + if [[ "$file_entity" == "all" ]]; then + for _bash_it_config_file in "${BASH_IT}/$subdirectory/available"/*.bash; do + to_enable="${_bash_it_config_file##*/}" + _enable-thing "$subdirectory" "$file_type" "${to_enable%."${file_type/alias/aliases}".bash}" "$load_priority" + done + else + to_enables=("${BASH_IT}/$subdirectory/available/$file_entity.${suffix}.bash") + if [[ ! -e "${to_enables[0]}" ]]; then + printf '%s\n' "sorry, $file_entity does not appear to be an available $file_type." + return + fi + + to_enable="${to_enables[0]##*/}" + # Check for existence of the file using a wildcard, since we don't know which priority might have been used when enabling it. + for enabled_plugin in "${BASH_IT}/$subdirectory/enabled"/{[[:digit:]][[:digit:]][[:digit:]]"${BASH_IT_LOAD_PRIORITY_SEPARATOR}${to_enable}","${to_enable}"} "${BASH_IT}/enabled"/[[:digit:]][[:digit:]][[:digit:]]"${BASH_IT_LOAD_PRIORITY_SEPARATOR?}${to_enable}"; do + if [[ -e "${enabled_plugin}" ]]; then + printf '%s\n' "$file_entity is already enabled." + return + fi + done + + mkdir -p "${BASH_IT}/enabled" + + # Load the priority from the file if it present there + local_file_priority="$(awk -F': ' '$1 == "# BASH_IT_LOAD_PRIORITY" { print $2 }' "${BASH_IT}/$subdirectory/available/$to_enable")" use_load_priority="${local_file_priority:-$load_priority}" - ln -s ../$subdirectory/available/$to_enable "${BASH_IT}/enabled/${use_load_priority}${BASH_IT_LOAD_PRIORITY_SEPARATOR}${to_enable}" - fi + ln -s "../$subdirectory/available/$to_enable" "${BASH_IT}/enabled/${use_load_priority}${BASH_IT_LOAD_PRIORITY_SEPARATOR}${to_enable}" + fi - _bash-it-clean-component-cache "${file_type}" + _bash-it-clean-component-cache "${file_type}" - printf '%s\n' "$file_entity enabled with priority $use_load_priority." + printf '%s\n' "$file_entity enabled with priority $use_load_priority." } -_help-completions() -{ - _about 'summarize all completions available in bash-it' - _group 'lib' +function _help-completions() { + _about 'summarize all completions available in bash-it' + _group 'lib' - _bash-it-completions + _bash-it-completions } -_help-aliases() -{ - _about 'shows help for all aliases, or a specific alias group' - _param '1: optional alias group' - _example '$ alias-help' - _example '$ alias-help git' - - if [ -n "$1" ]; then - case $1 in - custom) - alias_path='custom.aliases.bash' - ;; - *) - alias_path="available/$1.aliases.bash" - ;; - esac - cat "${BASH_IT}/aliases/$alias_path" | metafor alias | sed "s/$/'/" - else - typeset f - - for f in `sort <(compgen -G "${BASH_IT}/aliases/enabled/*") <(compgen -G "${BASH_IT}/enabled/*.aliases.bash")` - do - _help-list-aliases $f - done - - if [ -e "${BASH_IT}/aliases/custom.aliases.bash" ]; then - _help-list-aliases "${BASH_IT}/aliases/custom.aliases.bash" - fi - fi +function _help-aliases() { + _about 'shows help for all aliases, or a specific alias group' + _param '1: optional alias group' + _example '$ alias-help' + _example '$ alias-help git' + + if [[ -n "$1" ]]; then + case "$1" in + custom) + alias_path='custom.aliases.bash' + ;; + *) + alias_path="available/${1}.aliases.bash" + ;; + esac + metafor alias < "${BASH_IT}/aliases/$alias_path" | sed "s/$/'/" + else + local f + + for f in "${BASH_IT}/aliases/enabled"/* "${BASH_IT}/enabled"/*."aliases.bash"; do + [[ -f "$f" ]] || continue + _help-list-aliases "$f" + done + + if [[ -e "${BASH_IT}/aliases/custom.aliases.bash" ]]; then + _help-list-aliases "${BASH_IT}/aliases/custom.aliases.bash" + fi + fi } -_help-list-aliases () -{ - typeset file=$(basename $1 | sed -e 's/[0-9]*[-]*\(.*\)\.aliases\.bash/\1/g') - printf '\n\n%s:\n' "${file}" - # metafor() strips trailing quotes, restore them with sed.. - cat $1 | metafor alias | sed "s/$/'/" +function _help-list-aliases() { + local file + file="$(_bash-it-get-component-name-from-path "${1?}")" + printf '\n\n%s:\n' "${file}" + # metafor() strips trailing quotes, restore them with sed.. + metafor alias < "$1" | sed "s/$/'/" } -_help-plugins() -{ - _about 'summarize all functions defined by enabled bash-it plugins' - _group 'lib' - - # display a brief progress message... - printf '%s' 'please wait, building help...' - typeset grouplist=$(mktemp -t grouplist.XXXXXX) - typeset func - for func in $(_typeset_functions) - do - typeset group="$(typeset -f $func | metafor group)" - if [ -z "$group" ]; then - group='misc' - fi - typeset about="$(typeset -f $func | metafor about)" - _letterpress "$about" $func >> $grouplist.$group - echo $grouplist.$group >> $grouplist - done - # clear progress message - printf '\r%s\n' ' ' - typeset group - typeset gfile - for gfile in $(cat $grouplist | sort | uniq) - do - printf '%s\n' "${gfile##*.}:" - cat $gfile - printf '\n' - rm $gfile 2> /dev/null - done | less - rm $grouplist 2> /dev/null +function _help-plugins() { + _about 'summarize all functions defined by enabled bash-it plugins' + _group 'lib' + + local grouplist func group about gfile defn + # display a brief progress message... + printf '%s' 'please wait, building help...' + grouplist="$(mktemp -t grouplist.XXXXXX)" + while read -ra func; do + defn="$(declare -f "${func[2]}")" + group="$(metafor group <<< "$defn")" + if [[ -z "$group" ]]; then + group='misc' + fi + about="$(metafor about <<< "$defn")" + _letterpress "$about" "${func[2]}" >> "$grouplist.$group" + echo "$grouplist.$group" >> "$grouplist" + done < <(declare -F) + # clear progress message + printf '\r%s\n' ' ' + while IFS= read -r gfile; do + printf '%s\n' "${gfile##*.}:" + cat "$gfile" + printf '\n' + rm "$gfile" 2> /dev/null + done < <(sort -u "$grouplist") | less + rm "$grouplist" 2> /dev/null } -_help-profile () { - _about 'help message for profile command' - _group 'lib' +function _help-profile() { + _about 'help message for profile command' + _group 'lib' - echo "Manages profiles of bash it." - echo "Use 'bash-it profile list' to see all available profiles." - echo "Use 'bash-it profile save foo' to save the current configuration into a profile named 'foo'." - echo "Use 'bash-it profile load foo' to load an existing profile named 'foo'." - echo "Use 'bash-it profile rm foo' to remove an existing profile named 'foo'." + echo "Manages profiles of bash it." + echo "Use 'bash-it profile list' to see all available profiles." + echo "Use 'bash-it profile save foo' to save the current configuration into a profile named 'foo'." + echo "Use 'bash-it profile load foo' to load an existing profile named 'foo'." + echo "Use 'bash-it profile rm foo' to remove an existing profile named 'foo'." } -_help-update () { - _about 'help message for update command' - _group 'lib' +function _help-update() { + _about 'help message for update command' + _group 'lib' - echo "Check for a new version of Bash-it and update it." + echo "Check for a new version of Bash-it and update it." } -_help-migrate () { - _about 'help message for migrate command' - _group 'lib' +function _help-migrate() { + _about 'help message for migrate command' + _group 'lib' - echo "Migrates internal Bash-it structure to the latest version in case of changes." - echo "The 'migrate' command is run automatically when calling 'update', 'enable' or 'disable'." + echo "Migrates internal Bash-it structure to the latest version in case of changes." + echo "The 'migrate' command is run automatically when calling 'update', 'enable' or 'disable'." } -all_groups () -{ - about 'displays all unique metadata groups' - group 'lib' - - typeset func - typeset file=$(mktemp -t composure.XXXX) - for func in $(_typeset_functions) - do - typeset -f $func | metafor group >> $file - done - cat $file | sort | uniq - rm $file +function all_groups() { + about 'displays all unique metadata groups' + group 'lib' + + declare -f | metafor group | sort -u } -if ! type pathmunge > /dev/null 2>&1 -then - function pathmunge () { - about 'prevent duplicate directories in you PATH variable' - group 'helpers' - example 'pathmunge /path/to/dir is equivalent to PATH=/path/to/dir:$PATH' - example 'pathmunge /path/to/dir after is equivalent to PATH=$PATH:/path/to/dir' - - if ! [[ $PATH =~ (^|:)$1($|:) ]] ; then - if [ "$2" = "after" ] ; then - export PATH=$PATH:$1 - else - export PATH=$1:$PATH - fi - fi - } -fi +function pathmunge() { + about 'prevent duplicate directories in your PATH variable' + group 'helpers' + example 'pathmunge /path/to/dir is equivalent to PATH=/path/to/dir:$PATH' + example 'pathmunge /path/to/dir after is equivalent to PATH=$PATH:/path/to/dir' + + if [[ -d "${1:-}" && ! $PATH =~ (^|:)"${1}"($|:) ]]; then + if [[ "${2:-before}" == "after" ]]; then + export PATH="$PATH:${1}" + else + export PATH="${1}:$PATH" + fi + fi +} # `_bash-it-find-in-ancestor` uses the shell's ability to run a function in # a subshell to simplify our search to a simple `cd ..` and `[[ -r $1 ]]` diff --git a/lib/search.bash b/lib/search.bash old mode 100755 new mode 100644 index 8bd95b8e6d..2da8f0054d --- a/lib/search.bash +++ b/lib/search.bash @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +# shellcheck shell=bash # # Search by Konstantin Gredeskoul «github.com/kigster» #——————————————————————————————————————————————————————————————————————————————— @@ -47,49 +47,52 @@ # completions: git # -_bash-it-search() { - _about 'searches for given terms amongst bash-it plugins, aliases and completions' - _param '1: term1' - _param '2: [ term2 ]...' - _example '$ _bash-it-search @git ruby -rvm rake bundler' - - [[ -z "$(type _bash-it-array-contains-element 2>/dev/null)" ]] && source "${BASH_IT}/lib/utilities.bash" - - local component - export BASH_IT_SEARCH_USE_COLOR=true - declare -a BASH_IT_COMPONENTS=(aliases plugins completions) - - if [[ -z "$*" ]] ; then - _bash-it-search-help - return 0 - fi - - local -a args=() - for word in $@; do - if [[ ${word} == "--help" || ${word} == "-h" ]]; then - _bash-it-search-help - return 0 - elif [[ ${word} == "--refresh" || ${word} == "-r" ]]; then - _bash-it-clean-component-cache - elif [[ ${word} == "--no-color" || ${word} == '-c' ]]; then - export BASH_IT_SEARCH_USE_COLOR=false - else - args=(${args[@]} ${word}) - fi - done - - if [[ ${#args} -gt 0 ]]; then - for component in "${BASH_IT_COMPONENTS[@]}" ; do - _bash-it-search-component "${component}" "${args[@]}" - done - fi - - return 0 +function _bash-it-search() { + _about 'searches for given terms amongst bash-it plugins, aliases and completions' + _param '1: term1' + _param '2: [ term2 ]...' + _example '$ _bash-it-search @git ruby -rvm rake bundler' + + local component + local BASH_IT_SEARCH_USE_COLOR="${BASH_IT_SEARCH_USE_COLOR:=true}" + local -a BASH_IT_COMPONENTS=('aliases' 'plugins' 'completions') + + if [[ $# -eq 0 ]]; then + _bash-it-search-help + return 0 + fi + + local -a args=() + for word in "$@"; do + case "${word}" in + '-h' | '--help') + _bash-it-search-help + return 0 + ;; + '-r' | '--refresh') + _bash-it-clean-component-cache + ;; + '-c' | '--no-color') + BASH_IT_SEARCH_USE_COLOR=false + ;; + *) + args+=("${word}") + ;; + esac + done + + if [[ ${#args} -gt 0 ]]; then + for component in "${BASH_IT_COMPONENTS[@]}"; do + _bash-it-search-component "${component}" "${args[@]}" + done + fi + + return 0 } -_bash-it-search-help() { - printf "${echo_normal} -${echo_underline_yellow}USAGE${echo_normal} +function _bash-it-search-help() { + printf '%b' "${echo_normal-} +${echo_underline_yellow-}USAGE${echo_normal-} bash-it search [-|@]term1 [-|@]term2 ... \\ [ --enable | -e ] \\ @@ -98,9 +101,9 @@ ${echo_underline_yellow}USAGE${echo_normal} [ --refresh | -r ] \\ [ --help | -h ] -${echo_underline_yellow}DESCRIPTION${echo_normal} +${echo_underline_yellow-}DESCRIPTION${echo_normal-} - Use ${echo_bold_green}search${echo_normal} bash-it command to search for a list of terms or term negations + Use ${echo_bold_green-}search${echo_normal-} bash-it command to search for a list of terms or term negations across all components: aliases, completions and plugins. Components that are enabled are shown in green (or with a check box if --no-color option is used). @@ -117,42 +120,42 @@ ${echo_underline_yellow}DESCRIPTION${echo_normal} * To perform an exact match, use character '@' in front of the term, eg. '@git' would only match aliases, plugins and completions named 'git'. -${echo_underline_yellow}FLAGS${echo_normal} - --enable | -e ${echo_purple}Enable all matching componenents.${echo_normal} - --disable | -d ${echo_purple}Disable all matching componenents.${echo_normal} - --help | -h ${echo_purple}Print this help.${echo_normal} - --refresh | -r ${echo_purple}Force a refresh of the search cache.${echo_normal} - --no-color | -c ${echo_purple}Disable color output and use monochrome text.${echo_normal} +${echo_underline_yellow-}FLAGS${echo_normal-} + --enable | -e ${echo_purple-}Enable all matching componenents.${echo_normal-} + --disable | -d ${echo_purple-}Disable all matching componenents.${echo_normal-} + --help | -h ${echo_purple-}Print this help.${echo_normal-} + --refresh | -r ${echo_purple-}Force a refresh of the search cache.${echo_normal-} + --no-color | -c ${echo_purple-}Disable color output and use monochrome text.${echo_normal-} -${echo_underline_yellow}EXAMPLES${echo_normal} +${echo_underline_yellow-}EXAMPLES${echo_normal-} - For example, ${echo_bold_green}bash-it search git${echo_normal} would match any alias, completion + For example, ${echo_bold_green-}bash-it search git${echo_normal-} would match any alias, completion or plugin that has the word 'git' in either the module name or it's description. You should see something like this when you run this command: - ${echo_bold_green}❯ bash-it search git${echo_bold_blue} - ${echo_bold_yellow}aliases: ${echo_bold_green}git ${echo_normal}gitsvn - ${echo_bold_yellow}plugins: ${echo_normal}autojump ${echo_bold_green}git ${echo_normal}git-subrepo jgitflow jump - ${echo_bold_yellow}completions: ${echo_bold_green}git ${echo_normal}git_flow git_flow_avh${echo_normal} + ${echo_bold_green-}❯ bash-it search git${echo_bold_blue-} + ${echo_bold_yellow-}aliases: ${echo_bold_green-}git ${echo_normal-}gitsvn + ${echo_bold_yellow-}plugins: ${echo_normal-}autojump ${echo_bold_green-}git ${echo_normal-}git-subrepo jgitflow jump + ${echo_bold_yellow-}completions: ${echo_bold_green-}git ${echo_normal-}git_flow git_flow_avh${echo_normal-} You can exclude some terms by prefixing a term with a minus, eg: - ${echo_bold_green}❯ bash-it search git -flow -svn${echo_bold_blue} - ${echo_bold_yellow}aliases: ${echo_normal}git - ${echo_bold_yellow}plugins: ${echo_normal}autojump git git-subrepo jump - ${echo_bold_yellow}completions: ${echo_normal}git${echo_normal} + ${echo_bold_green-}❯ bash-it search git -flow -svn${echo_bold_blue-} + ${echo_bold_yellow-}aliases: ${echo_normal-}git + ${echo_bold_yellow-}plugins: ${echo_normal-}autojump git git-subrepo jump + ${echo_bold_yellow-}completions: ${echo_normal-}git${echo_normal-} Finally, if you prefix a term with '@' symbol, that indicates an exact match. Note, that we also pass the '--enable' flag, which would ensure that all matches are automatically enabled. The example is below: - ${echo_bold_green}❯ bash-it search @git --enable${echo_bold_blue} - ${echo_bold_yellow}aliases: ${echo_normal}git - ${echo_bold_yellow}plugins: ${echo_normal}git - ${echo_bold_yellow}completions: ${echo_normal}git${echo_normal} + ${echo_bold_green-}❯ bash-it search @git --enable${echo_bold_blue-} + ${echo_bold_yellow-}aliases: ${echo_normal-}git + ${echo_bold_yellow-}plugins: ${echo_normal-}git + ${echo_bold_yellow-}completions: ${echo_normal-}git${echo_normal-} -${echo_underline_yellow}SUMMARY${echo_normal} +${echo_underline_yellow-}SUMMARY${echo_normal-} Take advantage of the search functionality to discover what Bash-It can do for you. Try searching for partial term matches, mix and match with the @@ -164,197 +167,210 @@ ${echo_underline_yellow}SUMMARY${echo_normal} " } -_bash-it-is-partial-match() { - local component="$1" - local term="$2" - _bash-it-component-help "${component}" | _bash-it-egrep -i -q -- "${term}" +function _bash-it-is-partial-match() { + local component="${1?${FUNCNAME[0]}: component type must be specified}" + local term="${2:-}" + _bash-it-component-help "${component}" | _bash-it-egrep -i -q -- "${term}" } -_bash-it-component-term-matches-negation() { - local match="$1"; shift - local negative - for negative in "$@"; do - [[ "${match}" =~ "${negative}" ]] && return 0 - done +function _bash-it-component-term-matches-negation() { + local match="$1" + shift + local negative + for negative in "$@"; do + [[ "${match}" =~ ${negative} ]] && return 0 + done - return 1 + return 1 } -_bash-it-search-component() { - local component="$1" - shift - - _about 'searches for given terms amongst a given component' - _param '1: component type, one of: [ aliases | plugins | completions ]' - _param '2: term1 term2 @term3' - _param '3: [-]term4 [-]term5 ...' - _example '$ _bash-it-search-component aliases @git rake bundler -chruby' - - # if one of the search terms is --enable or --disable, we will apply - # this action to the matches further ` down. - local component_singular action action_func - local -a search_commands=(enable disable) - for search_command in "${search_commands[@]}"; do - if $(_bash-it-array-contains-element "--${search_command}" "$@"); then - component_singular=${component} - component_singular=${component_singular/es/} # aliases -> alias - component_singular=${component_singular/ns/n} # plugins -> plugin - - action="${search_command}" - action_func="_${action}-${component_singular}" - break - fi - done - - local -a terms=($@) # passed on the command line - - unset exact_terms - unset partial_terms - unset negative_terms - - local -a exact_terms=() # terms that should be included only if they match exactly - local -a partial_terms=() # terms that should be included if they match partially - local -a negative_terms=() # negated partial terms that should be excluded - - unset component_list - local -a component_list=( $(_bash-it-component-list "${component}") ) - local term - - for term in "${terms[@]}"; do - local search_term="${term:1}" - if [[ "${term:0:2}" == "--" ]] ; then - continue - elif [[ "${term:0:1}" == "-" ]] ; then - negative_terms=(${negative_terms[@]} "${search_term}") - elif [[ "${term:0:1}" == "@" ]] ; then - if $(_bash-it-array-contains-element "${search_term}" "${component_list[@]}"); then - exact_terms=(${exact_terms[@]} "${search_term}") - fi - else - partial_terms=(${partial_terms[@]} $(_bash-it-component-list-matching "${component}" "${term}") ) - fi - done - - local -a total_matches=( $(_bash-it-array-dedup ${exact_terms[@]} ${partial_terms[@]}) ) - - unset matches - declare -a matches=() - for match in ${total_matches[@]}; do - local include_match=true - if [[ ${#negative_terms[@]} -gt 0 ]]; then - ( _bash-it-component-term-matches-negation "${match}" "${negative_terms[@]}" ) && include_match=false - fi - ( ${include_match} ) && matches=(${matches[@]} "${match}") - done - _bash-it-search-result "${component}" "${action}" "${action_func}" "${matches[@]}" - unset matches final_matches terms +function _bash-it-search-component() { + _about 'searches for given terms amongst a given component' + _param '1: component type, one of: [ aliases | plugins | completions ]' + _param '2: term1 term2 @term3' + _param '3: [-]term4 [-]term5 ...' + _example '$ _bash-it-search-component aliases @git rake bundler -chruby' + + local component="${1?${FUNCNAME[0]}: component type must be specified}" + shift + + # if one of the search terms is --enable or --disable, we will apply + # this action to the matches further ` down. + local component_singular action action_func + local -a search_commands=('enable' 'disable') + for search_command in "${search_commands[@]}"; do + if _bash-it-array-contains-element "--${search_command}" "$@"; then + component_singular="${component/es/}" # aliases -> alias + component_singular="${component_singular/ns/n}" # plugins -> plugin + + action="${search_command}" + action_func="_${action}-${component_singular}" + break + fi + done + + local -a terms=("$@") # passed on the command line + + local -a exact_terms=() # terms that should be included only if they match exactly + local -a partial_terms=() # terms that should be included if they match partially + local -a negative_terms=() # negated partial terms that should be excluded + + local term line + + local -a component_list=() + while IFS='' read -r line; do + component_list+=("$line") + done < <(_bash-it-component-list "${component}") + + for term in "${terms[@]}"; do + local search_term="${term:1}" + if [[ "${term:0:2}" == "--" ]]; then + continue + elif [[ "${term:0:1}" == "-" ]]; then + negative_terms+=("${search_term}") + elif [[ "${term:0:1}" == "@" ]]; then + if _bash-it-array-contains-element "${search_term}" "${component_list[@]:-}"; then + exact_terms+=("${search_term}") + fi + else + while IFS='' read -r line; do + partial_terms+=("$line") + done < <(_bash-it-component-list-matching "${component}" "${term}") + + fi + done + + local -a total_matches=() + while IFS='' read -r line; do + total_matches+=("$line") + done < <(_bash-it-array-dedup "${exact_terms[@]:-}" "${partial_terms[@]:-}") + + local -a matches=() + for match in "${total_matches[@]}"; do + local -i include_match=1 + if [[ ${#negative_terms[@]} -gt 0 ]]; then + _bash-it-component-term-matches-negation "${match}" "${negative_terms[@]:-}" && include_match=0 + fi + ((include_match)) && matches+=("${match}") + done + + _bash-it-search-result "${component}" "${action:-}" "${action_func:-}" "${matches[@]:-}" } -_bash-it-search-result() { - local component="$1"; shift - local action="$1"; shift - local action_func="$1"; shift - local -a matches=($@) - - local color_component color_enable color_disable color_off - - color_sep=':' - - ( ${BASH_IT_SEARCH_USE_COLOR} ) && { - color_component='\e[1;34m' - color_enable='\e[1;32m' - suffix_enable='' - suffix_disable='' - color_disable='\e[0;0m' - color_off='\e[0;0m' - } - - ( ${BASH_IT_SEARCH_USE_COLOR} ) || { - color_component='' - suffix_enable=' ✓ ︎' - suffix_disable=' ' - color_enable='' - color_disable='' - color_off='' - } - - local match - local modified=0 - - if [[ "${#matches[@]}" -gt 0 ]] ; then - printf "${color_component}%13s${color_sep} ${color_off}" "${component}" - - for match in "${matches[@]}"; do - local enabled=0 - ( _bash-it-component-item-is-enabled "${component}" "${match}" ) && enabled=1 - - local match_color compatible_action suffix opposite_suffix - - (( ${enabled} )) && { - match_color=${color_enable} - suffix=${suffix_enable} - opposite_suffix=${suffix_disable} - compatible_action="disable" - } - - (( ${enabled} )) || { - match_color=${color_disable} - suffix=${suffix_disable} - opposite_suffix=${suffix_enable} - compatible_action="enable" - } - - local m="${match}${suffix}" - local len - len=${#m} - - printf " ${match_color}${match}${suffix}" # print current state - if [[ "${action}" == "${compatible_action}" ]]; then - if [[ ${action} == "enable" && ${BASH_IT_SEARCH_USE_COLOR} == false ]]; then - _bash-it-flash-term ${len} "${match}${suffix}" - else - _bash-it-erase-term ${len} - fi - modified=1 - result=$(${action_func} ${match}) - local temp="color_${compatible_action}" - match_color=${!temp} - _bash-it-rewind ${len} - printf "${match_color}${match}${opposite_suffix}" - fi - - printf "${color_off}" - done - - [[ ${modified} -gt 0 ]] && _bash-it-clean-component-cache ${component} - printf "\n" - fi +function _bash-it-search-result() { + local component="${1?${FUNCNAME[0]}: component type must be specified}" + shift + local action="${1:-}" + shift + local action_func="${1:-}" + shift + + local color_component color_enable color_disable color_off + local color_sep=':' line + + local -a matches=() + # Discard any empty arguments + while IFS='' read -r line; do + [[ -n "${line}" ]] && matches+=("$line") + done < <(_bash-it-array-dedup "${@}") + + if [[ "${BASH_IT_SEARCH_USE_COLOR}" == "true" ]]; then + color_component='\e[1;34m' + color_enable='\e[1;32m' + suffix_enable='' + suffix_disable='' + color_disable='\e[0;0m' + color_off='\e[0;0m' + else + color_component='' + suffix_enable=' ✓ ︎' + suffix_disable=' ' + color_enable='' + color_disable='' + color_off='' + fi + + local match + local -i modified=0 + + if [[ "${#matches[@]}" -gt 0 ]]; then + printf "${color_component}%13s${color_sep}${color_off} " "${component}" + + for match in "${matches[@]}"; do + local -i enabled=0 + _bash-it-component-item-is-enabled "${component}" "${match}" && enabled=1 + + local match_color compatible_action suffix opposite_suffix + + if ((enabled)); then + match_color="${color_enable}" + suffix="${suffix_enable}" + opposite_suffix="${suffix_disable}" + compatible_action="disable" + else + match_color="${color_disable}" + suffix="${suffix_disable}" + opposite_suffix="${suffix_enable}" + compatible_action="enable" + fi + + local matched="${match}${suffix}" + local -i len="${#matched}" + + printf '%b' "${match_color}${matched}" # print current state + if [[ "${action}" == "${compatible_action}" ]]; then + if [[ "${action}" == "enable" && "${BASH_IT_SEARCH_USE_COLOR}" == "true" ]]; then + _bash-it-flash-term "${len}" "${matched}" + else + _bash-it-erase-term "${len}" "${matched}" + fi + modified=1 + # shellcheck disable=SC2034 # no idea if `$result` is ever used + result=$("${action_func}" "${match}") + local temp="color_${compatible_action}" + match_color="${!temp}" + _bash-it-rewind "${len}" + printf '%b' "${match_color}${match}${opposite_suffix}" + fi + + printf '%b' "${color_off} " + done + + ((modified)) && _bash-it-clean-component-cache "${component}" + printf "\n" + fi } -_bash-it-rewind() { - local len="$1" - printf "\033[${len}D" +function _bash-it-rewind() { + local -i len="${1:-0}" + printf '%b' "\033[${len}D" } -_bash-it-flash-term() { - local len="$1" - local match="$2" - local delay=0.1 - local color - - for color in ${text_black} ${echo_bold_blue} ${bold_yellow} ${bold_red} ${echo_bold_green} ; do - sleep ${delay} - _bash-it-rewind "${len}" - printf "${color}${match}" - done +function _bash-it-flash-term() { + local -i len="${1:-0}" # redundant + local term="${2:-}" + # as currently implemented, `$match` has already been printed to screen the first time + local delay=0.1 + local color + [[ "${#term}" -gt 0 ]] && len="${#term}" + + for color in "${echo_black-}" "${echo_bold_blue-}" "${echo_bold_yellow-}" "${echo_bold_red-}" "${echo_bold_green-}" "${echo_normal-}"; do + sleep "${delay}" + _bash-it-rewind "${len}" + printf '%b' "${color}${term}" + done } -_bash-it-erase-term() { - local len="$1" - _bash-it-rewind ${len} - for a in {0..30}; do - [[ ${a} -gt ${len} ]] && break - printf "%.*s" $a " " - sleep 0.05 - done +function _bash-it-erase-term() { + local -i len="${1:-0}" i + local delay=0.05 + local term="${2:-}" # calculate length ourselves + [[ "${#term}" -gt 0 ]] && len="${#term}" + + _bash-it-rewind "${len}" + # white-out the already-printed term by printing blanks + for ((i = 0; i <= len; i++)); do + printf "%.*s" "$i" " " + sleep "${delay}" + done } diff --git a/lib/utilities.bash b/lib/utilities.bash index 575787d89d..d7b2b37631 100644 --- a/lib/utilities.bash +++ b/lib/utilities.bash @@ -47,9 +47,10 @@ function _bash-it-get-component-type-from-path() { # # function _bash-it-array-contains-element() { - local e - for e in "${@:2}"; do - [[ "$e" == "$1" ]] && return 0 + local e element="${1?}" + shift + for e in "$@"; do + [[ "$e" == "${element}" ]] && return 0 done return 1 } @@ -82,7 +83,7 @@ function _bash-it-component-help() { file="$(_bash-it-component-cache-file "${component}")" if [[ ! -s "${file}" || -z "$(find "${file}" -mmin -300)" ]]; then func="_bash-it-${component}" - "${func}" | _bash-it-egrep ' \[' >| "${file}" + "${func}" | _bash-it-egrep '\[[x ]\]' >| "${file}" fi cat "${file}" } @@ -144,7 +145,6 @@ function _bash-it-component-list-disabled() { } # Checks if a given item is enabled for a particular component/file-type. -# Uses the component cache if available. # # Returns: # 0 if an item of the component is enabled, 1 otherwise. @@ -152,13 +152,23 @@ function _bash-it-component-list-disabled() { # Examples: # _bash-it-component-item-is-enabled alias git && echo "git alias is enabled" function _bash-it-component-item-is-enabled() { - local component="$1" - local item="$2" - _bash-it-component-help "${component}" | _bash-it-egrep '\[x\]' | _bash-it-egrep -q -- "^${item}\s" + local component_type item_name each_file + + if [[ -f "${1}" ]]; then + item_name="$(_bash-it-get-component-name-from-path "${1}")" + component_type="$(_bash-it-get-component-type-from-path "${1}")" + else + component_type="${1}" item_name="${2}" + fi + + for each_file in "${BASH_IT}/enabled"/*"${BASH_IT_LOAD_PRIORITY_SEPARATOR?}${item_name}.${component_type}"*."bash" \ + "${BASH_IT}/${component_type}"*/"enabled/${item_name}.${component_type}"*."bash" \ + "${BASH_IT}/${component_type}"*/"enabled"/*"${BASH_IT_LOAD_PRIORITY_SEPARATOR?}${item_name}.${component_type}"*."bash"; do + [[ -f "${each_file}" ]] && return + done } # Checks if a given item is disabled for a particular component/file-type. -# Uses the component cache if available. # # Returns: # 0 if an item of the component is enabled, 1 otherwise. @@ -166,7 +176,5 @@ function _bash-it-component-item-is-enabled() { # Examples: # _bash-it-component-item-is-disabled alias git && echo "git aliases are disabled" function _bash-it-component-item-is-disabled() { - local component="$1" - local item="$2" - _bash-it-component-help "${component}" | _bash-it-egrep -v '\[x\]' | _bash-it-egrep -q -- "^${item}\s" + ! _bash-it-component-item-is-enabled "$@" } diff --git a/test/fixtures/go/go path/bin/.keep b/test/fixtures/go/go path/bin/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/fixtures/go/gopath/bin/.keep b/test/fixtures/go/gopath/bin/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/fixtures/go/gopath2/bin/.keep b/test/fixtures/go/gopath2/bin/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/lib/helpers.bats b/test/lib/helpers.bats index e8275e9dd4..2386b4b140 100644 --- a/test/lib/helpers.bats +++ b/test/lib/helpers.bats @@ -3,6 +3,7 @@ load ../test_helper load ../test_helper_libs load ../../plugins/available/base.plugin +load ../../themes/colors.theme function local_setup { setup_test_fixture @@ -289,8 +290,9 @@ function local_setup { assert_link_exist "$BASH_IT/enabled/225---nvm.plugin.bash" } -@test "helper: profile load command sanity" { +@test "helpers: profile load command sanity" { run _bash-it-profile-load "default" + assert_success assert_link_exist "$BASH_IT/enabled/150---general.aliases.bash" assert_link_exist "$BASH_IT/enabled/250---base.plugin.bash" @@ -299,7 +301,7 @@ function local_setup { assert_link_exist "$BASH_IT/enabled/325---system.completion.bash" } -@test "helper: profile save command sanity" { +@test "helpers: profile save command sanity" { run _enable-plugin "nvm" run _bash-it-profile-save "test" @@ -310,7 +312,7 @@ function local_setup { assert_file_exist "$BASH_IT/profiles/test.bash_it" } -@test "helper: profile save creates valid file with only plugin enabled" { +@test "helpers: profile save creates valid file with only plugin enabled" { run _enable-plugin "nvm" run _bash-it-profile-save "test" @@ -320,7 +322,7 @@ function local_setup { assert_line -n 2 "plugins nvm" } -@test "helper: profile save creates valid file with only completion enabled" { +@test "helpers: profile save creates valid file with only completion enabled" { run _enable-completion "bash-it" run _bash-it-profile-save "test" @@ -330,7 +332,7 @@ function local_setup { assert_line -n 2 "completion bash-it" } -@test "helper: profile save creates valid file with only aliases enabled" { +@test "helpers: profile save creates valid file with only aliases enabled" { run _enable-alias "general" run _bash-it-profile-save "test" @@ -340,7 +342,7 @@ function local_setup { assert_line -n 2 "aliases general" } -@test "helper: profile edge case, empty configuration" { +@test "helpers: profile edge case, empty configuration" { run _bash-it-profile-save "test" assert_line -n 3 "It seems like no configuration was enabled.." assert_line -n 4 "Make sure to double check that this is the wanted behavior." @@ -359,7 +361,7 @@ function local_setup { assert_link_not_exist "$BASH_IT/enabled/325---system.completion.bash" } -@test "helper: profile save and load" { +@test "helpers: profile save and load" { run _enable-alias "general" run _enable-plugin "base" run _enable-plugin "alias-completion" @@ -375,52 +377,52 @@ function local_setup { assert_link_exist "$BASH_IT/enabled/150---general.aliases.bash" } -@test "helper: profile load corrupted profile file: bad component" { +@test "helpers: profile load corrupted profile file: bad component" { run _bash-it-profile-load "test-bad-component" assert_line -n 1 -p "Bad line(#12) in profile, aborting load..." } -@test "helper: profile load corrupted profile file: bad subdirectory" { +@test "helpers: profile load corrupted profile file: bad subdirectory" { run _bash-it-profile-load "test-bad-type" assert_line -n 1 -p "Bad line(#5) in profile, aborting load..." } -@test "helper: profile rm sanity" { +@test "helpers: profile rm sanity" { run _bash-it-profile-save "test" assert_file_exist "$BASH_IT/profiles/test.bash_it" run _bash-it-profile-rm "test" - assert_line -n 0 "Removed profile \"test\" successfully!" + assert_line -n 0 "Removed profile 'test' successfully!" assert_file_not_exist "$BASH_IT/profiles/test.bash_it" } -@test "helper: profile rm no params" { +@test "helpers: profile rm no params" { run _bash-it-profile-rm "" assert_line -n 0 -p "Please specify profile name to remove..." } -@test "helper: profile load no params" { +@test "helpers: profile load no params" { run _bash-it-profile-load "" assert_line -n 0 -p "Please specify profile name to load, not changing configuration..." } -@test "helper: profile rm default" { +@test "helpers: profile rm default" { run _bash-it-profile-rm "default" assert_line -n 0 -p "Can not remove the default profile..." assert_file_exist "$BASH_IT/profiles/default.bash_it" } -@test "helper: profile rm bad profile name" { +@test "helpers: profile rm bad profile name" { run _bash-it-profile-rm "notexisting" - assert_line -n 0 -p "Could not find profile \"notexisting\"..." + assert_line -n 0 -p "Could not find profile 'notexisting'..." } -@test "helper: profile list sanity" { +@test "helpers: profile list sanity" { run _bash-it-profile-list assert_line -n 0 "Available profiles:" assert_line -n 1 "default" } -@test "helper: profile list more profiles" { +@test "helpers: profile list more profiles" { run _bash-it-profile-save "cactus" run _bash-it-profile-save "another" run _bash-it-profile-save "brother" @@ -732,5 +734,5 @@ function __migrate_all_components() { @test "helpers: describe the todo.txt-cli aliases without enabling them" { run _bash-it-aliases - assert_line "todo.txt-cli [ ] todo.txt-cli abbreviations" + assert_line "todo.txt-cli [ ] todo.txt-cli abbreviations" } diff --git a/test/lib/search.bats b/test/lib/search.bats old mode 100644 new mode 100755 index c8a9502771..057951a0a5 --- a/test/lib/search.bats +++ b/test/lib/search.bats @@ -28,42 +28,42 @@ function local_teardown { @test "search: plugin base" { export BASH_IT_SEARCH_USE_COLOR=false run _bash-it-search-component 'plugins' 'base' - assert_line -n 0 ' plugins: base ' + assert_line -n 0 ' plugins: base ' } @test "search: git" { run _bash-it-search 'git' --no-color - assert_line -n 0 ' aliases: git gitsvn ' + assert_line -n 0 ' aliases: git gitsvn ' assert_line -n 1 -p ' plugins:' for plugin in "autojump" "git" "gitstatus" "git-subrepo" "jgitflow" "jump" do echo $plugin assert_line -n 1 -p $plugin done - assert_line -n 2 ' completions: git git_flow git_flow_avh github-cli ' + assert_line -n 2 ' completions: git git_flow git_flow_avh github-cli ' } @test "search: ruby gem bundle rake rails" { run _bash-it-search rails ruby gem bundler rake --no-color - assert_line -n 0 ' aliases: bundler rails ' - assert_line -n 1 ' plugins: chruby chruby-auto rails ruby ' - assert_line -n 2 ' completions: bundler gem rake ' + assert_line -n 0 ' aliases: bundler rails ' + assert_line -n 1 ' plugins: chruby chruby-auto rails ruby ' + assert_line -n 2 ' completions: bundler gem rake ' } @test "search: rails ruby gem bundler rake -chruby" { run _bash-it-search rails ruby gem bundler rake -chruby --no-color - assert_line -n 0 ' aliases: bundler rails ' - assert_line -n 1 ' plugins: rails ruby ' - assert_line -n 2 ' completions: bundler gem rake ' + assert_line -n 0 ' aliases: bundler rails ' + assert_line -n 1 ' plugins: rails ruby ' + assert_line -n 2 ' completions: bundler gem rake ' } @test "search: @git" { run _bash-it-search '@git' --no-color - assert_line -n 0 ' aliases: git ' - assert_line -n 1 ' plugins: git ' - assert_line -n 2 ' completions: git ' + assert_line -n 0 ' aliases: git ' + assert_line -n 1 ' plugins: git ' + assert_line -n 2 ' completions: git ' } @test "search: @git --enable / --disable" { @@ -76,7 +76,7 @@ function local_teardown { run _bash-it-search '@git' --disable --no-color run _bash-it-search '@git' --no-color - assert_line -n 0 ' aliases: git ' - assert_line -n 0 ' aliases: git ' - assert_line -n 2 ' completions: git ' + assert_line -n 0 ' aliases: git ' + assert_line -n 1 ' plugins: git ' + assert_line -n 2 ' completions: git ' } diff --git a/test/plugins/go.plugin.bats b/test/plugins/go.plugin.bats index 4021e643ed..258e425447 100644 --- a/test/plugins/go.plugin.bats +++ b/test/plugins/go.plugin.bats @@ -3,6 +3,22 @@ load ../test_helper load ../test_helper_libs +function local_setup() +{ + setup_test_fixture +} + +function setup_go_path() +{ + local go_path="$1" + + # Make sure that the requested GO folder is available + assert_dir_exist "$go_path/bin" + + # Make sure that the requested GO folder is on the path + export GOPATH="$go_path:${GOPATH:-}" +} + # We test `go version` in each test to account for users with goenv and no system go. @test 'ensure _bash-it-gopath-pathmunge is defined' { @@ -14,42 +30,47 @@ load ../test_helper_libs @test 'plugins go: single entry in GOPATH' { { _command_exists go && go version &>/dev/null; } || skip 'golang not found' - export GOPATH="/foo" + setup_go_path "$BASH_IT/test/fixtures/go/gopath" load ../../plugins/available/go.plugin - assert_equal "$(cut -d':' -f1 <<<$PATH)" "/foo/bin" + assert_equal "$(cut -d':' -f1 <<<$PATH)" "$BASH_IT/test/fixtures/go/gopath/bin" } @test 'plugins go: single entry in GOPATH, with space' { { _command_exists go && go version &>/dev/null; } || skip 'golang not found' - export GOPATH="/foo bar" + setup_go_path "$BASH_IT/test/fixtures/go/go path" load ../../plugins/available/go.plugin - assert_equal "$(cut -d':' -f1 <<<$PATH)" "/foo bar/bin" + assert_equal "$(cut -d':' -f1 <<<$PATH)" "$BASH_IT/test/fixtures/go/go path/bin" } @test 'plugins go: single entry in GOPATH, with escaped space' { + skip 'huh?' { _command_exists go && go version &>/dev/null; } || skip 'golang not found' - export GOPATH="/foo\ bar" + setup_go_path "$BASH_IT/test/fixtures/go/go\ path" load ../../plugins/available/go.plugin - assert_equal "$(cut -d':' -f1 <<<$PATH)" "/foo\ bar/bin" + assert_equal "$(cut -d':' -f1 <<<$PATH)" "$BASH_IT/test/fixtures/go/go\ path/bin" } @test 'plugins go: multiple entries in GOPATH' { { _command_exists go && go version &>/dev/null; } || skip 'golang not found' - export GOPATH="/foo:/bar" + setup_go_path "$BASH_IT/test/fixtures/go/gopath" + setup_go_path "$BASH_IT/test/fixtures/go/gopath2" load ../../plugins/available/go.plugin - assert_equal "$(cut -d':' -f1,2 <<<$PATH)" "/foo/bin:/bar/bin" + assert_equal "$(cut -d':' -f1,2 <<<$PATH)" "$BASH_IT/test/fixtures/go/gopath2/bin:$BASH_IT/test/fixtures/go/gopath/bin" } @test 'plugins go: multiple entries in GOPATH, with space' { { _command_exists go && go version &>/dev/null; } || skip 'golang not found' - export GOPATH="/foo:/foo bar" + setup_go_path "$BASH_IT/test/fixtures/go/gopath" + setup_go_path "$BASH_IT/test/fixtures/go/go path" load ../../plugins/available/go.plugin - assert_equal "$(cut -d':' -f1,2 <<<$PATH)" "/foo/bin:/foo bar/bin" + assert_equal "$(cut -d':' -f1,2 <<<$PATH)" "$BASH_IT/test/fixtures/go/go path/bin:$BASH_IT/test/fixtures/go/gopath/bin" } @test 'plugins go: multiple entries in GOPATH, with escaped space' { + skip 'huh?' { _command_exists go && go version &>/dev/null; } || skip 'golang not found' - export GOPATH="/foo:/foo\ bar" + setup_go_path "$BASH_IT/test/fixtures/go/gopath" + setup_go_path "$BASH_IT/test/fixtures/go/go path" load ../../plugins/available/go.plugin - assert_equal "$(cut -d':' -f1,2 <<<$PATH)" "/foo/bin:/foo\ bar/bin" + assert_equal "$(cut -d':' -f1,2 <<<$PATH)" "$BASH_IT/test/fixtures/go/go\ path/bin:$BASH_IT/test/fixtures/go/gopath/bin" } diff --git a/test/plugins/ruby.plugin.bats b/test/plugins/ruby.plugin.bats index e40dfeaeaa..b80adde791 100755 --- a/test/plugins/ruby.plugin.bats +++ b/test/plugins/ruby.plugin.bats @@ -6,6 +6,8 @@ load ../test_helper_libs function local_setup { setup_test_fixture + _command_exists "ruby" && mkdir -p "$(ruby -e 'print Gem.user_dir')/bin" + export OLD_PATH="$PATH" export PATH="/usr/bin:/bin:/usr/sbin" }