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()
173 _gsu_available_commands
175 printf 'Available commands:\n'
179 if ((count % 4)); then
181 ((${#cmd} < 8)) && printf '\t'
189 # Print all options of the given optstring to stdout if the word in the current
190 # command line begins with a hyphen character.
192 # Returns 0 if the current word does not start with a hyphen, one otherwise.
193 gsu_complete_options()
195 local opts="$1" cword="$2" cur opt
200 cur="${words[$cword]}"
202 [[ ! "$cur" == -* ]] && return
204 for ((i=0; i < ${#opts}; i++)); do
206 [[ "$opt" == ":" ]] && continue
212 com_prefs_options='e'
215 Print the current preferences.
219 If -e is given, the config file is opened with the default editor.
220 Without options, the command prints out a list of all config variables,
221 together with their current value and the default value.
226 local i conf="${gsu_config_file:=${HOME:-}/.$gsu_name.rc}"
228 gsu_getopts "$com_prefs_options"
230 ((ret < 0)) && return
231 gsu_check_arg_count $# 0 0
232 ((ret < 0)) && return
234 if [[ "$o_e" == "true" ]]; then
238 (($? != 0)) && return
240 result="${EDITOR:-vi}"
242 (($? != 0)) && return
247 for ((i=0; i < ${#gsu_options[@]}; i++)); do
248 local name= option_type= default_value= required=
249 local description= help_text=
250 eval "${gsu_options[$i]}"
251 eval val='"${'${gsu_config_var_prefix}_$name:-'}"'
260 printf " %s: %s" "$option_type" "$description"
261 if [[ "$required" != "yes" && "$required" != "true" ]]; then
262 printf " [%s]" "$default_value"
265 [[ -n "$help_text" ]] && sed -e '/^[ ]*$/d; s/^[ ]*/# /g' <<< "$help_text"
266 printf "%s=%s" "$name" "$val"
267 [[ "$val" == "$default_value" ]] && printf " # default"
275 stty 0<&3 &> /dev/null
280 gsu_complete_options "$com_prefs_options" "$@"
283 _gsu_man_options='m:b:'
287 gsu_complete_options "$_gsu_man_options" "$@"
288 ((ret > 0)) && return
289 gsu_cword_is_option_parameter "$_gsu_man_options" "$@"
290 [[ "$result" == 'm' ]] && printf 'roff\ntext\nhtml\n'
296 Usage: man [-m <mode>] [-b <browser>]
298 -m: Set output format (text, roff or html). Default: roff.
299 -b: Use the specified browser. Implies html mode.
301 If stdout is not associated with a terminal device, the command
302 dumps the man page to stdout and exits.
304 Otherwise, it tries to display the manual page as follows. In text
305 mode, plain text is piped to $PAGER. In roff mode, the roff output
306 is filtered through nroff, then piped to $PAGER. For both formats,
307 if $PAGER is unset, less(1) is assumed.
309 In html mode, html output is written to a temporary file, and this
310 file is displayed as a page in the web browser. If -b is not given,
311 the command stored in the $BROWSER environment variable is executed
312 with the path to the temporary file as an argument. If $BROWSER is
313 unset, elinks(1) is assumed.
327 _gsu_change_roffify_state()
331 local old_state="$statep"
333 [[ "$old_state" == "$new_state" ]] && return 0
337 example) printf '.EE\n';;
338 enum) printf '.RE\n';;
342 example) printf '.EX\n';;
343 enum) printf '.RS 2\n';;
350 _gsu_print_protected_roff_line()
355 while [[ "${line:$n:1}" == ' ' ]]; do
359 printf '\\&%s\n' "${line//\\/\\\\}"
362 _gsu_roffify_maindoc()
364 local state='text' TAB=' '
368 _gsu_read_line 'line' || return
369 while _gsu_read_line next_line; do
370 if [[ "$next_line" =~ ^(----|====|~~~~) ]]; then # heading
371 printf '.SS %s\n' "$line"
372 _gsu_read_line line || return
373 _gsu_change_roffify_state 'state' 'text'
376 if [[ "${line:0:1}" == "$TAB" ]]; then # example
377 _gsu_change_roffify_state 'state' 'example'
378 printf '%s\n' "$line"
383 while [[ "${line:$n:1}" == ' ' ]]; do
387 if [[ "${line:0:1}" == '*' ]]; then # enum
389 _gsu_change_roffify_state 'state' 'enum'
390 printf '\n\(bu %s\n' "$line"
394 if [[ "$line" =~ ^$ ]]; then # new paragraph
395 _gsu_change_roffify_state 'state' 'text'
398 _gsu_print_protected_roff_line "$line"
402 _gsu_print_protected_roff_line "$line"
405 _gsu_extract_maindoc()
407 sed -e '1,/^#\{70,\}/d' -e '/^#\{70,\}/,$d' -e 's/^# *//' -e 's/^#//g' "$0"
412 local line cmd= desc= state='text' TAB=' '
414 while _gsu_read_line line; do
415 if [[ "${line:0:1}" != '#' ]]; then # com_foo()
421 if [[ "$line" =~ ^[[:space:]]*$ ]]; then
423 _gsu_change_roffify_state 'state' 'text'
426 if [[ -n "$cmd" ]]; then # desc or usage
427 if [[ -z "$desc" ]]; then # desc
432 _gsu_change_roffify_state 'state' 'text'
433 printf '\n.SS %s \\- %s\n' "$cmd" "$desc"
434 printf '\n.I %s\n' "$line"
440 if [[ "${line:0:1}" == "$TAB" ]]; then
441 _gsu_change_roffify_state 'state' 'example'
442 _gsu_print_protected_roff_line "$line"
445 if [[ "$line" == -*:* ]]; then
446 _gsu_change_roffify_state 'state' 'enum'
447 printf '.PP\n.B %s:\n' "${line%%:*}"
448 _gsu_print_protected_roff_line "${line#*:}"
451 _gsu_print_protected_roff_line "$line"
455 _gsu_roffify_autocmd()
457 local cmd="$1" help_txt="$2"
460 printf 'com_%s()\n' "$cmd"
461 sed -e 's/^/## /g' <<< "$help_txt"
462 } | _gsu_roffify_cmds
467 local name="$1" sect_num="$2" month_year="$3" pkg="$4" sect_name="$5"
472 .TH "${name^^}" "$sect_num" "$month_year" "$pkg" "$sect_name"
477 \fI\,<subcommand>\/\fR [\fI\,<options>\/\fR] [\fI\,<arguments>\/\fR]
480 _gsu_extract_maindoc | _gsu_roffify_maindoc
482 printf '\n.SH "GENERIC SUBCOMMANDS"\n'
483 printf 'The following commands are automatically created by gsu\n'
484 _gsu_roffify_autocmd "help" "$_gsu_help_txt"
485 _gsu_roffify_autocmd "man" "$_gsu_man_txt"
486 _gsu_roffify_autocmd "prefs" "$_gsu_prefs_txt"
487 _gsu_roffify_autocmd "complete" "$_gsu_complete_txt"
489 printf '\n.SH "LIST OF SUBCOMMANDS"\n'
490 printf 'Each command has its own set of options as described below.\n'
492 _gsu_get_command_regex
494 # only consider lines in the comment of the function
495 sed -nEe '/'"$ere"'/,/^[^#]/p' "$0" | _gsu_roffify_cmds
501 result="$(find "$file" -printf '%TB %TY' 2>/dev/null)" # GNU
502 (($? == 0)) && [[ -n "$result" ]] && return
503 result="$(stat -f %Sm -t '%B %Y' "$file" 2>/dev/null)" # BSD
504 (($? == 0)) && [[ -n "$result" ]] && return
505 result="$(date '+%B %Y' 2>/dev/null)" # POSIX
506 (($? == 0)) && [[ -n "$result" ]] && return
507 result='[unknown date]'
512 local equal_signs="=================================================="
513 local minus_signs="--------------------------------------------------"
514 local filter='cat' pager='cat' browser=${BROWSER:-elinks} tmpfile=
515 local com num isatty pipeline
517 gsu_getopts "$_gsu_man_options"
519 ((ret < 0)) && return
520 if [[ -n "$o_b" ]]; then
523 elif [[ -z "$o_m" ]]; then
527 _gsu_isatty && isatty='true' || isatty='false'
528 if [[ "$o_m" == 'roff' ]]; then
529 if [[ "$isatty" == 'true' ]]; then
530 filter='nroff -Tutf8 -mandoc'
531 pager="${PAGER:-less}"
533 elif [[ "$o_m" == 'text' ]]; then
534 if [[ "$isatty" == 'true' ]]; then
535 pager="${PAGER:-less}"
537 elif [[ "$o_m" == 'html' ]]; then
538 filter='groff -T html -m man'
539 if [[ "$isatty" == 'true' ]]; then
540 gsu_make_tempfile "gsu_html_man.XXXXXX.html"
541 ((ret < 0)) && return || tmpfile="$result"
542 trap "rm -f $tmpfile" RETURN EXIT
545 [[ "$pager" == 'less' ]] && export LESS=${LESS-RI}
549 _gsu_roff_man "$gsu_name" '1' "$result" \
550 "${gsu_package-${gsu_name^^}(1)}" \
551 "User Commands" "${gsu_banner_txt}" \
553 if [[ -n "$tmpfile" ]]; then
561 result="filter: $filter"
564 if [[ -n "$tmpfile" ]]; then
567 "$browser" "$tmpfile" || return
580 echo "$gsu_name (_${gsu_banner_txt}_) manual"
581 echo "${equal_signs:0:${#gsu_name} + ${#gsu_banner_txt} + 16}"
586 echo "$gsu_name usage"
587 echo "${minus_signs:0:${#gsu_name} + 6}"
590 echo "Each command has its own set of options as described below."
594 echo "Available commands:"
596 _gsu_available_commands
597 for com in $result; do
600 echo "${minus_signs:0:$num}"
602 echo "${minus_signs:0:$num}"
613 Usage: help [command]
615 Without arguments, print the list of available commands. Otherwise,
616 print the help text for the given command."
619 Command line completion.
621 Usage: complete [<cword> <word>...]
623 When executed without argument the command writes bash code to
624 stdout. This code is suitable to be evaled from .bashrc to enable
627 If at least one argument is given, all possible completions are
628 written to stdout. This can be used from the completion function of
636 _gsu_get_command_regex
640 gsu_short_msg "### $gsu_name -- $gsu_banner_txt ###"
643 printf "com_help()\n%s" "$_gsu_help_txt" | head -n 4; echo "--"
644 printf "com_man()\n%s" "$_gsu_man_txt" | head -n 4; echo "--"
645 printf "com_prefs()\n%s" "$_gsu_prefs_txt" | head -n 4; echo "--"
646 printf "com_complete()\n%s" "$_gsu_complete_txt" | head -n 4; echo "--"
647 grep -EA 2 "$ere" "$0"
648 } | grep -v -- '--' \
649 | sed -En "/$ere/"'!d
650 # remove everything but the command name
651 s/^com_(.*)\(\).*/\1/
653 # append tab after short commands (less than 8 chars)
654 s/^(.{1,7})$/\1'"$tab"'/g
656 # remove next line (should contain only ## anyway)
660 # append next line, removing leading ##
664 # replace newline by tab
667 # and print the sucker
670 echo "# Try $gsu_name help <command> for info on <command>."
674 if test "$1" = "help"; then
675 echo "$_gsu_help_txt"
679 if test "$1" = "man"; then
684 if test "$1" = "prefs"; then
685 echo "$_gsu_prefs_txt"
689 if test "$1" = "complete"; then
690 echo "$_gsu_complete_txt"
695 _gsu_get_command_regex "$1"
697 if ! grep -Eq "$ere" "$0"; then
698 _gsu_print_available_commands
700 ret=-$E_GSU_BAD_COMMAND
704 # only consider lines in the comment of the function
710 # if it did start with ##, jump to label p and print it
713 # otherwise, move on to next line
725 _gsu_available_commands
736 local cur="\${COMP_WORDS[\$COMP_CWORD]}";
739 candidates=(\$($0 complete "\$COMP_CWORD" "\${COMP_WORDS[@]}"));
740 if ((\$? == 0)); then
741 COMPREPLY=(\$(compgen -W "\${candidates[*]}" -- "\$cur"));
743 compopt -o filenames;
744 COMPREPLY=(\$(compgen -fd -- "\$cur"));
752 gsu_is_a_number "$cword"
753 ((ret < 0)) && return
754 if ((cword <= 1)); then
755 _gsu_available_commands
763 # if no completer is defined for this subcommand we exit unsuccessfully
764 # to let the generic completer above fall back to file name completion.
765 [[ "$(type -t "complete_$cmd")" != "function" ]] && exit 1
766 "complete_$cmd" "$cword" "${words[@]}"
767 # ignore errors, they would only clutter the completion output
771 # Find out if the current word is a parameter for an option.
773 # $1: usual getopts option string.
774 # $2: The current word number.
775 # $3..: All words of the current command line.
777 # return: If yes, $result contains the letter of the option for which the
778 # current word is a parameter. Otherwise, $result is empty.
780 gsu_cword_is_option_parameter()
782 local opts="$1" cword="$2"
787 ((cword == 0)) && return
788 ((${#opts} < 2)) && return
792 prev="${words[$((cword - 1))]}"
793 [[ ! "$prev" == -* ]] && return
796 for ((i=0; i <= $n; i++)); do
798 [[ "${opts:$((i + 1)):1}" != ":" ]] && continue
800 [[ ! "$prev" =~ ^-.*$opt$ ]] && continue
807 # Get the word number on which the cursor is, not counting options.
809 # This is useful for completing commands whose possible completions depend
810 # on the word number, for example mount.
812 # $1: Getopt option string.
813 # $2: The current word number.
814 # $3..: All words of the current command line.
816 # return: If the current word is an option, or a parameter to an option,
817 # this function sets $result to -1. Otherwise, the number of the non-option
818 # is returned in $result.
820 gsu_get_unnamed_arg_num()
822 local opts="$1" cword="$2" prev cur
828 cur="${words[$cword]}"
829 prev="${words[$((cword - 1))]}"
831 [[ "$cur" == -* ]] && return
832 [[ "$prev" == -* ]] && [[ "$opts" == *${prev#-}:* ]] && return
834 for ((i=1; i <= $cword; i++)); do
835 prev="${words[$((i - 1))]}"
837 [[ "$cur" == -* ]] && continue
838 if [[ "$prev" == -* ]]; then
840 [[ "$opts" != *$opt:* ]] && let n++
848 # Entry point for all gsu-based scripts.
850 # The startup part of the application script should source this file to load
851 # the functions defined here, and then call gsu(). Functions starting with com_
852 # are automatically recognized as subcommands.
860 # gsu_dir=${gsu_dir:-/system/location/where/gsu/is/installed}
861 # . $gsu_dir/subcommand || exit 1
869 _gsu_print_available_commands
874 if [[ "$(type -t "com_$arg")" == 'function' ]]; then
882 ret=-$E_GSU_BAD_COMMAND
885 _gsu_print_available_commands 1>&2