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 declare -A _gsu_help_text=() # indexed by autocmd
213 com_prefs_options='e'
215 _gsu_help_text['prefs']='
216 Print the current preferences.
220 If -e is given, the config file is opened with the default editor.
221 Without options, the command prints out a list of all config variables,
222 together with their current value and the default value.
227 local i conf="${gsu_config_file:=${HOME:-}/.$gsu_name.rc}"
229 gsu_getopts "$com_prefs_options"
231 ((ret < 0)) && return
232 gsu_check_arg_count $# 0 0
233 ((ret < 0)) && return
235 if [[ "$o_e" == "true" ]]; then
239 (($? != 0)) && return
241 result="${EDITOR:-vi}"
243 (($? != 0)) && return
248 for ((i=0; i < ${#gsu_options[@]}; i++)); do
249 local name= option_type= default_value= required=
250 local description= help_text=
251 eval "${gsu_options[$i]}"
252 eval val='"${'${gsu_config_var_prefix}_$name:-'}"'
261 printf " %s: %s" "$option_type" "$description"
262 if [[ "$required" != "yes" && "$required" != "true" ]]; then
263 printf " [%s]" "$default_value"
266 [[ -n "$help_text" ]] && sed -e '/^[ ]*$/d; s/^[ ]*/# /g' <<< "$help_text"
267 printf "%s=%s" "$name" "$val"
268 [[ "$val" == "$default_value" ]] && printf " # default"
276 stty 0<&3 &> /dev/null
281 gsu_complete_options "$com_prefs_options" "$@"
284 _gsu_man_options='m:b:'
288 gsu_complete_options "$_gsu_man_options" "$@"
289 ((ret > 0)) && return
290 gsu_cword_is_option_parameter "$_gsu_man_options" "$@"
291 [[ "$result" == 'm' ]] && printf 'roff\ntext\nhtml\n'
294 _gsu_help_text['man']='
297 Usage: man [-m <mode>] [-b <browser>]
299 -m: Set output format (text, roff or html). Default: roff.
300 -b: Use the specified browser. Implies html mode.
302 If stdout is not associated with a terminal device, the command
303 dumps the man page to stdout and exits.
305 Otherwise, it tries to display the manual page as follows. In text
306 mode, plain text is piped to $PAGER. In roff mode, the roff output
307 is filtered through nroff, then piped to $PAGER. For both formats,
308 if $PAGER is unset, less(1) is assumed.
310 In html mode, html output is written to a temporary file, and this
311 file is displayed as a page in the web browser. If -b is not given,
312 the command stored in the $BROWSER environment variable is executed
313 with the path to the temporary file as an argument. If $BROWSER is
314 unset, elinks(1) is assumed.
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 _gsu_print_protected_roff_line "$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_autocmds()
460 for cmd in "${!_gsu_help_text[@]}"; do
461 help_txt="${_gsu_help_text["$cmd"]}"
463 printf 'com_%s()\n' "$cmd"
464 sed -e 's/^/## /g' <<< "$help_txt"
465 } | _gsu_roffify_cmds
471 local name="$1" sect_num="$2" month_year="$3" pkg="$4" sect_name="$5"
476 .TH "${name^^}" "$sect_num" "$month_year" "$pkg" "$sect_name"
481 \fI\,<subcommand>\/\fR [\fI\,<options>\/\fR] [\fI\,<arguments>\/\fR]
484 _gsu_extract_maindoc | _gsu_roffify_maindoc
486 printf '\n.SH "GENERIC SUBCOMMANDS"\n'
487 printf 'The following commands are automatically created by gsu\n'
488 _gsu_roffify_autocmds
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
601 echo "${minus_signs:0:$num}"
603 echo "${minus_signs:0:$num}"
611 _gsu_help_text['help']='
614 Usage: help [-a] [command]
616 Without arguments, print the list of available commands. Otherwise,
617 print the help text for the given command.
619 -a: Also show the help of automatic commands. Ignored if a command
622 _gsu_help_text['complete']='
623 Command line completion.
625 Usage: complete [<cword> <word>...]
627 When executed without argument the command writes bash code to
628 stdout. This code is suitable to be evaled from .bashrc to enable
631 If at least one argument is given, all possible completions are
632 written to stdout. This can be used from the completion function of
639 local ere tab=' ' txt
641 gsu_getopts "$com_help_options"
643 ((ret < 0)) && return
645 _gsu_get_command_regex
649 gsu_short_msg "### $gsu_name -- $gsu_banner_txt ###"
652 if [[ "$o_a" == 'true' ]]; then
653 _gsu_mfcb() { printf '%s\n' "$2"; }
654 for cmd in "${!_gsu_help_text[@]}"; do
655 printf "com_%s()" "$cmd"
656 txt="${_gsu_help_text["$cmd"]}"
657 mapfile -n 3 -c 1 -C _gsu_mfcb <<< "$txt"
661 grep -EA 2 "$ere" "$0"
662 } | grep -v -- '--' \
663 | sed -En "/$ere/"'!d
664 # remove everything but the command name
665 s/^com_(.*)\(\).*/\1/
667 # append tab after short commands (less than 8 chars)
668 s/^(.{1,7})$/\1'"$tab"'/g
670 # remove next line (should contain only ## anyway)
674 # append next line, removing leading ##
678 # replace newline by tab
681 # and print the sucker
683 printf "\n# Try %s help <command> for info on <command>, or %s help -a to see\n" \
684 "$gsu_name" "$gsu_name"
685 printf '# also the subcommands which are automatically generated by gsu.\n'
689 for cmd in "${!_gsu_help_text[@]}"; do
690 [[ "$1" != "$cmd" ]] && continue
691 printf '%s\n' "${_gsu_help_text["$cmd"]}"
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
726 _gsu_available_commands
737 local cur="\${COMP_WORDS[\$COMP_CWORD]}";
740 candidates=(\$($0 complete "\$COMP_CWORD" "\${COMP_WORDS[@]}"));
741 if ((\$? == 0)); then
742 COMPREPLY=(\$(compgen -W "\${candidates[*]}" -- "\$cur"));
744 compopt -o filenames;
745 COMPREPLY=(\$(compgen -fd -- "\$cur"));
753 gsu_is_a_number "$cword"
754 ((ret < 0)) && return
755 if ((cword <= 1)); then
756 _gsu_available_commands
764 # if no completer is defined for this subcommand we exit unsuccessfully
765 # to let the generic completer above fall back to file name completion.
766 [[ "$(type -t "complete_$cmd")" != "function" ]] && exit 1
767 "complete_$cmd" "$cword" "${words[@]}"
768 # ignore errors, they would only clutter the completion output
772 # Find out if the current word is a parameter for an option.
774 # $1: usual getopts option string.
775 # $2: The current word number.
776 # $3..: All words of the current command line.
778 # return: If yes, $result contains the letter of the option for which the
779 # current word is a parameter. Otherwise, $result is empty.
781 gsu_cword_is_option_parameter()
783 local opts="$1" cword="$2"
788 ((cword == 0)) && return
789 ((${#opts} < 2)) && return
793 prev="${words[$((cword - 1))]}"
794 [[ ! "$prev" == -* ]] && return
797 for ((i=0; i <= $n; i++)); do
799 [[ "${opts:$((i + 1)):1}" != ":" ]] && continue
801 [[ ! "$prev" =~ ^-.*$opt$ ]] && continue
808 # Get the word number on which the cursor is, not counting options.
810 # This is useful for completing commands whose possible completions depend
811 # on the word number, for example mount.
813 # $1: Getopt option string.
814 # $2: The current word number.
815 # $3..: All words of the current command line.
817 # return: If the current word is an option, or a parameter to an option,
818 # this function sets $result to -1. Otherwise, the number of the non-option
819 # is returned in $result.
821 gsu_get_unnamed_arg_num()
823 local opts="$1" cword="$2" prev cur
829 cur="${words[$cword]}"
830 prev="${words[$((cword - 1))]}"
832 [[ "$cur" == -* ]] && return
833 [[ "$prev" == -* ]] && [[ "$opts" == *${prev#-}:* ]] && return
835 for ((i=1; i <= $cword; i++)); do
836 prev="${words[$((i - 1))]}"
838 [[ "$cur" == -* ]] && continue
839 if [[ "$prev" == -* ]]; then
841 [[ "$opts" != *$opt:* ]] && let n++
849 # Entry point for all gsu-based scripts.
851 # The startup part of the application script should source this file to load
852 # the functions defined here, and then call gsu(). Functions starting with com_
853 # are automatically recognized as subcommands.
861 # gsu_dir=${gsu_dir:-/system/location/where/gsu/is/installed}
862 # . $gsu_dir/subcommand || exit 1
870 _gsu_print_available_commands
875 if [[ "$(type -t "com_$arg")" == 'function' ]]; then
883 ret=-$E_GSU_BAD_COMMAND
886 _gsu_print_available_commands 1>&2