This repository has been archived by the owner on Mar 26, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
bashist.bash
295 lines (250 loc) · 7.29 KB
/
bashist.bash
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
## CONSTANTS ##
readonly _bashist_color_codes=(
clear # reset
black red green yellow blue purple cyan white # colors
bold dim # bold/bright, unbold/dim
rev # reverse video
under nounder # underline, nounderline
)
# main
#
# This function is called at the end of this file so that we can access
# functions defined later in this file.
main() {
## PLATFORM-SPECIFIC FLAGS ##
case "$(bashist::platform)" in
mac)
_bashist_sed_flags="-l"
;;
linux)
_bashist_sed_flags="-u"
;;
esac
_bashist_escapes=(
"$(tput sgr0)"
"$(tput setaf 0)" "$(tput setaf 1)" "$(tput setaf 2)" "$(tput setaf 3)"
"$(tput setaf 4)" "$(tput setaf 5)" "$(tput setaf 6)" "$(tput setaf 7)"
"$(tput bold)" "$(tput dim)"
"$(tput rev)"
"$(tput smul)" "$(tput rmul)"
) || true
}
## SHELL OPTIONS ##
# Enable advanced globbing patterns.
# See: http://stackoverflow.com/a/17191796
shopt -s extglob
## SHELL BUILT-IN OVERRIDES ##
# Prevent `pushd` built-in from producing output
pushd() {
command pushd "$@" > /dev/null
}
# Prevent `popd` built-in from producing output
popd() {
command popd "$@" > /dev/null
}
# Silence `tput` errors. Usually indicates an unknown terminal, which we can't
# do anything about.
tput() {
command tput 2> /dev/null
}
## I/O UTILITY FUNCTIONS ##
# bashist::color <format string> [<format string>...]
#
# Outputs formatted <format string>s
#
# FORMAT STRING
# A format string is a plain ol' shell string with some format codes mixed
# in. Format codes consist of a code wrapped in curly braces: `{code}`
#
# A format code applies to all following characters until it's overriden by
# another format code.
#
# CAVEATS
# You *must* send the `clear` format code when you're finished outputting
# formatted text, or you'll format the user's prompt and future programs!
#
# Example:
# '{red}Red, {bold}bold beautiful text!{clear}'
#
# FORMAT CODES
# See `$_bashist_color_codes` for a list of possible format codes
bashist::color() {
local strings="$@"
for (( i=0; i < ${#_bashist_color_codes[@]}; ++i )); do
strings=${strings//\{${_bashist_color_codes[i]}\}/${_bashist_escapes[i]}}
done
echo "${strings[@]}"
}
# bashist::error <message> [<message>...]
#
# Outputs <message>s to standard error
bashist::error() {
bashist::color "$@" 1>&2
}
# bashist::die <message> [<message>...]
#
# Outputs <message>s to standard error, then terminates with exit code 1
bashist::die() {
bashist::error "$@"
exit 1
}
# bashist::confirm <prompt>
#
# Prints <prompt> and waits for user agreement or refusal. Considers "y", "yes"
# agreement and "n", "no" refusal.
#
# RETURN VALUE
# Success if user agrees; failure if user refuses.
bashist::confirm() {
while true; do
case $(bashist::ask "$1 [y/n]") in
y|yes) return 0 ;;
n|no) return 1 ;;
esac
done
}
# bashist::ask <prompt> <default>
#
# Prints <prompt> followed by " " and waits for one line of user input.
# Outputs that user input.
#
# If <default> is specified, " [<default>]" is appended to <prompt> to indicate
# to the user that a default is available, and <default> will be output if the
# user's input is blank.
#
# Note that <prompt> is output to stderr so that command substitution--
# `$(bashist::ask "prompt")`--will capture only the user's input, and not the
# prompt.
bashist::ask() {
local answer prompt
prompt="$1"
if [[ ! -z "$2" ]]; then prompt+=" [$2]"; fi
read -p "${prompt} " answer
if [[ -z "${answer}" ]]; then answer=$2; fi
echo "${answer}"
}
# bashist::header <text> [<text>...]
#
# Outputs all <text>s, separated by `$IFS`, preceded with a '-->' to indicate a
# header. Recommended to use `bashist::tab` or `bashist::tab_command` to output a
# "section" beneath this header.
bashist::header() {
bashist::color "-->" "$@"
}
# <command> | bashist::tab
#
# Pipe the output of <command> to `bashist::tab` to indent <command>'s output by
# four spaces.
bashist::tab() {
sed ${_bashist_sed_flags} 's%^% %'
}
# bashist::tab_command <command> [<args>...]
#
# Runs <command> with any specified arguments, pipes its stdout and stderr
# through bashist::tab. Uses the `script` command to prevent <command> from
# detecting its stdout/stderr streams are not a TTY. (Often, programs will
# disable colorized output when stdout/stderr are not attached to a TTY.)
#
# In general, you should prefer `bashist::tab_command` to `bashist::tab` since
# it preserves colors and the command's exit code. It may, however, interfere
# with commands that make heavy use of terminal control libraries, like progress
# indicators.
bashist::tab_command() {
local cmd
declare -i exit_code
case "$(bashist::platform)" in
windows)
"$@" | bashist::tab ;;
linux)
script -q /dev/null -c "$(printf "%q " "$@")" --return | bashist::tab ;;
*)
script -q /dev/null "$@" | bashist::tab ;;
esac
exit_code=${PIPESTATUS[0]}
echo
return $exit_code
}
# bashist::ensure_cr_on_nl
#
# Due to some weird TTY bug, intermingling output between a host terminal and a
# backgrounded SSH terminal that's requested a psuedo-TTY drops carriage
# returns, producing output like the following:
#
# Line 1
# Line 2
# Line 3
#
# This function adds an additional carriage return after each line feed to
# guarantee the cursor returns to the beginning of each line. Due to sed
# limitations, this duplicates carriage returns, but that's invisible to the
# user.
bashist::ensure_cr_on_nl() {
sed ${_bashist_sed_flags} $'s/^/\r/'
}
# bashist::lines_to_args <lines>
#
# Converts a string of newline-separated (`\n`) values <lines> into a properly-
# escaped string of arguments.
#
# Example input:
# "long-filename-one.js
# filename with spaces.js
# empty.js"
#
# Example output:
# "long-filename-one.js filename\ with\ spaces.js empty.js"
bashist::lines_to_args() {
IFS=$'\n' line_array=($1)
printf "%q " "${line_array[@]}"
}
# bashist::regexp_escape <string>
#
# Outputs <string> escaped for use with basic regular expressions. See
# re_format(7) for details.
bashist::regexp_escape() {
sed 's/[]^$*.\[]/\\&/g' <<< "$1"
}
## PROVISIONING UTILITIES ##
# bashist::platform
#
# Outputs this machine's platform. "windows" on Windows, "mac" on OS X,
# "unsupported" otherwise.
bashist::platform() {
case "$(uname)" in
*NT*) echo "windows" ;;
Darwin) echo "mac" ;;
Linux) echo "linux" ;;
*) echo "unsupported" ;;
esac
}
# bashist::arch
#
# Outputs the architecture of this machine's CPU. "x86" or "x64".
bashist::arch() {
if [[ $(uname -m) == "x86_64" ]]; then
echo "x64"
else
echo "x86"
fi
}
# bashist::which <executable>
#
# Returns success code if <executable> exists on the user's $PATH; failure
# otherwise. More portable than `which`.
bashist::which() {
hash "$1" 2> /dev/null
}
## GENERAL CLI UTILITIES ##
# bashist::ensure_singleton <id>
#
# Ensures only one copy of <id> is running by writing a PID file.
bashist::ensure_singleton() {
pid_file="/tmp/$1"
touch "${pid_file}"
read pid < "${pid_file}" || true
if [[ ! -z "$pid" ]] && ps -p "$pid" > /dev/null; then
bashist::die "{red}'$0' is already running! aborting...{clear}"
fi
echo $$ > $pid_file
}
main "$@"