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
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
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
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.
191 # Returns 0 if the current word does not start with a hyphen, one otherwise.
192 gsu_complete_options()
194 local opts="$1" cword="$2" cur opt
199 cur="${words[$cword]}"
201 [[ ! "$cur" == -* ]] && return
203 for ((i=0; i < ${#opts}; i++)); do
205 [[ "$opt" == ":" ]] && continue
211 com_prefs_options='e'
214 Print the current preferences.
218 If -e is given, the config file is opened with the default editor.
219 Without options, the command prints out a list of all config variables,
220 together with their current value and the default value.
225 local i conf="${gsu_config_file:=${HOME:-}/.$gsu_name.rc}"
227 gsu_getopts "$com_prefs_options"
229 ((ret < 0)) && return
230 gsu_check_arg_count $# 0 0
231 ((ret < 0)) && return
233 if [[ "$o_e" == "true" ]]; then
237 (($? != 0)) && return
239 result="${EDITOR:-vi}"
241 (($? != 0)) && return
246 for ((i=0; i < ${#gsu_options[@]}; i++)); do
247 local name= option_type= default_value= required=
248 local description= help_text=
249 eval "${gsu_options[$i]}"
250 eval val='"${'${gsu_config_var_prefix}_$name:-'}"'
259 printf " %s: %s" "$option_type" "$description"
260 if [[ "$required" != "yes" && "$required" != "true" ]]; then
261 printf " [%s]" "$default_value"
264 [[ -n "$help_text" ]] && sed -e '/^[ ]*$/d; s/^[ ]*/# /g' <<< "$help_text"
265 printf "%s=%s" "$name" "$val"
266 [[ "$val" == "$default_value" ]] && printf " # default"
274 stty 0<&3 &> /dev/null
279 gsu_complete_options "$com_prefs_options" "$@"
282 _gsu_man_options='m:b:'
286 gsu_complete_options "$_gsu_man_options" "$@"
287 ((ret > 0)) && return
288 gsu_cword_is_option_parameter "$_gsu_man_options" "$@"
289 [[ "$result" == 'm' ]] && printf 'roff\ntext\nhtml\n'
295 Usage: man [-m <mode>] [-b <browser>]
297 -m: Set output format (text, roff or html). Default: roff.
298 -b: Use the specified browser. Implies html mode.
300 If stdout is not associated with a terminal device, the command
301 dumps the man page to stdout and exits.
303 Otherwise, it tries to display the manual page as follows. In text
304 mode, plain text is piped to $PAGER. In roff mode, the roff output
305 is filtered through nroff, then piped to $PAGER. For both formats,
306 if $PAGER is unset, less(1) is assumed.
308 In html mode, html output is written to a temporary file, and this
309 file is displayed as a page in the web browser. If -b is not given,
310 the command stored in the $BROWSER environment variable is executed
311 with the path to the temporary file as an argument. If $BROWSER is
312 unset, elinks(1) is assumed.
326 _gsu_change_roffify_state()
330 local old_state="$statep"
332 [[ "$old_state" == "$new_state" ]] && return 0
336 example) printf '.EE\n';;
337 enum) printf '.RE\n';;
341 example) printf '.EX\n';;
342 enum) printf '.RS 2\n';;
349 _gsu_print_protected_roff_line()
354 while [[ "${line:$n:1}" == ' ' ]]; do
358 printf '\\&%s\n' "${line//\\/\\\\}"
361 _gsu_roffify_maindoc()
363 local state='text' TAB=' '
367 _gsu_read_line 'line' || return
368 while _gsu_read_line next_line; do
369 if [[ "$next_line" =~ ^(----|====|~~~~) ]]; then # heading
370 printf '.SS %s\n' "$line"
371 _gsu_read_line line || return
372 _gsu_change_roffify_state 'state' 'text'
375 if [[ "${line:0:1}" == "$TAB" ]]; then # example
376 _gsu_change_roffify_state 'state' 'example'
377 printf '%s\n' "$line"
382 while [[ "${line:$n:1}" == ' ' ]]; do
386 if [[ "${line:0:1}" == '*' ]]; then # enum
388 _gsu_change_roffify_state 'state' 'enum'
389 printf '\n\(bu %s\n' "$line"
393 if [[ "$line" =~ ^$ ]]; then # new paragraph
394 _gsu_change_roffify_state 'state' 'text'
397 _gsu_print_protected_roff_line "$line"
401 _gsu_print_protected_roff_line "$line"
404 _gsu_extract_maindoc()
406 sed -e '1,/^#\{70,\}/d' -e '/^#\{70,\}/,$d' -e 's/^# *//' -e 's/^#//g' "$0"
411 local line cmd= desc= state='text' TAB=' '
413 while _gsu_read_line line; do
414 if [[ "${line:0:1}" != '#' ]]; then # com_foo()
420 if [[ "$line" =~ ^[[:space:]]*$ ]]; then
422 _gsu_change_roffify_state 'state' 'text'
425 if [[ -n "$cmd" ]]; then # desc or usage
426 if [[ -z "$desc" ]]; then # desc
431 _gsu_change_roffify_state 'state' 'text'
432 printf '\n.SS %s \\- %s\n' "$cmd" "$desc"
433 printf '\n.I %s\n' "$line"
439 if [[ "${line:0:1}" == "$TAB" ]]; then
440 _gsu_change_roffify_state 'state' 'example'
441 _gsu_print_protected_roff_line "$line"
444 if [[ "$line" == -*:* ]]; then
445 _gsu_change_roffify_state 'state' 'enum'
446 printf '.PP\n.B %s:\n' "${line%%:*}"
447 _gsu_print_protected_roff_line "${line#*:}"
450 _gsu_print_protected_roff_line "$line"
454 _gsu_roffify_autocmd()
456 local cmd="$1" help_txt="$2"
459 printf 'com_%s()\n' "$cmd"
460 sed -e 's/^/## /g' <<< "$help_txt"
461 } | _gsu_roffify_cmds
466 local name="$1" sect_num="$2" month_year="$3" pkg="$4" sect_name="$5"
471 .TH "${name^^}" "$sect_num" "$month_year" "$pkg" "$sect_name"
476 \fI\,<subcommand>\/\fR [\fI\,<options>\/\fR] [\fI\,<arguments>\/\fR]
479 _gsu_extract_maindoc | _gsu_roffify_maindoc
481 printf '\n.SH "GENERIC SUBCOMMANDS"\n'
482 printf 'The following commands are automatically created by gsu\n'
483 _gsu_roffify_autocmd "help" "$_gsu_help_txt"
484 _gsu_roffify_autocmd "man" "$_gsu_man_txt"
485 _gsu_roffify_autocmd "prefs" "$_gsu_prefs_txt"
486 _gsu_roffify_autocmd "complete" "$_gsu_complete_txt"
488 printf '\n.SH "LIST OF SUBCOMMANDS"\n'
489 printf 'Each command has its own set of options as described below.\n'
491 _gsu_get_command_regex
493 # only consider lines in the comment of the function
494 sed -nEe '/'"$ere"'/,/^[^#]/p' "$0" | _gsu_roffify_cmds
500 result="$(find "$file" -printf '%TB %TY' 2>/dev/null)" # GNU
501 (($? == 0)) && [[ -n "$result" ]] && return
502 result="$(stat -f %Sm -t '%B %Y' "$file" 2>/dev/null)" # BSD
503 (($? == 0)) && [[ -n "$result" ]] && return
504 result="$(date '+%B %Y' 2>/dev/null)" # POSIX
505 (($? == 0)) && [[ -n "$result" ]] && return
506 result='[unknown date]'
511 local equal_signs="=================================================="
512 local minus_signs="--------------------------------------------------"
513 local filter='cat' pager='cat' browser=${BROWSER:-elinks} tmpfile=
514 local com num isatty pipeline
516 gsu_getopts "$_gsu_man_options"
518 ((ret < 0)) && return
519 if [[ -n "$o_b" ]]; then
522 elif [[ -z "$o_m" ]]; then
526 _gsu_isatty && isatty='true' || isatty='false'
527 if [[ "$o_m" == 'roff' ]]; then
528 if [[ "$isatty" == 'true' ]]; then
529 filter='nroff -Tutf8 -mandoc'
530 pager="${PAGER:-less}"
532 elif [[ "$o_m" == 'text' ]]; then
533 if [[ "$isatty" == 'true' ]]; then
534 pager="${PAGER:-less}"
536 elif [[ "$o_m" == 'html' ]]; then
537 filter='groff -T html -m man'
538 if [[ "$isatty" == 'true' ]]; then
539 gsu_make_tempfile "gsu_html_man.XXXXXX.html"
540 ((ret < 0)) && return || tmpfile="$result"
541 trap "rm -f $tmpfile" RETURN EXIT
544 [[ "$pager" == 'less' ]] && export LESS=${LESS-RI}
548 _gsu_roff_man "$gsu_name" '1' "$result" \
549 "${gsu_package-${gsu_name^^}(1)}" \
550 "User Commands" "${gsu_banner_txt}" \
552 if [[ -n "$tmpfile" ]]; then
560 result="filter: $filter"
563 if [[ -n "$tmpfile" ]]; then
566 "$browser" "$tmpfile" || return
579 echo "$gsu_name (_${gsu_banner_txt}_) manual"
580 echo "${equal_signs:0:${#gsu_name} + ${#gsu_banner_txt} + 16}"
585 echo "$gsu_name usage"
586 echo "${minus_signs:0:${#gsu_name} + 6}"
589 echo "Each command has its own set of options as described below."
593 echo "Available commands:"
595 _gsu_available_commands
596 for com in $result; do
599 echo "${minus_signs:0:$num}"
601 echo "${minus_signs:0:$num}"
612 Usage: help [command]
614 Without arguments, print the list of available commands. Otherwise,
615 print the help text for the given command."
618 Command line completion.
620 Usage: complete [<cword> <word>...]
622 When executed without argument the command writes bash code to
623 stdout. This code is suitable to be evaled from .bashrc to enable
626 If at least one argument is given, all possible completions are
627 written to stdout. This can be used from the completion function of
635 _gsu_get_command_regex
639 gsu_short_msg "### $gsu_name -- $gsu_banner_txt ###"
642 printf "com_help()\n%s" "$_gsu_help_txt" | head -n 4; echo "--"
643 printf "com_man()\n%s" "$_gsu_man_txt" | head -n 4; echo "--"
644 printf "com_prefs()\n%s" "$_gsu_prefs_txt" | head -n 4; echo "--"
645 printf "com_complete()\n%s" "$_gsu_complete_txt" | head -n 4; echo "--"
646 grep -EA 2 "$ere" "$0"
647 } | grep -v -- '--' \
648 | sed -En "/$ere/"'!d
649 # remove everything but the command name
650 s/^com_(.*)\(\).*/\1/
652 # append tab after short commands (less than 8 chars)
653 s/^(.{1,7})$/\1'"$tab"'/g
655 # remove next line (should contain only ## anyway)
659 # append next line, removing leading ##
663 # replace newline by tab
666 # and print the sucker
669 echo "# Try $gsu_name help <command> for info on <command>."
673 if test "$1" = "help"; then
674 echo "$_gsu_help_txt"
678 if test "$1" = "man"; then
683 if test "$1" = "prefs"; then
684 echo "$_gsu_prefs_txt"
688 if test "$1" = "complete"; then
689 echo "$_gsu_complete_txt"
694 _gsu_get_command_regex "$1"
696 if ! grep -Eq "$ere" "$0"; then
697 _gsu_print_available_commands
699 ret=-$E_GSU_BAD_COMMAND
703 # only consider lines in the comment of the function
709 # if it did start with ##, jump to label p and print it
712 # otherwise, move on to next line
724 _gsu_available_commands
735 local cur="\${COMP_WORDS[\$COMP_CWORD]}";
738 candidates=(\$($0 complete "\$COMP_CWORD" "\${COMP_WORDS[@]}"));
739 if ((\$? == 0)); then
740 COMPREPLY=(\$(compgen -W "\${candidates[*]}" -- "\$cur"));
742 compopt -o filenames;
743 COMPREPLY=(\$(compgen -fd -- "\$cur"));
751 gsu_is_a_number "$cword"
752 ((ret < 0)) && return
753 if ((cword <= 1)); then
754 _gsu_available_commands
762 # if no completer is defined for this subcommand we exit unsuccessfully
763 # to let the generic completer above fall back to file name completion.
764 [[ "$(type -t "complete_$cmd")" != "function" ]] && exit 1
765 "complete_$cmd" "$cword" "${words[@]}"
766 # ignore errors, they would only clutter the completion output
770 # Find out if the current word is a parameter for an option.
772 # $1: usual getopts option string.
773 # $2: The current word number.
774 # $3..: All words of the current command line.
776 # return: If yes, $result contains the letter of the option for which the
777 # current word is a parameter. Otherwise, $result is empty.
779 gsu_cword_is_option_parameter()
781 local opts="$1" cword="$2"
786 ((cword == 0)) && return
787 ((${#opts} < 2)) && return
791 prev="${words[$((cword - 1))]}"
792 [[ ! "$prev" == -* ]] && return
795 for ((i=0; i <= $n; i++)); do
797 [[ "${opts:$((i + 1)):1}" != ":" ]] && continue
799 [[ ! "$prev" =~ ^-.*$opt$ ]] && continue
806 # Get the word number on which the cursor is, not counting options.
808 # This is useful for completing commands whose possible completions depend
809 # on the word number, for example mount.
811 # $1: Getopt option string.
812 # $2: The current word number.
813 # $3..: All words of the current command line.
815 # return: If the current word is an option, or a parameter to an option,
816 # this function sets $result to -1. Otherwise, the number of the non-option
817 # is returned in $result.
819 gsu_get_unnamed_arg_num()
821 local opts="$1" cword="$2" prev cur
827 cur="${words[$cword]}"
828 prev="${words[$((cword - 1))]}"
830 [[ "$cur" == -* ]] && return
831 [[ "$prev" == -* ]] && [[ "$opts" == *${prev#-}:* ]] && return
833 for ((i=1; i <= $cword; i++)); do
834 prev="${words[$((i - 1))]}"
836 [[ "$cur" == -* ]] && continue
837 if [[ "$prev" == -* ]]; then
839 [[ "$opts" != *$opt:* ]] && let n++
847 # Entry point for all gsu-based scripts.
849 # The startup part of the application script should source this file to load
850 # the functions defined here, and then call gsu(). Functions starting with com_
851 # are automatically recognized as subcommands.
859 # gsu_dir=${gsu_dir:-/system/location/where/gsu/is/installed}
860 # . $gsu_dir/subcommand || exit 1
868 _gsu_print_available_commands
873 if [[ "$(type -t "com_$arg")" == 'function' ]]; then
881 ret=-$E_GSU_BAD_COMMAND
884 _gsu_print_available_commands 1>&2