Skip to content

Commit 566a21e

Browse files
committed
feat: trap_{add,remove} works with multiple signals. Closes #6
1 parent 37b8dc5 commit 566a21e

File tree

2 files changed

+141
-62
lines changed

2 files changed

+141
-62
lines changed

pkg/src/public/bash-core.sh

Lines changed: 74 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -26,43 +26,50 @@ core.init() {
2626
# core.trap_remove 'some_handler' 'USR1'
2727
core.trap_add() {
2828
local function="$1"
29-
local signal_spec="$2"
3029

3130
# validation
3231
if [ -z "$function" ]; then
33-
printf '%s\n' "Error: core.trap_add: First argument cannot be empty"
32+
printf '%s\n' "Error: core.trap_add: Function cannot be empty"
3433
return 1
3534
fi
36-
if [ -z "$signal_spec" ]; then
37-
printf '%s\n' "Error: core.trap_add: Second argument cannot be empty"
38-
return 1
39-
fi
40-
local regex='^[0-9]+$'
41-
if [[ "$signal_spec" =~ $regex ]]; then
42-
printf '%s\n' "Error: core.trap_add: Passing numbers for the signal specs is prohibited"
43-
return 1
44-
fi; unset regex
45-
signal_spec=${signal_spec#SIG}
46-
if ! declare -f "$function" &>/dev/null; then
47-
printf '%s\n' "Error: core.trap_add: Function '$function' is not defined" >&2
35+
36+
if (($# <= 1)); then
37+
printf '%s\n' "Error: core.trap_add: Must specify at least one signal"
4838
return 1
4939
fi
40+
for signal_spec in "${@:2}"; do
41+
if [ -z "$signal_spec" ]; then
42+
printf '%s\n' "Error: core.trap_add: Signal must not be an empty string"
43+
return 1
44+
fi
5045

51-
# start
52-
___global_trap_table___["$signal_spec"]="${___global_trap_table___[$signal_spec]}"$'\x1C'"$function"
46+
local regex='^[0-9]+$'
47+
if [[ "$signal_spec" =~ $regex ]]; then
48+
printf '%s\n' "Error: core.trap_add: Passing numbers for the signal specs is prohibited"
49+
return 1
50+
fi; unset regex
51+
signal_spec=${signal_spec#SIG}
52+
if ! declare -f "$function" &>/dev/null; then
53+
printf '%s\n' "Error: core.trap_add: Function '$function' is not defined" >&2
54+
return 1
55+
fi
5356

54-
# rho (WET)
55-
local global_trap_handler_name=
56-
printf -v global_trap_handler_name '%q' "core.trap_handler_${signal_spec}"
57+
# start
58+
___global_trap_table___["$signal_spec"]="${___global_trap_table___[$signal_spec]}"$'\x1C'"$function"
5759

58-
if ! eval "$global_trap_handler_name() {
59-
core.util.trap_handler_common '$signal_spec'
60-
}"; then
61-
printf '%s\n' "Error: core.trap_add: Could not eval function"
62-
return 1
63-
fi
64-
# shellcheck disable=SC2064
65-
trap "$global_trap_handler_name" "$signal_spec"
60+
# rho (WET)
61+
local global_trap_handler_name=
62+
printf -v global_trap_handler_name '%q' "core.trap_handler_${signal_spec}"
63+
64+
if ! eval "$global_trap_handler_name() {
65+
core.util.trap_handler_common '$signal_spec'
66+
}"; then
67+
printf '%s\n' "Error: core.trap_add: Could not eval function"
68+
return 1
69+
fi
70+
# shellcheck disable=SC2064
71+
trap "$global_trap_handler_name" "$signal_spec"
72+
done
6673
}
6774

6875
# @description Removes a handler for a particular `trap` signal or event. Currently,
@@ -76,50 +83,57 @@ core.trap_add() {
7683
# core.trap_remove 'some_handler' 'USR1'
7784
core.trap_remove() {
7885
local function="$1"
79-
local signal_spec="$2"
8086

8187
# validation
8288
if [ -z "$function" ]; then
83-
printf '%s\n' "Error: core.trap_remove: First argument cannot be empty"
84-
return 1
85-
fi
86-
if [ -z "$signal_spec" ]; then
87-
printf '%s\n' "Error: core.trap_remove: Second argument cannot be empty"
89+
printf '%s\n' "Error: core.trap_remove: Function cannot be empty"
8890
return 1
8991
fi
90-
local regex='^[0-9]+$'
91-
if [[ "$signal_spec" =~ $regex ]]; then
92-
printf '%s\n' "Error: core.trap_remove: Passing numbers for the signal specs is prohibited"
93-
return 1
94-
fi; unset regex
95-
signal_spec="${signal_spec#SIG}"
96-
if ! declare -f "$function" &>/dev/null; then
97-
printf '%s\n' "Error: core.trap_remove: Function '$function' is not defined" >&2
92+
93+
if (($# <= 1)); then
94+
printf '%s\n' "Error: core.trap_remove: Must specify at least one signal"
9895
return 1
9996
fi
100-
101-
# start
102-
local -a trap_handlers=()
103-
local new_trap_handlers=
104-
IFS=$'\x1C' read -ra trap_handlers <<< "${___global_trap_table___[$signal_spec]}"
105-
for trap_handler in "${trap_handlers[@]}"; do
106-
if [ -z "$trap_handler" ] || [ "$trap_handler" = $'\x1C' ]; then
107-
continue
97+
for signal_spec in "${@:2}"; do
98+
if [ -z "$signal_spec" ]; then
99+
printf '%s\n' "Error: core.trap_add: Signal must not be an empty string"
100+
return 1
108101
fi
109102

110-
if [ "$trap_handler" = "$function" ]; then
111-
continue
103+
local regex='^[0-9]+$'
104+
if [[ "$signal_spec" =~ $regex ]]; then
105+
printf '%s\n' "Error: core.trap_remove: Passing numbers for the signal specs is prohibited"
106+
return 1
107+
fi; unset regex
108+
signal_spec="${signal_spec#SIG}"
109+
if ! declare -f "$function" &>/dev/null; then
110+
printf '%s\n' "Error: core.trap_remove: Function '$function' is not defined" >&2
111+
return 1
112112
fi
113113

114-
new_trap_handlers+=$'\x1C'"$trap_handler"
115-
done; unset trap_handler
116-
117-
___global_trap_table___["$signal_spec"]="$new_trap_handlers"
118-
119-
# rho (WET)
120-
local global_trap_handler_name=
121-
printf -v global_trap_handler_name '%q' "___global_trap_${signal_spec}_handler___"
122-
unset -f "$global_trap_handler_name"
114+
# start
115+
local -a trap_handlers=()
116+
local new_trap_handlers=
117+
IFS=$'\x1C' read -ra trap_handlers <<< "${___global_trap_table___[$signal_spec]}"
118+
for trap_handler in "${trap_handlers[@]}"; do
119+
if [ -z "$trap_handler" ] || [ "$trap_handler" = $'\x1C' ]; then
120+
continue
121+
fi
122+
123+
if [ "$trap_handler" = "$function" ]; then
124+
continue
125+
fi
126+
127+
new_trap_handlers+=$'\x1C'"$trap_handler"
128+
done; unset trap_handler
129+
130+
___global_trap_table___["$signal_spec"]="$new_trap_handlers"
131+
132+
# rho (WET)
133+
local global_trap_handler_name=
134+
printf -v global_trap_handler_name '%q' "___global_trap_${signal_spec}_handler___"
135+
unset -f "$global_trap_handler_name"
136+
done
123137
}
124138

125139
# @description Modifies current shell options and pushes information to stack, so

tests/trap.bats

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,27 @@ load './util/init.sh'
99
# The '${___global_trap_table___[nokey]}' is there to ensure that the
1010
# ___global_trap_table___ is an actual associative array. If '___global_trap_table___' is not an associative array, the index with 'nokey' still returns the value of the variable (no error will be thrown). These were origianlly done when the 'core.init' function was not called within these tests
1111

12+
@test "core.trap_add fails when function is not supplied" {
13+
run core.trap_add '' 'USR1'
14+
15+
assert_failure
16+
assert_output -p "Function cannot be empty"
17+
}
18+
19+
@test "core.trap_add fails when signal is not supplied" {
20+
run core.trap_add 'function'
21+
22+
assert_failure
23+
assert_output -p "Must specify at least one signal"
24+
}
25+
26+
@test "core.trap_add fails when signal is empty" {
27+
run core.trap_add 'function' ''
28+
29+
assert_failure
30+
assert_output -p "Signal must not be an empty string"
31+
}
32+
1233
@test "core.trap_add fails when function specified does not exist" {
1334
run core.trap_add 'nonexistent' 'USR1'
1435

@@ -43,14 +64,45 @@ load './util/init.sh'
4364
[ "${___global_trap_table___[USR1]}" = $'\x1Csomefunction\x1Csomefunction2' ]
4465
}
4566

67+
@test "core.trap_add adds function properly 3" {
68+
somefunction() { :; }
69+
core.init
70+
core.trap_add 'somefunction' 'USR1' 'USR2'
71+
72+
[ "${___global_trap_table___[nokey]}" != $'\x1Csomefunction' ]
73+
[ "${___global_trap_table___[USR1]}" = $'\x1Csomefunction' ]
74+
[ "${___global_trap_table___[USR2]}" = $'\x1Csomefunction' ]
75+
}
76+
77+
@test "core.trap_remove fails when function is not supplied" {
78+
run core.trap_remove '' 'USR1'
79+
80+
assert_failure
81+
assert_output -p "Function cannot be empty"
82+
}
83+
84+
@test "core.trap_remove fails when signal is not supplied" {
85+
run core.trap_remove 'function'
86+
87+
assert_failure
88+
assert_output -p "Must specify at least one signal"
89+
}
90+
91+
@test "core.trap_remove fails when signal is empty" {
92+
run core.trap_remove 'function' ''
93+
94+
assert_failure
95+
assert_output -p "Signal must not be an empty string"
96+
}
97+
4698
@test "core.trap_remove fails when function specified does not exist" {
4799
run core.trap_remove 'nonexistent' 'USR1'
48100

49101
assert_failure
50102
assert_output -p "Function 'nonexistent' is not defined"
51103
}
52104

53-
@test "core.trap_remove removes trap function properly 1" {
105+
@test "core.trap_remove removes trap function properly" {
54106
somefunction() { :; }
55107
core.init
56108
core.trap_add 'somefunction' 'USR1'
@@ -71,7 +123,18 @@ load './util/init.sh'
71123
[ "${___global_trap_table___[USR1]}" = $'\x1Csomefunction2' ]
72124
}
73125

74-
@test "core.trap_remove removes trap function properly 3" {
126+
@test "core.trap_add removes function properly 3" {
127+
somefunction() { :; }
128+
core.init
129+
core.trap_add 'somefunction' 'USR1'
130+
core.trap_add 'somefunction' 'USR2'
131+
core.trap_remove 'somefunction' 'USR1' 'USR2'
132+
133+
[ "${___global_trap_table___[USR1]}" = '' ]
134+
[ "${___global_trap_table___[USR2]}" = '' ]
135+
}
136+
137+
@test "core.trap_remove removes trap function properly 4" {
75138
somefunction() { :; }
76139
somefunction2() { :; }
77140
core.init
@@ -82,3 +145,5 @@ load './util/init.sh'
82145
[ "${___global_trap_table___[nokey]}" != $'\x1Csomefunction' ]
83146
[ "${___global_trap_table___[USR1]}" = $'\x1Csomefunction' ]
84147
}
148+
149+

0 commit comments

Comments
 (0)