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
689 local -a cmds=() descs=()
692 while read cmd desc; do
693 ((maxlen < ${#cmd})) && maxlen=${#cmd}
694 cmds[${#cmds[@]}]=$cmd
695 descs[${#descs[@]}]=$desc
697 for ((i = 0; i < ${#cmds[@]}; i++)); do
698 printf '%-*s %s\n' $maxlen ${cmds[$i]} \
702 printf "\n# Try %s help <command> for info on <command>, or %s help -a to see\n" \
703 "$gsu_name" "$gsu_name"
704 printf '# also the subcommands which are automatically generated by gsu.\n'
708 for cmd in "${!_gsu_help_text[@]}"; do
709 [[ "$1" != "$cmd" ]] && continue
710 printf '%s\n' "${_gsu_help_text["$cmd"]}"
714 _gsu_get_command_regex "$1"
716 if ! grep -Eq "$ere" "$0"; then
717 _gsu_print_available_commands
719 ret=-$E_GSU_BAD_COMMAND
723 # only consider lines in the comment of the function
729 # if it did start with ##, jump to label p and print it
732 # otherwise, move on to next line
745 _gsu_available_commands
756 local cur="\${COMP_WORDS[\$COMP_CWORD]}";
759 candidates=(\$($0 complete "\$COMP_CWORD" "\${COMP_WORDS[@]}"));
760 if ((\$? == 0)); then
761 COMPREPLY=(\$(compgen -W "\${candidates[*]}" -- "\$cur"));
763 compopt -o filenames;
764 COMPREPLY=(\$(compgen -fd -- "\$cur"));
772 gsu_is_a_number "$cword"
773 ((ret < 0)) && return
774 if ((cword <= 1)); then
775 _gsu_available_commands
783 # if no completer is defined for this subcommand we exit unsuccessfully
784 # to let the generic completer above fall back to file name completion.
785 [[ "$(type -t "complete_$cmd")" != "function" ]] && exit 1
786 "complete_$cmd" "$cword" "${words[@]}"
787 # ignore errors, they would only clutter the completion output
791 # Find out if the current word is a parameter for an option.
793 # $1: usual getopts option string.
794 # $2: The current word number.
795 # $3..: All words of the current command line.
797 # return: If yes, $result contains the letter of the option for which the
798 # current word is a parameter. Otherwise, $result is empty.
800 gsu_cword_is_option_parameter()
802 local opts="$1" cword="$2"
807 ((cword == 0)) && return
808 ((${#opts} < 2)) && return
812 prev="${words[$((cword - 1))]}"
813 [[ ! "$prev" == -* ]] && return
816 for ((i=0; i <= $n; i++)); do
818 [[ "${opts:$((i + 1)):1}" != ":" ]] && continue
820 [[ ! "$prev" =~ ^-.*$opt$ ]] && continue
827 # Get the word number on which the cursor is, not counting options.
829 # This is useful for completing commands whose possible completions depend
830 # on the word number, for example mount.
832 # $1: Getopt option string.
833 # $2: The current word number.
834 # $3..: All words of the current command line.
836 # return: If the current word is an option, or a parameter to an option,
837 # this function sets $result to -1. Otherwise, the number of the non-option
838 # is returned in $result.
840 gsu_get_unnamed_arg_num()
842 local opts="$1" cword="$2" prev cur
848 cur="${words[$cword]}"
849 prev="${words[$((cword - 1))]}"
851 [[ "$cur" == -* ]] && return
852 [[ "$prev" == -* ]] && [[ "$opts" == *${prev#-}:* ]] && return
854 for ((i=1; i <= $cword; i++)); do
855 prev="${words[$((i - 1))]}"
857 [[ "$cur" == -* ]] && continue
858 if [[ "$prev" == -* ]]; then
860 [[ "$opts" != *$opt:* ]] && let n++
868 # Entry point for all gsu-based scripts.
870 # The startup part of the application script should source this file to load
871 # the functions defined here, and then call gsu(). Functions starting with com_
872 # are automatically recognized as subcommands.
880 # gsu_dir=${gsu_dir:-/system/location/where/gsu/is/installed}
881 # . $gsu_dir/subcommand || exit 1
889 _gsu_print_available_commands
894 if [[ "$(type -t "com_$arg")" == 'function' ]]; then
902 ret=-$E_GSU_BAD_COMMAND
905 _gsu_print_available_commands 1>&2