man: Make "roff" the default output mode.
[gsu.git] / subcommand
1 #!/bin/bash
2 # Copyright (C) 2006 Andre Noll
3 # Licensed under the LGPL, version 3. See COPYING and COPYING.LESSER.
4
5 if [[ "$(type -t _gsu_setup)" != "function" ]]; then
6 gsu_dir=${gsu_dir:-${BASH_SOURCE[0]%/*}}
7 . "$gsu_dir/common" || exit 1
8 _gsu_setup
9 fi
10
11 _gsu_usage()
12 {
13 gsu_short_msg "# Usage: $gsu_name command [options]"
14 }
15
16 # Return an extended regular expression to match against $0.
17 #
18 # When called without argument, the expression matches all lines which define a
19 # subcommand.
20 #
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.
23 #
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()
27 {
28 local cmd="${1:-[-a-zA-Z_0-9]{1,\}}"
29 result="^com_($cmd)\(\)"
30 }
31
32 _gsu_available_commands()
33 {
34 local ere
35
36 _gsu_get_command_regex
37 ere="$result"
38 result="$({
39 printf "help\nman\nprefs\ncomplete\n"
40 sed -Ee '
41 # if line matches, isolate command name
42 s/'"$ere"'/\1/g
43
44 # if there is a match, (print it and) start next cycle
45 t
46
47 # otherwise delete it
48 d
49 ' "$0"
50 } | sort | tr '\n' ' ')"
51 }
52
53 # Check number of arguments.
54 #
55 # Usage: gsu_check_arg_count <num_given> <num1> [<num2>]
56 #
57 # Check that <num_given> is between <num1> and <num2> inclusively.
58 # If only <num1> ist given, num2 is assumed to be infinity.
59 #
60 # Examples:
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
65 gsu_check_arg_count()
66 {
67 ret=-$E_GSU_BAD_ARG_COUNT
68 if (($# == 2)); then # only num1 is given
69 result="at least $2 args required, $1 given"
70 (($1 < $2)) && return
71 ret=$GSU_SUCCESS
72 return
73 fi
74 # num1 and num2 given
75 result="need at least $2 args, $1 given"
76 (($1 < $2)) && return
77 result="need at most $3 args, $1 given"
78 (($1 > $3)) && return
79 ret=$GSU_SUCCESS
80 }
81
82 # Wrapper for the bash getopts builtin.
83 #
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.
89 #
90 # Example:
91 # gsu_getopts abc:x:y
92 # eval "$result"
93 # (($ret < 0)) && return
94 #
95 # [[ "$o_a" = 'true' ]] && echo 'The -a flag was given'
96 # [[ -n "$o_c" ]] && echo "The -c option was given with arg $o_c"
97 gsu_getopts()
98 {
99 local i c tab=' ' cr='
100 '
101
102 gsu_check_arg_count $# 1 1
103 if (($ret < 0)); then
104 gsu_err_msg
105 exit 1
106 fi
107
108 ret=-$E_GSU_GETOPTS
109 result="invalid optstring $1"
110 if [[ -z "$1" ]] || grep -q '::' <<< "$1" ; then
111 gsu_err_msg
112 exit 1
113 fi
114
115 for ((i=0; i < ${#1}; i++)); do
116 c=${1:$i:1}
117 case "$c" in
118 [a-zA-Z:]);;
119 *)
120 ret=-$E_GSU_GETOPTS
121 result="invalid character $c in optstring"
122 gsu_err_msg
123 exit 1
124 esac
125 done
126 result="local _gsu_getopts_opt"
127 for ((i=0; i < ${#1}; i++)); do
128 c1=${1:$i:1}
129 c2=${1:$(($i + 1)):1}
130 result+=" o_$c1="
131 if [[ "$c2" = ":" ]]; then
132 let i++
133 else
134 result+="false"
135 fi
136 done
137 result+="
138 OPTIND=1
139 while getopts $1 _gsu_getopts_opt \"\$@\"; do
140 case \"\$_gsu_getopts_opt\" in
141 "
142 for ((i=0; i < ${#1}; i++)); do
143 c1=${1:$i:1}
144 c2=${1:$(($i + 1)):1}
145 result+="$tab$tab$c1) o_$c1="
146 if [[ "$c2" = ":" ]]; then
147 result+="\"\$OPTARG\""
148 let i++
149 else
150 result+="true"
151 fi
152 result+=";;$cr"
153 done
154 result+="
155 *)
156 ret=-\$E_GSU_GETOPTS
157 result=\"invalid option given\"
158 return
159 ;;
160 esac
161 done
162 shift \$((\$OPTIND - 1))
163 "
164 ret=$GSU_SUCCESS
165 }
166
167 _gsu_print_available_commands()
168 {
169 local cmd cmds
170 local -i count=0
171
172 _gsu_available_commands
173 cmds="$result"
174 printf 'Available commands:\n'
175 for cmd in $cmds; do
176 printf '%s' "$cmd"
177 let ++count
178 if (($count % 4)); then
179 printf '\t'
180 ((${#cmd} < 8)) && printf '\t'
181 else
182 printf '\n'
183 fi
184 done
185 printf '\n'
186 }
187
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 #
191 # Returns 0 if the current word does not start with a hyphen, one otherwise.
192 gsu_complete_options()
193 {
194 local opts="$1" cword="$2" cur opt
195 local -a words
196
197 shift 2
198 words=("$@")
199 cur="${words[$cword]}"
200 ret=0
201 [[ ! "$cur" == -* ]] && return
202
203 for ((i=0; i < ${#opts}; i++)); do
204 opt="${opts:$i:1}"
205 [[ "$opt" == ":" ]] && continue
206 printf "%s" "-$opt "
207 done
208 ret=1
209 }
210
211 com_prefs_options='e'
212
213 _gsu_prefs_txt="
214 Print the current preferences.
215
216 Usage: prefs [-e]
217
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.
221 "
222
223 com_prefs()
224 {
225 local i conf="${gsu_config_file:=${HOME:-}/.$gsu_name.rc}"
226
227 gsu_getopts "$com_prefs_options"
228 eval "$result"
229 (($ret < 0)) && return
230 gsu_check_arg_count $# 0 0
231 (($ret < 0)) && return
232
233 if [[ "$o_e" == "true" ]]; then
234 ret=-$E_GSU_MKDIR
235 result="${conf%/*}"
236 mkdir -p "$result"
237 (($? != 0)) && return
238 ret=-$E_GSU_EDITOR
239 result="${EDITOR:-vi}"
240 "$result" "$conf"
241 (($? != 0)) && return
242 ret=$GSU_SUCCESS
243 return
244 fi
245
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:-'}"'
251 case "$required" in
252 true|yes)
253 printf "# required"
254 ;;
255 *)
256 printf "# optional"
257 ;;
258 esac
259 printf " %s: %s" "$option_type" "$description"
260 if [[ "$required" != "yes" && "$required" != "true" ]]; then
261 printf " [%s]" "$default_value"
262 fi
263 echo
264 [[ -n "$help_text" ]] && sed -e '/^[ ]*$/d; s/^[ ]*/# /g' <<< "$help_text"
265 printf "%s=%s" "$name" "$val"
266 [[ "$val" == "$default_value" ]] && printf " # default"
267 echo
268 done
269 }
270
271 _gsu_isatty()
272 {(
273 exec 3<&1
274 stty 0<&3 &> /dev/null
275 )}
276
277 complete_prefs()
278 {
279 gsu_complete_options "$com_prefs_options" "$@"
280 }
281
282 _gsu_man_options='m:b:'
283
284 complete_man()
285 {
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'
290 }
291
292 _gsu_man_txt='
293 Print the manual.
294
295 Usage: man [-m <mode>] [-b <browser>]
296
297 -m: Set output format (text, roff or html). Default: roff.
298 -b: Use the specified browser. Implies html mode.
299
300 If stdout is not associated with a terminal device, the command
301 dumps the man page to stdout and exits.
302
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.
307
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.
313 '
314
315 _gsu_read_line()
316 {
317 local -n p="$1"
318 local l OIFS="$IFS"
319
320 IFS=
321 read -r l || return
322 IFS="$OIFS"
323 p="$l"
324 }
325
326 _gsu_change_roffify_state()
327 {
328 local -n statep="$1"
329 local new_state="$2"
330 local old_state="$statep"
331
332 [[ "$old_state" == "$new_state" ]] && return 0
333
334 case "$old_state" in
335 text);;
336 example) printf '.EE\n';;
337 enum) printf '.RE\n';;
338 esac
339 case "$new_state" in
340 text);;
341 example) printf '.EX\n';;
342 enum) printf '.RS 2\n';;
343 esac
344
345 statep="$new_state"
346 return 1
347 }
348
349 _gsu_print_protected_roff_line()
350 {
351 local line="$1"
352 local -i n=0
353
354 while [[ "${line:$n:1}" == ' ' ]]; do
355 let n++
356 done
357 line="${line:$n}"
358 printf '\\&%s\n' "${line//\\/\\\\}"
359 }
360
361 _gsu_roffify_maindoc()
362 {
363 local state='text' TAB=' '
364 local line next_line
365 local -i n
366
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'
373 continue
374 fi
375 if [[ "${line:0:1}" == "$TAB" ]]; then # example
376 _gsu_change_roffify_state 'state' 'example'
377 printf '%s\n' "$line"
378 line="$next_line"
379 continue
380 fi
381 n=0
382 while [[ "${line:$n:1}" == ' ' ]]; do
383 let n++
384 done
385 line=${line:$n};
386 if [[ "${line:0:1}" == '*' ]]; then # enum
387 line=${line#\*}
388 _gsu_change_roffify_state 'state' 'enum'
389 printf '\n\(bu %s\n' "$line"
390 line="$next_line"
391 continue
392 fi
393 if [[ "$line" =~ ^$ ]]; then # new paragraph
394 _gsu_change_roffify_state 'state' 'text'
395 printf '.PP\n'
396 else
397 _gsu_print_protected_roff_line "$line"
398 fi
399 line="$next_line"
400 done
401 _gsu_print_protected_roff_line "$line"
402 }
403
404 _gsu_extract_maindoc()
405 {
406 sed -e '1,/^#\{70,\}/d' -e '/^#\{70,\}/,$d' -e 's/^# *//' -e 's/^#//g' "$0"
407 }
408
409 _gsu_roffify_cmds()
410 {
411 local line cmd= desc= state='text' TAB=' '
412
413 while _gsu_read_line line; do
414 if [[ "${line:0:1}" != '#' ]]; then # com_foo()
415 line="${line#com_}"
416 cmd="${line%()}"
417 continue
418 fi
419 line="${line####}"
420 if [[ "$line" =~ ^[[:space:]]*$ ]]; then
421 printf '.PP\n'
422 _gsu_change_roffify_state 'state' 'text'
423 continue
424 fi
425 if [[ -n "$cmd" ]]; then # desc or usage
426 if [[ -z "$desc" ]]; then # desc
427 desc="$line"
428 continue
429 fi
430 # usage
431 _gsu_change_roffify_state 'state' 'text'
432 printf '\n.SS %s \\- %s\n' "$cmd" "$desc"
433 printf '\n.I %s\n' "$line"
434 cmd=
435 desc=
436 continue
437 fi
438 line="${line# }"
439 if [[ "${line:0:1}" == "$TAB" ]]; then
440 _gsu_change_roffify_state 'state' 'example'
441 _gsu_print_protected_roff_line "$line"
442 continue
443 fi
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#*:}"
448 continue
449 fi
450 _gsu_print_protected_roff_line "$line"
451 done
452 }
453
454 _gsu_roffify_autocmd()
455 {
456 local cmd="$1" help_txt="$2"
457
458 {
459 printf 'com_%s()\n' "$cmd"
460 sed -e 's/^/## /g' <<< "$help_txt"
461 } | _gsu_roffify_cmds
462 }
463
464 _gsu_roff_man()
465 {
466 local name="$1" sect_num="$2" month_year="$3" pkg="$4" sect_name="$5"
467 local purpose="$6"
468 local ere
469
470 cat << EOF
471 .TH "${name^^}" "$sect_num" "$month_year" "$pkg" "$sect_name"
472 .SH NAME
473 $name \- $purpose
474 .SH SYNOPSIS
475 .B $name
476 \fI\,<subcommand>\/\fR [\fI\,<options>\/\fR] [\fI\,<arguments>\/\fR]
477 .SH DESCRIPTION
478 EOF
479 _gsu_extract_maindoc | _gsu_roffify_maindoc
480
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"
487
488 printf '\n.SH "LIST OF SUBCOMMANDS"\n'
489 printf 'Each command has its own set of options as described below.\n'
490
491 _gsu_get_command_regex
492 ere="$result"
493 # only consider lines in the comment of the function
494 sed -nEe '/'"$ere"'/,/^[^#]/p' "$0" | _gsu_roffify_cmds
495 }
496
497 _gsu_file_mtime()
498 {
499 local file="$1"
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]'
507 }
508
509 com_man()
510 {
511 local equal_signs="=================================================="
512 local minus_signs="--------------------------------------------------"
513 local filter='cat' pager='cat' browser=${BROWSER:-elinks} tmpfile=
514 local com num isatty pipeline
515
516 gsu_getopts "$_gsu_man_options"
517 eval "$result"
518 ((ret < 0)) && return
519 if [[ -n "$o_b" ]]; then
520 o_m='html'
521 browser="$o_b"
522 elif [[ -z "$o_m" ]]; then
523 o_m='roff'
524 fi
525
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}"
531 fi
532 elif [[ "$o_m" == 'text' ]]; then
533 if [[ "$isatty" == 'true' ]]; then
534 pager="${PAGER:-less}"
535 fi
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
542 fi
543 fi
544 [[ "$pager" == 'less' ]] && export LESS=${LESS-RI}
545 case "$o_m" in
546 roff|html)
547 _gsu_file_mtime "$0"
548 _gsu_roff_man "$gsu_name" '1' "$result" \
549 "${gsu_package-${gsu_name^^}(1)}" \
550 "User Commands" "${gsu_banner_txt}" \
551 | $filter | {
552 if [[ -n "$tmpfile" ]]; then
553 cat > "$tmpfile"
554 else
555 $pager
556 fi
557 }
558 if (($? != 0)); then
559 ret=-$E_GSU_XCMD
560 result="filter: $filter"
561 return
562 fi
563 if [[ -n "$tmpfile" ]]; then
564 ret=-$E_GSU_XCMD
565 result="$browser"
566 "$browser" "$tmpfile" || return
567 fi
568 ret=$GSU_SUCCESS
569 return
570 ;;
571 text) ;;
572 "") ;;
573 *)
574 ret=-$E_GSU_INVAL
575 result="$o_m"
576 return
577 esac
578 {
579 echo "$gsu_name (_${gsu_banner_txt}_) manual"
580 echo "${equal_signs:0:${#gsu_name} + ${#gsu_banner_txt} + 16}"
581 echo
582 _gsu_extract_maindoc
583 echo "----"
584 echo
585 echo "$gsu_name usage"
586 echo "${minus_signs:0:${#gsu_name} + 6}"
587 printf "\t"
588 _gsu_usage 2>&1
589 echo "Each command has its own set of options as described below."
590 echo
591 echo "----"
592 echo
593 echo "Available commands:"
594
595 _gsu_available_commands
596 for com in $result; do
597 num=${#com}
598 (($num < 4)) && num=4
599 echo "${minus_signs:0:$num}"
600 echo "$com"
601 echo "${minus_signs:0:$num}"
602 "$0" help "$com"
603 echo
604 done
605 } | $pager
606 ret=$GSU_SUCCESS
607 }
608
609 _gsu_help_txt="
610 Print online help.
611
612 Usage: help [command]
613
614 Without arguments, print the list of available commands. Otherwise,
615 print the help text for the given command."
616
617 _gsu_complete_txt="
618 Command line completion.
619
620 Usage: complete [<cword> <word>...]
621
622 When executed without argument the command writes bash code to
623 stdout. This code is suitable to be evaled from .bashrc to enable
624 completion.
625
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
628 the subcommand.
629 "
630
631 com_help()
632 {
633 local ere tab=' '
634
635 _gsu_get_command_regex
636 ere="$result"
637
638 if (($# == 0)); then
639 gsu_short_msg "### $gsu_name -- $gsu_banner_txt ###"
640 _gsu_usage 2>&1
641 {
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/
651
652 # append tab after short commands (less than 8 chars)
653 s/^(.{1,7})$/\1'"$tab"'/g
654
655 # remove next line (should contain only ## anyway)
656 N
657 s/#.*//
658
659 # append next line, removing leading ##
660 N
661 s/#+ *//g
662
663 # replace newline by tab
664 y/\n/'"$tab"'/
665
666 # and print the sucker
667 p'
668 echo
669 echo "# Try $gsu_name help <command> for info on <command>."
670 ret=$GSU_SUCCESS
671 return
672 fi
673 if test "$1" = "help"; then
674 echo "$_gsu_help_txt"
675 ret=$GSU_SUCCESS
676 return
677 fi
678 if test "$1" = "man"; then
679 echo "$_gsu_man_txt"
680 ret=$GSU_SUCCESS
681 return
682 fi
683 if test "$1" = "prefs"; then
684 echo "$_gsu_prefs_txt"
685 ret=$GSU_SUCCESS
686 return
687 fi
688 if test "$1" = "complete"; then
689 echo "$_gsu_complete_txt"
690 ret=$GSU_SUCCESS
691 return
692 fi
693 ret=$GSU_SUCCESS
694 _gsu_get_command_regex "$1"
695 ere="$result"
696 if ! grep -Eq "$ere" "$0"; then
697 _gsu_print_available_commands
698 result="$1"
699 ret=-$E_GSU_BAD_COMMAND
700 return
701 fi
702 sed -nEe '
703 # only consider lines in the comment of the function
704 /'"$ere"'/,/^[^#]/ {
705
706 # remove leading ##
707 s/^## *//
708
709 # if it did start with ##, jump to label p and print it
710 tp
711
712 # otherwise, move on to next line
713 d
714
715 # print it
716 :p
717 p
718 }
719 ' "$0"
720 }
721
722 complete_help()
723 {
724 _gsu_available_commands
725 echo "$result"
726 }
727
728 com_complete()
729 {
730 local cmd n cword
731 local -a words
732
733 if (($# == 0)); then
734 cat <<EOF
735 local cur="\${COMP_WORDS[\$COMP_CWORD]}";
736 local -a candidates;
737
738 candidates=(\$($0 complete "\$COMP_CWORD" "\${COMP_WORDS[@]}"));
739 if ((\$? == 0)); then
740 COMPREPLY=(\$(compgen -W "\${candidates[*]}" -- "\$cur"));
741 else
742 compopt -o filenames;
743 COMPREPLY=(\$(compgen -fd -- "\$cur"));
744 fi
745 EOF
746 ret=$GSU_SUCCESS
747 return
748 fi
749
750 cword="$1"
751 gsu_is_a_number "$cword"
752 (($ret < 0)) && return
753 if (($cword <= 1)); then
754 _gsu_available_commands
755 echo "${result}"
756 ret=$GSU_SUCCESS
757 return
758 fi
759 shift
760 words=("$@")
761 cmd="${words[1]}"
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
767 ret=$GSU_SUCCESS
768 }
769
770 # Find out if the current word is a parameter for an option.
771 #
772 # $1: usual getopts option string.
773 # $2: The current word number.
774 # $3..: All words of the current command line.
775 #
776 # return: If yes, $result contains the letter of the option for which the
777 # current word is a parameter. Otherwise, $result is empty.
778 #
779 gsu_cword_is_option_parameter()
780 {
781 local opts="$1" cword="$2"
782 local opt prev i n
783 local -a words
784
785 result=
786 (($cword == 0)) && return
787 ((${#opts} < 2)) && return
788
789 shift 2
790 words=("$@")
791 prev="${words[$(($cword - 1))]}"
792 [[ ! "$prev" == -* ]] && return
793
794 n=$((${#opts} - 1))
795 for ((i=0; i <= $n; i++)); do
796 opt="${opts:$i:1}"
797 [[ "${opts:$(($i + 1)):1}" != ":" ]] && continue
798 let i++
799 [[ ! "$prev" =~ ^-.*$opt$ ]] && continue
800 result="$opt"
801 return
802 done
803 ret=0
804 }
805
806 # Get the word number on which the cursor is, not counting options.
807 #
808 # This is useful for completing commands whose possible completions depend
809 # on the word number, for example mount.
810 #
811 # $1: Getopt option string.
812 # $2: The current word number.
813 # $3..: All words of the current command line.
814 #
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.
818 #
819 gsu_get_unnamed_arg_num()
820 {
821 local opts="$1" cword="$2" prev cur
822 local -i i n=0
823 local -a words
824
825 shift 2
826 words=("$@")
827 cur="${words[$cword]}"
828 prev="${words[$(($cword - 1))]}"
829 result=-1
830 [[ "$cur" == -* ]] && return
831 [[ "$prev" == -* ]] && [[ "$opts" == *${prev#-}:* ]] && return
832
833 for ((i=1; i <= $cword; i++)); do
834 prev="${words[$(($i - 1))]}"
835 cur="${words[$i]}"
836 [[ "$cur" == -* ]] && continue
837 if [[ "$prev" == -* ]]; then
838 opt=${prev#-}
839 [[ "$opts" != *$opt:* ]] && let n++
840 continue
841 fi
842 let n++
843 done
844 result="$(($n - 1))"
845 }
846
847 # Entry point for all gsu-based scripts.
848 #
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.
852 #
853 # Minimal example:
854 #
855 # com_hello()
856 # {
857 # echo 'hello world'
858 # }
859 # gsu_dir=${gsu_dir:-/system/location/where/gsu/is/installed}
860 # . $gsu_dir/subcommand || exit 1
861 # gsu "$@"
862 gsu()
863 {
864 local i
865
866 if (($# == 0)); then
867 _gsu_usage
868 _gsu_print_available_commands
869 exit 1
870 fi
871 arg="$1"
872 shift
873 if [[ "$(type -t "com_$arg")" == 'function' ]]; then
874 "com_$arg" "$@"
875 if (("$ret" < 0)); then
876 gsu_err_msg
877 exit 1
878 fi
879 exit 0
880 fi
881 ret=-$E_GSU_BAD_COMMAND
882 result="$arg"
883 gsu_err_msg
884 _gsu_print_available_commands
885 exit 1
886 }