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"
100 local c c1 c2 tab=' ' cr='
103 gsu_check_arg_count $# 1 1
110 result="invalid optstring $1"
111 if [[ -z "$1" ]] || grep -q '::' <<< "$1" ; then
116 for ((i=0; i < ${#1}; i++)); do
122 result="invalid character $c in optstring"
127 result="local _gsu_getopts_opt"
128 for ((i=0; i < ${#1}; i++)); do
132 if [[ "$c2" = ":" ]]; then
140 while getopts $1 _gsu_getopts_opt \"\$@\"; do
141 case \"\$_gsu_getopts_opt\" in
143 for ((i=0; i < ${#1}; i++)); do
146 result+="$tab$tab$c1) o_$c1="
147 if [[ "$c2" = ":" ]]; then
148 result+="\"\$OPTARG\""
158 result=\"invalid option given\"
163 shift \$((\$OPTIND - 1))
168 _gsu_print_available_commands()
171 local -i maxlen=0 cols width=80 count=0
173 result=$(stty size 2>/dev/null)
175 gsu_is_a_number "${result#* }"
176 ((ret >= 0)) && ((result > 0)) && width=$result
178 _gsu_available_commands
181 ((${#cmd} > maxlen)) && maxlen=${#cmd}
184 ((width < maxlen)) && cols=1 || cols=$((width / maxlen))
185 printf 'Available commands:'
187 ((count % cols == 0)) && printf '\n'
188 printf '%-*s' $maxlen $cmd
194 # Print all options of the given optstring to stdout if the word in the current
195 # command line begins with a hyphen character.
197 # Returns 0 if the current word does not start with a hyphen, one otherwise.
198 gsu_complete_options()
200 local opts="$1" cword="$2" cur opt
205 cur="${words[$cword]}"
207 [[ ! "$cur" == -* ]] && return
209 for ((i=0; i < ${#opts}; i++)); do
211 [[ "$opt" == ":" ]] && continue
217 declare -A _gsu_help_text=() # indexed by autocmd
218 com_prefs_options='e'
220 _gsu_help_text['prefs']='
221 Print the current preferences.
225 If -e is given, the config file is opened with the default editor.
226 Without options, the command prints out a list of all config variables,
227 together with their current value and the default value.
232 local i conf="${gsu_config_file:=${HOME:-}/.$gsu_name.rc}"
234 gsu_getopts "$com_prefs_options"
236 ((ret < 0)) && return
237 gsu_check_arg_count $# 0 0
238 ((ret < 0)) && return
240 if [[ "$o_e" == "true" ]]; then
244 (($? != 0)) && return
246 result="${EDITOR:-vi}"
248 (($? != 0)) && return
253 for ((i=0; i < ${#gsu_options[@]}; i++)); do
254 local name= option_type= default_value= required=
255 local description= help_text=
256 eval "${gsu_options[$i]}"
257 eval val='"${'${gsu_config_var_prefix}_$name:-'}"'
266 printf " %s: %s" "$option_type" "$description"
267 if [[ "$required" != "yes" && "$required" != "true" ]]; then
268 printf " [%s]" "$default_value"
271 [[ -n "$help_text" ]] && sed -e '/^[ ]*$/d; s/^[ ]*/# /g' <<< "$help_text"
272 printf "%s=%s" "$name" "$val"
273 [[ "$val" == "$default_value" ]] && printf " # default"
281 stty 0<&3 &> /dev/null
286 gsu_complete_options "$com_prefs_options" "$@"
289 _gsu_man_options='m:b:'
293 gsu_complete_options "$_gsu_man_options" "$@"
294 ((ret > 0)) && return
295 gsu_cword_is_option_parameter "$_gsu_man_options" "$@"
296 [[ "$result" == 'm' ]] && printf 'roff\ntext\nhtml\n'
299 _gsu_help_text['man']='
302 Usage: man [-m <mode>] [-b <browser>]
304 -m: Set output format (text, roff or html). Default: roff.
305 -b: Use the specified browser. Implies html mode.
307 If stdout is not associated with a terminal device, the command
308 dumps the man page to stdout and exits.
310 Otherwise, it tries to display the manual page as follows. In text
311 mode, plain text is piped to $PAGER. In roff mode, the roff output
312 is filtered through nroff, then piped to $PAGER. For both formats,
313 if $PAGER is unset, less(1) is assumed.
315 In html mode, html output is written to a temporary file, and this
316 file is displayed as a page in the web browser. If -b is not given,
317 the command stored in the $BROWSER environment variable is executed
318 with the path to the temporary file as an argument. If $BROWSER is
319 unset, elinks(1) is assumed.
333 _gsu_change_roffify_state()
337 local old_state="$statep"
339 [[ "$old_state" == "$new_state" ]] && return 0
343 example) printf '.EE\n';;
344 enum) printf '.RE\n';;
348 example) printf '.EX\n';;
349 enum) printf '.RS 2\n';;
356 _gsu_print_protected_roff_line()
361 while [[ "${line:$n:1}" == ' ' ]]; do
365 printf '\\&%s\n' "${line//\\/\\\\}"
368 _gsu_roffify_maindoc()
370 local state='text' TAB=' '
374 _gsu_read_line 'line' || return
375 while _gsu_read_line next_line; do
376 if [[ "$next_line" =~ ^(----|====|~~~~) ]]; then # heading
377 printf '.SS %s\n' "$line"
378 _gsu_read_line line || return
379 _gsu_change_roffify_state 'state' 'text'
382 if [[ "${line:0:1}" == "$TAB" ]]; then # example
383 _gsu_change_roffify_state 'state' 'example'
384 _gsu_print_protected_roff_line "$line"
389 while [[ "${line:$n:1}" == ' ' ]]; do
393 if [[ "${line:0:1}" == '*' ]]; then # enum
395 _gsu_change_roffify_state 'state' 'enum'
396 printf '\n\(bu %s\n' "$line"
400 if [[ "$line" =~ ^$ ]]; then # new paragraph
401 _gsu_change_roffify_state 'state' 'text'
404 _gsu_print_protected_roff_line "$line"
408 _gsu_print_protected_roff_line "$line"
411 _gsu_extract_maindoc()
413 sed -e '1,/^#\{70,\}/d' -e '/^#\{70,\}/,$d' -e 's/^# *//' -e 's/^#//g' "$0"
418 local line cmd= desc= state='text' TAB=' '
420 while _gsu_read_line line; do
421 if [[ "${line:0:1}" != '#' ]]; then # com_foo()
427 if [[ "$line" =~ ^[[:space:]]*$ ]]; then
429 _gsu_change_roffify_state 'state' 'text'
432 if [[ -n "$cmd" ]]; then # desc or usage
433 if [[ -z "$desc" ]]; then # desc
438 _gsu_change_roffify_state 'state' 'text'
439 printf '\n.SS %s \\- %s\n' "$cmd" "$desc"
440 printf '\n.I %s\n' "$line"
446 if [[ "${line:0:1}" == "$TAB" ]]; then
447 _gsu_change_roffify_state 'state' 'example'
448 _gsu_print_protected_roff_line "$line"
451 if [[ "$line" == -*:* ]]; then
452 _gsu_change_roffify_state 'state' 'enum'
453 printf '.PP\n.B %s:\n' "${line%%:*}"
454 _gsu_print_protected_roff_line "${line#*:}"
457 _gsu_print_protected_roff_line "$line"
461 _gsu_roffify_autocmds()
465 for cmd in "${!_gsu_help_text[@]}"; do
466 help_txt="${_gsu_help_text["$cmd"]}"
468 printf 'com_%s()\n' "$cmd"
469 sed -e 's/^/## /g' <<< "$help_txt"
470 } | _gsu_roffify_cmds
476 local name="$1" sect_num="$2" month_year="$3" pkg="$4" sect_name="$5"
481 .TH "${name^^}" "$sect_num" "$month_year" "$pkg" "$sect_name"
486 \fI\,<subcommand>\/\fR [\fI\,<options>\/\fR] [\fI\,<arguments>\/\fR]
489 _gsu_extract_maindoc | _gsu_roffify_maindoc
491 printf '\n.SH "GENERIC SUBCOMMANDS"\n'
492 printf 'The following commands are automatically created by gsu\n'
493 _gsu_roffify_autocmds
495 printf '\n.SH "LIST OF SUBCOMMANDS"\n'
496 printf 'Each command has its own set of options as described below.\n'
498 _gsu_get_command_regex
500 # only consider lines in the comment of the function
501 sed -nEe '/'"$ere"'/,/^[^#]/p' "$0" | _gsu_roffify_cmds
507 result="$(find "$file" -printf '%TB %TY' 2>/dev/null)" # GNU
508 (($? == 0)) && [[ -n "$result" ]] && return
509 result="$(stat -f %Sm -t '%B %Y' "$file" 2>/dev/null)" # BSD
510 (($? == 0)) && [[ -n "$result" ]] && return
511 result="$(date '+%B %Y' 2>/dev/null)" # POSIX
512 (($? == 0)) && [[ -n "$result" ]] && return
513 result='[unknown date]'
518 local equal_signs="=================================================="
519 local minus_signs="--------------------------------------------------"
520 local filter='cat' pager='cat' browser=${BROWSER:-elinks} tmpfile=
521 local com num isatty pipeline
523 gsu_getopts "$_gsu_man_options"
525 ((ret < 0)) && return
526 if [[ -n "$o_b" ]]; then
529 elif [[ -z "$o_m" ]]; then
533 _gsu_isatty && isatty='true' || isatty='false'
534 if [[ "$o_m" == 'roff' ]]; then
535 if [[ "$isatty" == 'true' ]]; then
536 filter='nroff -Tutf8 -mandoc'
537 pager="${PAGER:-less}"
539 elif [[ "$o_m" == 'text' ]]; then
540 if [[ "$isatty" == 'true' ]]; then
541 pager="${PAGER:-less}"
543 elif [[ "$o_m" == 'html' ]]; then
544 filter='groff -T html -m man'
545 if [[ "$isatty" == 'true' ]]; then
546 gsu_make_tempfile "gsu_html_man.XXXXXX.html"
547 ((ret < 0)) && return || tmpfile="$result"
548 trap "rm -f $tmpfile" RETURN EXIT
551 [[ "$pager" == 'less' ]] && export LESS=${LESS-RI}
555 _gsu_roff_man "$gsu_name" '1' "$result" \
556 "${gsu_package-${gsu_name^^}(1)}" \
557 "User Commands" "${gsu_banner_txt}" \
559 if [[ -n "$tmpfile" ]]; then
567 result="filter: $filter"
570 if [[ -n "$tmpfile" ]]; then
573 "$browser" "$tmpfile" || return
586 echo "$gsu_name (_${gsu_banner_txt}_) manual"
587 echo "${equal_signs:0:${#gsu_name} + ${#gsu_banner_txt} + 16}"
592 echo "$gsu_name usage"
593 echo "${minus_signs:0:${#gsu_name} + 6}"
596 echo "Each command has its own set of options as described below."
600 echo "Available commands:"
602 _gsu_available_commands
603 for com in $result; do
606 echo "${minus_signs:0:$num}"
608 echo "${minus_signs:0:$num}"
616 _gsu_help_text['help']='
619 Usage: help [-a] [command]
621 Without arguments, print the list of available commands. Otherwise,
622 print the help text for the given command.
624 -a: Also show the help of automatic commands. Ignored if a command
627 _gsu_help_text['complete']='
628 Command line completion.
630 Usage: complete [<cword> <word>...]
632 When executed without argument the command writes bash code to
633 stdout. This code is suitable to be evaled from .bashrc to enable
636 If at least one argument is given, all possible completions are
637 written to stdout. This can be used from the completion function of
644 local ere tab=' ' txt
646 gsu_getopts "$com_help_options"
648 ((ret < 0)) && return
650 _gsu_get_command_regex
654 gsu_short_msg "### $gsu_name -- $gsu_banner_txt ###"
657 if [[ "$o_a" == 'true' ]]; then
658 _gsu_mfcb() { printf '%s\n' "$2"; }
659 for cmd in "${!_gsu_help_text[@]}"; do
660 printf "com_%s()" "$cmd"
661 txt="${_gsu_help_text["$cmd"]}"
662 mapfile -n 3 -c 1 -C _gsu_mfcb <<< "$txt"
666 grep -EA 2 "$ere" "$0"
667 } | grep -v -- '--' \
668 | sed -En "/$ere/"'!d
669 # remove everything but the command name
670 s/^com_(.*)\(\).*/\1/
672 # append tab after short commands (less than 8 chars)
673 s/^(.{1,7})$/\1'"$tab"'/g
675 # remove next line (should contain only ## anyway)
679 # append next line, removing leading ##
683 # replace newline by tab
686 # and print the sucker
688 printf "\n# Try %s help <command> for info on <command>, or %s help -a to see\n" \
689 "$gsu_name" "$gsu_name"
690 printf '# also the subcommands which are automatically generated by gsu.\n'
694 for cmd in "${!_gsu_help_text[@]}"; do
695 [[ "$1" != "$cmd" ]] && continue
696 printf '%s\n' "${_gsu_help_text["$cmd"]}"
700 _gsu_get_command_regex "$1"
702 if ! grep -Eq "$ere" "$0"; then
703 _gsu_print_available_commands
705 ret=-$E_GSU_BAD_COMMAND
709 # only consider lines in the comment of the function
715 # if it did start with ##, jump to label p and print it
718 # otherwise, move on to next line
731 _gsu_available_commands
742 local cur="\${COMP_WORDS[\$COMP_CWORD]}";
745 candidates=(\$($0 complete "\$COMP_CWORD" "\${COMP_WORDS[@]}"));
746 if ((\$? == 0)); then
747 COMPREPLY=(\$(compgen -W "\${candidates[*]}" -- "\$cur"));
749 compopt -o filenames;
750 COMPREPLY=(\$(compgen -fd -- "\$cur"));
758 gsu_is_a_number "$cword"
759 ((ret < 0)) && return
760 if ((cword <= 1)); then
761 _gsu_available_commands
769 # if no completer is defined for this subcommand we exit unsuccessfully
770 # to let the generic completer above fall back to file name completion.
771 [[ "$(type -t "complete_$cmd")" != "function" ]] && exit 1
772 "complete_$cmd" "$cword" "${words[@]}"
773 # ignore errors, they would only clutter the completion output
777 # Find out if the current word is a parameter for an option.
779 # $1: usual getopts option string.
780 # $2: The current word number.
781 # $3..: All words of the current command line.
783 # return: If yes, $result contains the letter of the option for which the
784 # current word is a parameter. Otherwise, $result is empty.
786 gsu_cword_is_option_parameter()
788 local opts="$1" cword="$2"
793 ((cword == 0)) && return
794 ((${#opts} < 2)) && return
798 prev="${words[$((cword - 1))]}"
799 [[ ! "$prev" == -* ]] && return
802 for ((i=0; i <= $n; i++)); do
804 [[ "${opts:$((i + 1)):1}" != ":" ]] && continue
806 [[ ! "$prev" =~ ^-.*$opt$ ]] && continue
813 # Get the word number on which the cursor is, not counting options.
815 # This is useful for completing commands whose possible completions depend
816 # on the word number, for example mount.
818 # $1: Getopt option string.
819 # $2: The current word number.
820 # $3..: All words of the current command line.
822 # return: If the current word is an option, or a parameter to an option,
823 # this function sets $result to -1. Otherwise, the number of the non-option
824 # is returned in $result.
826 gsu_get_unnamed_arg_num()
828 local opts="$1" cword="$2" prev cur
834 cur="${words[$cword]}"
835 prev="${words[$((cword - 1))]}"
837 [[ "$cur" == -* ]] && return
838 [[ "$prev" == -* ]] && [[ "$opts" == *${prev#-}:* ]] && return
840 for ((i=1; i <= $cword; i++)); do
841 prev="${words[$((i - 1))]}"
843 [[ "$cur" == -* ]] && continue
844 if [[ "$prev" == -* ]]; then
846 [[ "$opts" != *$opt:* ]] && let n++
854 # Entry point for all gsu-based scripts.
856 # The startup part of the application script should source this file to load
857 # the functions defined here, and then call gsu(). Functions starting with com_
858 # are automatically recognized as subcommands.
866 # gsu_dir=${gsu_dir:-/system/location/where/gsu/is/installed}
867 # . $gsu_dir/subcommand || exit 1
875 _gsu_print_available_commands
880 if [[ "$(type -t "com_$arg")" == 'function' ]]; then
888 ret=-$E_GSU_BAD_COMMAND
891 _gsu_print_available_commands 1>&2