Skip to content

Commit 811ad1e

Browse files
committed
chore: Initial commit
0 parents  commit 811ad1e

File tree

13 files changed

+216675
-0
lines changed

13 files changed

+216675
-0
lines changed

.editorconfig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
root = true
2+
3+
[*.{sh,bash,bats},bin/*] # TODO: not clean enough
4+
indent_style = tab
5+
end_of_line = lf
6+
charset = utf-8
7+
trim_trailing_whitespace = true
8+
insert_final_newline = true

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* text=auto eol=lf

.gitignore

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

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.gen() {
4+
deno run --allow-read --allow-write ./scripts/parse.ts --write
5+
}

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# bash-tty
2+
3+
`tput` is slow as a dog. Terminfo database from ncurses version `6.3`
4+
5+
## Installation
6+
7+
Use [Basalt](https://github.com/hyperupcall/basalt), a Bash package manager, to add this project as a dependency
8+
9+
```sh
10+
basalt add 'hyperupcall/bash-tty'
11+
```
12+
13+
## Roadmap
14+
15+
- Handle terminal attributes with formatting

bake

Lines changed: 364 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,364 @@
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_cfg_stacktrace" = 'yes' ]; then
27+
if __bake_is_color; then
28+
printf '\033[4m%s\033[0m\n' 'Stacktrace:'
29+
else
30+
printf '%s\n' 'Stacktrace:'
31+
fi
32+
33+
local i=
34+
for ((i=0;i<${#FUNCNAME[@]}-1;i++)); do
35+
local __bash_source=${BASH_SOURCE[$i]}; __bash_source="${__bash_source##*/}"
36+
printf '%s\n' " in ${FUNCNAME[$i]} ($__bash_source:${BASH_LINENO[$i-1]})"
37+
done; unset -v i __bash_source
38+
fi
39+
} >&2
40+
41+
# @description Function 'trap' calls on 'ERR'
42+
# @internal
43+
__bake_trap_err() {
44+
local error_code=$?
45+
46+
__bake_print_big "<- ERROR"
47+
__bake_internal_error "Your 'Bakefile.sh' did not exit successfully"
48+
__bake_print_stacktrace
49+
50+
exit $error_code
51+
} >&2
52+
53+
# @description Test whether color should be outputed
54+
# @exitcode 0 if should print color
55+
# @exitcode 1 if should not print color
56+
# @internal
57+
__bake_is_color() {
58+
if [[ -v NO_COLOR || $TERM == dumb ]]; then
59+
return 1
60+
else
61+
return 0
62+
fi
63+
}
64+
65+
# @description Prints `$1` formatted as an internal Bake error to standard error
66+
# @arg $1 Text to print
67+
# @internal
68+
__bake_internal_error() {
69+
if __bake_is_color; then
70+
printf "\033[0;31m%s:\033[0m %s\n" "Error (bake)" "$1"
71+
else
72+
printf '%s: %s\n' 'Error (bake)' "$1"
73+
fi
74+
} >&2
75+
76+
# @description Prints `$1` formatted as an internal Bake warning to standard error
77+
# @arg $1 Text to print
78+
# @internal
79+
__bake_internal_warn() {
80+
if __bake_is_color; then
81+
printf "\033[0;33m%s:\033[0m %s\n" "Warn (bake)" "$1"
82+
else
83+
printf '%s: %s\n' 'Warn (bake)' "$1"
84+
fi
85+
} >&2
86+
87+
# @description Calls `__bake_internal_error` and terminates with code 1
88+
# @arg $1 string Text to print
89+
# @internal
90+
__bake_internal_die() {
91+
__bake_internal_error "$1. Exiting"
92+
exit 1
93+
}
94+
95+
# @description Prints `$1` formatted as an error to standard error
96+
# @arg $1 string Text to print
97+
# @internal
98+
__bake_error() {
99+
if __bake_is_color; then
100+
printf "\033[0;31m%s:\033[0m %s\n" 'Error' "$1"
101+
else
102+
printf '%s: %s\n' 'Error' "$1"
103+
fi
104+
} >&2
105+
106+
# @description Nicely prints all 'Basalt.sh' tasks to standard output
107+
# @internal
108+
__bake_print_tasks() {
109+
# shellcheck disable=SC1007,SC2034
110+
local regex="^(([[:space:]]*function[[:space:]]*)?task\.(.*?)\(\)).*"
111+
local line=
112+
printf '%s\n' 'Tasks:'
113+
while IFS= read -r line || [ -n "$line" ]; do
114+
if [[ "$line" =~ $regex ]]; then
115+
printf '%s\n' " -> ${BASH_REMATCH[3]}"
116+
fi
117+
done < "$BAKE_FILE"; unset -v line
118+
} >&2
119+
120+
# @description Prints text that takes up the whole terminal width
121+
# @arg $1 string Text to print
122+
# @internal
123+
__bake_print_big() {
124+
local print_text="$1"
125+
126+
# shellcheck disable=SC1007
127+
local _stty_height= _stty_width=
128+
read -r _stty_height _stty_width < <(
129+
if command -v stty &>/dev/null; then
130+
stty size
131+
else
132+
printf '%s\n' '20 80'
133+
fi
134+
)
135+
136+
local separator_text=
137+
# shellcheck disable=SC2183
138+
printf -v separator_text '%*s' $((_stty_width - ${#print_text} - 1))
139+
printf -v separator_text '%s' "${separator_text// /=}"
140+
if __bake_is_color; then
141+
printf '\033[1m%s %s\033[0m\n' "$print_text" "$separator_text"
142+
else
143+
printf '%s %s\n' "$print_text" "$separator_text"
144+
fi
145+
} >&2
146+
147+
__bake_set_vars() {
148+
unset REPLY; REPLY=
149+
local -i total_shifts=0
150+
151+
local __bake_arg=
152+
for arg; do case $arg in
153+
-f)
154+
BAKE_FILE=$2
155+
if [ -z "$BAKE_FILE" ]; then
156+
__bake_internal_die 'File must not be empty. Exiting'
157+
fi
158+
((total_shifts += 2))
159+
if ! shift 2; then
160+
__bake_internal_die 'Failed to shift'
161+
fi
162+
163+
if [ ! -e "$BAKE_FILE" ]; then
164+
__bake_internal_die "Specified file '$BAKE_FILE' does not exist"
165+
fi
166+
if [ ! -f "$BAKE_FILE" ]; then
167+
__bake_internal_die "Specified path '$BAKE_FILE' is not actually a file"
168+
fi
169+
;;
170+
-h)
171+
local flag_help='yes'
172+
if ! shift; then
173+
__bake_internal_die 'Failed to shift'
174+
fi
175+
esac done
176+
177+
if [ -n "$BAKE_FILE" ]; then
178+
BAKE_ROOT=$(
179+
# shellcheck disable=SC1007
180+
CDPATH= cd -- "${BAKE_FILE%/*}"
181+
printf '%s\n' "$PWD"
182+
)
183+
BAKE_FILE="$BAKE_ROOT/${BAKE_FILE##*/}"
184+
else
185+
if ! BAKE_ROOT=$(
186+
while [ ! -f 'Bakefile.sh' ] && [ "$PWD" != / ]; do
187+
if ! cd ..; then
188+
exit 1
189+
fi
190+
done
191+
192+
if [ "$PWD" = / ]; then
193+
exit 1
194+
fi
195+
196+
printf '%s' "$PWD"
197+
); then
198+
__bake_internal_die "Could not find 'Bakefile.sh'"
199+
fi
200+
BAKE_FILE="$BAKE_ROOT/Bakefile.sh"
201+
fi
202+
203+
if [ "$flag_help" = 'yes' ]; then
204+
cat <<-EOF
205+
Usage: bake [-h] [-f <Bakefile>] [var=value ...] <task> [args ...]
206+
EOF
207+
__bake_print_tasks
208+
exit
209+
fi
210+
211+
REPLY=$total_shifts
212+
}
213+
214+
# @description Prints `$1` formatted as an error and the stacktrace to standard error,
215+
# then exits with code 1
216+
# @arg $1 string Text to print
217+
bake.die() {
218+
if [ -n "$1" ]; then
219+
__bake_error "$1. Exiting"
220+
else
221+
__bake_error 'Exiting'
222+
fi
223+
__bake_print_big '<- ERROR'
224+
225+
__bake_print_stacktrace
226+
227+
exit 1
228+
}
229+
230+
# @description Prints `$1` formatted as a warning to standard error
231+
# @arg $1 string Text to print
232+
bake.warn() {
233+
if __bake_is_color; then
234+
printf "\033[1;33m%s:\033[0m %s\n" 'Warn' "$1"
235+
else
236+
printf '%s: %s\n' 'Warn' "$1"
237+
fi
238+
} >&2
239+
240+
# @description Prints `$1` formatted as information to standard output
241+
# @arg $1 string Text to print
242+
bake.info() {
243+
if __bake_is_color; then
244+
printf "\033[0;34m%s:\033[0m %s\n" 'Info' "$1"
245+
else
246+
printf '%s: %s\n' 'Info' "$1"
247+
fi
248+
}
249+
250+
# @description Dies if any of the supplied variables are empty. Deprecated in favor of 'bake.assert_not_empty'
251+
# @arg $@ string Variable names to print
252+
# @see bake.assert_not_empty
253+
bake.assert_nonempty() {
254+
__bake_internal_warn "Function 'bake.assert_nonempty' is deprecated. Please use 'bake.assert_not_empty' instead"
255+
bake.assert_not_empty
256+
}
257+
258+
# @description Dies if any of the supplied variables are empty
259+
# @arg $@ string Variable names to print
260+
bake.assert_not_empty() {
261+
local variable_name=
262+
for variable_name; do
263+
local -n variable="$variable_name"
264+
265+
if [ -z "$variable" ]; then
266+
bake.die "Failed because variable '$variable_name' is empty"
267+
fi
268+
done; unset -v variable_name
269+
}
270+
271+
# @description Dies if a command cannot be found
272+
# @arg $1 string Command to test for existence
273+
bake.assert_cmd() {
274+
local cmd=$1
275+
276+
if [ -z "$cmd" ]; then
277+
bake.die "Argument must not be empty"
278+
fi
279+
280+
if ! command -v "$cmd" &>/dev/null; then
281+
bake.die "Failed to find command '$cmd'. Please install it before continuing"
282+
fi
283+
}
284+
285+
# @description Edit configuration that affects the behavior of Bake
286+
# @arg $1 string Configuration option to change
287+
# @arg $2 string Value of configuration property
288+
bake.cfg() {
289+
local cfg=$1
290+
local value=$2
291+
292+
case $cfg in
293+
stacktrace)
294+
__bake_cfg_stacktrace=$value
295+
esac
296+
}
297+
298+
__bake_main() {
299+
__bake_cfg_stacktrace='yes'
300+
301+
set -Eeo pipefail
302+
shopt -s dotglob extglob globasciiranges globstar lastpipe shift_verbose
303+
export LANG='C' LC_CTYPE='C' LC_NUMERIC='C' LC_TIME='C' LC_COLLATE='C' LC_MONETARY='C' LC_MESSAGES='C' \
304+
LC_PAPER='C' LC_NAME='C' LC_ADDRESS='C' LC_TELEPHONE='C' LC_MEASUREMENT='C' LC_IDENTIFICATION='C' LC_ALL='C'
305+
trap '__bake_trap_err' 'ERR'
306+
307+
# Set `BAKE_{ROOT,FILE}`
308+
BAKE_ROOT=; BAKE_FILE=
309+
__bake_set_vars "$@"
310+
if ! shift "$REPLY"; then
311+
__bake_internal_die 'Failed to shift'
312+
fi
313+
314+
local __bake_key= __bake_value=
315+
local __bake_arg=
316+
for __bake_arg; do case $__bake_arg in
317+
*=*)
318+
IFS='=' read -r __bake_key __bake_value <<< "$__bake_arg"
319+
320+
declare -g "$__bake_key"
321+
local -n __bake_variable="$__bake_key"
322+
__bake_variable="$__bake_value"
323+
324+
if ! shift; then
325+
__bake_internal_die 'Failed to shift'
326+
fi
327+
;;
328+
*) break
329+
esac done; unset -v __bake_arg
330+
# Note: Don't unset '__bake_variable' or none of the variables will stay set
331+
unset -v __bake_key __bake_value
332+
333+
local __bake_task="$1"
334+
if [ -z "$__bake_task" ]; then
335+
__bake_internal_error "No valid task supplied"
336+
__bake_print_tasks
337+
exit 1
338+
fi
339+
if ! shift; then
340+
__bake_internal_die 'Failed to shift'
341+
fi
342+
343+
if ! cd "$BAKE_ROOT"; then
344+
__bake_internal_die "Failed to cd"
345+
fi
346+
347+
# shellcheck disable=SC2097,SC1007,SC1090
348+
__bake_task= source "$BAKE_FILE"
349+
350+
if declare -f task."$__bake_task" >/dev/null 2>&1; then
351+
__bake_print_big "-> RUNNING TASK '$__bake_task'"
352+
if declare -f init >/dev/null 2>&1; then
353+
init "$__bake_task"
354+
fi
355+
task."$__bake_task" "$@"
356+
__bake_print_big "<- DONE"
357+
else
358+
__bake_internal_error "Task '$__bake_task' not found"
359+
__bake_print_tasks
360+
exit 1
361+
fi
362+
}
363+
364+
__bake_main "$@"

0 commit comments

Comments
 (0)