From: Andre Noll Date: Sat, 30 Aug 2014 12:34:27 +0000 (+0200) Subject: Move all files to the top level directory. X-Git-Url: http://git.tuebingen.mpg.de/?p=gsu.git;a=commitdiff_plain;h=f31cc416b1acdbb8f5703b208d943ce500a5b840 Move all files to the top level directory. With gsu being a separate repository, there is no point in burying the gsu files below misc/gsu. --- diff --git a/common b/common new file mode 100644 index 0000000..bd8276b --- /dev/null +++ b/common @@ -0,0 +1,89 @@ +#!/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 +} + +# 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 +} + +gsu_short_msg() +{ + echo "$1" 1>&2 +} + +gsu_msg() +{ + gsu_short_msg "$_gsu_self: $1" +} + +gsu_date_msg() +{ + gsu_short_msg "$_gsu_self $(date): $1" +} + +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" +} + +_gsu_setup() +{ + _gsu_self="$(basename $0)" + gsu_name="${gsu_name:=$_gsu_self}" + gsu_config_var_prefix="${gsu_config_var_prefix:=$gsu_name}" + gsu_banner_txt="${gsu_banner_txt:-set \$gsu_banner_txt to customize this message}" + _gsu_init_errors +} diff --git a/config b/config new file mode 100644 index 0000000..09330f3 --- /dev/null +++ b/config @@ -0,0 +1,85 @@ +#!/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 +} + +# 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/gui b/gui new file mode 100644 index 0000000..eb9f29f --- /dev/null +++ b/gui @@ -0,0 +1,212 @@ +#!/bin/bash + +if [[ $(type -t gsu_is_a_number) != "function" ]]; then + GSU_DIR=${GSU_DIR:=${HOME:-}/.gsu} + . $GSU_DIR/common || exit 1 +fi + +export GSU_NODE_NAME_PATTERN='[a-zA-Z_]' + +_get_geometry() +{ + local x y + result="$(stty size)" + if (($? != 0)); then + gsu_msg "fatal: could not get terminal geometry" + exit 1 + fi + x="${result#* }" + y="${result%% *}" + (($x > 190)) && x=190 + result="$y $x" +} + +gsu_infobox() +{ + _get_geometry + dialog --infobox "$1" $result +} + +gsu_checklist_all_on() +{ + local header="$1" + local items="$2" + local i state opts num=0 + + _get_geometry + ops="$result 16" + for i in $items; do + let num++ + opts+=" $i $num on" + done + result=$(dialog --checklist "$header" $opts 3>&1 1>&2 2>&3 3>&-) + ret="$?" +} + +gsu_radiolist() +{ + local header="$1" + local selected_item="$2" + local items="$3" + local i state ops num=0 + + _get_geometry + ops="$result 16" + for i in $items; do + let num++ + if [[ "$i" == "$selected_item" ]]; then + state="on" + else + state="off" + fi + ops+=" $i $num $state" + done + result=$(dialog --radiolist "$header" $ops 3>&1 1>&2 2>&3 3>&-) + ret="$?" +} + +gsu_inputbox() +{ + local g text="$1" init="$2" + + _get_geometry + g="$result" + result="$(dialog --inputbox "$text" $g "$init" 3>&1 1>&2 2>&3 3>&-)" + ret="$?" +} + +gsu_textbox() +{ + local file="$1" + + _get_geometry + dialog --textbox "$file" $result +} + +# dialog segfaults if message is too long. Hence we always use a temporary file +gsu_msgbox() +{ + local tmp="$(mktemp gsu_msgbox.XXXXXXXXXX)" + + if (($? != 0)); then + dialog --msgbox "mktemp error" 0 0 + return + fi + echo "$1" > "$tmp" + gsu_textbox "$tmp" + rm -f "$tmp" +} + +gsu_cmd_output_box() +{ + local tmp="$(mktemp)" + + if (($? != 0)); then + dialog --msgbox "mktemp error" 0 0 + return + fi + $@ > "$tmp" 2>&1 + echo "exit code: $?" >> "$tmp" + gsu_textbox "$tmp" + rm -f "$tmp" +} + +gsu_yesno() +{ + local text="$1" + + _get_geometry + dialog --yesno "$text" $result + ret=$? + if (($ret == 0)); then + result="yes" + elif (($ret == 1)); then + result="no" + else + result= + fi +} + +gsu_menu() +{ + local header="${1:-root}" + local items="$2" + local i state opts num=0 + + _get_geometry + opts="$result 16" + for i in $items; do + let num++ + opts+=" $i $num" + done + result="$(dialog --menu "$gsu_banner_txt ($header)" $opts 3>&1 1>&2 2>&3 3>&-)" + ret="$?" +} + +_get_level() +{ + local tmp="${1%%$GSU_NODE_NAME_PATTERN*}" + result="${#tmp}" +} + +_get_subtree() +{ + local tree="$1" root="${2%/}" + local TAB=' ' + + first="$(grep -n "$TAB\{1,\}$root/" <<< "$tree")" + [[ -z "$first" ]] && return + + line_num="${first%%:*}" + _get_level "${first#*:}" + level="$result" + + #echo "line: $line_num, root: $root, indent level: $level" + result="$(sed -e "1,${line_num}d;" <<< "$tree" \ + | sed -e "/^$TAB\{1,$level\}$GSU_NODE_NAME_PATTERN/,\$d" \ + | sed -e "/^$TAB\{$(($level + 2))\}/d")" + ret="$level" +} + +_get_root_nodes() +{ + local tree="$1" TAB=' ' + + result="$(grep "^${TAB}${GSU_NODE_NAME_PATTERN}" <<< "$tree")" +} + +_browse() +{ + local header="$1" old_header + local tree="$2" subtree="$3" + + while :; do + gsu_menu "$header" "$subtree" + (($ret != 0)) && return + [[ -z "$result" ]] && return + if [[ "${result%/}" != "$result" ]]; then + old_header="$header" + header="$result" + _get_subtree "$tree" "$header" + _browse "$header" "$tree" "$result" + header="$old_header" + continue + fi + eval ${gsu_name}_$result + done +} + +gsu_gui() +{ + local tree="$1" subtree + + _gsu_setup + type -t dialog &> /dev/null + if (($? != 0)); then + gsu_msg "dialog executable not found" + exit 1 + fi + _get_root_nodes "$tree" + subtree="$result" + _browse "main menu" "$tree" "$subtree" +} diff --git a/misc/gsu/common b/misc/gsu/common deleted file mode 100644 index bd8276b..0000000 --- a/misc/gsu/common +++ /dev/null @@ -1,89 +0,0 @@ -#!/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 -} - -# 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 -} - -gsu_short_msg() -{ - echo "$1" 1>&2 -} - -gsu_msg() -{ - gsu_short_msg "$_gsu_self: $1" -} - -gsu_date_msg() -{ - gsu_short_msg "$_gsu_self $(date): $1" -} - -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" -} - -_gsu_setup() -{ - _gsu_self="$(basename $0)" - gsu_name="${gsu_name:=$_gsu_self}" - gsu_config_var_prefix="${gsu_config_var_prefix:=$gsu_name}" - gsu_banner_txt="${gsu_banner_txt:-set \$gsu_banner_txt to customize this message}" - _gsu_init_errors -} diff --git a/misc/gsu/config b/misc/gsu/config deleted file mode 100644 index 09330f3..0000000 --- a/misc/gsu/config +++ /dev/null @@ -1,85 +0,0 @@ -#!/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 -} - -# 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/gui b/misc/gsu/gui deleted file mode 100644 index eb9f29f..0000000 --- a/misc/gsu/gui +++ /dev/null @@ -1,212 +0,0 @@ -#!/bin/bash - -if [[ $(type -t gsu_is_a_number) != "function" ]]; then - GSU_DIR=${GSU_DIR:=${HOME:-}/.gsu} - . $GSU_DIR/common || exit 1 -fi - -export GSU_NODE_NAME_PATTERN='[a-zA-Z_]' - -_get_geometry() -{ - local x y - result="$(stty size)" - if (($? != 0)); then - gsu_msg "fatal: could not get terminal geometry" - exit 1 - fi - x="${result#* }" - y="${result%% *}" - (($x > 190)) && x=190 - result="$y $x" -} - -gsu_infobox() -{ - _get_geometry - dialog --infobox "$1" $result -} - -gsu_checklist_all_on() -{ - local header="$1" - local items="$2" - local i state opts num=0 - - _get_geometry - ops="$result 16" - for i in $items; do - let num++ - opts+=" $i $num on" - done - result=$(dialog --checklist "$header" $opts 3>&1 1>&2 2>&3 3>&-) - ret="$?" -} - -gsu_radiolist() -{ - local header="$1" - local selected_item="$2" - local items="$3" - local i state ops num=0 - - _get_geometry - ops="$result 16" - for i in $items; do - let num++ - if [[ "$i" == "$selected_item" ]]; then - state="on" - else - state="off" - fi - ops+=" $i $num $state" - done - result=$(dialog --radiolist "$header" $ops 3>&1 1>&2 2>&3 3>&-) - ret="$?" -} - -gsu_inputbox() -{ - local g text="$1" init="$2" - - _get_geometry - g="$result" - result="$(dialog --inputbox "$text" $g "$init" 3>&1 1>&2 2>&3 3>&-)" - ret="$?" -} - -gsu_textbox() -{ - local file="$1" - - _get_geometry - dialog --textbox "$file" $result -} - -# dialog segfaults if message is too long. Hence we always use a temporary file -gsu_msgbox() -{ - local tmp="$(mktemp gsu_msgbox.XXXXXXXXXX)" - - if (($? != 0)); then - dialog --msgbox "mktemp error" 0 0 - return - fi - echo "$1" > "$tmp" - gsu_textbox "$tmp" - rm -f "$tmp" -} - -gsu_cmd_output_box() -{ - local tmp="$(mktemp)" - - if (($? != 0)); then - dialog --msgbox "mktemp error" 0 0 - return - fi - $@ > "$tmp" 2>&1 - echo "exit code: $?" >> "$tmp" - gsu_textbox "$tmp" - rm -f "$tmp" -} - -gsu_yesno() -{ - local text="$1" - - _get_geometry - dialog --yesno "$text" $result - ret=$? - if (($ret == 0)); then - result="yes" - elif (($ret == 1)); then - result="no" - else - result= - fi -} - -gsu_menu() -{ - local header="${1:-root}" - local items="$2" - local i state opts num=0 - - _get_geometry - opts="$result 16" - for i in $items; do - let num++ - opts+=" $i $num" - done - result="$(dialog --menu "$gsu_banner_txt ($header)" $opts 3>&1 1>&2 2>&3 3>&-)" - ret="$?" -} - -_get_level() -{ - local tmp="${1%%$GSU_NODE_NAME_PATTERN*}" - result="${#tmp}" -} - -_get_subtree() -{ - local tree="$1" root="${2%/}" - local TAB=' ' - - first="$(grep -n "$TAB\{1,\}$root/" <<< "$tree")" - [[ -z "$first" ]] && return - - line_num="${first%%:*}" - _get_level "${first#*:}" - level="$result" - - #echo "line: $line_num, root: $root, indent level: $level" - result="$(sed -e "1,${line_num}d;" <<< "$tree" \ - | sed -e "/^$TAB\{1,$level\}$GSU_NODE_NAME_PATTERN/,\$d" \ - | sed -e "/^$TAB\{$(($level + 2))\}/d")" - ret="$level" -} - -_get_root_nodes() -{ - local tree="$1" TAB=' ' - - result="$(grep "^${TAB}${GSU_NODE_NAME_PATTERN}" <<< "$tree")" -} - -_browse() -{ - local header="$1" old_header - local tree="$2" subtree="$3" - - while :; do - gsu_menu "$header" "$subtree" - (($ret != 0)) && return - [[ -z "$result" ]] && return - if [[ "${result%/}" != "$result" ]]; then - old_header="$header" - header="$result" - _get_subtree "$tree" "$header" - _browse "$header" "$tree" "$result" - header="$old_header" - continue - fi - eval ${gsu_name}_$result - done -} - -gsu_gui() -{ - local tree="$1" subtree - - _gsu_setup - type -t dialog &> /dev/null - if (($? != 0)); then - gsu_msg "dialog executable not found" - exit 1 - fi - _get_root_nodes "$tree" - subtree="$result" - _browse "main menu" "$tree" "$subtree" -} diff --git a/misc/gsu/subcommand b/misc/gsu/subcommand deleted file mode 100644 index 6b5df0a..0000000 --- a/misc/gsu/subcommand +++ /dev/null @@ -1,601 +0,0 @@ -#!/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]" -} - -# Return an extended regular expression to match against $0. -# -# When called without argument, the expression matches all lines which define a -# subcommand. -# -# If an argument is given, the returned expression matches only the subcommand -# passed as $1. This is useful to tell if a string is a valid subcommand. -# -# Regardless of whether an argument is given, the returned expression contains -# exactly one parenthesized subexpression for matching the command name. -_gsu_get_command_regex() -{ - local cmd="${1:-[-a-zA-Z_0-9]+}" - result="^com_($cmd)\(\)" -} - -_gsu_available_commands() -{ - local ere - - _gsu_get_command_regex - ere="$result" - result="$({ - printf "help\nman\nprefs\ncomplete\n" - sed -Ee ' - # if line matches, isolate command name - s/'"$ere"'/\1/g - - # if there is a match, (print it and) start next cycle - t - - # otherwise delete it - d - ' $0 - } | sort | tr '\n' ' ')" -} - -_gsu_print_available_commands() -{( - local i count=0 - 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 -} - -gsu_complete_options() -{ - local opts="$1" cword="$2" cur opt - local -a words - - shift 2 - words=("$@") - cur="${words[$cword]}" - ret=0 - [[ ! "$cur" == -* ]] && return - - ret=0 - for ((i=0; i < ${#opts}; i++)); do - opt="${opts:$i:1}" - [[ "$opt" == ":" ]] && continue - printf "%s" "-$opt " - let ret++ - done -} - -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}" - - gsu_getopts "e" - eval "$result" - (($ret < 0)) && return - gsu_check_arg_count $# 0 0 - (($ret < 0)) && return - - if [[ "$o_e" == "true" ]]; 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 -} - -complete_prefs() -{ - gsu_complete_options "e" "$@" -} - -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 -} - -_gsu_banner_msg() -{ - gsu_short_msg "### $_gsu_self -- ###" -} - -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." - -export gsu_complete_txt=" -Command line completion. - -Usage: complete [ ...] - -When executed without argument the command writes bash code to -stdout. This code is suitable to be evaled from .bashrc to enable -completion. - -If at least one argument is given, all possible completions are -written to stdout. This can be used from the completion function of -the subcommand. -" - -_com_help() -{ - local a b ere tab=' ' - - _gsu_get_command_regex - ere="$result" - - if (($# == 0)); 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 "--" - printf "com_complete()\n$gsu_complete_txt" | head -n 4; echo "--" - grep -EA 2 "$ere" $0 - } | grep -v -- '--' \ - | sed -En "/$ere/"'!d - # remove everything but the command name - s/^com_(.*)\(\).*/\1/ - - # append tab after short commands (less than 8 chars) - s/^(.{1,7})$/\1'"$tab"'/g - - # remove next line (should contain only ## anyway) - N - s/#.*// - - # append next line, removing leading ## - N - s/#+ *//g - - # replace newline by tab - y/\n/'"$tab"'/ - - # and print the sucker - p' - 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 - if test "$1" = "complete"; then - echo "$gsu_complete_txt" - ret=$GSU_SUCCESS - return - fi - ret=$GSU_SUCCESS - _gsu_get_command_regex "$1" - ere="$result" - if ! grep -Eq "$ere" $0; then - _gsu_print_available_commands - result="$1" - ret=-$E_GSU_BAD_COMMAND - return - fi - sed -nEe ' - # only consider lines in the comment of the function - /'"$ere"'/,/^[^#]/ { - - # remove leading ## - s/^## *// - - # if it did start with ##, jump to label p and print it - tp - - # otherwise, move on to next line - d - - # print it - :p - p - } - ' $0 -} - -complete_help() -{ - _gsu_available_commands - echo "$result" -} - -# Wrapper for the bash getopts builtin. -# -# 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 argument and either the emtpy -# string or the given argument for options that take an argument. -# -# Example: -# gsu_getopts abc:x:y -# eval "$result" -# (($ret < 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 _gsu_getopts_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 _gsu_getopts_opt \"\$@\"; do - case \"\$_gsu_getopts_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 -} - -_com_complete() -{ - local cmd n cword - local -a words - - if (($# == 0)); then - cat < [] -# -# 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 -} diff --git a/subcommand b/subcommand new file mode 100644 index 0000000..6b5df0a --- /dev/null +++ b/subcommand @@ -0,0 +1,601 @@ +#!/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]" +} + +# Return an extended regular expression to match against $0. +# +# When called without argument, the expression matches all lines which define a +# subcommand. +# +# If an argument is given, the returned expression matches only the subcommand +# passed as $1. This is useful to tell if a string is a valid subcommand. +# +# Regardless of whether an argument is given, the returned expression contains +# exactly one parenthesized subexpression for matching the command name. +_gsu_get_command_regex() +{ + local cmd="${1:-[-a-zA-Z_0-9]+}" + result="^com_($cmd)\(\)" +} + +_gsu_available_commands() +{ + local ere + + _gsu_get_command_regex + ere="$result" + result="$({ + printf "help\nman\nprefs\ncomplete\n" + sed -Ee ' + # if line matches, isolate command name + s/'"$ere"'/\1/g + + # if there is a match, (print it and) start next cycle + t + + # otherwise delete it + d + ' $0 + } | sort | tr '\n' ' ')" +} + +_gsu_print_available_commands() +{( + local i count=0 + 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 +} + +gsu_complete_options() +{ + local opts="$1" cword="$2" cur opt + local -a words + + shift 2 + words=("$@") + cur="${words[$cword]}" + ret=0 + [[ ! "$cur" == -* ]] && return + + ret=0 + for ((i=0; i < ${#opts}; i++)); do + opt="${opts:$i:1}" + [[ "$opt" == ":" ]] && continue + printf "%s" "-$opt " + let ret++ + done +} + +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}" + + gsu_getopts "e" + eval "$result" + (($ret < 0)) && return + gsu_check_arg_count $# 0 0 + (($ret < 0)) && return + + if [[ "$o_e" == "true" ]]; 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 +} + +complete_prefs() +{ + gsu_complete_options "e" "$@" +} + +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 +} + +_gsu_banner_msg() +{ + gsu_short_msg "### $_gsu_self -- ###" +} + +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." + +export gsu_complete_txt=" +Command line completion. + +Usage: complete [ ...] + +When executed without argument the command writes bash code to +stdout. This code is suitable to be evaled from .bashrc to enable +completion. + +If at least one argument is given, all possible completions are +written to stdout. This can be used from the completion function of +the subcommand. +" + +_com_help() +{ + local a b ere tab=' ' + + _gsu_get_command_regex + ere="$result" + + if (($# == 0)); 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 "--" + printf "com_complete()\n$gsu_complete_txt" | head -n 4; echo "--" + grep -EA 2 "$ere" $0 + } | grep -v -- '--' \ + | sed -En "/$ere/"'!d + # remove everything but the command name + s/^com_(.*)\(\).*/\1/ + + # append tab after short commands (less than 8 chars) + s/^(.{1,7})$/\1'"$tab"'/g + + # remove next line (should contain only ## anyway) + N + s/#.*// + + # append next line, removing leading ## + N + s/#+ *//g + + # replace newline by tab + y/\n/'"$tab"'/ + + # and print the sucker + p' + 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 + if test "$1" = "complete"; then + echo "$gsu_complete_txt" + ret=$GSU_SUCCESS + return + fi + ret=$GSU_SUCCESS + _gsu_get_command_regex "$1" + ere="$result" + if ! grep -Eq "$ere" $0; then + _gsu_print_available_commands + result="$1" + ret=-$E_GSU_BAD_COMMAND + return + fi + sed -nEe ' + # only consider lines in the comment of the function + /'"$ere"'/,/^[^#]/ { + + # remove leading ## + s/^## *// + + # if it did start with ##, jump to label p and print it + tp + + # otherwise, move on to next line + d + + # print it + :p + p + } + ' $0 +} + +complete_help() +{ + _gsu_available_commands + echo "$result" +} + +# Wrapper for the bash getopts builtin. +# +# 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 argument and either the emtpy +# string or the given argument for options that take an argument. +# +# Example: +# gsu_getopts abc:x:y +# eval "$result" +# (($ret < 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 _gsu_getopts_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 _gsu_getopts_opt \"\$@\"; do + case \"\$_gsu_getopts_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 +} + +_com_complete() +{ + local cmd n cword + local -a words + + if (($# == 0)); then + cat < [] +# +# 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 +}