diff --git a/bash_it.sh b/bash_it.sh index a7960c752f..dfe6f6159b 100755 --- a/bash_it.sh +++ b/bash_it.sh @@ -49,6 +49,17 @@ do fi done +# Load vendors +BASH_IT_LOG_PREFIX="vendor: " +for _bash_it_vendor_init in "${BASH_IT}"/vendor/init.d/*.bash +do + _log_debug "Loading \"$(basename "${_bash_it_vendor_init}" .bash)\"..." + # shellcheck disable=SC1090 + source "${_bash_it_vendor_init}" +done +unset _bash_it_vendor_init + +BASH_IT_LOG_PREFIX="core: main: " # Load the global "enabled" directory # "family" param is empty so that files get sources in glob order # shellcheck source=./scripts/reloader.bash @@ -62,7 +73,7 @@ do done # Load theme, if a theme was set -if [[ ! -z "${BASH_IT_THEME}" ]]; then +if [[ -n "${BASH_IT_THEME}" ]]; then _log_debug "Loading \"${BASH_IT_THEME}\" theme..." # Load colors and helpers first so they can be used in base theme BASH_IT_LOG_PREFIX="themes: colors: " diff --git a/clean_files.txt b/clean_files.txt index f94b2dcafe..d738d15159 100644 --- a/clean_files.txt +++ b/clean_files.txt @@ -39,10 +39,13 @@ themes/barbuk themes/atomic themes/axin themes/base.theme.bash +themes/command_duration.theme.bash # plugins # plugins/available/basher.plugin.bash +plugins/available/cmd-returned-notify.plugin.bash +plugins/available/xterm.plugin.bash # completions # diff --git a/docs/development.rst b/docs/development.rst index b49ff1418f..1c9a577049 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -38,6 +38,7 @@ The main ``bash_it.sh`` script loads the frameworks individual components in the * ``lib/composure.bash`` +* ``vendor/init.d/*.bash`` * Files in ``lib`` with the exception of ``appearance.bash`` - this means that ``composure.bash`` is loaded again here (possible improvement?) * Enabled ``aliases`` * Enabled ``plugins`` @@ -78,6 +79,59 @@ Having the order based on a numeric priority in a common directory allows for mo These items are subject to change. When making changes to the internal functionality, this page needs to be updated as well. +Working with vendored libs +-------------------------- + +Vendored libs are external libraries, meaning source code not maintained by Bash-it +developers. +They are ``git subtrees`` curated in the ``vendor/`` folder. To ease the work with git +vendored libs as subtrees we use the `git-vendor `_ tool. + +For more information on ``git vendor`` there are a short `usage description `_ +in the repositories ``README`` file and a website with the `manual page `_. + +To support a flexible loading of external libraries, a file unique to the vendored +library must be placed in ``vendor/init.d/`` with the ``.bash`` extension. + +Rebasing a feature branch with an added/updated vendored library +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If your feature branch with a newly added/updated vendored lib has fallen behind master +you might need to rebase it before creating a PR. However rebasing with dangling +subtree commits can cause problems. +The following rebase strategy will pause the rebase at the point where you added a +subtree and let you add it again before continuing the rebasing. + +:: + [feature/branch] $ git rebase --rebase-merges --strategy subtree master + fatal: refusing to merge unrelated histories + Could not apply 0d6a56b... Add-preexec-from-https-github-com-rcaloras-bash-preexec-0-4-1- # Add "preexec" from "https://github.com/rcaloras/bash-preexec@0.4.1" + [feature/branch] $ git vendor add preexec https://github.com/rcaloras/bash-preexec 0.4.1 + ... + [feature/branch] $ git rebase --continue + +If rebasing makes you a little uneasy (as it probably should). You can always test in +another branch. + +:: + [feater/branch] $ git checkout -b feature/branch-test-rebase + [feater/branch-test-rebase] $ git rebase --rebase-merges --strategy subtree master + ... + +Afterwards you can make sure the rebase was successful by running ``git vendor list`` +to see if your library is still recognized as a vendored lib + +:: + [feature/branch] $ git vendor list + preexec@0.4.1: + name: preexec + dir: vendor/github.com/rcaloras/bash-preexec + repo: https://github.com/rcaloras/bash-preexec + ref: 0.4.1 + commit: 8fe585c5cf377a3830b895fe26e694b020d8db1a + + + Plugin Disable Callbacks ------------------------ diff --git a/lib/preexec.bash b/lib/preexec.bash deleted file mode 100644 index 2386f46a74..0000000000 --- a/lib/preexec.bash +++ /dev/null @@ -1,199 +0,0 @@ -#!/usr/bin/env bash -# http://www.twistedmatrix.com/users/glyph/preexec.bash.txt -# preexec.bash -- Bash support for ZSH-like 'preexec' and 'precmd' functions. - -# The 'preexec' function is executed before each interactive command is -# executed, with the interactive command as its argument. The 'precmd' -# function is executed before each prompt is displayed. - -# To use, in order: - -# 1. source this file -# 2. define 'preexec' and/or 'precmd' functions (AFTER sourcing this file), -# 3. as near as possible to the end of your shell setup, run 'preexec_install' -# to kick everything off. - -# Note: this module requires 2 bash features which you must not otherwise be -# using: the "DEBUG" trap, and the "PROMPT_COMMAND" variable. preexec_install -# will override these and if you override one or the other this _will_ break. - -# This is known to support bash3, as well as *mostly* support bash2.05b. It -# has been tested with the default shells on MacOS X 10.4 "Tiger", Ubuntu 5.10 -# "Breezy Badger", Ubuntu 6.06 "Dapper Drake", and Ubuntu 6.10 "Edgy Eft". - - -# Copy screen-run variables from the remote host, if they're available. - -if [[ "$SCREEN_RUN_HOST" == "" ]] -then - SCREEN_RUN_HOST="$LC_SCREEN_RUN_HOST" - SCREEN_RUN_USER="$LC_SCREEN_RUN_USER" -fi - -# This variable describes whether we are currently in "interactive mode"; -# i.e. whether this shell has just executed a prompt and is waiting for user -# input. It documents whether the current command invoked by the trace hook is -# run interactively by the user; it's set immediately after the prompt hook, -# and unset as soon as the trace hook is run. -preexec_interactive_mode="" - -# Default do-nothing implementation of preexec. -function preexec () { - true -} - -# Default do-nothing implementation of precmd. -function precmd () { - true -} - -# This function is installed as the PROMPT_COMMAND; it is invoked before each -# interactive prompt display. It sets a variable to indicate that the prompt -# was just displayed, to allow the DEBUG trap, below, to know that the next -# command is likely interactive. -function preexec_invoke_cmd () { - precmd - preexec_interactive_mode="yes" -} - -# This function is installed as the DEBUG trap. It is invoked before each -# interactive prompt display. Its purpose is to inspect the current -# environment to attempt to detect if the current command is being invoked -# interactively, and invoke 'preexec' if so. -function preexec_invoke_exec () { - if [[ -n "$COMP_LINE" ]] - then - # We're in the middle of a completer. This obviously can't be - # an interactively issued command. - return - fi - if [[ -z "$preexec_interactive_mode" ]] - then - # We're doing something related to displaying the prompt. Let the - # prompt set the title instead of me. - return - else - # If we're in a subshell, then the prompt won't be re-displayed to put - # us back into interactive mode, so let's not set the variable back. - # In other words, if you have a subshell like - # (sleep 1; sleep 2) - # You want to see the 'sleep 2' as a set_command_title as well. - if [[ 0 -eq "$BASH_SUBSHELL" ]] - then - preexec_interactive_mode="" - fi - fi - if [[ "preexec_invoke_cmd" == "$BASH_COMMAND" ]] - then - # Sadly, there's no cleaner way to detect two prompts being displayed - # one after another. This makes it important that PROMPT_COMMAND - # remain set _exactly_ as below in preexec_install. Let's switch back - # out of interactive mode and not trace any of the commands run in - # precmd. - - # Given their buggy interaction between BASH_COMMAND and debug traps, - # versions of bash prior to 3.1 can't detect this at all. - preexec_interactive_mode="" - return - fi - - # In more recent versions of bash, this could be set via the "BASH_COMMAND" - # variable, but using history here is better in some ways: for example, "ps - # auxf | less" will show up with both sides of the pipe if we use history, - # but only as "ps auxf" if not. - local this_command=`history 1 | sed -e "s/^[ ]*[0-9]*[ ]*//g"`; - - # If none of the previous checks have earlied out of this function, then - # the command is in fact interactive and we should invoke the user's - # preexec hook with the running command as an argument. - preexec "$this_command" -} - -# Execute this to set up preexec and precmd execution. -function preexec_install () { - - # *BOTH* of these options need to be set for the DEBUG trap to be invoked - # in ( ) subshells. This smells like a bug in bash to me. The null stderr - # redirections are to quiet errors on bash2.05 (i.e. OSX's default shell) - # where the options can't be set, and it's impossible to inherit the trap - # into subshells. - - set -o functrace > /dev/null 2>&1 - shopt -s extdebug > /dev/null 2>&1 - - # Finally, install the actual traps. - if [[ ! -z "${PROMPT_COMMAND// }" ]]; then - PROMPT_COMMAND="${PROMPT_COMMAND}"$'\n'"preexec_invoke_cmd" - else - PROMPT_COMMAND="preexec_invoke_cmd" - fi - trap 'preexec_invoke_exec' DEBUG -} - -# Since this is the reason that 99% of everybody is going to bother with a -# pre-exec hook anyway, we'll include it in this module. - -# Change the title of the xterm. -function preexec_xterm_title () { - local title="$1" - echo -ne "\033]0;$title\007" > /dev/stderr -} - -function preexec_screen_title () { - local title="$1" - echo -ne "\033k$1\033\\" > /dev/stderr -} - -# Abbreviate the "user@host" string as much as possible to preserve space in -# screen titles. Elide the host if the host is the same, elide the user if the -# user is the same. -function preexec_screen_user_at_host () { - local RESULT="" - if [[ "$SCREEN_RUN_HOST" == "$SCREEN_HOST" ]] - then - return - else - if [[ "$SCREEN_RUN_USER" == "$USER" ]] - then - echo -n "@${SCREEN_HOST}" - else - echo -n "${USER}@${SCREEN_HOST}" - fi - fi -} - -function preexec_xterm_title_install () { - # These functions are defined here because they only make sense with the - # preexec_install below. - function precmd () { - preexec_xterm_title "${TERM} - ${USER}@${SCREEN_HOST} `dirs -0` $PROMPTCHAR" - if [[ "${TERM}" == screen ]] - then - preexec_screen_title "`preexec_screen_user_at_host`${PROMPTCHAR}" - fi - } - - function preexec () { - preexec_xterm_title "${TERM} - $1 {`dirs -0`} (${USER}@${SCREEN_HOST})" - if [[ "${TERM}" == screen ]] - then - local cutit="$1" - local cmdtitle=`echo "$cutit" | cut -d " " -f 1` - if [[ "$cmdtitle" == "exec" ]] - then - local cmdtitle=`echo "$cutit" | cut -d " " -f 2` - fi - if [[ "$cmdtitle" == "screen" ]] - then - # Since stacked screens are quite common, it would be nice to - # just display them as '$$'. - local cmdtitle="${PROMPTCHAR}" - else - local cmdtitle=":$cmdtitle" - fi - preexec_screen_title "`preexec_screen_user_at_host`${PROMPTCHAR}$cmdtitle" - fi - } - - preexec_install -} diff --git a/plugins/available/cmd-returned-notify.plugin.bash b/plugins/available/cmd-returned-notify.plugin.bash new file mode 100644 index 0000000000..a305087553 --- /dev/null +++ b/plugins/available/cmd-returned-notify.plugin.bash @@ -0,0 +1,16 @@ +# shellcheck shell=bash +cite about-plugin +about-plugin 'Alert (BEL) when process ends after a threshold of seconds' + +precmd_return_notification() { + export LAST_COMMAND_DURATION=$(($(date +%s) - ${LAST_COMMAND_TIME:=$(date +%s)})) + [[ ${LAST_COMMAND_DURATION} -gt ${NOTIFY_IF_COMMAND_RETURNS_AFTER:-5} ]] && echo -e "\a" + export LAST_COMMAND_TIME= +} + +preexec_return_notification() { + [ -z "${LAST_COMMAND_TIME}" ] && export LAST_COMMAND_TIME=$(date +%s) +} + +precmd_functions+=(precmd_return_notification) +preexec_functions+=(preexec_return_notification) diff --git a/plugins/available/xterm.plugin.bash b/plugins/available/xterm.plugin.bash index c55dccd4f4..c5fe90090d 100644 --- a/plugins/available/xterm.plugin.bash +++ b/plugins/available/xterm.plugin.bash @@ -1,30 +1,33 @@ +# shellcheck shell=bash cite about-plugin about-plugin 'automatically set your xterm title with host and location info' - -_short-dirname () { - local dir_name=`dirs +0` - [ "$SHORT_TERM_LINE" = true ] && [ ${#dir_name} -gt 8 ] && echo ${dir_name##*/} || echo $dir_name +_short-dirname() { + local dir_name=$(dirs +0) + [ "$SHORT_TERM_LINE" = true ] && [ "${#dir_name}" -gt 8 ] && echo "${dir_name##*/}" || echo "${dir_name}" } -_short-command () { - local input_command="$@" - [ "$SHORT_TERM_LINE" = true ] && [ ${#input_command} -gt 8 ] && echo ${input_command%% *} || echo $input_command +_short-command() { + local input_command="$*" + [ "$SHORT_TERM_LINE" = true ] && [ "${#input_command}" -gt 8 ] && echo "${input_command%% *}" || echo "${input_command}" } -set_xterm_title () { - local title="$1" - echo -ne "\033]0;$title\007" +set_xterm_title() { + local title="$1" + echo -ne "\033]0;$title\007" } -precmd () { - set_xterm_title "${SHORT_USER:-${USER}}@${SHORT_HOSTNAME:-${HOSTNAME}} `_short-dirname` $PROMPTCHAR" +precmd_xterm_title() { + set_xterm_title "${SHORT_USER:-${USER}}@${SHORT_HOSTNAME:-${HOSTNAME}} $(_short-dirname) $PROMPT_CHAR" } -preexec () { - set_xterm_title "`_short-command $1` {`_short-dirname`} (${SHORT_USER:-${USER}}@${SHORT_HOSTNAME:-${HOSTNAME}})" +preexec_xterm_title() { + set_xterm_title "$(_short-command "${1}") {$(_short-dirname)} (${SHORT_USER:-${USER}}@${SHORT_HOSTNAME:-${HOSTNAME}})" } case "$TERM" in - xterm*|rxvt*) preexec_install;; + xterm* | rxvt*) + precmd_functions+=(precmd_xterm_title) + preexec_functions+=(preexec_xterm_title) + ;; esac diff --git a/test/fixtures/plugin/xterm/files/arg0 b/test/fixtures/plugin/xterm/files/arg0 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/fixtures/plugin/xterm/files/arg1 b/test/fixtures/plugin/xterm/files/arg1 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/plugins/cmd-returned-notify.plugin.bats b/test/plugins/cmd-returned-notify.plugin.bats new file mode 100644 index 0000000000..69233601e9 --- /dev/null +++ b/test/plugins/cmd-returned-notify.plugin.bats @@ -0,0 +1,47 @@ +#!/usr/bin/env bats + +load ../test_helper +load ../../lib/helpers +load ../../lib/composure + +load ../../plugins/available/cmd-returned-notify.plugin + +@test "plugins cmd-returned-notify: notify after elapsed time" { + export NOTIFY_IF_COMMAND_RETURNS_AFTER=0 + export LAST_COMMAND_TIME=$(date +%s) + sleep 1 + run precmd_return_notification + assert_success + assert_output $'\a' +} + +@test "plugins cmd-returned-notify: do not notify before elapsed time" { + export NOTIFY_IF_COMMAND_RETURNS_AFTER=10 + export LAST_COMMAND_TIME=$(date +%s) + sleep 1 + run precmd_return_notification + assert_success + assert_output $'' +} + +@test "plugins cmd-returned-notify: preexec no output" { + export LAST_COMMAND_TIME= + run preexec_return_notification + assert_success + assert_output "" +} + +@test "plugins cmd-returned-notify: preexec no output env set" { + export LAST_COMMAND_TIME=$(date +%s) + run preexec_return_notification + assert_failure + assert_output "" +} + +@test "plugins cmd-returned-notify: preexec set LAST_COMMAND_TIME" { + export LAST_COMMAND_TIME= + assert_equal "${LAST_COMMAND_TIME}" "" + NOW=$(date +%s) + preexec_return_notification + assert_equal "${LAST_COMMAND_TIME}" "${NOW}" +} diff --git a/test/plugins/xterm.plugin.bats b/test/plugins/xterm.plugin.bats new file mode 100644 index 0000000000..6ded3ceabc --- /dev/null +++ b/test/plugins/xterm.plugin.bats @@ -0,0 +1,54 @@ +#!/usr/bin/env bats + +load ../test_helper +load ../../lib/composure + +load ../../plugins/available/xterm.plugin + +function local_setup { + setup_test_fixture + + # Copy the test fixture to the Bash-it folder + if command_exists -v rsync + then + rsync -a "$BASH_IT/test/fixtures/plugin/xterm/" "$BASH_IT/" + else + find "$BASH_IT/test/fixtures/plugin/xterm" \ + -mindepth 1 -maxdepth 1 \ + -exec cp -r {} "$BASH_IT/" \; + fi +} + +@test "plugins xterm: shorten command output" { + export SHORT_TERM_LINE=true + run _short-command ${BASH_IT}/test/fixtures/plugin/xterm/files/* + assert_success + assert_output ${BASH_IT}/test/fixtures/plugin/xterm/files/arg0 +} + +@test "plugins xterm: full command output" { + export SHORT_TERM_LINE=false + run _short-command ${BASH_IT}/test/fixtures/plugin/xterm/files/* + assert_success + assert_output "$(echo ${BASH_IT}/test/fixtures/plugin/xterm/files/*)" +} + +@test "plugins xterm: shorten dirname output" { + export SHORT_TERM_LINE=true + run _short-dirname + assert_success + assert_output "$(basename $PWD)" +} + +@test "plugins xterm: full dirname output" { + export SHORT_TERM_LINE=false + run _short-dirname + assert_success + assert_output $PWD +} + +@test "plugins xterm: set xterm title" { + run set_xterm_title title + assert_success + assert_output $'\033]0;title\007' +} diff --git a/themes/command_duration.theme.bash b/themes/command_duration.theme.bash index c721003e8d..cf91785cb1 100644 --- a/themes/command_duration.theme.bash +++ b/themes/command_duration.theme.bash @@ -1,10 +1,10 @@ -#!/usr/bin/env bash +# shellcheck shell=bash if [ -z "$BASH_IT_COMMAND_DURATION" ] || [ "$BASH_IT_COMMAND_DURATION" != true ]; then - _command_duration() { - echo -n - } - return + _command_duration() { + echo -n + } + return fi # Define tmp dir and file @@ -17,57 +17,53 @@ COMMAND_DURATION_MIN_SECONDS=${COMMAND_DURATION_MIN_SECONDS:-'1'} trap _command_duration_delete_temp_file EXIT HUP INT TERM _command_duration_delete_temp_file() { - if [[ -f "$COMMAND_DURATION_FILE" ]]; then - rm -f "$COMMAND_DURATION_FILE" - fi + if [[ -f "$COMMAND_DURATION_FILE" ]]; then + rm -f "$COMMAND_DURATION_FILE" + fi } _command_duration_pre_exec() { - date +%s.%1N > "$COMMAND_DURATION_FILE" + date +%s.%1N > "$COMMAND_DURATION_FILE" } _command_duration() { - local command_duration command_start current_time - local minutes seconds deciseconds - local command_start_sseconds current_time_seconds command_start_deciseconds current_time_deciseconds - current_time=$(date +%s.%1N) + local command_duration command_start current_time + local minutes seconds deciseconds + local command_start_sseconds current_time_seconds command_start_deciseconds current_time_deciseconds + current_time=$(date +%s.%1N) - if [[ -f "$COMMAND_DURATION_FILE" ]]; then - command_start=$(< "$COMMAND_DURATION_FILE") - command_start_sseconds=${command_start%.*} - current_time_seconds=${current_time%.*} + if [[ -f "$COMMAND_DURATION_FILE" ]]; then + command_start=$(< "$COMMAND_DURATION_FILE") + command_start_sseconds=${command_start%.*} + current_time_seconds=${current_time%.*} - command_start_deciseconds=$((10#${command_start#*.})) - current_time_deciseconds=$((10#${current_time#*.})) + command_start_deciseconds=$((10#${command_start#*.})) + current_time_deciseconds=$((10#${current_time#*.})) - # seconds - command_duration=$(( current_time_seconds - command_start_sseconds )) + # seconds + command_duration=$((current_time_seconds - command_start_sseconds)) - if (( current_time_deciseconds >= command_start_deciseconds )); then - deciseconds=$(( (current_time_deciseconds - command_start_deciseconds) )) - else - ((command_duration-=1)) - deciseconds=$(( 10 - ( (command_start_deciseconds - current_time_deciseconds) ) )) - fi - command rm "$COMMAND_DURATION_FILE" - else - command_duration=0 - fi + if ((current_time_deciseconds >= command_start_deciseconds)); then + deciseconds=$(((current_time_deciseconds - command_start_deciseconds))) + else + ((command_duration -= 1)) + deciseconds=$((10 - ((command_start_deciseconds - current_time_deciseconds)))) + fi + command rm "$COMMAND_DURATION_FILE" + else + command_duration=0 + fi - if (( command_duration > 0 )); then - minutes=$(( command_duration / 60 )) - seconds=$(( command_duration % 60 )) - fi + if ((command_duration > 0)); then + minutes=$((command_duration / 60)) + seconds=$((command_duration % 60)) + fi - if (( minutes > 0 )); then - printf "%s%s%dm %ds" "$COMMAND_DURATION_ICON" "$COMMAND_DURATION_COLOR" "$minutes" "$seconds" - elif (( seconds >= COMMAND_DURATION_MIN_SECONDS )); then - printf "%s%s%d.%01ds" "$COMMAND_DURATION_ICON" "$COMMAND_DURATION_COLOR" "$seconds" "$deciseconds" - fi + if ((minutes > 0)); then + printf "%s%s%dm %ds" "$COMMAND_DURATION_ICON" "$COMMAND_DURATION_COLOR" "$minutes" "$seconds" + elif ((seconds >= COMMAND_DURATION_MIN_SECONDS)); then + printf "%s%s%d.%01ds" "$COMMAND_DURATION_ICON" "$COMMAND_DURATION_COLOR" "$seconds" "$deciseconds" + fi } -preexec() ( - _command_duration_pre_exec -) - -preexec_install +preexec_functions+=(_command_duration_pre_exec) diff --git a/vendor/init.d/preexec.bash b/vendor/init.d/preexec.bash new file mode 100644 index 0000000000..f8140afc86 --- /dev/null +++ b/vendor/init.d/preexec.bash @@ -0,0 +1 @@ +source ${BASH_IT}/vendor/github.com/rcaloras/bash-preexec/bash-preexec.sh