Skip to content

Commit 6afddc8

Browse files
committed
fix: Style improvements and robustness
1 parent 0810d12 commit 6afddc8

File tree

7 files changed

+311
-11
lines changed

7 files changed

+311
-11
lines changed

Bakefile.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# shellcheck shell=bash
2+
3+
task.test() {
4+
bats tests
5+
}

bake

Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
#!/usr/bin/env bash
2+
3+
# @name Bake
4+
# @brief Bake: A Bash-based Make alternative
5+
# @description Bake is a dead-simple task runner used to quickly cobble together shell scripts
6+
#
7+
# In a few words, Bake lets you call the following 'print' task with './bake print'
8+
#
9+
# ```bash
10+
# #!/usr/bin/env bash
11+
# task.print() {
12+
# printf '%s\n' 'Contrived example'
13+
# }
14+
# ```
15+
#
16+
# Learn more about it [on GitHub](https://github.com/hyperupcall/bake)
17+
18+
if [ "$0" != "${BASH_SOURCE[0]}" ] && [ "$BAKE_INTERNAL_CAN_SOURCE" != 'yes' ]; then
19+
printf '%s\n' "Error: This file should not be sourced" >&2
20+
return 1
21+
fi
22+
23+
# @description Prints stacktrace
24+
# @internal
25+
__bake_print_stacktrace() {
26+
if __bake_is_color; then
27+
printf '\033[4m%s\033[0m\n' 'Stacktrace:'
28+
else
29+
printf '%s\n' 'Stacktrace:'
30+
fi
31+
32+
local i=
33+
for ((i=2; i<${#FUNCNAME[@]}-1; i++)); do
34+
local bash_source=${BASH_SOURCE[$i]}; bash_source="${bash_source##*/}"
35+
printf '%s\n' " -> $bash_source:${BASH_LINENO[$i]} ${FUNCNAME[$i]}()"
36+
done; unset -v i
37+
} >&2
38+
39+
# @description Function 'trap' calls on 'ERR'
40+
# @internal
41+
__bake_trap_err() {
42+
local error_code=$?
43+
44+
__bake_print_big "<- ERROR"
45+
__bake_internal_error "Your 'Bakefile.sh' did not exit successfully"
46+
__bake_print_stacktrace
47+
48+
exit $error_code
49+
} >&2
50+
51+
# @description Test whether color should be outputed
52+
# @exitcode 0 if should print color
53+
# @exitcode 1 if should not print color
54+
# @internal
55+
__bake_is_color() {
56+
! [[ -v NO_COLOR || $TERM == dumb ]]
57+
}
58+
59+
# @description Prints `$1` formatted as an internal Bake error to standard error
60+
# @arg $1 Text to print
61+
# @internal
62+
__bake_internal_error() {
63+
if __bake_is_color; then
64+
printf "\033[0;31m%s:\033[0m %s\n" "Error (bake)" "$1"
65+
else
66+
printf '%s: %s\n' 'Error (bake)' "$1"
67+
fi
68+
} >&2
69+
70+
# @description Calls `__bake_internal_error` and terminates with code 1
71+
# @arg $1 string Text to print
72+
# @internal
73+
__bake_internal_die() {
74+
__bake_internal_error "$1. Exiting"
75+
exit 1
76+
}
77+
78+
# @description Prints `$1` formatted as an error to standard error
79+
# @arg $1 string Text to print
80+
# @internal
81+
__bake_error() {
82+
if __bake_is_color; then
83+
printf "\033[0;31m%s:\033[0m %s\n" 'Error' "$1"
84+
else
85+
printf '%s: %s\n' 'Error' "$1"
86+
fi
87+
} >&2
88+
89+
# @description Nicely prints all 'Basalt.sh' tasks to standard output
90+
# @internal
91+
__bake_print_tasks() {
92+
# shellcheck disable=SC1007,SC2034
93+
local regex="^(([[:space:]]*function[[:space:]]*)?task\.(.*?)\(\)).*"
94+
local line=
95+
printf '%s\n' 'Tasks:'
96+
while IFS= read -r line || [ -n "$line" ]; do
97+
if [[ "$line" =~ $regex ]]; then
98+
printf '%s\n' " -> ${BASH_REMATCH[3]}"
99+
fi
100+
done < "$BAKE_FILE"; unset -v line
101+
} >&2
102+
103+
# @description Prints text that takes up the whole terminal width
104+
# @arg $1 string Text to print
105+
# @internal
106+
__bake_print_big() {
107+
local print_text="$1"
108+
109+
# shellcheck disable=SC1007
110+
local _stty_height= _stty_width=
111+
read -r _stty_height _stty_width < <(
112+
if command -v stty &>/dev/null; then
113+
stty size
114+
else
115+
printf '%s\n' '20 80'
116+
fi
117+
)
118+
119+
local separator_text=
120+
# shellcheck disable=SC2183
121+
printf -v separator_text '%*s' $((_stty_width - ${#print_text} - 1))
122+
printf -v separator_text '%s' "${separator_text// /=}"
123+
if __bake_is_color; then
124+
printf '\033[1m%s %s\033[0m\n' "$print_text" "$separator_text"
125+
else
126+
printf '%s %s\n' "$print_text" "$separator_text"
127+
fi
128+
} >&2
129+
130+
__bake_set_vars() {
131+
unset REPLY; REPLY=
132+
local -i total_shifts=0
133+
134+
if [ "$1" = '-f' ]; then
135+
BAKE_FILE=$2
136+
if [ -z "$BAKE_FILE" ]; then
137+
__bake_internal_die 'File must not be empty. Exiting'
138+
fi
139+
total_shifts=$((total_shifts + 2))
140+
141+
if [ ! -e "$BAKE_FILE" ]; then
142+
__bake_internal_die "Specified file '$BAKE_FILE' does not exist"
143+
fi
144+
if [ ! -f "$BAKE_FILE" ]; then
145+
__bake_internal_die "Specified path '$BAKE_FILE' is not actually a file"
146+
fi
147+
fi
148+
149+
if [ -n "$BAKE_FILE" ]; then
150+
BAKE_ROOT=$(
151+
# shellcheck disable=SC1007
152+
CDPATH= cd -- "${BAKE_FILE%/*}"
153+
printf '%s\n' "$PWD"
154+
)
155+
BAKE_FILE="$BAKE_ROOT/${BAKE_FILE##*/}"
156+
else
157+
if ! BAKE_ROOT=$(
158+
while [ ! -f 'Bakefile.sh' ] && [ "$PWD" != / ]; do
159+
if ! cd ..; then
160+
exit 1
161+
fi
162+
done
163+
164+
if [ "$PWD" = / ]; then
165+
exit 1
166+
fi
167+
168+
printf '%s' "$PWD"
169+
); then
170+
__bake_internal_die "Could not find 'Bakefile.sh'"
171+
fi
172+
BAKE_FILE="$BAKE_ROOT/Bakefile.sh"
173+
fi
174+
175+
REPLY=$total_shifts
176+
}
177+
178+
# @description Prints `$1` formatted as an error and the stacktrace to standard error,
179+
# then exits with code 1
180+
# @arg $1 string Text to print
181+
bake.die() {
182+
if [ -n "$1" ]; then
183+
__bake_error "$1. Exiting"
184+
else
185+
__bake_error 'Exiting'
186+
fi
187+
__bake_print_big '<- ERROR'
188+
189+
__bake_print_stacktrace
190+
191+
exit 1
192+
}
193+
194+
# @description Prints `$1` formatted as a warning to standard error
195+
# @arg $1 string Text to print
196+
bake.warn() {
197+
if __bake_is_color; then
198+
printf "\033[1;33m%s:\033[0m %s\n" 'Warn' "$1"
199+
else
200+
printf '%s: %s\n' 'Warn' "$1"
201+
fi
202+
} >&2
203+
204+
# @description Prints `$1` formatted as information to standard output
205+
# @arg $1 string Text to print
206+
bake.info() {
207+
if __bake_is_color; then
208+
printf "\033[0;34m%s:\033[0m %s\n" 'Info' "$1"
209+
else
210+
printf '%s: %s\n' 'Info' "$1"
211+
fi
212+
}
213+
214+
# @description Dies if any of the supplied variables are empty
215+
# @arg $@ string Variable names to print
216+
bake.assert_nonempty() {
217+
local variable_name=
218+
for variable_name; do
219+
local -n variable="$variable_name"
220+
221+
if [ -z "$variable" ]; then
222+
bake.die "Failed because variable '$variable_name' is empty"
223+
fi
224+
done; unset -v variable_name
225+
}
226+
227+
# @description Dies if a command cannot be found
228+
# @arg $1 string Command to test for existence
229+
bake.assert_cmd() {
230+
local cmd=$1
231+
232+
if [ -z "$cmd" ]; then
233+
bake.die "Argument must not be empty"
234+
fi
235+
236+
if ! command -v "$cmd" &>/dev/null; then
237+
bake.die "Failed to find command '$cmd'. Please install it before continuing"
238+
fi
239+
}
240+
241+
__bake_main() {
242+
set -Eeo pipefail
243+
shopt -s dotglob extglob globasciiranges globstar lastpipe nullglob shift_verbose
244+
export LANG='C' LC_CTYPE='C' LC_NUMERIC='C' LC_TIME='C' LC_COLLATE='C' LC_MONETARY='C' LC_MESSAGES='C' \
245+
LC_PAPER='C' LC_NAME='C' LC_ADDRESS='C' LC_TELEPHONE='C' LC_MEASUREMENT='C' LC_IDENTIFICATION='C' LC_ALL='C'
246+
trap '__bake_trap_err' 'ERR'
247+
248+
# Set `BAKE_{ROOT,FILE}`
249+
BAKE_ROOT=; BAKE_FILE=
250+
__bake_set_vars "$@"
251+
if ! shift "$REPLY"; then
252+
__bake_internal_die 'Failed to shift'
253+
fi
254+
255+
local task=$1
256+
if [ -z "$task" ]; then
257+
__bake_internal_error "No valid task supplied"
258+
__bake_print_tasks
259+
exit 1
260+
fi
261+
if ! shift; then
262+
__bake_internal_die 'Failed to shift'
263+
fi
264+
265+
if ! cd "$BAKE_ROOT"; then
266+
__bake_internal_die "Failed to cd"
267+
fi
268+
269+
# shellcheck disable=SC2097,SC1007,SC1090
270+
task= source "$BAKE_FILE"
271+
272+
if declare -f task."$task" >/dev/null 2>&1; then
273+
__bake_print_big "-> RUNNING TASK '$task'"
274+
if declare -f init >/dev/null 2>&1; then
275+
init "$task"
276+
fi
277+
task."$task" "$@"
278+
__bake_print_big "<- DONE"
279+
else
280+
__bake_internal_error "Task '$task' not found"
281+
__bake_print_tasks
282+
exit 1
283+
fi
284+
}
285+
286+
__bake_main "$@"

basalt.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,8 @@ manDirs = []
1515
[run.shellEnvironment]
1616

1717
[run.setOptions]
18+
errexit = 'on'
19+
pipefail = 'on'
1820

1921
[run.shoptOptions]
22+
shift_verbose = 'on'

pkg/src/parse.sh

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ bash_object.parse_querytree() {
99

1010
local flag_parser_type=
1111

12-
for arg; do case "$arg" in
12+
for arg; do case $arg in
1313
--simple)
1414
flag_parser_type='simple'
1515
shift ;;
@@ -51,10 +51,10 @@ bash_object.parse_querytree() {
5151
PARSER_COLUMN_NUMBER+=1
5252

5353
if [ -n "${TRACE_BASH_OBJECT_PARSE+x}" ]; then
54-
echo "-- $mode: '$char'" >&3
54+
printf '%s\n' "-- $mode: '$char'" >&3
5555
fi
5656

57-
case "$mode" in
57+
case $mode in
5858
MODE_DEFAULT)
5959
if [ "$char" = . ]; then
6060
mode='MODE_EXPECTING_BRACKET'
@@ -200,6 +200,6 @@ bash_object.parse_virtual_object() {
200200
esac
201201
done <<< "$virtual_metadatas"
202202

203-
REPLY1="$virtual_object_name"
204-
REPLY2="$vmd_dtype"
203+
REPLY1=$virtual_object_name
204+
REPLY2=$vmd_dtype
205205
}

pkg/src/public/bobject.sh

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22

33
bobject() {
44
local subcmd="$1"
5-
shift
5+
if ! shift; then
6+
bash_object.util.die 'ERROR_INTERNAL' 'Shift failed, but was expected to succeed'
7+
return
8+
fi
69

7-
case "$subcmd" in
10+
case $subcmd in
811
get-string)
912
bash_object.traverse-get string "$@"
1013
;;

pkg/src/traverse-get.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# shellcheck shell=bash
22

33
bash_object.traverse-get() {
4-
unset REPLY
4+
unset REPLY; REPLY=
55

66
if [ -n "${TRACE_BASH_OBJECT_TRAVERSE+x}" ]; then
77
stdtrace.log 0 ''
@@ -11,7 +11,7 @@ bash_object.traverse-get() {
1111
local flag_as_what=
1212
local -a args=()
1313

14-
for arg; do case "$arg" in
14+
for arg; do case $arg in
1515
--ref)
1616
if [ -n "$flag_as_what" ]; then
1717
bash_object.util.die 'ERROR_ARGUMENTS_INVALID' "Flags '--ref' and '--value' are mutually exclusive"

pkg/src/traverse-set.sh

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ bash_object.traverse-set() {
99
local flag_pass_by_what=
1010
local -a args=()
1111

12-
for arg; do case "$arg" in
12+
for arg; do case $arg in
1313
--ref)
1414
if [ -n "$flag_pass_by_what" ]; then
1515
bash_object.util.die 'ERROR_ARGUMENTS_INVALID' "Flags '--ref' and '--value' are mutually exclusive"
@@ -31,7 +31,10 @@ bash_object.traverse-set() {
3131
*)
3232
args+=("$arg")
3333
;;
34-
esac; shift; done
34+
esac; if ! shift; then
35+
bash_object.util.die 'ERROR_INTERNAL' 'Shift failed, but was expected to succeed'
36+
return
37+
fi; done
3538

3639
if [ -z "$flag_pass_by_what" ]; then
3740
bash_object.util.die 'ERROR_ARGUMENTS_INVALID' "Must pass either the '--ref' or '--value' flag"

0 commit comments

Comments
 (0)