-
Notifications
You must be signed in to change notification settings - Fork 4
/
rerun
executable file
·374 lines (344 loc) · 8.85 KB
/
rerun
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
#!/usr/bin/env bash
#
# rerun - a simple command runner because it's easy to forget
# standard operating procedure.
#
SH="/usr/bin/env bash"
PROG=`basename $0`
USAGE="
Usage: $PROG [-h][-v][-V] [-M <dir>] [-L <dir>] [--replay <file>] [module:[command [options]]]
"
PAD=" "
DIFF=diff
export RERUN=$0
examples() {
echo $(tput sgr 0 1)"Examples:$(tput sgr0)
| \$ $PROG
| => List all modules.
| \$ $PROG freddy
| => List all freddy commands.
| \$ $PROG freddy:dance --jumps 3
| => Execute the freddy dance command."
}
#
# colorizing functions
#
# bold - bold the given text
bold() { echo -e "\033[1m$*\033[0m" ; reset ; }
# reset the terminal
reset () { tput sgr0 ; }
# print a ascii art banner with version info
banner() {
echo $(tput setaf 1) " _ __ ___ _ __ _ _ _ __"
echo $(tput setaf 2) "| '__/ _ \ '__| | | | '_ \ "
echo $(tput setaf 3) "| | | __/ | | |_| | | | |"
echo $(tput setaf 4) "|_| \___|_| \__,_|_| |_|"
echo $(tput setaf 5) "Version: v0.1. License: Apache 2.0."
reset
}
# print help. A banner or the unix manual if it exists
man() {
local module=$1
# Try autogenerating the doc if stubbs:docs is around
if [ ! -f $RERUN_MODULES/$module/$module.1 \
-a -f $RERUN_MODULES/stubbs/commands/docs/default.sh ]
then
$RERUN stubbs:docs -name $module
fi
if [ -f $RERUN_MODULES/$module/$module.1 ]
then
nroff -man $RERUN_MODULES/$module/$module.1 | ${PAGER:more}
else
echo "Manual not found."
fi
}
#
# utility functions -
#
# print error message and exit
die() {
[[ "$RERUN_COLOR" == "true" ]] && bold "ERROR: $*" >&2 || echo "ERROR: $*" >&2
exit 1
}
# print USAGE and exit
rerun_syntax_error() {
die "$USAGE"
}
# check option has its argument
rerun_syntax_check() {
[ "$1" -lt 2 ] && rerun_syntax_error
}
# get system OS name
rerun_platforminfo() {
uname -s
}
# get module dir for specified module
rerun_lookupModule() {
mod=$1
echo $RERUN_MODULES/$mod
}
# lookup a handler for specified command in module
rerun_lookupHandler() {
mod=$1
cmd=$2
mod_dir=`rerun_lookupModule $mod`
cmd_dir=$mod_dir/commands/${cmd}
os=`rerun_platforminfo`
if [ -f "$cmd_dir/${os}.sh" ]
then
echo $cmd_dir/${os}.sh; # found an os-specific handler
else
echo $cmd_dir/default.sh; # return the generic one
fi
}
# Checks if handler exists by attempting to look it up.
# return 0 if exists, 1 otherwise
rerun_existsHandler() {
mod=$1
cmd=$2
script=`rerun_lookupHandler $mod $cmd`
if [ -f "$script" ]
then return 0
else return 1
fi
}
# Check if module exists
rerun_existsModule() {
[ -f $RERUN_MODULES/$1/metadata ] && return 0 || return 1
}
rerun_extractLog() {
file=$1
[ -f $file ] || die "file does not exist: $file"
SIZE=$(awk '/^__LOG_BELOW__/ {print NR + 1; exit 0; }' $file) || die "failed sizing log"
tail -n+$SIZE $file || die "failed extracting log"
}
rerun_metadataLookup() {
field=$1
file=$2
[ ! -r $file ] && { echo "file not found: $file" ; return 1 ; }
while read line
do
key=${line%%=*}
value=${line##*=}
[ "$key" == "$field" ] && {
echo $value ; break ;
}
done < $file
}
#
# Main body of the script.
#
# Use env var property if it exists otherwise...
# ...the default modules base directory is cwd.
[ -n "$RERUN_MODULES" ] || RERUN_MODULES=$(pwd)/modules
#
# process command line options
#
MODULE="" COMMAND=""
while [ "$#" -gt 0 ]; do
OPT="$1"
case "$OPT" in
# options without arguments
-h*|--h*)
banner
bold "$USAGE"
examples
exit 0
;;
-v)
VERBOSE="-vx"
;;
-V)
VERBOSE="-vx"
set -vx
;;
# options with arguments
--man*)
rerun_syntax_check "$#"
man $2
exit 0
;;
-M)
rerun_syntax_check "$#"
RERUN_MODULES="$2"
shift
;;
-L)
rerun_syntax_check "$#"
RERUN_LOGS="$2"
shift
;;
--replay)
rerun_syntax_check "$#"
REPLAY="$2"
shift
;;
# unknown option
-?)
rerun_syntax_error
;;
# end of options, just arguments left
*)
break
esac
shift
done
# Read the module:command context
#
# Define regex pattern to parse command line input
regex='([^:]+)([:]?[ ]?)([-A-Za-z0-9_]*)([ ]*)(.*)'
if [[ "$@" =~ $regex ]]
then
MODULE=${BASH_REMATCH[1]}; # module
[ "${BASH_REMATCH[2]}" == ': ' ] && shift ;# eat the extra space char
COMMAND=${BASH_REMATCH[3]/ /}; # command
# BASH_REMATCH[4] contains the whitespace separating command and options
# BASH_REMATCH[5] contains command options
else
MODULE=${1/:/} # module (minus colon)
fi
shift; # Shift over to the command options
#
# ensure modules directory is set and exists
[ -n "$RERUN_MODULES" -a -d "$RERUN_MODULES" ] || {
die RERUN_MODULES not set to an existing directory: $RERUN_MODULES
}
#
# Listing mode
#
# Module listing
[ -z "$MODULE" -a -z "$COMMAND" ] && {
bold "[modules]"
for mod in $RERUN_MODULES/*
do
$(rerun_existsModule $(basename $mod)) && {
mod_name=$(basename $mod)
mod_desc=$(rerun_metadataLookup DESCRIPTION $mod/metadata)
echo "${PAD}${mod_name}: ${mod_desc}"
}
done
exit 0
}
# Command listing
[ -n "$MODULE" -a -z "$COMMAND" ] && {
$(rerun_existsModule $MODULE) || die "module not found: $MODULE"
bold "[commands]"
shopt -s nullglob # enable
for cmd in $RERUN_MODULES/$MODULE/commands/*/default.sh
do
cmd_name=$(basename $(dirname $cmd))
cmd_metadata=$RERUN_MODULES/$MODULE/commands/${cmd_name}/metadata
[ -f $cmd_metadata ] && cmd_desc=$(rerun_metadataLookup DESCRIPTION $cmd_metadata)
echo " ${cmd_name}: ${cmd_desc}"
# List the options
if [ -d $RERUN_MODULES/$MODULE/commands/${cmd_name} ]
then
printf "%s%s\n" "$PAD" "[options]"
shopt -s nullglob # enable
for opt_metadata in $RERUN_MODULES/$MODULE/commands/${cmd_name}/*.option; do
cmd_param=$(basename $(echo ${opt_metadata%%.option}))
opt_name=$(rerun_metadataLookup NAME $opt_metadata)
opt_desc=$(rerun_metadataLookup DESCRIPTION $opt_metadata)
opt_arg=$(rerun_metadataLookup ARGUMENTS $opt_metadata)
opt_req=$(rerun_metadataLookup REQUIRED $opt_metadata)
opt_def=$(rerun_metadataLookup DEFAULT $opt_metadata)
opt_short=$(rerun_metadataLookup SHORT $opt_metadata)
# option usage summary
argstring=
if [ -n "${opt_short}" ]
then
argstring=$(printf ' -%s|--%s' "${opt_short}" "${opt_name}")
else
argstring=$(printf " --%s" "${opt_name}" )
fi
[ "true" == "${opt_arg}" ] && {
argstring=$(printf "%s <%s>" $argstring ${opt_def})
}
[ "true" != "${opt_req}" ] && {
opt_usage=$(printf "[%s: %s]" "${argstring}" "${opt_desc}")
} || {
opt_usage=$(printf "%s: %s" "${argstring}" "${opt_desc}")
}
printf "%s %s\n" "$PAD" "$opt_usage"
done
fi
done
exit 0
}
#
# Execution mode
#
[ -z $MODULE ] && rerun_syntax_error "module not specified"
[ -z $COMMAND ] && rerun_syntax_error "command not specified"
#
# If the script exists for the specified command, execute it
#
if ( rerun_existsHandler $MODULE $COMMAND )
then
export RERUN RERUN_MODULES MODULE COMMAND
MODULE_DIR=`rerun_lookupModule $MODULE`
export MODULE_DIR
CMD_SCRIPT=`rerun_lookupHandler $MODULE $COMMAND`
[ -r $MODULE_DIR/metadata ] && . $MODULE_DIR/metadata
[ -r $MODULE_DIR/commands/${COMMAND}/metadata ] && {
. $MODULE_DIR/commands/${COMMAND}/metadata
}
#
# execute the command implementation
#
if [ -n "$RERUN_LOGS" -a -d "$RERUN_LOGS" ]
then
# set up the log file
TSTAMP=$(date '+%Y-%m-%dT%H%M%S-%Z')
LOG=$RERUN_LOGS/$MODULE-$COMMAND-$TSTAMP-$$.running
#
# Execute the command script
#
$SH $VERBOSE $CMD_SCRIPT "$@" >> $LOG
RETVAL=$?
# Generate replay metadata
(
cat <<-EOF
#
# Command replay log
#
RERUN="$0"
MODULE="$MODULE"
COMMAND="$COMMAND"
OPTIONS="$*"
USER="$USER"
DATE="$TSTAMP"
EXIT_STATUS="$RETVAL"
__LOG_BELOW__
EOF
) > ${LOG%*.running}.metadata || die
#
# Generate .replay
cat ${LOG%*.running}.metadata $LOG > $RERUN_LOGS/$MODULE-$COMMAND-$TSTAMP.replay || die
rm -f $LOG ${LOG%*.running}.metadata; # cleanup working files
# New log
LOG=$RERUN_LOGS/$MODULE-$COMMAND-$TSTAMP.replay
# Reset the "latest" symlink
rm -f $RERUN_LOGS/$MODULE-$COMMAND-latest.replay; #remove old symlink
ln -s $LOG $RERUN_LOGS/$MODULE-$COMMAND-latest.replay; # recreate it
if [ -n "$REPLAY" ]
then
# extract the log content from checklog
rerun_extractLog $REPLAY > $RERUN_LOGS/rerun-$$.checklog
rerun_extractLog $LOG > $RERUN_LOGS/rerun-$$.log
$DIFF $RERUN_LOGS/rerun-$$.checklog $RERUN_LOGS/rerun-$$.log > $RERUN_LOGS/rerun-$$.diff
RETVAL=$?
[ $RETVAL -eq 1 ] && {
bold "[diff]"
cat $RERUN_LOGS/rerun-$$.diff
}
rm -f $RERUN_LOGS/rerun-$$.{log,checklog,diff}
fi
else
$SH $VERBOSE $CMD_SCRIPT "$@"
RETVAL=$?
fi
else
die command not found: \"$COMMAND\"
fi
exit ${RETVAL}