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.
190 gsu_complete_options()
192 local opts="$1" cword="$2" cur opt
197 cur="${words[$cword]}"
199 [[ ! "$cur" == -* ]] && return
202 for ((i=0; i < ${#opts}; i++)); do
204 [[ "$opt" == ":" ]] && continue
210 com_prefs_options='e'
213 Print the current preferences.
217 If -e is given, the config file is opened with the default editor.
218 Without options, the command prints out a list of all config variables,
219 together with their current value and the default value.
224 local i conf="${gsu_config_file:=${HOME:-}/.$gsu_name.rc}"
226 gsu_getopts "$com_prefs_options"
228 (($ret < 0)) && return
229 gsu_check_arg_count $# 0 0
230 (($ret < 0)) && return
232 if [[ "$o_e" == "true" ]]; then
236 (($? != 0)) && return
238 result="${EDITOR:-vi}"
240 (($? != 0)) && return
245 for ((i=0; i < ${#gsu_options[@]}; i++)); do
246 local name= option_type= default_value= required=
247 local description= help_text=
248 eval "${gsu_options[$i]}"
249 eval val='"${'${gsu_config_var_prefix}_$name:-'}"'
258 printf "%s: %s" "$option_type" "$description"
259 if [[ "$required" != "yes" && "$required" != "true" ]]; then
260 printf " [%s]" "$default_value"
263 [[ -n "$help_text" ]] && sed -e '/^[ ]*$/d; s/^[ ]*/# /g' <<< "$help_text"
264 printf "%s=%s" "$name" "$val"
265 [[ "$val" == "$default_value" ]] && printf " # default"
273 stty 0<&3 &> /dev/null
278 gsu_complete_options "$com_prefs_options" "$@"
281 _gsu_man_options='m:b:'
285 gsu_complete_options "$_gsu_man_options" "$@"
286 ((ret > 0)) && return
287 gsu_cword_is_option_parameter "$_gsu_man_options" "$@"
288 [[ "$result" == 'm' ]] && printf 'roff\ntext\nhtml\n'
294 Usage: man [-m <mode>] [-b <browser>]
296 -m: Set output format (text, roff or html). Default: text.
297 -b: Use the specified browser. Implies html mode.
299 If stdout is not associated with a terminal device, the command
300 dumps the man page to stdout and exits.
302 Otherwise, it tries to display the manual page as follows. In text
303 mode, plain text is piped to $PAGER. In roff mode, the roff output
304 is filtered through nroff, then piped to $PAGER. For both formats,
305 if $PAGER is unset, less(1) is assumed.
307 In html mode, html output is written to a temporary file, and this
308 file is displayed as a page in the web browser. If -b is not given,
309 the command stored in the $BROWSER environment variable is executed
310 with the path to the temporary file as an argument. If $BROWSER is
311 unset, elinks(1) is assumed.
313 It is recommended to specify the output format with -m as the default
314 mode might change in future versions of gsu.
328 _gsu_change_roffify_state()
332 local old_state="$statep"
334 [[ "$old_state" == "$new_state" ]] && return 0
338 example) printf '.EE\n';;
339 enum) printf '.RE\n';;
343 example) printf '.EX\n';;
344 enum) printf '.RS 2\n';;
351 _gsu_print_protected_roff_line()
356 while [[ "${line:$n:1}" == ' ' ]]; do
360 printf '\\&%s\n' "${line//\\/\\\\}"
363 _gsu_roffify_maindoc()
365 local state='text' TAB=' '
369 _gsu_read_line 'line' || return
370 while _gsu_read_line next_line; do
371 if [[ "$next_line" =~ ^(----|====|~~~~) ]]; then # heading
372 printf '.SS %s\n' "$line"
373 _gsu_read_line line || return
374 _gsu_change_roffify_state 'state' 'text'
377 if [[ "${line:0:1}" == "$TAB" ]]; then # example
378 _gsu_change_roffify_state 'state' 'example'
379 printf '%s\n' "$line"
384 while [[ "${line:$n:1}" == ' ' ]]; do
388 if [[ "${line:0:1}" == '*' ]]; then # enum
390 _gsu_change_roffify_state 'state' 'enum'
391 printf '\n\(bu %s\n' "$line"
395 if [[ "$line" =~ ^$ ]]; then # new paragraph
396 _gsu_change_roffify_state 'state' 'text'
399 _gsu_print_protected_roff_line "$line"
403 _gsu_print_protected_roff_line "$line"
406 _gsu_extract_maindoc()
408 sed -e '1,/^#\{70,\}/d' -e '/^#\{70,\}/,$d' -e 's/^# *//' -e 's/^#//g' "$0"
413 local line cmd= desc= state='text' TAB=' '
415 while _gsu_read_line line; do
416 if [[ "${line:0:1}" != '#' ]]; then # com_foo()
422 if [[ "$line" =~ ^[[:space:]]*$ ]]; then
424 _gsu_change_roffify_state 'state' 'text'
427 if [[ -n "$cmd" ]]; then # desc or usage
428 if [[ -z "$desc" ]]; then # desc
433 _gsu_change_roffify_state 'state' 'text'
434 printf '\n.SS %s \\- %s\n' "$cmd" "$desc"
435 printf '\n.I %s\n' "$line"
441 if [[ "${line:0:1}" == "$TAB" ]]; then
442 _gsu_change_roffify_state 'state' 'example'
443 _gsu_print_protected_roff_line "$line"
446 if [[ "$line" == -*:* ]]; then
447 _gsu_change_roffify_state 'state' 'enum'
448 printf '.PP\n.B %s:\n' "${line%%:*}"
449 _gsu_print_protected_roff_line "${line#*:}"
452 _gsu_print_protected_roff_line "$line"
456 _gsu_roffify_autocmd()
458 local cmd="$1" help_txt="$2"
461 printf 'com_%s()\n' "$cmd"
462 sed -e 's/^/## /g' <<< "$help_txt"
463 } | _gsu_roffify_cmds
468 local name="$1" sect_num="$2" month_year="$3" pkg="$4" sect_name="$5"
473 .TH "${name^^}" "$sect_num" "$month_year" "$pkg" "$sect_name"
478 \fI\,<subcommand>\/\fR [\fI\,<options>\/\fR] [\fI\,<arguments>\/\fR]
481 _gsu_extract_maindoc | _gsu_roffify_maindoc
483 printf '\n.SH "GENERIC SUBCOMMANDS"\n'
484 printf 'The following commands are automatically created by gsu\n'
485 _gsu_roffify_autocmd "help" "$_gsu_help_txt"
486 _gsu_roffify_autocmd "man" "$_gsu_man_txt"
487 _gsu_roffify_autocmd "prefs" "$_gsu_prefs_txt"
488 _gsu_roffify_autocmd "complete" "$_gsu_complete_txt"
490 printf '\n.SH "LIST OF SUBCOMMANDS"\n'
491 printf 'Each command has its own set of options as described below.\n'
493 _gsu_get_command_regex
495 # only consider lines in the comment of the function
496 sed -nEe '/'"$ere"'/,/^[^#]/p' "$0" | _gsu_roffify_cmds
502 result="$(find "$file" -printf '%TB %TY' 2>/dev/null)" # GNU
503 (($? == 0)) && [[ -n "$result" ]] && return
504 result="$(stat -f %Sm -t '%B %Y' "$file" 2>/dev/null)" # BSD
505 (($? == 0)) && [[ -n "$result" ]] && return
506 result="$(date '+%B %Y' 2>/dev/null)" # POSIX
507 (($? == 0)) && [[ -n "$result" ]] && return
508 result='[unknown date]'
513 local equal_signs="=================================================="
514 local minus_signs="--------------------------------------------------"
515 local filter='cat' pager='cat' browser=${BROWSER:-elinks} tmpfile=
516 local com num isatty pipeline
518 gsu_getopts "$_gsu_man_options"
520 ((ret < 0)) && return
521 if [[ -n "$o_b" ]]; then
524 elif [[ -z "$o_m" ]]; then
528 _gsu_isatty && isatty='true' || isatty='false'
529 if [[ "$o_m" == 'roff' ]]; then
530 if [[ "$isatty" == 'true' ]]; then
531 filter='nroff -Tutf8 -mandoc'
532 pager="${PAGER:-less}"
534 elif [[ "$o_m" == 'text' ]]; then
535 if [[ "$isatty" == 'true' ]]; then
536 pager="${PAGER:-less}"
538 elif [[ "$o_m" == 'html' ]]; then
539 filter='groff -T html -m man'
540 if [[ "$isatty" == 'true' ]]; then
541 gsu_make_tempfile "gsu_html_man.XXXXXX.html"
542 ((ret < 0)) && return || tmpfile="$result"
543 trap "rm -f $tmpfile" RETURN EXIT
546 [[ "$pager" == 'less' ]] && export LESS=${LESS-RI}
550 _gsu_roff_man "$gsu_name" '1' "$result" \
551 "${gsu_package-${gsu_name^^}(1)}" \
552 "User Commands" "${gsu_banner_txt}" \
554 if [[ -n "$tmpfile" ]]; then
562 result="filter: $filter"
565 if [[ -n "$tmpfile" ]]; then
568 "$browser" "$tmpfile" || return
581 echo "$gsu_name (_${gsu_banner_txt}_) manual"
582 echo "${equal_signs:0:${#gsu_name} + ${#gsu_banner_txt} + 16}"
587 echo "$gsu_name usage"
588 echo "${minus_signs:0:${#gsu_name} + 6}"
591 echo "Each command has its own set of options as described below."
595 echo "Available commands:"
597 _gsu_available_commands
598 for com in $result; do
600 (($num < 4)) && num=4
601 echo "${minus_signs:0:$num}"
603 echo "${minus_signs:0:$num}"
614 Usage: help [command]
616 Without arguments, print the list of available commands. Otherwise,
617 print the help text for the given command."
620 Command line completion.
622 Usage: complete [<cword> <word>...]
624 When executed without argument the command writes bash code to
625 stdout. This code is suitable to be evaled from .bashrc to enable
628 If at least one argument is given, all possible completions are
629 written to stdout. This can be used from the completion function of
637 _gsu_get_command_regex
641 gsu_short_msg "### $gsu_name -- $gsu_banner_txt ###"
644 printf "com_help()\n%s" "$_gsu_help_txt" | head -n 4; echo "--"
645 printf "com_man()\n%s" "$_gsu_man_txt" | head -n 4; echo "--"
646 printf "com_prefs()\n%s" "$_gsu_prefs_txt" | head -n 4; echo "--"
647 printf "com_complete()\n%s" "$_gsu_complete_txt" | head -n 4; echo "--"
648 grep -EA 2 "$ere" "$0"
649 } | grep -v -- '--' \
650 | sed -En "/$ere/"'!d
651 # remove everything but the command name
652 s/^com_(.*)\(\).*/\1/
654 # append tab after short commands (less than 8 chars)
655 s/^(.{1,7})$/\1'"$tab"'/g
657 # remove next line (should contain only ## anyway)
661 # append next line, removing leading ##
665 # replace newline by tab
668 # and print the sucker
671 echo "# Try $gsu_name help <command> for info on <command>."
675 if test "$1" = "help"; then
676 echo "$_gsu_help_txt"
680 if test "$1" = "man"; then
685 if test "$1" = "prefs"; then
686 echo "$_gsu_prefs_txt"
690 if test "$1" = "complete"; then
691 echo "$_gsu_complete_txt"
696 _gsu_get_command_regex "$1"
698 if ! grep -Eq "$ere" "$0"; then
699 _gsu_print_available_commands
701 ret=-$E_GSU_BAD_COMMAND
705 # only consider lines in the comment of the function
711 # if it did start with ##, jump to label p and print it
714 # otherwise, move on to next line
726 _gsu_available_commands
737 local cur="\${COMP_WORDS[\$COMP_CWORD]}";
740 candidates=(\$($0 complete "\$COMP_CWORD" "\${COMP_WORDS[@]}"));
741 COMPREPLY=(\$(compgen -W "\${candidates[*]}" -- "\$cur"));
748 gsu_is_a_number "$cword"
749 (($ret < 0)) && return
750 if (($cword <= 1)); then
751 _gsu_available_commands
759 ret=$GSU_SUCCESS # It's not an error if no completer was defined
760 [[ "$(type -t "complete_$cmd")" != "function" ]] && return
761 "complete_$cmd" "$cword" "${words[@]}"
762 # ignore errors, they would only clutter the completion output
766 # Find out if the current word is a parameter for an option.
768 # $1: usual getopts option string.
769 # $2: The current word number.
770 # $3..: All words of the current command line.
772 # return: If yes, $result contains the letter of the option for which the
773 # current word is a parameter. Otherwise, $result is empty.
775 gsu_cword_is_option_parameter()
777 local opts="$1" cword="$2"
782 (($cword == 0)) && return
783 ((${#opts} < 2)) && return
787 prev="${words[$(($cword - 1))]}"
788 [[ ! "$prev" == -* ]] && return
791 for ((i=0; i <= $n; i++)); do
793 [[ "${opts:$(($i + 1)):1}" != ":" ]] && continue
795 [[ ! "$prev" =~ ^-.*$opt$ ]] && continue
802 # Get the word number on which the cursor is, not counting options.
804 # This is useful for completing commands whose possible completions depend
805 # on the word number, for example mount.
807 # $1: Getopt option string.
808 # $2: The current word number.
809 # $3..: All words of the current command line.
811 # return: If the current word is an option, or a parameter to an option,
812 # this function sets $result to -1. Otherwise, the number of the non-option
813 # is returned in $result.
815 gsu_get_unnamed_arg_num()
817 local opts="$1" cword="$2" prev cur
823 cur="${words[$cword]}"
824 prev="${words[$(($cword - 1))]}"
826 [[ "$cur" == -* ]] && return
827 [[ "$prev" == -* ]] && [[ "$opts" == *${prev#-}:* ]] && return
829 for ((i=1; i <= $cword; i++)); do
830 prev="${words[$(($i - 1))]}"
832 [[ "$cur" == -* ]] && continue
833 if [[ "$prev" == -* ]]; then
835 [[ "$opts" != *$opt:* ]] && let n++
843 # Entry point for all gsu-based scripts.
845 # The startup part of the application script should source this file to load
846 # the functions defined here, and then call gsu(). Functions starting with com_
847 # are automatically recognized as subcommands.
855 # gsu_dir=${gsu_dir:-/system/location/where/gsu/is/installed}
856 # . $gsu_dir/subcommand || exit 1
864 _gsu_print_available_commands
869 if [[ "$(type -t "com_$arg")" == 'function' ]]; then
871 if (("$ret" < 0)); then
877 ret=-$E_GSU_BAD_COMMAND
880 _gsu_print_available_commands