X-Git-Url: http://git.tuebingen.mpg.de/?p=gsu.git;a=blobdiff_plain;f=funcs%2Fgsu;h=673605e43bbca8e66940722b7c587b72ba84964b;hp=8c5748ed95756412c189cc978db37ebdfe4f1e24;hb=ad6133ac9cea3d873291675a030c1361cd5cf6b9;hpb=3683ec6fde4e579fa227346b426ce6dca5d75542 diff --git a/funcs/gsu b/funcs/gsu index 8c5748e..673605e 100644 --- a/funcs/gsu +++ b/funcs/gsu @@ -1,17 +1,24 @@ #!/bin/bash # gsu -- the global subcommand utility -# (C) 2006-2007 Andre Noll +# (C) 2006-2010 Andre Noll -gsu_init_errors() +_gsu_init_errors() { gsu_errors=" GSU_SUCCESS success E_GSU_BAD_COMMAND invalid command E_GSU_NOT_A_NUMBER not a number +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_GSU_EDITOR failed to execute editor +E_GSU_MKDIR failed to create directory +E_GSU_GETOPTS getopts error $gsu_errors " - local a b i - local i=0 + local a b i=0 while read a b; do if test -z "$a"; then continue @@ -19,12 +26,12 @@ $gsu_errors #echo "a:$a, b: $b" gsu_error_txt[i]="$b" eval $a=$i - i=$((i + 1)) + i=$(($i + 1)) done << EOF $gsu_errors EOF } -export -f gsu_init_errors +export -f _gsu_init_errors # check if $1 is a number gsu_is_a_number() @@ -38,6 +45,37 @@ gsu_is_a_number() } 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 @@ -46,27 +84,29 @@ export -f gsu_short_msg gsu_msg() { - gsu_short_msg "$gsu_self: $1" + gsu_short_msg "$_gsu_self: $1" } export -f gsu_msg gsu_date_msg() { - gsu_short_msg "$gsu_self $(date): $1" + gsu_short_msg "$_gsu_self $(date): $1" } export -f gsu_date_msg -gsu_banner_msg() + + +_gsu_banner_msg() { - local txt="*** $gsu_self --" + 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 ***" + gsu_short_msg "$txt ###" } -export -f gsu_banner_msg +export -f _gsu_banner_msg gsu_err_msg() { @@ -87,15 +127,141 @@ gsu_err_msg() else txt="${gsu_error_txt[$err]}" fi - echo "$gsu_self: $txt" 1>&2 + gsu_msg "$txt" } export -f gsu_err_msg -gsu_usage() +_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 [-e] + +If -e is given, the config file is opened with the default editor. Without +options, the command prints out a list of all cmt config variables, together +with their current value and the default value." +_com_prefs() +{ + local i conf="${gsu_config_file:=$HOME/.$gsu_name.rc}" + + if [[ "$1" = "-e" ]]; then + ret=-$E_GSU_MKDIR + result="${conf%/*}" + mkdir -p "$result" + [[ $? -ne 0 ]] && return + ret=-$E_GSU_EDITOR + result="${EDITOR:-vi}" + "$result" "$conf" + [[ $? -ne 0 ]] && return + ret=$GSU_SUCCESS + return + fi + + 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() { - gsu_short_msg "Usage: $gsu_self command [options]" + 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 gsu_usage +export -f _com_man export gsu_help_txt=" Print online help. @@ -104,15 +270,19 @@ Usage: help [command] Without arguments, print the list of available commands. Otherwise, print the help text for the given command." -com_help() + +_com_help() { local a b if test -z "$1"; then - gsu_banner_msg - gsu_usage - # sed is magic, baby - grep -A 2 "^com_\([a-zA-Z_0-9]\+\)()" $0 \ - | grep -v -- '--' \ + _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 \ @@ -120,91 +290,252 @@ com_help() | sed -e 's/^com_\([a-zA-Z_0-9]\+\)()#*/\1\t/' \ | sort \ | while read a b; do - echo -en "$a\t" - if test ${#a} -lt 8; then - echo -en "\t" - fi - echo "$b" + printf "$a\t" + if test ${#a} -lt 8; then + printf "\t" + fi + echo "$b" done echo - gsu_msg "Try $gsu_self help for info on ." + echo "# Try $_gsu_self help for info on ." ret=$GSU_SUCCESS return fi if test "$1" = "help"; then - gsu_short_msg "$gsu_help_txt" + 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 + sed -e "1,/^com_$1()$/d" -e '/^{/,$d' -e 's/^## *//' $0 return fi - gsu_print_available_commands + _gsu_print_available_commands result="$1" ret=-$E_GSU_BAD_COMMAND } -export -f com_help +export -f _com_help -gsu_available_commands() +# internal gsu function that syntactically checks the gsu_options array +# for errors and parses the config file. +_gsu_check_options() { - result="$( (echo help; grep "^com_[a-z_]\+()" $0) \ - | sed -e 's/^com_//' -e 's/()//' \ - | sort \ - | tr '\n' ' ')" - ret=$SUCCESS + 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_available_commands +export -f _gsu_check_options -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 +# Wrapper for bash's getopts. +# +# Aborts on programming errors such as missing or invalid option string. On +# success $result contains shell code that can be eval'ed. For each defined +# option x, the local variable o_x will be created when calling eval "$result". +# o_x contains true/false for options without an argument or the emtpy string/the +# given argument, depending on whether this option was contained in the "$@" +# array. +# +# Example: +# gsu_getopts abc:x:y +# eval "$result" +# [[ $ret -lt 0 ]] && return +# +# [[ "$o_a" = "true ]] && echo "The -a flag was given" +# [[ -n "$o_c" ]] && echo "The -c option was given with arg $o_c" +gsu_getopts() +{ + local i c tab=' ' cr=' +' + + gsu_check_arg_count $# 1 1 + if [[ $ret -lt 0 ]]; then + gsu_err_msg + exit 1 + fi + + ret=-$E_GSU_GETOPTS + result="invalid optstring $1" + if [[ -z "$1" || "$1" =~ "::" ]]; then + gsu_err_msg + exit 1 + fi + + for ((i=0; i < ${#1}; i++)); do + c=${1:$i:1} + case "$c" in + [a-zA-Z:]);; + *) + ret=-$E_GSU_GETOPTS + result="invalid character $c in optstring" + gsu_err_msg + exit 1 + esac + done + result="local opt" + for ((i=0; i < ${#1}; i++)); do + c1=${1:$i:1} + c2=${1:$(($i + 1)):1} + result+=" o_$c1" + if [[ "$c2" = ":" ]]; then + let i++ else - printf "\t" - if test ${#i} -lt 8; then - printf "\t" - fi + result+="=false" fi done - echo -) 2>&1 + result+=" + OPTIND=1 + while getopts $1 opt \"\$@\"; do + case \"\$opt\" in +" + for ((i=0; i < ${#1}; i++)); do + c1=${1:$i:1} + c2=${1:$(($i + 1)):1} + result+="$tab$tab$c1) o_$c1=" + if [[ "$c2" = ":" ]]; then + result+="\"\$OPTARG\"" + let i++ + else + result+="true" + fi + result+=";;$cr" + done + result+=" + *) + ret=-\$E_GSU_GETOPTS + result=\"invalid option given\" + return + ;; + esac + done + shift \$((\$OPTIND - 1)) +" + ret=$GSU_SUCCESS } -export -f gsu_print_available_commands +export -f gsu_getopts gsu() { - gsu_self="$(basename $0)" - gsu_init_errors - gsu_available_commands + local i + _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 + _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 + com_$arg "$@" + if [[ "$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 + _gsu_print_available_commands exit 1 } -# no need to export this +export -f gsu + +# TODO: gsu_strerror: get error string