2 # Copyright (C) 2006 Andre Noll
3 # Licensed under the LGPL, version 3. See COPYING and COPYING.LESSER.
5 if [[ "$(type -t _gsu_setup)" != "function" ]]; then
6 gsu_dir=${gsu_dir:-${BASH_SOURCE[0]%/*}}
7 . "$gsu_dir/common" || exit 1
13 gsu_short_msg "# Usage: $gsu_name command [options]"
16 # Return an extended regular expression to match against $0.
18 # When called without argument, the expression matches all lines which define a
21 # If an argument is given, the returned expression matches only the subcommand
22 # passed as $1. This is useful to tell if a string is a valid subcommand.
24 # Regardless of whether an argument is given, the returned expression contains
25 # exactly one parenthesized subexpression for matching the command name.
26 _gsu_get_command_regex()
28 local cmd="${1:-[-a-zA-Z_0-9]{1,\}}"
29 result="^com_($cmd)\(\)"
32 _gsu_available_commands()
36 _gsu_get_command_regex
39 printf "help\nman\nprefs\ncomplete\n"
41 # if line matches, isolate command name
44 # if there is a match, (print it and) start next cycle
50 } | sort | tr '\n' ' ')"
53 # Check number of arguments.
55 # Usage: gsu_check_arg_count <num_given> <num1> [<num2>]
57 # Check that <num_given> is between <num1> and <num2> inclusively.
58 # If only <num1> ist given, num2 is assumed to be infinity.
61 # 0 0 no argument allowed
62 # 1 1 exactly one argument required
63 # 0 2 at most two arguments admissible
64 # 2 at least two arguments required
67 ret=-$E_GSU_BAD_ARG_COUNT
68 if (($# == 2)); then # only num1 is given
69 result="at least $2 args required, $1 given"
75 result="need at least $2 args, $1 given"
77 result="need at most $3 args, $1 given"
82 # Wrapper for the bash getopts builtin.
84 # Aborts on programming errors such as missing or invalid option string. On
85 # success $result contains shell code that can be eval'ed. For each defined
86 # option x, the local variable o_x will be created when calling eval "$result".
87 # o_x contains true/false for options without argument and either the empty
88 # string or the given argument for options that take an argument.
93 # (($ret < 0)) && return
95 # [[ "$o_a" = 'true' ]] && echo 'The -a flag was given'
96 # [[ -n "$o_c" ]] && echo "The -c option was given with arg $o_c"
99 local i c tab=' ' cr='
102 gsu_check_arg_count $# 1 1
103 if (($ret < 0)); then
109 result="invalid optstring $1"
110 if [[ -z "$1" ]] || grep -q '::' <<< "$1" ; then
115 for ((i=0; i < ${#1}; i++)); do
121 result="invalid character $c in optstring"
126 result="local _gsu_getopts_opt"
127 for ((i=0; i < ${#1}; i++)); do
129 c2=${1:$(($i + 1)):1}
131 if [[ "$c2" = ":" ]]; then
139 while getopts $1 _gsu_getopts_opt \"\$@\"; do
140 case \"\$_gsu_getopts_opt\" in
142 for ((i=0; i < ${#1}; i++)); do
144 c2=${1:$(($i + 1)):1}
145 result+="$tab$tab$c1) o_$c1="
146 if [[ "$c2" = ":" ]]; then
147 result+="\"\$OPTARG\""
157 result=\"invalid option given\"
162 shift \$((\$OPTIND - 1))
167 _gsu_print_available_commands()
172 _gsu_available_commands
174 printf 'Available commands:\n'
178 if (($count % 4)); then
180 ((${#cmd} < 8)) && printf '\t'
188 # Print all options of the given optstring to stdout if the word in the current
189 # command line begins with a hyphen character.
191 # Returns 0 if the current word does not start with a hyphen, one otherwise.
192 gsu_complete_options()
194 local opts="$1" cword="$2" cur opt
199 cur="${words[$cword]}"
201 [[ ! "$cur" == -* ]] && return
203 for ((i=0; i < ${#opts}; i++)); do
205 [[ "$opt" == ":" ]] && continue
211 com_prefs_options='e'
214 Print the current preferences.
218 If -e is given, the config file is opened with the default editor.
219 Without options, the command prints out a list of all config variables,
220 together with their current value and the default value.
225 local i conf="${gsu_config_file:=${HOME:-}/.$gsu_name.rc}"
227 gsu_getopts "$com_prefs_options"
229 (($ret < 0)) && return
230 gsu_check_arg_count $# 0 0
231 (($ret < 0)) && return
233 if [[ "$o_e" == "true" ]]; then
237 (($? != 0)) && return
239 result="${EDITOR:-vi}"
241 (($? != 0)) && return
246 for ((i=0; i < ${#gsu_options[@]}; i++)); do
247 local name= option_type= default_value= required=
248 local description= help_text=
249 eval "${gsu_options[$i]}"
250 eval val='"${'${gsu_config_var_prefix}_$name:-'}"'
259 printf "%s: %s" "$option_type" "$description"
260 if [[ "$required" != "yes" && "$required" != "true" ]]; then
261 printf " [%s]" "$default_value"
264 [[ -n "$help_text" ]] && sed -e '/^[ ]*$/d; s/^[ ]*/# /g' <<< "$help_text"
265 printf "%s=%s" "$name" "$val"
266 [[ "$val" == "$default_value" ]] && printf " # default"
274 stty 0<&3 &> /dev/null
279 gsu_complete_options "$com_prefs_options" "$@"
282 _gsu_man_options='m:b:'
286 gsu_complete_options "$_gsu_man_options" "$@"
287 ((ret > 0)) && return
288 gsu_cword_is_option_parameter "$_gsu_man_options" "$@"
289 [[ "$result" == 'm' ]] && printf 'roff\ntext\nhtml\n'
295 Usage: man [-m <mode>] [-b <browser>]
297 -m: Set output format (text, roff or html). Default: text.
298 -b: Use the specified browser. Implies html mode.
300 If stdout is not associated with a terminal device, the command
301 dumps the man page to stdout and exits.
303 Otherwise, it tries to display the manual page as follows. In text
304 mode, plain text is piped to $PAGER. In roff mode, the roff output
305 is filtered through nroff, then piped to $PAGER. For both formats,
306 if $PAGER is unset, less(1) is assumed.
308 In html mode, html output is written to a temporary file, and this
309 file is displayed as a page in the web browser. If -b is not given,
310 the command stored in the $BROWSER environment variable is executed
311 with the path to the temporary file as an argument. If $BROWSER is
312 unset, elinks(1) is assumed.
314 It is recommended to specify the output format with -m as the default
315 mode might change in future versions of gsu.
329 _gsu_change_roffify_state()
333 local old_state="$statep"
335 [[ "$old_state" == "$new_state" ]] && return 0
339 example) printf '.EE\n';;
340 enum) printf '.RE\n';;
344 example) printf '.EX\n';;
345 enum) printf '.RS 2\n';;
352 _gsu_print_protected_roff_line()
357 while [[ "${line:$n:1}" == ' ' ]]; do
361 printf '\\&%s\n' "${line//\\/\\\\}"
364 _gsu_roffify_maindoc()
366 local state='text' TAB=' '
370 _gsu_read_line 'line' || return
371 while _gsu_read_line next_line; do
372 if [[ "$next_line" =~ ^(----|====|~~~~) ]]; then # heading
373 printf '.SS %s\n' "$line"
374 _gsu_read_line line || return
375 _gsu_change_roffify_state 'state' 'text'
378 if [[ "${line:0:1}" == "$TAB" ]]; then # example
379 _gsu_change_roffify_state 'state' 'example'
380 printf '%s\n' "$line"
385 while [[ "${line:$n:1}" == ' ' ]]; do
389 if [[ "${line:0:1}" == '*' ]]; then # enum
391 _gsu_change_roffify_state 'state' 'enum'
392 printf '\n\(bu %s\n' "$line"
396 if [[ "$line" =~ ^$ ]]; then # new paragraph
397 _gsu_change_roffify_state 'state' 'text'
400 _gsu_print_protected_roff_line "$line"
404 _gsu_print_protected_roff_line "$line"
407 _gsu_extract_maindoc()
409 sed -e '1,/^#\{70,\}/d' -e '/^#\{70,\}/,$d' -e 's/^# *//' -e 's/^#//g' "$0"
414 local line cmd= desc= state='text' TAB=' '
416 while _gsu_read_line line; do
417 if [[ "${line:0:1}" != '#' ]]; then # com_foo()
423 if [[ "$line" =~ ^[[:space:]]*$ ]]; then
425 _gsu_change_roffify_state 'state' 'text'
428 if [[ -n "$cmd" ]]; then # desc or usage
429 if [[ -z "$desc" ]]; then # desc
434 _gsu_change_roffify_state 'state' 'text'
435 printf '\n.SS %s \\- %s\n' "$cmd" "$desc"
436 printf '\n.I %s\n' "$line"
442 if [[ "${line:0:1}" == "$TAB" ]]; then
443 _gsu_change_roffify_state 'state' 'example'
444 _gsu_print_protected_roff_line "$line"
447 if [[ "$line" == -*:* ]]; then
448 _gsu_change_roffify_state 'state' 'enum'
449 printf '.PP\n.B %s:\n' "${line%%:*}"
450 _gsu_print_protected_roff_line "${line#*:}"
453 _gsu_print_protected_roff_line "$line"
457 _gsu_roffify_autocmd()
459 local cmd="$1" help_txt="$2"
462 printf 'com_%s()\n' "$cmd"
463 sed -e 's/^/## /g' <<< "$help_txt"
464 } | _gsu_roffify_cmds
469 local name="$1" sect_num="$2" month_year="$3" pkg="$4" sect_name="$5"
474 .TH "${name^^}" "$sect_num" "$month_year" "$pkg" "$sect_name"
479 \fI\,<subcommand>\/\fR [\fI\,<options>\/\fR] [\fI\,<arguments>\/\fR]
482 _gsu_extract_maindoc | _gsu_roffify_maindoc
484 printf '\n.SH "GENERIC SUBCOMMANDS"\n'
485 printf 'The following commands are automatically created by gsu\n'
486 _gsu_roffify_autocmd "help" "$_gsu_help_txt"
487 _gsu_roffify_autocmd "man" "$_gsu_man_txt"
488 _gsu_roffify_autocmd "prefs" "$_gsu_prefs_txt"
489 _gsu_roffify_autocmd "complete" "$_gsu_complete_txt"
491 printf '\n.SH "LIST OF SUBCOMMANDS"\n'
492 printf 'Each command has its own set of options as described below.\n'
494 _gsu_get_command_regex
496 # only consider lines in the comment of the function
497 sed -nEe '/'"$ere"'/,/^[^#]/p' "$0" | _gsu_roffify_cmds
503 result="$(find "$file" -printf '%TB %TY' 2>/dev/null)" # GNU
504 (($? == 0)) && [[ -n "$result" ]] && return
505 result="$(stat -f %Sm -t '%B %Y' "$file" 2>/dev/null)" # BSD
506 (($? == 0)) && [[ -n "$result" ]] && return
507 result="$(date '+%B %Y' 2>/dev/null)" # POSIX
508 (($? == 0)) && [[ -n "$result" ]] && return
509 result='[unknown date]'
514 local equal_signs="=================================================="
515 local minus_signs="--------------------------------------------------"
516 local filter='cat' pager='cat' browser=${BROWSER:-elinks} tmpfile=
517 local com num isatty pipeline
519 gsu_getopts "$_gsu_man_options"
521 ((ret < 0)) && return
522 if [[ -n "$o_b" ]]; then
525 elif [[ -z "$o_m" ]]; then
529 _gsu_isatty && isatty='true' || isatty='false'
530 if [[ "$o_m" == 'roff' ]]; then
531 if [[ "$isatty" == 'true' ]]; then
532 filter='nroff -Tutf8 -mandoc'
533 pager="${PAGER:-less}"
535 elif [[ "$o_m" == 'text' ]]; then
536 if [[ "$isatty" == 'true' ]]; then
537 pager="${PAGER:-less}"
539 elif [[ "$o_m" == 'html' ]]; then
540 filter='groff -T html -m man'
541 if [[ "$isatty" == 'true' ]]; then
542 gsu_make_tempfile "gsu_html_man.XXXXXX.html"
543 ((ret < 0)) && return || tmpfile="$result"
544 trap "rm -f $tmpfile" RETURN EXIT
547 [[ "$pager" == 'less' ]] && export LESS=${LESS-RI}
551 _gsu_roff_man "$gsu_name" '1' "$result" \
552 "${gsu_package-${gsu_name^^}(1)}" \
553 "User Commands" "${gsu_banner_txt}" \
555 if [[ -n "$tmpfile" ]]; then
563 result="filter: $filter"
566 if [[ -n "$tmpfile" ]]; then
569 "$browser" "$tmpfile" || return
582 echo "$gsu_name (_${gsu_banner_txt}_) manual"
583 echo "${equal_signs:0:${#gsu_name} + ${#gsu_banner_txt} + 16}"
588 echo "$gsu_name usage"
589 echo "${minus_signs:0:${#gsu_name} + 6}"
592 echo "Each command has its own set of options as described below."
596 echo "Available commands:"
598 _gsu_available_commands
599 for com in $result; do
601 (($num < 4)) && num=4
602 echo "${minus_signs:0:$num}"
604 echo "${minus_signs:0:$num}"
615 Usage: help [command]
617 Without arguments, print the list of available commands. Otherwise,
618 print the help text for the given command."
621 Command line completion.
623 Usage: complete [<cword> <word>...]
625 When executed without argument the command writes bash code to
626 stdout. This code is suitable to be evaled from .bashrc to enable
629 If at least one argument is given, all possible completions are
630 written to stdout. This can be used from the completion function of
638 _gsu_get_command_regex
642 gsu_short_msg "### $gsu_name -- $gsu_banner_txt ###"
645 printf "com_help()\n%s" "$_gsu_help_txt" | head -n 4; echo "--"
646 printf "com_man()\n%s" "$_gsu_man_txt" | head -n 4; echo "--"
647 printf "com_prefs()\n%s" "$_gsu_prefs_txt" | head -n 4; echo "--"
648 printf "com_complete()\n%s" "$_gsu_complete_txt" | head -n 4; echo "--"
649 grep -EA 2 "$ere" "$0"
650 } | grep -v -- '--' \
651 | sed -En "/$ere/"'!d
652 # remove everything but the command name
653 s/^com_(.*)\(\).*/\1/
655 # append tab after short commands (less than 8 chars)
656 s/^(.{1,7})$/\1'"$tab"'/g
658 # remove next line (should contain only ## anyway)
662 # append next line, removing leading ##
666 # replace newline by tab
669 # and print the sucker
672 echo "# Try $gsu_name help <command> for info on <command>."
676 if test "$1" = "help"; then
677 echo "$_gsu_help_txt"
681 if test "$1" = "man"; then
686 if test "$1" = "prefs"; then
687 echo "$_gsu_prefs_txt"
691 if test "$1" = "complete"; then
692 echo "$_gsu_complete_txt"
697 _gsu_get_command_regex "$1"
699 if ! grep -Eq "$ere" "$0"; then
700 _gsu_print_available_commands
702 ret=-$E_GSU_BAD_COMMAND
706 # only consider lines in the comment of the function
712 # if it did start with ##, jump to label p and print it
715 # otherwise, move on to next line
727 _gsu_available_commands
738 local cur="\${COMP_WORDS[\$COMP_CWORD]}";
741 candidates=(\$($0 complete "\$COMP_CWORD" "\${COMP_WORDS[@]}"));
742 if ((\$? == 0)); then
743 COMPREPLY=(\$(compgen -W "\${candidates[*]}" -- "\$cur"));
745 compopt -o filenames;
746 COMPREPLY=(\$(compgen -fd -- "\$cur"));
754 gsu_is_a_number "$cword"
755 (($ret < 0)) && return
756 if (($cword <= 1)); then
757 _gsu_available_commands
765 # if no completer is defined for this subcommand we exit unsuccessfully
766 # to let the generic completer above fall back to file name completion.
767 [[ "$(type -t "complete_$cmd")" != "function" ]] && exit 1
768 "complete_$cmd" "$cword" "${words[@]}"
769 # ignore errors, they would only clutter the completion output
773 # Find out if the current word is a parameter for an option.
775 # $1: usual getopts option string.
776 # $2: The current word number.
777 # $3..: All words of the current command line.
779 # return: If yes, $result contains the letter of the option for which the
780 # current word is a parameter. Otherwise, $result is empty.
782 gsu_cword_is_option_parameter()
784 local opts="$1" cword="$2"
789 (($cword == 0)) && return
790 ((${#opts} < 2)) && return
794 prev="${words[$(($cword - 1))]}"
795 [[ ! "$prev" == -* ]] && return
798 for ((i=0; i <= $n; i++)); do
800 [[ "${opts:$(($i + 1)):1}" != ":" ]] && continue
802 [[ ! "$prev" =~ ^-.*$opt$ ]] && continue
809 # Get the word number on which the cursor is, not counting options.
811 # This is useful for completing commands whose possible completions depend
812 # on the word number, for example mount.
814 # $1: Getopt option string.
815 # $2: The current word number.
816 # $3..: All words of the current command line.
818 # return: If the current word is an option, or a parameter to an option,
819 # this function sets $result to -1. Otherwise, the number of the non-option
820 # is returned in $result.
822 gsu_get_unnamed_arg_num()
824 local opts="$1" cword="$2" prev cur
830 cur="${words[$cword]}"
831 prev="${words[$(($cword - 1))]}"
833 [[ "$cur" == -* ]] && return
834 [[ "$prev" == -* ]] && [[ "$opts" == *${prev#-}:* ]] && return
836 for ((i=1; i <= $cword; i++)); do
837 prev="${words[$(($i - 1))]}"
839 [[ "$cur" == -* ]] && continue
840 if [[ "$prev" == -* ]]; then
842 [[ "$opts" != *$opt:* ]] && let n++
850 # Entry point for all gsu-based scripts.
852 # The startup part of the application script should source this file to load
853 # the functions defined here, and then call gsu(). Functions starting with com_
854 # are automatically recognized as subcommands.
862 # gsu_dir=${gsu_dir:-/system/location/where/gsu/is/installed}
863 # . $gsu_dir/subcommand || exit 1
871 _gsu_print_available_commands
876 if [[ "$(type -t "com_$arg")" == 'function' ]]; then
878 if (("$ret" < 0)); then
884 ret=-$E_GSU_BAD_COMMAND
887 _gsu_print_available_commands