Skip to content

Commit fe46a97

Browse files
committed
Add APIs for external precmd/preexec integrations
1 parent fd2ffa8 commit fe46a97

File tree

1 file changed

+59
-18
lines changed

1 file changed

+59
-18
lines changed

bash-preexec.sh

Lines changed: 59 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ __bp_inside_preexec=0
6262
# Initial PROMPT_COMMAND string that is removed from PROMPT_COMMAND post __bp_install
6363
__bp_install_string=$'__bp_trap_string="$(trap -p DEBUG)"\ntrap - DEBUG\n__bp_install'
6464

65+
# The command string that is registered to the DEBUG trap.
66+
__bp_trapdebug_string='__bp_preexec_invoke_exec "$_"'
67+
6568
# Fails if any of the given variables are readonly
6669
# Reference https://stackoverflow.com/a/4441178
6770
__bp_require_not_readonly() {
@@ -143,15 +146,19 @@ __bp_precmd_invoke_cmd() {
143146
return
144147
fi
145148
local __bp_inside_precmd=1
149+
__bp_invoke_precmd_functions "$__bp_last_ret_value" "$__bp_last_argument_prev_command"
150+
}
146151

152+
__bp_invoke_precmd_functions() {
153+
local __lastexit=$1 __lastarg=$2
147154
# Invoke every function defined in our function array.
148155
local precmd_function
149156
for precmd_function in "${precmd_functions[@]}"; do
150157

151158
# Only execute this function if it actually exists.
152159
# Test existence of functions with: declare -[Ff]
153160
if type -t "$precmd_function" 1>/dev/null; then
154-
__bp_set_ret_value "$__bp_last_ret_value" "$__bp_last_argument_prev_command"
161+
__bp_set_ret_value "$__lastexit" "$__lastarg"
155162
# Quote our function invocation to prevent issues with IFS
156163
"$precmd_function"
157164
fi
@@ -244,32 +251,40 @@ __bp_preexec_invoke_exec() {
244251
return
245252
fi
246253

247-
# Invoke every function defined in our function array.
254+
local preexec_ret_value
255+
__bp_invoke_preexec_functions "${__bp_last_ret_value:-}" "$__bp_last_argument_prev_command" "$this_command"
256+
257+
# Restore the last argument of the last executed command, and set the return
258+
# value of the DEBUG trap to be the return code of the last preexec function
259+
# to return an error.
260+
# If `extdebug` is enabled a non-zero return value from any preexec function
261+
# will cause the user's command not to execute.
262+
# Run `shopt -s extdebug` to enable
263+
__bp_set_ret_value "$preexec_ret_value" "$__bp_last_argument_prev_command"
264+
}
265+
266+
# This function invokes every function defined in our function array. This
267+
# function assigns the last error exit status to the variable
268+
# `preexec_ret_value`. If there is no error, preexec_ret_value is set to `0`.
269+
__bp_invoke_preexec_functions() {
270+
local __lastexit=$1 __lastarg=$2 __this_command=$3
248271
local preexec_function
249272
local preexec_function_ret_value
250-
local preexec_ret_value=0
273+
preexec_ret_value=0
251274
for preexec_function in "${preexec_functions[@]:-}"; do
252275

253276
# Only execute each function if it actually exists.
254277
# Test existence of function with: declare -[fF]
255278
if type -t "$preexec_function" 1>/dev/null; then
256-
__bp_set_ret_value ${__bp_last_ret_value:-}
279+
__bp_set_ret_value "$__lastexit" "$__lastarg"
257280
# Quote our function invocation to prevent issues with IFS
258-
"$preexec_function" "$this_command"
281+
"$preexec_function" "$__this_command"
259282
preexec_function_ret_value="$?"
260283
if [[ "$preexec_function_ret_value" != 0 ]]; then
261284
preexec_ret_value="$preexec_function_ret_value"
262285
fi
263286
fi
264287
done
265-
266-
# Restore the last argument of the last executed command, and set the return
267-
# value of the DEBUG trap to be the return code of the last preexec function
268-
# to return an error.
269-
# If `extdebug` is enabled a non-zero return value from any preexec function
270-
# will cause the user's command not to execute.
271-
# Run `shopt -s extdebug` to enable
272-
__bp_set_ret_value "$preexec_ret_value" "$__bp_last_argument_prev_command"
273288
}
274289

275290
__bp_install() {
@@ -278,7 +293,7 @@ __bp_install() {
278293
return 1;
279294
fi
280295

281-
trap '__bp_preexec_invoke_exec "$_"' DEBUG
296+
trap "$__bp_trapdebug_string" DEBUG
282297

283298
# Preserve any prior DEBUG trap as a preexec function
284299
local prior_trap=$(sed "s/[^']*'\(.*\)'[^']*/\1/" <<<"${__bp_trap_string:-}")
@@ -319,10 +334,13 @@ __bp_install() {
319334
fi;
320335
PROMPT_COMMAND+='__bp_interactive_mode'
321336

322-
# Add two functions to our arrays for convenience
323-
# of definition.
324-
precmd_functions+=(precmd)
325-
preexec_functions+=(preexec)
337+
# Add two functions to our arrays for convenience of definition only when
338+
# the functions have not yet added.
339+
if [[ ! ${__bp_installed_convenience_functions-} ]]; then
340+
__bp_installed_convenience_functions=1
341+
precmd_functions+=(precmd)
342+
preexec_functions+=(preexec)
343+
fi
326344

327345
# Invoke our two functions manually that were added to $PROMPT_COMMAND
328346
__bp_precmd_invoke_cmd
@@ -345,6 +363,29 @@ __bp_install_after_session_init() {
345363
PROMPT_COMMAND+=${__bp_install_string}
346364
}
347365

366+
__bp_uninstall() {
367+
# Remove __bp_install hook from PROMPT_COMMAND
368+
if [[ ${PROMPT_COMMAND-} == *"$__bp_install_string"* ]]; then
369+
PROMPT_COMMAND="${PROMPT_COMMAND//$__bp_install_string[;$'\n']}" # Edge case of appending to PROMPT_COMMAND
370+
PROMPT_COMMAND="${PROMPT_COMMAND//$__bp_install_string}"
371+
fi
372+
373+
# Remove precmd hook from PROMPT_COMMAND
374+
PROMPT_COMMAND=${PROMPT_COMMAND/#$'__bp_precmd_invoke_cmd\n'/$'\n'}
375+
PROMPT_COMMAND=${PROMPT_COMMAND%$'\n__bp_interactive_mode'}
376+
PROMPT_COMMAND=${PROMPT_COMMAND#$'\n'}
377+
378+
# Remove preexec hook in the DEBUG trap
379+
local q="'" Q="'\''"
380+
if [[ $(trap -p DEBUG) == "trap -- '${__bp_trapdebug_string//$q/$Q}' DEBUG" ]]; then
381+
if [[ ${__bp_trap_string-} ]]; then
382+
eval -- "$__bp_trap_string"
383+
else
384+
trap - DEBUG
385+
fi
386+
fi
387+
}
388+
348389
# Run our install so long as we're not delaying it.
349390
if [[ -z "${__bp_delay_install:-}" ]]; then
350391
__bp_install_after_session_init

0 commit comments

Comments
 (0)