From 14bfd7f2bb19c802a5251d4d4289ecd77c8d24ed Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Sat, 19 Jan 2019 20:34:21 +0100 Subject: [PATCH 01/15] Add home page link. People who like gsu might also like lopsub or one of my other projects. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 81768f5..cb0340f 100644 --- a/README.md +++ b/README.md @@ -727,3 +727,4 @@ References ---------- - [bash](http://www.gnu.org/software/bash/bash.html) - [dialog](http://www.invisible-island.net/dialog/dialog.html) +- [The author's home page](http://people.tuebingen.mpg.de/maan/) -- 2.39.2 From 8c9e5c951d304529421c11e2a7b5bc2ca145b637 Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Thu, 20 Dec 2018 21:43:25 +0100 Subject: [PATCH 02/15] Allow for more than one config file. This is easy to do, simple to implement, and backwards compatible (assuming that nobody has names of config files which contain spaces). --- README.md | 7 +++++++ config | 6 ++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cb0340f..1fbcef9 100644 --- a/README.md +++ b/README.md @@ -651,6 +651,13 @@ following two statements are equivalent If an option is set both in the environment and in the config file, the environment takes precedence. +The `$gsu_config_file` variable can actually contain more than one +filename, separated by spaces. The config files are processed in +order, so that an option that is specified in the second config file +overwrites the definition given in the first. This is useful for +applications which implement a system-wide config file in addition +to a per-user config file. + ___Checking config options___ The gsu config module defines two public functions for this purpose: diff --git a/config b/config index 70a83f1..6152679 100644 --- a/config +++ b/config @@ -6,7 +6,7 @@ # file. gsu_check_options() { - local i conf="${gsu_config_file:=${HOME:-}/.$gsu_name.rc}" val orig_val + local i f conf="${gsu_config_file:=${HOME:-}/.$gsu_name.rc}" val orig_val local name option_type default_value required description help_text for ((i=0; i < ${#gsu_options[@]}; i++)); do @@ -15,7 +15,9 @@ gsu_check_options() eval orig_${gsu_config_var_prefix}_$name='"'${val}'"' done - [[ -r "$conf" ]] && source "$conf" + for f in $conf; do + [[ -r "$f" ]] && source "$f" + done for ((i=0; i < ${#gsu_options[@]}; i++)); do name= -- 2.39.2 From 4b006b9a403f59daee07d62fdc2553409872f14c Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Thu, 19 Jul 2018 21:04:09 +0200 Subject: [PATCH 03/15] subcommand: Don't print error messages to stdout. If an invalid command name was given, we print the list of available commands, followed by an error message. The list of commands is sent to stdout while the error message goes to stderr. This is a bit unfortunate, so print both to stderr instead. --- subcommand | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subcommand b/subcommand index 5e55580..561ca1a 100644 --- a/subcommand +++ b/subcommand @@ -881,6 +881,6 @@ gsu() ret=-$E_GSU_BAD_COMMAND result="$arg" gsu_err_msg - _gsu_print_available_commands + _gsu_print_available_commands 1>&2 exit 1 } -- 2.39.2 From b4093a0d7e090bdea9f69594bbb6ea5023b6a362 Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Mon, 27 Jan 2020 10:40:28 +0100 Subject: [PATCH 04/15] subcommand: Declare two variables of gsu_getopts() as local. Without the declaration, applications that also use these variables may encounter problems. --- subcommand | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subcommand b/subcommand index 561ca1a..e695b58 100644 --- a/subcommand +++ b/subcommand @@ -96,7 +96,7 @@ gsu_check_arg_count() # [[ -n "$o_c" ]] && echo "The -c option was given with arg $o_c" gsu_getopts() { - local i c tab=' ' cr=' + local i c c1 c2 tab=' ' cr=' ' gsu_check_arg_count $# 1 1 -- 2.39.2 From 5bb6eedeb6a8f26d73aec0ad7f2d451d6a611a3c Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Mon, 27 Jan 2020 10:43:47 +0100 Subject: [PATCH 05/15] subcommand: Declare local variable i of gsu_getopts() as integer. We only use it as the loop index. --- subcommand | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/subcommand b/subcommand index e695b58..6cb53d5 100644 --- a/subcommand +++ b/subcommand @@ -96,7 +96,8 @@ gsu_check_arg_count() # [[ -n "$o_c" ]] && echo "The -c option was given with arg $o_c" gsu_getopts() { - local i c c1 c2 tab=' ' cr=' + local -i i + local c c1 c2 tab=' ' cr=' ' gsu_check_arg_count $# 1 1 -- 2.39.2 From 4047e938ebff10557898cb43148e70ce9414c324 Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Thu, 22 Sep 2022 23:01:18 +0200 Subject: [PATCH 06/15] subcommand: Fix -e of com_prefs(). The config variable may consist of more than one path, and we want to pass each as a separate argument to the editor. --- subcommand | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subcommand b/subcommand index 6cb53d5..e544ab0 100644 --- a/subcommand +++ b/subcommand @@ -238,7 +238,7 @@ com_prefs() (($? != 0)) && return ret=-$E_GSU_EDITOR result="${EDITOR:-vi}" - "$result" "$conf" + "$result" $conf (($? != 0)) && return ret=$GSU_SUCCESS return -- 2.39.2 From 1365cd34daa4f3fa07d422d95a296767201839c1 Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Tue, 13 Sep 2022 00:01:41 +0200 Subject: [PATCH 07/15] subcommand: Properly escape example lines in maindoc. These lines may well contain control characters such as the single quote. --- subcommand | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subcommand b/subcommand index e544ab0..415a4f0 100644 --- a/subcommand +++ b/subcommand @@ -375,7 +375,7 @@ _gsu_roffify_maindoc() fi if [[ "${line:0:1}" == "$TAB" ]]; then # example _gsu_change_roffify_state 'state' 'example' - printf '%s\n' "$line" + _gsu_print_protected_roff_line "$line" line="$next_line" continue fi -- 2.39.2 From ddf42f9d5b83568bb322b0621c14e906d9cac511 Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Thu, 13 Sep 2018 21:26:31 +0200 Subject: [PATCH 08/15] Implement help -a. In most cases, the user does not want to see the four subcommands which are auto-generated by gsu, so omit them from the default output and provide -a to show them anyway. This patch has been languishing in a development branch for several years, so it's kind of well tested. --- subcommand | 89 +++++++++++++++++++++++++++--------------------------- 1 file changed, 45 insertions(+), 44 deletions(-) diff --git a/subcommand b/subcommand index 415a4f0..e5c0001 100644 --- a/subcommand +++ b/subcommand @@ -209,9 +209,10 @@ gsu_complete_options() ret=1 } +declare -A _gsu_help_text=() # indexed by autocmd com_prefs_options='e' -_gsu_prefs_txt=" +_gsu_help_text['prefs']=' Print the current preferences. Usage: prefs [-e] @@ -219,7 +220,7 @@ 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 config variables, together with their current value and the default value. -" +' com_prefs() { @@ -290,7 +291,7 @@ complete_man() [[ "$result" == 'm' ]] && printf 'roff\ntext\nhtml\n' } -_gsu_man_txt=' +_gsu_help_text['man']=' Print the manual. Usage: man [-m ] [-b ] @@ -452,14 +453,17 @@ _gsu_roffify_cmds() done } -_gsu_roffify_autocmd() +_gsu_roffify_autocmds() { - local cmd="$1" help_txt="$2" + local cmd help_txt - { - printf 'com_%s()\n' "$cmd" - sed -e 's/^/## /g' <<< "$help_txt" - } | _gsu_roffify_cmds + for cmd in "${!_gsu_help_text[@]}"; do + help_txt="${_gsu_help_text["$cmd"]}" + { + printf 'com_%s()\n' "$cmd" + sed -e 's/^/## /g' <<< "$help_txt" + } | _gsu_roffify_cmds + done } _gsu_roff_man() @@ -481,10 +485,7 @@ EOF printf '\n.SH "GENERIC SUBCOMMANDS"\n' printf 'The following commands are automatically created by gsu\n' - _gsu_roffify_autocmd "help" "$_gsu_help_txt" - _gsu_roffify_autocmd "man" "$_gsu_man_txt" - _gsu_roffify_autocmd "prefs" "$_gsu_prefs_txt" - _gsu_roffify_autocmd "complete" "$_gsu_complete_txt" + _gsu_roffify_autocmds printf '\n.SH "LIST OF SUBCOMMANDS"\n' printf 'Each command has its own set of options as described below.\n' @@ -607,15 +608,18 @@ com_man() ret=$GSU_SUCCESS } -_gsu_help_txt=" +_gsu_help_text['help']=' Print online help. -Usage: help [command] +Usage: help [-a] [command] Without arguments, print the list of available commands. Otherwise, -print the help text for the given command." +print the help text for the given command. + +-a: Also show the help of automatic commands. Ignored if a command +is given.' -_gsu_complete_txt=" +_gsu_help_text['complete']=' Command line completion. Usage: complete [ ...] @@ -627,11 +631,16 @@ 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_options='a' com_help() { - local ere tab=' ' + local ere tab=' ' txt + + gsu_getopts "$com_help_options" + eval "$result" + ((ret < 0)) && return _gsu_get_command_regex ere="$result" @@ -640,10 +649,15 @@ com_help() gsu_short_msg "### $gsu_name -- $gsu_banner_txt ###" _gsu_usage 2>&1 { - printf "com_help()\n%s" "$_gsu_help_txt" | head -n 4; echo "--" - printf "com_man()\n%s" "$_gsu_man_txt" | head -n 4; echo "--" - printf "com_prefs()\n%s" "$_gsu_prefs_txt" | head -n 4; echo "--" - printf "com_complete()\n%s" "$_gsu_complete_txt" | head -n 4; echo "--" + if [[ "$o_a" == 'true' ]]; then + _gsu_mfcb() { printf '%s\n' "$2"; } + for cmd in "${!_gsu_help_text[@]}"; do + printf "com_%s()" "$cmd" + txt="${_gsu_help_text["$cmd"]}" + mapfile -n 3 -c 1 -C _gsu_mfcb <<< "$txt" + printf -- '--\n' + done + fi grep -EA 2 "$ere" "$0" } | grep -v -- '--' \ | sed -En "/$ere/"'!d @@ -666,32 +680,18 @@ com_help() # and print the sucker p' - echo - echo "# Try $gsu_name help for info on ." + printf "\n# Try %s help for info on , or %s help -a to see\n" \ + "$gsu_name" "$gsu_name" + printf '# also the subcommands which are automatically generated by gsu.\n' ret=$GSU_SUCCESS return fi - if test "$1" = "help"; then - echo "$_gsu_help_txt" + for cmd in "${!_gsu_help_text[@]}"; do + [[ "$1" != "$cmd" ]] && continue + printf '%s\n' "${_gsu_help_text["$cmd"]}" 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 + done _gsu_get_command_regex "$1" ere="$result" if ! grep -Eq "$ere" "$0"; then @@ -718,6 +718,7 @@ com_help() p } ' "$0" + ret=$GSU_SUCCESS } complete_help() -- 2.39.2 From 64712f59781b7d1029773b6f555f74c964129040 Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Wed, 6 Jul 2022 00:10:59 +0200 Subject: [PATCH 09/15] gui: Treat dialog exit code 255 just like 1. Dialog exits with code 255 if escape was pressed. This change makes the escape key more useful in the main menu invoked via _browse() since it no longer terminates the program but results to show the parent menu. --- gui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui b/gui index 4297564..e9eb1a8 100644 --- a/gui +++ b/gui @@ -30,7 +30,7 @@ _set_dialog_ret() case "$ec" in 0) ret=$GSU_SUCCESS;; - 1) ret=1;; # cancelled + 1|255) ret=1;; # cancelled *) result="dialog exit code $ec" ret=-$E_GSU_DIALOG -- 2.39.2 From 5dfe50002b64c19dab9625360165a3415f03eb99 Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Wed, 6 Jul 2022 00:13:21 +0200 Subject: [PATCH 10/15] gui: Don't show items in menus. The numbers at the right of each item are of little value. This patch gets rid of them, which also simplifies the code a bit. --- gui | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/gui b/gui index e9eb1a8..72ddb28 100644 --- a/gui +++ b/gui @@ -97,15 +97,13 @@ _gsu_menu() { local header="${1:-root}" local items="$2" - local i opts num=0 + local geom _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>&-)" + geom=$result + result="$(dialog --no-lines --no-items --menu \ + "$gsu_banner_txt ($header)" \ + $geom 16 $items 3>&1 1>&2 2>&3 3>&-)" _set_dialog_ret $? } -- 2.39.2 From 7975dbb183a73844f4ea2f9475a44681fda6ed0c Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Wed, 2 Aug 2023 22:35:16 +0200 Subject: [PATCH 11/15] subcommand: Improve formatting of command list. If a gsu application is run without specifying a subcommand we print the available subcommands in tabular form. Currently, this output always consists of four columns and we also assume that the length of a subcommand name is shorter than 16 characters. If longer names exist, the help output is formatted incorrectly. This commit should fix that. We now compute the maximal command length and the width of the terminal upfront, and derive the number of columns for the output from this information. --- subcommand | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/subcommand b/subcommand index e5c0001..817486d 100644 --- a/subcommand +++ b/subcommand @@ -168,20 +168,25 @@ gsu_getopts() _gsu_print_available_commands() { local cmd cmds - local -i count=0 + local -i maxlen=0 cols width=80 count=0 + result=$(stty size 2>/dev/null) + if (($? == 0)); then + gsu_is_a_number "${result#* }" + ((ret >= 0)) && ((result > 0)) && width=$result + fi _gsu_available_commands - cmds="$result" - printf 'Available commands:\n' + cmds=$result + for cmd in $cmds; do + ((${#cmd} > maxlen)) && maxlen=${#cmd} + done + let maxlen++ + ((width < maxlen)) && cols=1 || cols=$((width / maxlen)) + printf 'Available commands:' for cmd in $cmds; do - printf '%s' "$cmd" + ((count % cols == 0)) && printf '\n' + printf '%-*s' $maxlen $cmd let ++count - if ((count % 4)); then - printf '\t' - ((${#cmd} < 8)) && printf '\t' - else - printf '\n' - fi done printf '\n' } -- 2.39.2 From 241af60a8f3df56c18fb13b0cb22df23297b2538 Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Wed, 2 Aug 2023 23:09:11 +0200 Subject: [PATCH 12/15] com_help(): Improve formating of command/description list The help output, which consists of the subcommand name and the one-line description, is formatted incorrectly if the name of one or more subcommands exceeds 16 characters. Pre-compute the maximal length of the command names upfront to fix that. --- subcommand | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/subcommand b/subcommand index 817486d..d37481c 100644 --- a/subcommand +++ b/subcommand @@ -684,7 +684,21 @@ com_help() y/\n/'"$tab"'/ # and print the sucker - p' + p + ' | { + local -a cmds=() descs=() + local -i i maxlen=1 + local cmd desc + while read cmd desc; do + ((maxlen < ${#cmd})) && maxlen=${#cmd} + cmds[${#cmds[@]}]=$cmd + descs[${#descs[@]}]=$desc + done + for ((i = 0; i < ${#cmds[@]}; i++)); do + printf '%-*s %s\n' $maxlen ${cmds[$i]} \ + "${descs[$i]}" + done + } printf "\n# Try %s help for info on , or %s help -a to see\n" \ "$gsu_name" "$gsu_name" printf '# also the subcommands which are automatically generated by gsu.\n' -- 2.39.2 From 4003d415635d974a8d040589057fce086752a385 Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Wed, 25 Oct 2023 21:13:04 +0200 Subject: [PATCH 13/15] README: Fix typo/braino. The tree node is called "storage/" not "block_devices/" a few lines earlier. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1fbcef9..815009f 100644 --- a/README.md +++ b/README.md @@ -444,7 +444,7 @@ tree could look like this: dmesg ' -In this tree, `hardware/`, `block_devices/` and `log/` are the only +In this tree, `hardware/`, `storage/` and `log/` are the only internal nodes. Note that these are written with a trailing slash character while the leaf nodes have no slash at the end. All entries of the menu tree must be indented by tab characters. -- 2.39.2 From 21ea6b920df2ad6d1003112fac5cf1e81aeeb73d Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Thu, 26 Oct 2023 01:17:05 +0200 Subject: [PATCH 14/15] README: Mention that the gui example script must be named "lsi". By default, the prefix for the gsu action handlers is the basename of the script and an underscore character. In the example script of README.md all handlers are prefixed with "lsi_", so tell the reader that the script will only work if it is saved under the name "lsi". --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 815009f..44e111f 100644 --- a/README.md +++ b/README.md @@ -496,9 +496,9 @@ to do is to source the gsu gui module and to call `gsu_gui()`: ___Example___ -The complete lsi script below can be used as a starting point -for your own gsu gui application. If you cut and paste it, be -sure to not turn tab characters into space characters. +The complete lsi script below can be used as a starting point for your +own gsu gui application. If you cut and paste it, be sure to not turn +tab characters into space characters. The script must be named "lsi". #!/bin/bash -- 2.39.2 From b94faafa111dbec1b5cc45fb588f9258b4ca5abc Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Tue, 22 Aug 2023 18:06:57 +0200 Subject: [PATCH 15/15] gui: Remember menu position. That's generally the expected behaviour, and it's not too hard to implement. We let _gsu_menu() take an additional argument for the default item and maintain its value in _browse(). --- gui | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/gui b/gui index 72ddb28..fbcbb01 100644 --- a/gui +++ b/gui @@ -95,14 +95,14 @@ gsu_msgbox() _gsu_menu() { - local header="${1:-root}" - local items="$2" + local header=${1:-root} dflt_item=$2 items=$3 local geom _get_geometry geom=$result - result="$(dialog --no-lines --no-items --menu \ - "$gsu_banner_txt ($header)" \ + result="$(dialog --no-lines --no-items \ + --default-item "$dflt_item" \ + --menu "$gsu_banner_txt ($header)" \ $geom 16 $items 3>&1 1>&2 2>&3 3>&-)" _set_dialog_ret $? } @@ -154,12 +154,14 @@ _get_root_nodes() _browse() { - local header="$1" old_header - local tree="$2" subtree="$3" + local header=$1 tree=$2 + local -a subtree=($3) + local old_header dflt_item=${subtree[0]} while :; do - _gsu_menu "$header" "$subtree" + _gsu_menu "$header" "$dflt_item" "${subtree[*]}" ((ret < 0)) && return + dflt_item=$result [[ -z "$result" ]] && return # menu was cancelled if [[ "${result%/}" != "$result" ]]; then old_header="$header" -- 2.39.2