com_prefs(): Add missing space character.
[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: text.
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 It is recommended to specify the output format with -m as the default
315 mode might change in future versions of gsu.
316 '
317
318 _gsu_read_line()
319 {
320 local -n p="$1"
321 local l OIFS="$IFS"
322
323 IFS=
324 read -r l || return
325 IFS="$OIFS"
326 p="$l"
327 }
328
329 _gsu_change_roffify_state()
330 {
331 local -n statep="$1"
332 local new_state="$2"
333 local old_state="$statep"
334
335 [[ "$old_state" == "$new_state" ]] && return 0
336
337 case "$old_state" in
338 text);;
339 example) printf '.EE\n';;
340 enum) printf '.RE\n';;
341 esac
342 case "$new_state" in
343 text);;
344 example) printf '.EX\n';;
345 enum) printf '.RS 2\n';;
346 esac
347
348 statep="$new_state"
349 return 1
350 }
351
352 _gsu_print_protected_roff_line()
353 {
354 local line="$1"
355 local -i n=0
356
357 while [[ "${line:$n:1}" == ' ' ]]; do
358 let n++
359 done
360 line="${line:$n}"
361 printf '\\&%s\n' "${line//\\/\\\\}"
362 }
363
364 _gsu_roffify_maindoc()
365 {
366 local state='text' TAB=' '
367 local line next_line
368 local -i n
369
370 _gsu_read_line 'line' || return
371 while _gsu_read_line next_line; do
372 if [[ "$next_line" =~ ^(----|====|~~~~) ]]; then # heading
373 printf '.SS %s\n' "$line"
374 _gsu_read_line line || return
375 _gsu_change_roffify_state 'state' 'text'
376 continue
377 fi
378 if [[ "${line:0:1}" == "$TAB" ]]; then # example
379 _gsu_change_roffify_state 'state' 'example'
380 printf '%s\n' "$line"
381 line="$next_line"
382 continue
383 fi
384 n=0
385 while [[ "${line:$n:1}" == ' ' ]]; do
386 let n++
387 done
388 line=${line:$n};
389 if [[ "${line:0:1}" == '*' ]]; then # enum
390 line=${line#\*}
391 _gsu_change_roffify_state 'state' 'enum'
392 printf '\n\(bu %s\n' "$line"
393 line="$next_line"
394 continue
395 fi
396 if [[ "$line" =~ ^$ ]]; then # new paragraph
397 _gsu_change_roffify_state 'state' 'text'
398 printf '.PP\n'
399 else
400 _gsu_print_protected_roff_line "$line"
401 fi
402 line="$next_line"
403 done
404 _gsu_print_protected_roff_line "$line"
405 }
406
407 _gsu_extract_maindoc()
408 {
409 sed -e '1,/^#\{70,\}/d' -e '/^#\{70,\}/,$d' -e 's/^# *//' -e 's/^#//g' "$0"
410 }
411
412 _gsu_roffify_cmds()
413 {
414 local line cmd= desc= state='text' TAB=' '
415
416 while _gsu_read_line line; do
417 if [[ "${line:0:1}" != '#' ]]; then # com_foo()
418 line="${line#com_}"
419 cmd="${line%()}"
420 continue
421 fi
422 line="${line####}"
423 if [[ "$line" =~ ^[[:space:]]*$ ]]; then
424 printf '.PP\n'
425 _gsu_change_roffify_state 'state' 'text'
426 continue
427 fi
428 if [[ -n "$cmd" ]]; then # desc or usage
429 if [[ -z "$desc" ]]; then # desc
430 desc="$line"
431 continue
432 fi
433 # usage
434 _gsu_change_roffify_state 'state' 'text'
435 printf '\n.SS %s \\- %s\n' "$cmd" "$desc"
436 printf '\n.I %s\n' "$line"
437 cmd=
438 desc=
439 continue
440 fi
441 line="${line# }"
442 if [[ "${line:0:1}" == "$TAB" ]]; then
443 _gsu_change_roffify_state 'state' 'example'
444 _gsu_print_protected_roff_line "$line"
445 continue
446 fi
447 if [[ "$line" == -*:* ]]; then
448 _gsu_change_roffify_state 'state' 'enum'
449 printf '.PP\n.B %s:\n' "${line%%:*}"
450 _gsu_print_protected_roff_line "${line#*:}"
451 continue
452 fi
453 _gsu_print_protected_roff_line "$line"
454 done
455 }
456
457 _gsu_roffify_autocmd()
458 {
459 local cmd="$1" help_txt="$2"
460
461 {
462 printf 'com_%s()\n' "$cmd"
463 sed -e 's/^/## /g' <<< "$help_txt"
464 } | _gsu_roffify_cmds
465 }
466
467 _gsu_roff_man()
468 {
469 local name="$1" sect_num="$2" month_year="$3" pkg="$4" sect_name="$5"
470 local purpose="$6"
471 local ere
472
473 cat << EOF
474 .TH "${name^^}" "$sect_num" "$month_year" "$pkg" "$sect_name"
475 .SH NAME
476 $name \- $purpose
477 .SH SYNOPSIS
478 .B $name
479 \fI\,<subcommand>\/\fR [\fI\,<options>\/\fR] [\fI\,<arguments>\/\fR]
480 .SH DESCRIPTION
481 EOF
482 _gsu_extract_maindoc | _gsu_roffify_maindoc
483
484 printf '\n.SH "GENERIC SUBCOMMANDS"\n'
485 printf 'The following commands are automatically created by gsu\n'
486 _gsu_roffify_autocmd "help" "$_gsu_help_txt"
487 _gsu_roffify_autocmd "man" "$_gsu_man_txt"
488 _gsu_roffify_autocmd "prefs" "$_gsu_prefs_txt"
489 _gsu_roffify_autocmd "complete" "$_gsu_complete_txt"
490
491 printf '\n.SH "LIST OF SUBCOMMANDS"\n'
492 printf 'Each command has its own set of options as described below.\n'
493
494 _gsu_get_command_regex
495 ere="$result"
496 # only consider lines in the comment of the function
497 sed -nEe '/'"$ere"'/,/^[^#]/p' "$0" | _gsu_roffify_cmds
498 }
499
500 _gsu_file_mtime()
501 {
502 local file="$1"
503 result="$(find "$file" -printf '%TB %TY' 2>/dev/null)" # GNU
504 (($? == 0)) && [[ -n "$result" ]] && return
505 result="$(stat -f %Sm -t '%B %Y' "$file" 2>/dev/null)" # BSD
506 (($? == 0)) && [[ -n "$result" ]] && return
507 result="$(date '+%B %Y' 2>/dev/null)" # POSIX
508 (($? == 0)) && [[ -n "$result" ]] && return
509 result='[unknown date]'
510 }
511
512 com_man()
513 {
514 local equal_signs="=================================================="
515 local minus_signs="--------------------------------------------------"
516 local filter='cat' pager='cat' browser=${BROWSER:-elinks} tmpfile=
517 local com num isatty pipeline
518
519 gsu_getopts "$_gsu_man_options"
520 eval "$result"
521 ((ret < 0)) && return
522 if [[ -n "$o_b" ]]; then
523 o_m='html'
524 browser="$o_b"
525 elif [[ -z "$o_m" ]]; then
526 o_m='text'
527 fi
528
529 _gsu_isatty && isatty='true' || isatty='false'
530 if [[ "$o_m" == 'roff' ]]; then
531 if [[ "$isatty" == 'true' ]]; then
532 filter='nroff -Tutf8 -mandoc'
533 pager="${PAGER:-less}"
534 fi
535 elif [[ "$o_m" == 'text' ]]; then
536 if [[ "$isatty" == 'true' ]]; then
537 pager="${PAGER:-less}"
538 fi
539 elif [[ "$o_m" == 'html' ]]; then
540 filter='groff -T html -m man'
541 if [[ "$isatty" == 'true' ]]; then
542 gsu_make_tempfile "gsu_html_man.XXXXXX.html"
543 ((ret < 0)) && return || tmpfile="$result"
544 trap "rm -f $tmpfile" RETURN EXIT
545 fi
546 fi
547 [[ "$pager" == 'less' ]] && export LESS=${LESS-RI}
548 case "$o_m" in
549 roff|html)
550 _gsu_file_mtime "$0"
551 _gsu_roff_man "$gsu_name" '1' "$result" \
552 "${gsu_package-${gsu_name^^}(1)}" \
553 "User Commands" "${gsu_banner_txt}" \
554 | $filter | {
555 if [[ -n "$tmpfile" ]]; then
556 cat > "$tmpfile"
557 else
558 $pager
559 fi
560 }
561 if (($? != 0)); then
562 ret=-$E_GSU_XCMD
563 result="filter: $filter"
564 return
565 fi
566 if [[ -n "$tmpfile" ]]; then
567 ret=-$E_GSU_XCMD
568 result="$browser"
569 "$browser" "$tmpfile" || return
570 fi
571 ret=$GSU_SUCCESS
572 return
573 ;;
574 text) ;;
575 "") ;;
576 *)
577 ret=-$E_GSU_INVAL
578 result="$o_m"
579 return
580 esac
581 {
582 echo "$gsu_name (_${gsu_banner_txt}_) manual"
583 echo "${equal_signs:0:${#gsu_name} + ${#gsu_banner_txt} + 16}"
584 echo
585 _gsu_extract_maindoc
586 echo "----"
587 echo
588 echo "$gsu_name usage"
589 echo "${minus_signs:0:${#gsu_name} + 6}"
590 printf "\t"
591 _gsu_usage 2>&1
592 echo "Each command has its own set of options as described below."
593 echo
594 echo "----"
595 echo
596 echo "Available commands:"
597
598 _gsu_available_commands
599 for com in $result; do
600 num=${#com}
601 (($num < 4)) && num=4
602 echo "${minus_signs:0:$num}"
603 echo "$com"
604 echo "${minus_signs:0:$num}"
605 "$0" help "$com"
606 echo
607 done
608 } | $pager
609 ret=$GSU_SUCCESS
610 }
611
612 _gsu_help_txt="
613 Print online help.
614
615 Usage: help [command]
616
617 Without arguments, print the list of available commands. Otherwise,
618 print the help text for the given command."
619
620 _gsu_complete_txt="
621 Command line completion.
622
623 Usage: complete [<cword> <word>...]
624
625 When executed without argument the command writes bash code to
626 stdout. This code is suitable to be evaled from .bashrc to enable
627 completion.
628
629 If at least one argument is given, all possible completions are
630 written to stdout. This can be used from the completion function of
631 the subcommand.
632 "
633
634 com_help()
635 {
636 local ere tab=' '
637
638 _gsu_get_command_regex
639 ere="$result"
640
641 if (($# == 0)); then
642 gsu_short_msg "### $gsu_name -- $gsu_banner_txt ###"
643 _gsu_usage 2>&1
644 {
645 printf "com_help()\n%s" "$_gsu_help_txt" | head -n 4; echo "--"
646 printf "com_man()\n%s" "$_gsu_man_txt" | head -n 4; echo "--"
647 printf "com_prefs()\n%s" "$_gsu_prefs_txt" | head -n 4; echo "--"
648 printf "com_complete()\n%s" "$_gsu_complete_txt" | head -n 4; echo "--"
649 grep -EA 2 "$ere" "$0"
650 } | grep -v -- '--' \
651 | sed -En "/$ere/"'!d
652 # remove everything but the command name
653 s/^com_(.*)\(\).*/\1/
654
655 # append tab after short commands (less than 8 chars)
656 s/^(.{1,7})$/\1'"$tab"'/g
657
658 # remove next line (should contain only ## anyway)
659 N
660 s/#.*//
661
662 # append next line, removing leading ##
663 N
664 s/#+ *//g
665
666 # replace newline by tab
667 y/\n/'"$tab"'/
668
669 # and print the sucker
670 p'
671 echo
672 echo "# Try $gsu_name help <command> for info on <command>."
673 ret=$GSU_SUCCESS
674 return
675 fi
676 if test "$1" = "help"; then
677 echo "$_gsu_help_txt"
678 ret=$GSU_SUCCESS
679 return
680 fi
681 if test "$1" = "man"; then
682 echo "$_gsu_man_txt"
683 ret=$GSU_SUCCESS
684 return
685 fi
686 if test "$1" = "prefs"; then
687 echo "$_gsu_prefs_txt"
688 ret=$GSU_SUCCESS
689 return
690 fi
691 if test "$1" = "complete"; then
692 echo "$_gsu_complete_txt"
693 ret=$GSU_SUCCESS
694 return
695 fi
696 ret=$GSU_SUCCESS
697 _gsu_get_command_regex "$1"
698 ere="$result"
699 if ! grep -Eq "$ere" "$0"; then
700 _gsu_print_available_commands
701 result="$1"
702 ret=-$E_GSU_BAD_COMMAND
703 return
704 fi
705 sed -nEe '
706 # only consider lines in the comment of the function
707 /'"$ere"'/,/^[^#]/ {
708
709 # remove leading ##
710 s/^## *//
711
712 # if it did start with ##, jump to label p and print it
713 tp
714
715 # otherwise, move on to next line
716 d
717
718 # print it
719 :p
720 p
721 }
722 ' "$0"
723 }
724
725 complete_help()
726 {
727 _gsu_available_commands
728 echo "$result"
729 }
730
731 com_complete()
732 {
733 local cmd n cword
734 local -a words
735
736 if (($# == 0)); then
737 cat <<EOF
738 local cur="\${COMP_WORDS[\$COMP_CWORD]}";
739 local -a candidates;
740
741 candidates=(\$($0 complete "\$COMP_CWORD" "\${COMP_WORDS[@]}"));
742 if ((\$? == 0)); then
743 COMPREPLY=(\$(compgen -W "\${candidates[*]}" -- "\$cur"));
744 else
745 compopt -o filenames;
746 COMPREPLY=(\$(compgen -fd -- "\$cur"));
747 fi
748 EOF
749 ret=$GSU_SUCCESS
750 return
751 fi
752
753 cword="$1"
754 gsu_is_a_number "$cword"
755 (($ret < 0)) && return
756 if (($cword <= 1)); then
757 _gsu_available_commands
758 echo "${result}"
759 ret=$GSU_SUCCESS
760 return
761 fi
762 shift
763 words=("$@")
764 cmd="${words[1]}"
765 # if no completer is defined for this subcommand we exit unsuccessfully
766 # to let the generic completer above fall back to file name completion.
767 [[ "$(type -t "complete_$cmd")" != "function" ]] && exit 1
768 "complete_$cmd" "$cword" "${words[@]}"
769 # ignore errors, they would only clutter the completion output
770 ret=$GSU_SUCCESS
771 }
772
773 # Find out if the current word is a parameter for an option.
774 #
775 # $1: usual getopts option string.
776 # $2: The current word number.
777 # $3..: All words of the current command line.
778 #
779 # return: If yes, $result contains the letter of the option for which the
780 # current word is a parameter. Otherwise, $result is empty.
781 #
782 gsu_cword_is_option_parameter()
783 {
784 local opts="$1" cword="$2"
785 local opt prev i n
786 local -a words
787
788 result=
789 (($cword == 0)) && return
790 ((${#opts} < 2)) && return
791
792 shift 2
793 words=("$@")
794 prev="${words[$(($cword - 1))]}"
795 [[ ! "$prev" == -* ]] && return
796
797 n=$((${#opts} - 1))
798 for ((i=0; i <= $n; i++)); do
799 opt="${opts:$i:1}"
800 [[ "${opts:$(($i + 1)):1}" != ":" ]] && continue
801 let i++
802 [[ ! "$prev" =~ ^-.*$opt$ ]] && continue
803 result="$opt"
804 return
805 done
806 ret=0
807 }
808
809 # Get the word number on which the cursor is, not counting options.
810 #
811 # This is useful for completing commands whose possible completions depend
812 # on the word number, for example mount.
813 #
814 # $1: Getopt option string.
815 # $2: The current word number.
816 # $3..: All words of the current command line.
817 #
818 # return: If the current word is an option, or a parameter to an option,
819 # this function sets $result to -1. Otherwise, the number of the non-option
820 # is returned in $result.
821 #
822 gsu_get_unnamed_arg_num()
823 {
824 local opts="$1" cword="$2" prev cur
825 local -i i n=0
826 local -a words
827
828 shift 2
829 words=("$@")
830 cur="${words[$cword]}"
831 prev="${words[$(($cword - 1))]}"
832 result=-1
833 [[ "$cur" == -* ]] && return
834 [[ "$prev" == -* ]] && [[ "$opts" == *${prev#-}:* ]] && return
835
836 for ((i=1; i <= $cword; i++)); do
837 prev="${words[$(($i - 1))]}"
838 cur="${words[$i]}"
839 [[ "$cur" == -* ]] && continue
840 if [[ "$prev" == -* ]]; then
841 opt=${prev#-}
842 [[ "$opts" != *$opt:* ]] && let n++
843 continue
844 fi
845 let n++
846 done
847 result="$(($n - 1))"
848 }
849
850 # Entry point for all gsu-based scripts.
851 #
852 # The startup part of the application script should source this file to load
853 # the functions defined here, and then call gsu(). Functions starting with com_
854 # are automatically recognized as subcommands.
855 #
856 # Minimal example:
857 #
858 # com_hello()
859 # {
860 # echo 'hello world'
861 # }
862 # gsu_dir=${gsu_dir:-/system/location/where/gsu/is/installed}
863 # . $gsu_dir/subcommand || exit 1
864 # gsu "$@"
865 gsu()
866 {
867 local i
868
869 if (($# == 0)); then
870 _gsu_usage
871 _gsu_print_available_commands
872 exit 1
873 fi
874 arg="$1"
875 shift
876 if [[ "$(type -t "com_$arg")" == 'function' ]]; then
877 "com_$arg" "$@"
878 if (("$ret" < 0)); then
879 gsu_err_msg
880 exit 1
881 fi
882 exit 0
883 fi
884 ret=-$E_GSU_BAD_COMMAND
885 result="$arg"
886 gsu_err_msg
887 _gsu_print_available_commands
888 exit 1
889 }