forked from Visorask/sarotate
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlibrary
412 lines (370 loc) · 12.7 KB
/
library
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
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
#############################
### ###
### Logging ###
### ###
#############################
### stdlib - foundation library for Bash scripts | Need Bash version 4.3 or above - see http://tiswww.case.edu/php/chet/bash/NEWS
### Areas covered:
### - PATH manipulation
### - logging
### - error handling
################################################# INITIALIZATION #######################################################
#
# make sure we do nothing in case the library is sourced more than once in the same shell
#
[[ $__stdlib_sourced__ ]] && return
__stdlib_sourced__=1
#
# The only code that executes when the library is sourced
#
__stdlib_init__() {
__log_init__
# call future init functions here
}
################################################# LIBRARY IMPORTER #####################################################
#
# import: source a library from $BASE_HOME
# Example:
# import lib/assertions.sh company/lib/xyz.sh ...
#
# IMPORTANT NOTE: If your library has global variables declared with 'declare' statement, you need to add -g flag to those.
# Since the library gets sourced inside the `import` function, globals declared without the -g option would
# be local to the function and hence be unavailable to other functions.
import() {
local lib rc=0
[[ $BASE_HOME ]] || { printf '%s\n' "ERROR: BASE_HOME not set; import functionality needs it" >&2; return 1; }
for lib; do
lib=$BASE_HOME/$lib
if [[ -f "$lib" ]]; then
source "$lib"
else
printf 'ERROR: %s\n' "Library '$lib' does not exist" >&2
rc=1
fi
done
return $rc
}
################################################# PATH MANIPULATION ####################################################
# add a new directory to $PATH
add_to_path() {
local dir re prepend=0 opt strict=1
OPTIND=1
while getopts sp opt; do
case "$opt" in
n) strict=0 ;; # don't care if directory exists or not before adding it to PATH
p) prepend=1 ;; # prepend the directory to PATH instead of appending
*) log_error "add_to_path - invalid option '$opt'"
return
;;
esac
done
shift $((OPTIND-1))
for dir; do
((strict)) && [[ ! -d $dir ]] && continue
re="(^$dir:|:$dir:|:$dir$)"
if ! [[ $PATH =~ $re ]]; then
((prepend)) && PATH="$dir:$PATH" || PATH="$PATH:$dir"
fi
done
}
# remove duplicates in $PATH
dedupe_path() { PATH="$(perl -e 'print join(":", grep { not $seen{$_}++ } split(/:/, $ENV{PATH}))')"; }
# print directories in $PATH, one per line
print_path() {
local -a dirs; local dir
IFS=: read -ra dirs <<< "$PATH"
for dir in "${dirs[@]}"; do printf '%s\n' "$dir"; done
}
#################################################### LOGGING ###########################################################
__log_init__() {
if [[ -t 1 ]]; then
# colors for logging in interactive mode
[[ $COLOR_RED ]] || COLOR_RED="\033[0;31m"
[[ $COLOR_GREEN ]] || COLOR_GREEN="\033[0;34m"
[[ $COLOR_YELLOW ]] || COLOR_YELLOW="\033[0;33m"
[[ $COLOR_BLUE ]] || COLOR_BLUE="\033[0;32m"
[[ $COLOR_OFF ]] || COLOR_OFF="\033[0m"
else
# no colors to be used if non-interactive
COLOR_RED= COLOR_GREEN= COLOR_YELLOW= COLOR_BLUE= COLOR_OFF=
fi
readonly COLOR_RED COLOR_GREEN COLOR_YELLOW COLOR_BLUE COLOR_OFF
#
# map log level strings (FATAL, ERROR, etc.) to numeric values
#
# Note the '-g' option passed to declare - it is essential
#
unset _log_levels _loggers_level_map
declare -gA _log_levels _loggers_level_map
_log_levels=([FATAL]=0 [ERROR]=1 [WARN]=2 [INFO]=3 [DEBUG]=4 [VERBOSE]=5)
#
# hash to map loggers to their log levels
# the default logger "default" has INFO as its default log level
#
_loggers_level_map["default"]=3 # the log level for the default logger is INFO
}
#
# set_log_level
#
set_log_level() {
local logger=default in_level l
[[ $1 = "-l" ]] && { logger=$2; shift 2 2>/dev/null; }
in_level="${1:-INFO}"
if [[ $logger ]]; then
l="${_log_levels[$in_level]}"
if [[ $l ]]; then
_loggers_level_map[$logger]=$l
else
printf '%(%Y-%m-%d:%H:%M:%S)T %-7s %s\n' -1 WARN \
"${BASH_SOURCE[2]}:${BASH_LINENO[1]} Unknown log level '$in_level' for logger '$logger'; setting to INFO"
_loggers_level_map[$logger]=3
fi
else
printf '%(%Y-%m-%d:%H:%M:%S)T %-7s %s\n' -1 WARN \
"${BASH_SOURCE[2]}:${BASH_LINENO[1]} Option '-l' needs an argument" >&2
fi
}
#
# Core and private log printing logic to be called by all logging functions.
# Note that we don't make use of any external commands like 'date' and hence we don't fork at all.
# We use the Bash's printf builtin instead.
#
_print_log() {
local in_level=$1; shift
local logger=default log_level_set log_level
[[ $1 = "-l" ]] && { logger=$2; shift 2; }
log_level="${_log_levels[$in_level]}"
log_level_set="${_loggers_level_map[$logger]}"
if [[ $log_level_set ]]; then
((log_level_set >= log_level)) && {
printf '%(%Y-%m-%d:%H:%M:%S)T %-7s %s ' -1 "$in_level" "${BASH_SOURCE[2]}:${BASH_LINENO[1]}"
printf '%s\n' "$@"
}
else
printf '%(%Y-%m-%d:%H:%M:%S)T %-7s %s\n' -1 WARN "${BASH_SOURCE[2]}:${BASH_LINENO[1]} Unknown logger '$logger'"
fi
}
#
# core function for logging contents of a file
#
_print_log_file() {
local in_level=$1; shift
local logger=default log_level_set log_level file
[[ $1 = "-l" ]] && { logger=$2; shift 2; }
file=$1
log_level="${_log_levels[$in_level]}"
log_level_set="${_loggers_level_map[$logger]}"
if [[ $log_level_set ]]; then
if ((log_level_set >= log_level)) && [[ -f $file ]]; then
log_debug "Contents of file '$1':"
cat -- "$1"
fi
else
printf '%(%Y-%m-%d:%H:%M:%S)T %s\n' -1 "WARN ${BASH_SOURCE[2]}:${BASH_LINENO[1]} Unknown logger '$logger'"
fi
}
#
# main logging functions
#
log_fatal() { _print_log FATAL "$@"; }
log_error() { _print_log ERROR "$@"; }
log_warn() { _print_log WARN "$@"; }
log_info() { _print_log INFO "$@"; }
log_debug() { _print_log DEBUG "$@"; }
log_verbose() { _print_log VERBOSE "$@"; }
#
# logging file content
#
log_info_file() { _print_log_file INFO "$@"; }
log_debug_file() { _print_log_file DEBUG "$@"; }
log_verbose_file() { _print_log_file VERBOSE "$@"; }
#
# logging for function entry and exit
#
log_info_enter() { _print_log INFO "Entering function ${FUNCNAME[1]}"; }
log_debug_enter() { _print_log DEBUG "Entering function ${FUNCNAME[1]}"; }
log_verbose_enter() { _print_log VERBOSE "Entering function ${FUNCNAME[1]}"; }
log_info_leave() { _print_log INFO "Leaving function ${FUNCNAME[1]}"; }
log_debug_leave() { _print_log DEBUG "Leaving function ${FUNCNAME[1]}"; }
log_verbose_leave() { _print_log VERBOSE "Leaving function ${FUNCNAME[1]}"; }
#
# THe print routines don't prefix the messages with the timestamp
#
print_error() {
{
printf "${COLOR_RED}ERROR: "
printf '%s\n' "$@"
printf "$COLOR_OFF"
} >&2
}
print_warn() {
printf "${COLOR_YELLOW}WARN: "
printf '%s\n' "$@"
printf "$COLOR_OFF"
}
print_info() {
printf "$COLOR_BLUE"
printf '%s\n' "$@"
printf "$COLOR_OFF"
}
print_success() {
printf "${COLOR_GREEN}SUCCESS: "
printf '%s\n' "$@"
printf "$COLOR_OFF"
}
print_message() {
printf '%s\n' "$@"
}
# print only if output is going to terminal
print_tty() {
if [[ -t 1 ]]; then
printf '%s\n' "$@"
fi
}
################################################## ERROR HANDLING ######################################################
dump_trace() {
local frame=0 line func source n=0
while caller "$frame"; do
((frame++))
done | while read line func source; do
((n++ == 0)) && {
printf 'Encountered a fatal error\n'
}
printf '%4s at %s\n' " " "$func ($source:$line)"
done
}
exit_if_error() {
(($#)) || return
local num_re='^[0-9]+'
local rc=$1; shift
local message="${@:-No message specified}"
if ! [[ $rc =~ $num_re ]]; then
log_error "'$rc' is not a valid exit code; it needs to be a number greater than zero. Treating it as 1."
rc=1
fi
((rc)) && {
log_fatal "$message"
dump_trace "$@"
exit $rc
}
return 0
}
fatal_error() {
local ec=$? # grab the current exit code
((ec == 0)) && ec=1 # if it is zero, set exit code to 1
exit_if_error "$ec" "$@"
}
#
# run a simple command (no compound statements or pipelines) and exit if it exits with non-zero
#
run_simple() {
log_debug "Running command: $*"
"$@"
exit_if_error $? "run failed: $@"
}
#
# safe cd
#
base_cd() {
local dir=$1
[[ $dir ]] || fatal_error "No arguments or an empty string passed to base_cd"
cd -- "$dir" || fatal_error "Can't cd to '$dir'"
}
base_cd_nonfatal() {
local dir=$1
[[ $dir ]] || return 1
cd -- "$dir" || return 1
return 0
}
#
# safe_unalias
#
safe_unalias() {
# Ref: https://stackoverflow.com/a/61471333/6862601
local alias_name
for alias_name; do
[[ ${BASH_ALIASES[$alias_name]} ]] && unalias "$alias_name"
done
return 0
}
################################################# MISC FUNCTIONS #######################################################
#
# For functions that need to return a single value, we use the global variable OUTPUT.
# For functions that need to return multiple values, we use the global variable OUTPUT_ARRAY.
# These global variables eliminate the need for a subshell when the caller wants to retrieve the
# returned values.
#
# Each function that makes use of these global variables would call __clear_output__ as the very first step.
#
__clear_output__() { unset OUTPUT OUTPUT_ARRAY; }
#
# return path to parent script's source directory
#
get_my_source_dir() {
__clear_output__
# Reference: https://stackoverflow.com/a/246128/6862601
OUTPUT="$(cd "$(dirname "${BASH_SOURCE[1]}")" >/dev/null 2>&1 && pwd -P)"
}
#
# wait for user to hit Enter key
#
wait_for_enter() {
local prompt=${1:-"Press Enter to continue"}
read -r -n1 -s -p "Press Enter to continue" </dev/tty
}
#################################################### END OF FUNCTIONS ##################################################
__stdlib_init__
#############################
### ###
### Requirements ###
### ###
#############################
### Required Variables ###
cf="config.yml"
st="--stripComments"
### End Required Variables ###
### Detect if fd is installed. ###
fdDetect() {
cmd_fd=$(curl -sL "https://api.github.com/repos/sharkdp/fd/releases/latest" | grep "fd_.*_amd64" | grep "browser_download_url" | cut -d'"' -f4)
fd_ver=$(curl -sL "https://api.github.com/repos/sharkdp/fd/releases/latest" | grep "fd_.*_amd64" | grep "browser_download_url" | cut -d'/' -f8 | cut -d'v' -f2)
if [[ ! -x "/usr/bin/fd" ]]; then
log_info "Installing fd as it is needed for this script."
curl -LO "$cmd_fd"
sudo dpkg -i fd_"$fd_ver"_amd64.deb
rm -rf fd_"$fd_ver"_amd64.deb
log_info "$(fd -V) is now installed."
fi
if [[ "$(fd -V | cut -d' ' -f2)" == "$fd_ver" ]]; then
log_info "fd is already at the current version."
else
log_info "Updating $(fd -V)" to "$fd_ver".
curl -LO "$cmd_fd"
sudo dpkg -i fd_"$fd_ver"_amd64.deb
rm -rf fd_"$fd_ver"_amd64.deb
log_info "$(fd -V) is updated."
fi
}
### Detect if yyq is installed
yyqDetect() {
cmd_yyq=$(curl -sL "https://api.github.com/repos/mikefarah/yq/releases/latest" | grep "yq_linux_amd64" | grep "browser_download_url" | cut -d'"' -f4)
yyq_ver=$(curl -sL "https://api.github.com/repos/mikefarah/yq/releases/latest" | grep "yq_linux_amd64" | grep "browser_download_url" | cut -d'/' -f8)
if [[ ! -x "/usr/local/bin/yyq" ]]; then
log_info "Installing yyq as it is needed for this script."
curl -LO "$cmd_yyq"
sudo mv yq_linux_amd64 "/usr/local/bin/yyq"
sudo chmod 775 "/usr/local/bin/yyq"
sudo chown root:root "/usr/local/bin/yyq"
log_info "$(yyq --version) is now installed."
fi
if [[ "$(yyq --version | cut -d' ' -f3)" == "$yyq_ver" ]]; then
log_info "yyq is already at the current version."
else
log_info "Updating $(yyq --version) to $yyq_ver."
sudo rm -rf "usr/local/bin/yyq"
curl -LO "$cmd_yyq"
sudo mv yq_linux_amd64 "/usr/local/bin/yyq"
sudo chmod 775 "/usr/local/bin/yyq"
sudo chown root:root "/usr/local/bin/yyq"
log_info "$(yyq --version) is now updated."
fi
}