Skip to content

Commit 728cf65

Browse files
committed
chore: Initial commit
0 parents  commit 728cf65

File tree

8 files changed

+335
-0
lines changed

8 files changed

+335
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.basalt/

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# bash-core
2+
3+
Useful functions for any Bash program. Often vital for Basalt programs
4+
5+
STATUS: BETA
6+
7+
## Summary
8+
9+
Added functions: `core.trap_add` (not working), `core.trap_remove` `core.shopt_push`, and `core.shopt_pop`
10+
11+
## Installation
12+
13+
Use [Basalt](https://github.com/hyperupcall/basalt), a Bash package manager, to add this project as a dependency
14+
15+
16+
```sh
17+
basalt add hyperupcall/bash-core
18+
```

basalt.toml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[package]
2+
name = 'bash-core'
3+
slug = 'core'
4+
version = ''
5+
authors = ['Edwin Kofler <edwin@kofler.dev>']
6+
description = 'Core lightweight functions that any Bash programmer will love'
7+
8+
[run]
9+
dependencies = ['https://github.com/hyperupcall/bats-all.git@v4.1.0']
10+
sourceDirs = ['./pkg/lib/public']
11+
builtinDirs = []
12+
binDirs = []
13+
completionDirs = []
14+
manDirs = []
15+
16+
[run.shellEnvironment]
17+
18+
[run.setOptions]
19+
20+
[run.shoptOptions]

pkg/lib/public/bash-core.sh

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
# shellcheck shell=bash
2+
3+
declare -Ag ___global_trap_table___=()
4+
declare -ag ___global_shopt_stack___=()
5+
6+
# @description Get version of the package, from the point of the
7+
# callsite. In other words, it returns the version of the package
8+
# that has the file containing the direct caller of this function
9+
# @set REPLY The full path to the directory
10+
core.get_package_dir() {
11+
# local start_dir="${1:-"${BASH_SOURCE[1]}"}"
12+
13+
# while [ ! -f 'basalt.toml' ] && [ "$PWD" != / ]; do
14+
# if ! cd ..; then
15+
# return 1
16+
# fi
17+
# done
18+
:
19+
}
20+
21+
core.trap_add() {
22+
local function="$1"
23+
local signal_spec="$2"
24+
25+
# validation
26+
local regex='^[0-9]+$'
27+
if [[ "$signal_spec" =~ $regex ]]; then
28+
printf '%s\n' "Error: core.trap_add: Passing numbers for the signal specs is prohibited"
29+
return 1
30+
fi; unset regex
31+
signal_spec="${signal_spec#SIG}"
32+
33+
if ! declare -f "$function" &>/dev/null; then
34+
printf '%s\n' "Error: core.trap_add: Function '$function' not defined" >&2
35+
return 1
36+
fi
37+
38+
# start
39+
___global_trap_table___["$signal_spec"]="${___global_trap_table___[$signal_spec]}"$'\x1C'"$function"
40+
41+
local global_trap_handler_name=
42+
printf -v global_trap_handler_name '%q' "__global_trap_${signal_spec}_handler___"
43+
44+
eval 'if ! '"$global_trap_handler_name"'() {
45+
local trap_handlers= trap_handler=
46+
IFS=$'"'\x1C'"' read -ra trap_handlers <<< "${___global_trap_table___[$signal_spec]}"
47+
for trap_func in "${trap_handlers[@]}"; do
48+
if declare -f "$trap_handler"; then
49+
:
50+
else
51+
printf "%s\n" "Warn: core.trap_add: Function '"'\$function'"' registered for signal '"'\$signal_spec'"' no longer exists" >&2
52+
fi
53+
done
54+
}; then
55+
false
56+
fi'
57+
trap "$global_trap_handler_name" "$signal_spec"
58+
}
59+
60+
core.trap_remove() {
61+
local function="$1"
62+
local signal_spec="$2"
63+
64+
# validation
65+
local regex='^[0-9]+$'
66+
if [[ "$signal_spec" =~ $regex ]]; then
67+
printf '%s\n' "Error: core.trap_add: Passing numbers for the signal specs is prohibited"
68+
return 1
69+
fi; unset regex
70+
signal_spec="${signal_spec#SIG}"
71+
72+
if ! declare -f "$function" &>/dev/null; then
73+
printf '%s\n' "Error: core.trap_add: Function '$function' not defined" >&2
74+
return 1
75+
fi
76+
77+
# start
78+
local -a trap_handlers=()
79+
local new_trap_handlers=
80+
IFS=$'\x1C' read -ra trap_handlers <<< "${___global_trap_table___[$signal_spec]}"
81+
for trap_handler in "${trap_handlers[@]}"; do
82+
if [ -z "$trap_handler" ] || [ "$trap_handler" = $'\x1C' ]; then
83+
continue
84+
fi
85+
86+
if [ "$trap_handler" = "$function" ]; then
87+
continue
88+
fi
89+
90+
new_trap_handlers+=$'\x1C'"$trap_handler"
91+
done; unset trap_handler
92+
93+
___global_trap_table___["$signal_spec"]="$new_trap_handlers"
94+
}
95+
96+
core.shopt_push() {
97+
local shopt_action="$1"
98+
local shopt_name="$2"
99+
100+
if [ -z "$shopt_action" ]; then
101+
printf '%s\n' "Error: core.shopt_push: First argument cannot be empty"
102+
return 1
103+
fi
104+
105+
if [ -z "$shopt_name" ]; then
106+
printf '%s\n' "Error: core.shopt_push: Second argument cannot be empty"
107+
return 1
108+
fi
109+
110+
local -i previous_shopt_errcode=
111+
if shopt -q "$shopt_name"; then
112+
previous_shopt_errcode=$?
113+
else
114+
previous_shopt_errcode=$?
115+
fi
116+
117+
if [ "$shopt_action" = '-s' ]; then
118+
if shopt -s "$shopt_name"; then :; else
119+
# on error, option will not be set
120+
return $?
121+
fi
122+
elif [ "$shopt_action" = '-u' ]; then
123+
if shopt -u "$shopt_name"; then :; else
124+
# on error, option will not be set
125+
return $?
126+
fi
127+
else
128+
printf '%s\n' "Error: core.shopt_push: Accepted actions are either '-s' or '-u'" >&2
129+
return 1
130+
fi
131+
132+
133+
if (( previous_shopt_errcode == 0)); then
134+
___global_shopt_stack___+=(-s "$shopt_name")
135+
else
136+
___global_shopt_stack___+=(-u "$shopt_name")
137+
fi
138+
}
139+
140+
# shellcheck disable=SC2120
141+
core.shopt_pop() {
142+
# local repeat="$1"
143+
144+
# TODO: wait until supporting it since on error, may be hard to ensure proper state of the stack
145+
# local regex='^[0-9]+$'
146+
# if [ -n "$repeat" ] && [[ "$repeat" =~ $regex ]]; then
147+
# for ((i=0; i<repeat; i++)); do
148+
# core.shopt_pop
149+
# return
150+
# done; unset i
151+
# fi; unset regex
152+
153+
if (( ${#___global_shopt_stack___[@]} == 0 )); then
154+
printf '%s\n' "Error: core.shopt_pop: Unable to pop as nothing is in the shopt stack"
155+
return 1
156+
fi
157+
158+
if (( ${#___global_shopt_stack___[@]} & 1 )); then
159+
printf '%s\n' "Fatal: core.shopt_pop: Shopt stack is malformed"
160+
return 1
161+
fi
162+
163+
# Stack now guaranteed to have at least 2 elements
164+
local shopt_action="${___global_shopt_stack___[-2]}"
165+
local shopt_name="${___global_shopt_stack___[-1]}"
166+
167+
if shopt -u "$shopt_name"; then :; else
168+
local errcode=$?
169+
printf '%s\n' "Fatal: core.shopt_pop: Could not restore previous option" >&2
170+
return $errcode
171+
fi
172+
___global_shopt_stack___=("${___global_shopt_stack___[@]::${#___global_shopt_stack___[@]}-2}")
173+
}

tests/shopt.bats

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#!/usr/bin/env bats
2+
3+
load './util/init.sh'
4+
5+
@test "Shopt push works" {
6+
shopt -u extglob
7+
8+
core.shopt_push -s extglob
9+
10+
assert [ "${#___global_shopt_stack___[@]}" = 2 ]
11+
assert [ ${___global_shopt_stack___[0]} = '-u' ]
12+
assert [ ${___global_shopt_stack___[1]} = 'extglob' ]
13+
assert shopt -q extglob
14+
15+
core.shopt_pop
16+
assert [ "${#___global_shopt_stack___[@]}" = 0 ]
17+
refute shopt -q extglob
18+
}
19+
20+
@test "Shopt works multi" {
21+
shopt -u extglob
22+
shopt -u dotglob
23+
shopt -u failglob
24+
25+
core.shopt_push -s extglob
26+
core.shopt_push -s dotglob
27+
core.shopt_push -s failglob
28+
29+
assert shopt -q extglob
30+
assert shopt -q dotglob
31+
assert shopt -q failglob
32+
33+
core.shopt_pop
34+
core.shopt_pop
35+
core.shopt_pop
36+
37+
assert [ "${#___global_shopt_stack___[@]}" = 0 ]
38+
refute shopt -q extglob
39+
refute shopt -q dotglob
40+
refute shopt -q failglob
41+
}

tests/trap.bats

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#!/usr/bin/env bats
2+
3+
load './util/init.sh'
4+
5+
# Note that this and similar functions only test for array appending, not
6+
# actual execution of the functions on the signal. There seems to be a limitation
7+
# of Bats that prevents this from working
8+
9+
@test "Fails when function specified does not exist" {
10+
run core.trap_add 'nonexistent' 'USR1'
11+
12+
assert_failure
13+
assert_output -p "Function 'nonexistent' not defined"
14+
}
15+
16+
@test "Fails when number is given for signal" {
17+
run core.trap_add 'function' '0'
18+
19+
assert_failure
20+
assert_output -p "Passing numbers for the signal specs is prohibited"
21+
}
22+
23+
@test "adds trap function properly" {
24+
somefunction() { :; }
25+
core.trap_add 'somefunction' 'USR1'
26+
27+
[ "${___global_trap_table___[USR1]}" = $'\x1Csomefunction' ]
28+
}
29+
30+
@test "adds trap function properly 2" {
31+
somefunction() { :; }
32+
somefunction2() { :; }
33+
core.trap_add 'somefunction' 'USR1'
34+
core.trap_add 'somefunction2' 'USR1'
35+
36+
[ "${___global_trap_table___[USR1]}" = $'\x1Csomefunction\x1Csomefunction2' ]
37+
}
38+
39+
@test "removes trap function properly 1" {
40+
somefunction() { :; }
41+
core.trap_add 'somefunction' 'USR1'
42+
core.trap_remove 'somefunction' 'USR1'
43+
44+
[ "${___global_trap_table___[USR1]}" = '' ]
45+
}
46+
47+
@test "removes trap function properly 2" {
48+
somefunction() { :; }
49+
somefunction2() { :; }
50+
core.trap_add 'somefunction' 'USR1'
51+
core.trap_add 'somefunction2' 'USR1'
52+
core.trap_remove 'somefunction' 'USR1'
53+
54+
[ "${___global_trap_table___[USR1]}" = $'\x1Csomefunction2' ]
55+
}
56+
57+
@test "removes trap function properly 3" {
58+
somefunction() { :; }
59+
somefunction2() { :; }
60+
core.trap_add 'somefunction' 'USR1'
61+
core.trap_add 'somefunction2' 'USR1'
62+
core.trap_remove 'somefunction2' 'USR1'
63+
64+
[ "${___global_trap_table___[USR1]}" = $'\x1Csomefunction' ]
65+
}

tests/util/init.sh

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# shellcheck shell=bash
2+
3+
eval "$(basalt-package-init)"
4+
basalt.package-init
5+
basalt.package-load
6+
basalt.load 'github.com/hyperupcall/bats-all' 'load.bash'
7+
8+
load './util/test_util.sh'
9+
10+
setup() {
11+
cd "$BATS_TEST_TMPDIR"
12+
}
13+
14+
teardown() {
15+
cd "$BATS_SUITE_TMPDIR"
16+
}

tests/util/test_util.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# shellcheck shell=bash

0 commit comments

Comments
 (0)