2 # gsu -- the global subcommand utility
3 # (C) 2006-2011 Andre Noll
9 E_GSU_BAD_COMMAND invalid command
10 E_GSU_NOT_A_NUMBER not a number
11 E_GSU_BAD_CONFIG_VAR invalid config variable
12 E_GSU_NEED_VALUE value required but not given
13 E_GSU_BAD_BOOL bad value for boolian option
14 E_GSU_BAD_OPTION_TYPE invalid option type
15 E_GSU_BAD_ARG_COUNT invalid number of arguments
16 E_GSU_EDITOR failed to execute editor
17 E_GSU_MKDIR failed to create directory
18 E_GSU_GETOPTS getopts error
34 export -f _gsu_init_errors
36 # check if $1 is a number
40 if test "$1" -eq "$1" &> /dev/null; then
43 ret=-$E_GSU_NOT_A_NUMBER
46 export -f gsu_is_a_number
48 # Check number of arguments.
50 # Usage: gsu_check_arg_count <num_given> <num1> [<num2>]
52 # Check that <num_given> is between <num1> and <num2> inclusively.
53 # If only <num1> ist given, num2 is assumed to be infinity.
56 # 0 0 no argument allowed
57 # 1 1 exactly one argument required
58 # 0 2 at most two arguments admissible
59 # 2 at least two arguments reqired
63 ret=-$E_GSU_BAD_ARG_COUNT
64 if [[ $# -eq 2 ]]; then # only num1 is given
65 result="at least $2 args required, $1 given"
66 [[ $1 -lt $2 ]] && return
71 result="need at least $2 args, $1 given"
72 [[ $1 -lt $2 ]] && return
73 result="need at most $3 args, $1 given"
74 [[ $1 -gt $3 ]] && return
77 export -f gsu_check_arg_count
83 export -f gsu_short_msg
87 gsu_short_msg "$_gsu_self: $1"
93 gsu_short_msg "$_gsu_self $(date): $1"
95 export -f gsu_date_msg
101 local txt="### $_gsu_self --"
102 if test -z "$gsu_banner_txt"; then
103 txt="$txt set \$gsu_banner_txt to customize this message"
105 txt="$txt $gsu_banner_txt"
107 gsu_short_msg "$txt ###"
109 export -f _gsu_banner_msg
113 local txt="$result" err
115 gsu_is_a_number "$ret"
116 if test $ret -lt 0; then
117 gsu_msg "unknown error ($ret:$txt)"
120 if test $result -ge 0; then
121 gsu_msg "unknown error ($result:$txt)"
125 if test -n "$txt"; then
126 txt="$txt: ${gsu_error_txt[$err]}"
128 txt="${gsu_error_txt[$err]}"
132 export -f gsu_err_msg
136 gsu_short_msg "# Usage: $_gsu_self command [options]"
140 # Each line matching this is recognized as a subcommand. The name
141 # of the subcommand is the first subexpression.
142 export gsu_command_regex='^com_\([a-zA-Z_0-9]\+\)()'
144 _gsu_available_commands()
147 printf "help\nman\nprefs\n"
148 sed -ne "s/$gsu_command_regex/\1/g;T;p" $0
149 } | sort | tr '\n' ' ')"
151 export -f _gsu_available_commands
153 _gsu_print_available_commands()
156 gsu_short_msg "Available commands:"
157 for i in $gsu_cmds; do
159 count=$(($count + 1))
160 if test $(($count % 4)) -eq 0; then
164 if test ${#i} -lt 8; then
172 export -f _gsu_print_available_commands
174 export gsu_prefs_txt="
175 Print the current preferences.
179 If -e is given, the config file is opened with the default editor. Without
180 options, the command prints out a list of all cmt config variables, together
181 with their current value and the default value."
184 local i conf="${gsu_config_file:=$HOME/.$gsu_name.rc}"
186 if [[ "$1" = "-e" ]]; then
190 [[ $? -ne 0 ]] && return
192 result="${EDITOR:-vi}"
194 [[ $? -ne 0 ]] && return
199 for ((i=0; i < ${#gsu_options[@]}; i++)); do
200 local name= option_type= default_value= required=
201 local description= help_text=
202 eval "${gsu_options[$i]}"
203 eval val='"$'${gsu_config_var_prefix}_$name'"'
212 printf " $option_type: $description"
213 if [[ "$required" != "yes" && "$required" != "true" ]]; then
214 printf " [$default_value]"
217 [[ -n "$help_text" ]] && sed -e '/^[ ]*$/d; s/^[ ]*/# /g' <<< "$help_text"
219 [[ "$val" == "$default_value" ]] && printf " # default"
232 local equal_signs="=================================================="
233 local minus_signs="--------------------------------------------------"
236 echo "$_gsu_self (_${gsu_banner_txt}_) manual"
237 echo "${equal_signs:0:${#_gsu_self} + ${#gsu_banner_txt} + 16}"
240 sed -e '1,/^#\{70,\}/d' -e '/^#\{70,\}/,$d' $0 -e 's/^# *//'
243 echo "$_gsu_self usage"
244 echo "${minus_signs:0:${#_gsu_self} + 6}"
247 echo "Each command has its own set of options as described below."
251 echo "Available commands:"
253 _gsu_available_commands
254 for com in $result; do
256 if test $num -lt 4; then
259 echo "${minus_signs:0:$num}"
261 echo "${minus_signs:0:$num}"
269 export gsu_help_txt="
272 Usage: help [command]
274 Without arguments, print the list of available commands. Otherwise,
275 print the help text for the given command."
280 if test -z "$1"; then
284 printf "com_help()\n$gsu_help_txt" | head -n 4; echo "--"
285 printf "com_man()\n$gsu_man_txt" | head -n 4; echo "--"
286 printf "com_prefs()\n$gsu_prefs_txt" | head -n 4; echo "--"
287 grep -A 2 "$gsu_command_regex" $0
288 } | grep -v -- '--' \
289 | sed -e "/$gsu_command_regex/bs" \
290 -e 'H;$!d;x;s/\n//g;b' \
292 -e 'x;s/\n//g;${p;x;}' \
293 | sed -e "s/${gsu_command_regex}#*/\1\t/" \
297 if test ${#a} -lt 8; then
303 echo "# Try $_gsu_self help <command> for info on <command>."
307 if test "$1" = "help"; then
312 if test "$1" = "man"; then
317 if test "$1" = "prefs"; then
318 echo "$gsu_prefs_txt"
323 if grep -q "^com_$1()" $0; then
324 sed -e "1,/^com_$1()$/d" -e '/^{/,$d' -e 's/^## *//' $0
327 _gsu_print_available_commands
329 ret=-$E_GSU_BAD_COMMAND
333 # internal gsu function that syntactically checks the gsu_options array
334 # for errors and parses the config file.
337 local i conf="${gsu_config_file:=$HOME/.$gsu_name.rc}" val
339 for ((i=0; i < ${#gsu_options[@]}; i++)); do
340 eval "${gsu_options[$i]}"
341 eval val='"'\$$name'"'
342 eval orig_${gsu_config_var_prefix}_$name='"'${val}'"'
345 [[ -r "$conf" ]] && source "$conf"
347 for ((i=0; i < ${#gsu_options[@]}; i++)); do
348 local name= option_type= default_value= required=
349 local description= help_text=
352 eval "${gsu_options[$i]}"
355 # Check name. It must be non-empty and consist of [a-zA-Z_0-9]
356 # only. Moreover it must not start with [a-zA-Z].
358 ret=-$E_GSU_BAD_CONFIG_VAR
359 result="name: '$name'"
360 # bash's =~ works only for 3.2 and newer, so use grep
361 echo "$name" | grep '^[a-zA-Z][a-zA-Z_0123456789]*$' &> /dev/null;
362 [[ $? -ne 0 ]] && return
364 eval orig_val='"'\$orig_${gsu_config_var_prefix}_$name'"'
365 if [[ -z "$orig_val" ]]; then
366 eval val='"'\$$name'"'
372 ret=-$E_GSU_NEED_VALUE
374 [[ -z "$val" ]] && return
380 result="required: $required, name: $name, val: $val"
384 eval ${gsu_config_var_prefix}_$name='"'\${val:=$default_value}'"'
385 # Check option type. ATM, only num and string are supported
386 # Other types may be added without breaking compatibility
387 case "$option_type" in
391 gsu_is_a_number "$val"
392 [[ $ret -lt 0 ]] && return
395 ret=-$E_GSU_BAD_OPTION_TYPE
396 result="$name/$option_type"
402 export -f _gsu_check_options
404 # Wrapper for bash's getopts.
406 # Aborts on programming errors such as missing or invalid option string. On
407 # success $result contains shell code that can be eval'ed. For each defined
408 # option x, the local variable o_x will be created when calling eval "$result".
409 # o_x contains true/false for options without an argument or the emtpy string/the
410 # given argument, depending on whether this option was contained in the "$@"
414 # gsu_getopts abc:x:y
416 # [[ $ret -lt 0 ]] && return
418 # [[ "$o_a" = "true ]] && echo "The -a flag was given"
419 # [[ -n "$o_c" ]] && echo "The -c option was given with arg $o_c"
422 local i c tab=' ' cr='
425 gsu_check_arg_count $# 1 1
426 if [[ $ret -lt 0 ]]; then
432 result="invalid optstring $1"
433 if [[ -z "$1" ]] || grep -q '::' <<< "$1" ; then
438 for ((i=0; i < ${#1}; i++)); do
444 result="invalid character $c in optstring"
450 for ((i=0; i < ${#1}; i++)); do
452 c2=${1:$(($i + 1)):1}
454 if [[ "$c2" = ":" ]]; then
462 while getopts $1 opt \"\$@\"; do
465 for ((i=0; i < ${#1}; i++)); do
467 c2=${1:$(($i + 1)):1}
468 result+="$tab$tab$c1) o_$c1="
469 if [[ "$c2" = ":" ]]; then
470 result+="\"\$OPTARG\""
480 result=\"invalid option given\"
485 shift \$((\$OPTIND - 1))
489 export -f gsu_getopts
494 _gsu_self="$(basename $0)"
495 gsu_name="${gsu_name:=$_gsu_self}"
496 gsu_config_var_prefix="${gsu_config_var_prefix:=$gsu_name}"
499 if [[ "$ret" -lt 0 ]]; then
500 if [[ "$1" != "help" && "$1" != "man" ]]; then
505 _gsu_available_commands
507 if test $# -eq 0; then
509 _gsu_print_available_commands
514 # check internal commands
515 if [[ "$arg" = "help" || "$arg" = "man" || "$arg" = "prefs" ]]; then
517 if [[ "$ret" -lt 0 ]]; then
525 for i in $gsu_cmds; do
526 if test "$arg" = "$i"; then
528 if [[ "$ret" -lt 0 ]]; then
536 ret=-$E_GSU_BAD_COMMAND
539 _gsu_print_available_commands
544 # TODO: gsu_strerror: get error string