Split gsu.
authorAndre Noll <maan@systemlinux.org>
Fri, 23 Sep 2011 12:03:38 +0000 (14:03 +0200)
committerAndre Noll <maan@tuebingen.mpg.de>
Fri, 29 Aug 2014 19:39:58 +0000 (21:39 +0200)
This patch splits the gsu file into two files for config parsing and
subcommand handling respectively.  Each part may be used independently
of the other.

Moreover, all scripts which use gsu now include the needed parts
directly rather than relying on the shell to have already sourced
the needed gsu files. This shortens bash startup time and makes gsu
easier to use for people whose login shell is not bash.

funcs/gsu [deleted file]
misc/gsu/common [new file with mode: 0644]
misc/gsu/config [new file with mode: 0644]
misc/gsu/subcommand [new file with mode: 0644]

diff --git a/funcs/gsu b/funcs/gsu
deleted file mode 100644 (file)
index 1a35fca..0000000
--- a/funcs/gsu
+++ /dev/null
@@ -1,544 +0,0 @@
-#!/bin/bash
-# gsu -- the global subcommand utility
-# (C) 2006-2011 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_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=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 <num_given> <num1> [<num2>]
-#
-# Check that <num_given> is between <num1> and <num2> inclusively.
-# If only <num1> 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
-
-# Each line matching this is recognized as a subcommand. The name
-# of the subcommand is the first subexpression.
-export gsu_command_regex='^com_\([-a-zA-Z_0-9]\+\)()'
-
-_gsu_available_commands()
-{
-       result="$({
-               printf "help\nman\nprefs\n"
-               sed -ne "s/$gsu_command_regex/\1/g;T;p" $0
-               } | sort | tr '\n' ' ')"
-}
-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()
-{
-       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 "$gsu_command_regex" $0
-               } | grep -v -- '--' \
-                       | sed -e "/$gsu_command_regex/bs" \
-                               -e 'H;$!d;x;s/\n//g;b' \
-                               -e :s \
-                               -e 'x;s/\n//g;${p;x;}' \
-                       | sed -e "s/${gsu_command_regex}#*/\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 <command> for info on <command>."
-               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
-
-# 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" ]] || grep -q '::' <<< "$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
-                       result+="=false"
-               fi
-       done
-       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_getopts
-
-gsu()
-{
-       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
-               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 [[ "$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
diff --git a/misc/gsu/common b/misc/gsu/common
new file mode 100644 (file)
index 0000000..63b2ab6
--- /dev/null
@@ -0,0 +1,95 @@
+#!/bin/bash
+# (C) 2006-2011 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_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=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
+
+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_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_setup()
+{
+       _gsu_self="$(basename $0)"
+       gsu_name="${gsu_name:=$_gsu_self}"
+       gsu_config_var_prefix="${gsu_config_var_prefix:=$gsu_name}"
+       _gsu_init_errors
+}
+export -f _gsu_setup
diff --git a/misc/gsu/config b/misc/gsu/config
new file mode 100644 (file)
index 0000000..df96af2
--- /dev/null
@@ -0,0 +1,88 @@
+#!/bin/bash
+
+# Syntactically check the gsu_options array for errors and parse 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
+
+# Call gsu_check_options(), die on errors.
+gsu_check_options_or_die()
+{
+       gsu_check_options
+       if (($ret < 0)); then
+               gsu_err_msg
+               exit 1
+       fi
+}
+
+if [[ "$(type -t _gsu_setup)" != "function" ]]; then
+       gsu_dir=${gsu_dir:=$HOME/.gsu}
+       . $gsu_dir/common || exit 1
+       _gsu_setup
+fi
diff --git a/misc/gsu/subcommand b/misc/gsu/subcommand
new file mode 100644 (file)
index 0000000..332e5a0
--- /dev/null
@@ -0,0 +1,380 @@
+#!/bin/bash
+# (C) 2006-2011 Andre Noll
+
+if [[ $(type -t gsu_is_a_number) != "function" ]]; then
+       GSU_DIR=${GSU_DIR:=$HOME/.gsu}
+       . $GSU_DIR/common || exit 1
+fi
+
+_gsu_usage()
+{
+       gsu_short_msg "# Usage: $_gsu_self command [options]"
+}
+export -f _gsu_usage
+
+# Each line matching this is recognized as a subcommand. The name
+# of the subcommand is the first subexpression.
+export gsu_command_regex='^com_\([-a-zA-Z_0-9]\+\)()'
+
+_gsu_available_commands()
+{
+       result="$({
+               printf "help\nman\nprefs\n"
+               sed -ne "s/$gsu_command_regex/\1/g;T;p" $0
+               } | sort | tr '\n' ' ')"
+}
+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()
+{
+       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
+
+_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
+
+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 "$gsu_command_regex" $0
+               } | grep -v -- '--' \
+                       | sed -e "/$gsu_command_regex/bs" \
+                               -e 'H;$!d;x;s/\n//g;b' \
+                               -e :s \
+                               -e 'x;s/\n//g;${p;x;}' \
+                       | sed -e "s/${gsu_command_regex}#*/\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 <command> for info on <command>."
+               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
+
+# 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" ]] || grep -q '::' <<< "$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
+                       result+="=false"
+               fi
+       done
+       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_getopts
+
+gsu()
+{
+       local i
+       _gsu_setup
+       _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 [[ "$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
+
+# Check number of arguments.
+#
+# Usage: gsu_check_arg_count <num_given> <num1> [<num2>]
+#
+# Check that <num_given> is between <num1> and <num2> inclusively.
+# If only <num1> 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
+