#!/bin/bash # gsu -- the global subcommand utility # (C) 2006-2009 Andre Noll _gsu_init_errors() { gsu_errors=" GSU_SUCCESS success E_GSU_BAD_COMMAND invalid command E_GSU_NOT_A_NUMBER not a number E_GSU_SOURCE error in config file E_GSU_CONFIG bad/missing config file option E_GSU_BAD_CONFIG_VAR invalid config variable E_GSU_NEED_VALUE value required but not given E_GSU_BAD_BOOL bad value for boolian option E_GSU_BAD_OPTION_TYPE invalid option type E_GSU_BAD_ARG_COUNT invalid number of arguments E_NO_DEFAULT missing default value $gsu_errors " local a b i=0 while read a b; do if test -z "$a"; then continue fi #echo "a:$a, b: $b" gsu_error_txt[i]="$b" eval $a=$i i=$(($i + 1)) done << EOF $gsu_errors EOF } export -f _gsu_init_errors # check if $1 is a number gsu_is_a_number() { result="$1" if test "$1" -eq "$1" &> /dev/null; then ret=$GSU_SUCCESS else ret=-$E_GSU_NOT_A_NUMBER fi } export -f gsu_is_a_number # Check number of arguments. # # Usage: gsu_check_arg_count [] # # Check that is between and inclusively. # If only ist given, num2 is assumed to be infinity. # # Examples: # 0 0 no argument allowed # 1 1 exactly one argument required # 0 2 at most two arguments admissible # 2 at least two arguments reqired # gsu_check_arg_count() { ret=-$E_GSU_BAD_ARG_COUNT if [[ $# -eq 2 ]]; then # only num1 is given result="at least $2 args required, $1 given" [[ $1 -lt $2 ]] && return ret=$GSU_SUCCESS return fi # num1 and num2 given result="need at least $2 args, $1 given" [[ $1 -lt $2 ]] && return result="need at most $3 args, $1 given" [[ $1 -gt $3 ]] && return ret=$GSU_SUCCESS } export -f gsu_check_arg_count gsu_short_msg() { echo "$1" 1>&2 } export -f gsu_short_msg gsu_msg() { gsu_short_msg "$_gsu_self: $1" } export -f gsu_msg gsu_date_msg() { gsu_short_msg "$_gsu_self $(date): $1" } export -f gsu_date_msg _gsu_banner_msg() { local txt="### $_gsu_self --" if test -z "$gsu_banner_txt"; then txt="$txt set \$gsu_banner_txt to customize this message" else txt="$txt $gsu_banner_txt" fi gsu_short_msg "$txt ###" } export -f _gsu_banner_msg gsu_err_msg() { local txt="$result" err gsu_is_a_number "$ret" if test $ret -lt 0; then gsu_msg "unknown error ($ret:$txt)" exit 1 fi if test $result -ge 0; then gsu_msg "unknown error ($result:$txt)" exit 1 fi err=$((0 - $result)) if test -n "$txt"; then txt="$txt: ${gsu_error_txt[$err]}" else txt="${gsu_error_txt[$err]}" fi gsu_msg "$txt" } export -f gsu_err_msg _gsu_usage() { gsu_short_msg "# Usage: $_gsu_self command [options]" } export -f _gsu_usage _gsu_available_commands() { result="$( (printf "help\nman\nprefs\n"; grep "^com_[a-z_]\+()" $0) \ | sed -e 's/^com_//' -e 's/()//' \ | sort \ | tr '\n' ' ')" ret=$GSU_SUCCESS } export -f _gsu_available_commands _gsu_print_available_commands() {( local i count gsu_short_msg "Available commands:" for i in $gsu_cmds; do printf "$i" count=$(($count + 1)) if test $(($count % 4)) -eq 0; then echo else printf "\t" if test ${#i} -lt 8; then printf "\t" fi fi done echo ) 2>&1 } export -f _gsu_print_available_commands export gsu_prefs_txt=" Print the current preferences. Usage: prefs Print out a list of all cmt config variables, together with their current value and the default value." _com_prefs() { local i for ((i=0; i < ${#gsu_options[@]}; i++)); do local name= option_type= default_value= required= local description= help_text= eval "${gsu_options[$i]}" eval val='"$'${gsu_config_var_prefix}_$name'"' case "$required" in true|yes) printf "# required" ;; *) printf "# optional" ;; esac printf " $option_type: $description" if [[ "$required" != "yes" && "$required" != "true" ]]; then printf " [$default_value]" fi echo [[ -n "$help_text" ]] && sed -e '/^[ ]*$/d; s/^[ ]*/# /g' <<< "$help_text" printf "$name=$val" [[ "$val" == "$default_value" ]] && printf " # default" echo done } export -f _com_prefs export gsu_man_txt=" Print the manual. Usage: man" _com_man() { local equal_signs="==================================================" local minus_signs="--------------------------------------------------" local com num echo "$_gsu_self (_${gsu_banner_txt}_) manual" echo "${equal_signs:0:${#_gsu_self} + ${#gsu_banner_txt} + 16}" echo sed -e '1,/^#\{70,\}/d' -e '/^#\{70,\}/,$d' $0 -e 's/^# *//' echo "----" echo echo "$_gsu_self usage" echo "${minus_signs:0:${#_gsu_self} + 6}" printf "\t" _gsu_usage 2>&1 echo "Each command has its own set of options as described below." echo echo "----" echo echo "Available commands:" _gsu_available_commands for com in $result; do num=${#com} if test $num -lt 4; then num=4 fi echo "${minus_signs:0:$num}" echo "$com" echo "${minus_signs:0:$num}" $0 help $com echo done ret=$GSU_SUCCESS } export -f _com_man export gsu_help_txt=" Print online help. Usage: help [command] Without arguments, print the list of available commands. Otherwise, print the help text for the given command." _com_help() { local a b if test -z "$1"; then _gsu_banner_msg 2>&1 _gsu_usage 2>&1 { printf "com_help()\n$gsu_help_txt" | head -n 4; echo "--" printf "com_man()\n$gsu_man_txt" | head -n 4; echo "--" printf "com_prefs()\n$gsu_prefs_txt" | head -n 4; echo "--" grep -A 2 "^com_\([a-zA-Z_0-9]\+\)()" $0 } | grep -v -- '--' \ | sed -e '/^com_\([a-zA-Z_0-9]\+\)()/bs' \ -e 'H;$!d;x;s/\n//g;b' \ -e :s \ -e 'x;s/\n//g;${p;x;}' \ | sed -e 's/^com_\([a-zA-Z_0-9]\+\)()#*/\1\t/' \ | sort \ | while read a b; do printf "$a\t" if test ${#a} -lt 8; then printf "\t" fi echo "$b" done echo echo "# Try $_gsu_self help for info on ." ret=$GSU_SUCCESS return fi if test "$1" = "help"; then echo "$gsu_help_txt" ret=$GSU_SUCCESS return fi if test "$1" = "man"; then echo "$gsu_man_txt" ret=$GSU_SUCCESS return fi if test "$1" = "prefs"; then echo "$gsu_prefs_txt" ret=$GSU_SUCCESS return fi ret=$GSU_SUCCESS if grep -q "^com_$1()" $0; then sed -e "1,/com_$1()/d" -e '/^{/,$d' -e 's/^## *//' $0 return fi _gsu_print_available_commands result="$1" ret=-$E_GSU_BAD_COMMAND } export -f _com_help # internal gsu function that syntactically checks the gsu_options array # for errors and parses the config file. _gsu_check_options() { local i conf="${gsu_config_file:=$HOME/.$gsu_name.rc}" val for ((i=0; i < ${#gsu_options[@]}; i++)); do eval "${gsu_options[$i]}" eval val='"'\$$name'"' eval orig_${gsu_config_var_prefix}_$name='"'${val}'"' done [[ -r "$conf" ]] && source "$conf" for ((i=0; i < ${#gsu_options[@]}; i++)); do local name= option_type= default_value= required= local description= help_text= local val orig_val eval "${gsu_options[$i]}" # Check name. It must be non-empty and consist of [a-zA-Z_0-9] # only. Moreover it must not start with [a-zA-Z]. ret=-$E_GSU_BAD_CONFIG_VAR result="name: '$name'" # bash's =~ works only for 3.2 and newer, so use grep echo "$name" | grep '^[a-zA-Z][a-zA-Z_0123456789]*$' &> /dev/null; [[ $? -ne 0 ]] && return eval orig_val='"'\$orig_${gsu_config_var_prefix}_$name'"' if [[ -z "$orig_val" ]]; then eval val='"'\$$name'"' else val="$orig_val" fi case "$required" in true|yes) ret=-$E_GSU_NEED_VALUE result="$name" [[ -z "$val" ]] && return ;; false|no) ;; *) ret=-$E_GSU_BAD_BOOL result="required: $required, name: $name, val: $val" return esac eval ${gsu_config_var_prefix}_$name='"'${val:=$default_value}'"' # Check option type. ATM, only num and string are supported # Other types may be added without breaking compatibility case "$option_type" in string) ;; num) gsu_is_a_number "$val" [[ $ret -lt 0 ]] && return ;; *) ret=-$E_GSU_BAD_OPTION_TYPE result="$name/$option_type" return esac done ret=$GSU_SUCCESS } export -f _gsu_check_options gsu() { local i gsu_is_a_number "${BASH_VERSINFO[0]}" if [[ $ret -lt 0 ]]; then gsu_msg "fatal: failed to determine bash version" exit 1 fi if [[ "${BASH_VERSINFO[0]}" -lt 4 ]]; then gsu_msg "fatal: This script requires at least bash 4.0" exit 1 fi _gsu_self="$(basename $0)" gsu_name="${gsu_name:=$_gsu_self}" gsu_config_var_prefix="${gsu_config_var_prefix:=$gsu_name}" _gsu_init_errors _gsu_check_options if [[ "$ret" -lt 0 ]]; then if [[ "$1" != "help" && "$1" != "man" ]]; then gsu_err_msg exit 1 fi fi _gsu_available_commands gsu_cmds="$result" if test $# -eq 0; then _gsu_usage _gsu_print_available_commands exit 1 fi arg="$1" shift # check internal commands if [[ "$arg" = "help" || "$arg" = "man" || "$arg" = "prefs" ]]; then _com_$arg "$@" if [[ "$ret" -lt 0 ]]; then gsu_err_msg exit 1 fi exit 0 fi # external commands for i in $gsu_cmds; do if test "$arg" = "$i"; then com_$arg "$@" if test $ret -lt 0; then gsu_err_msg exit 1 fi exit 0 fi done ret=-$E_GSU_BAD_COMMAND result="$arg" gsu_err_msg _gsu_print_available_commands exit 1 } export -f gsu # TODO: gsu_strerror: get error string