Merge branch 't/default_gsu_dir'
[gsu.git] / subcommand
1 #!/bin/bash
2 # (C) 2006-2011 Andre Noll
3
4 if [[ "$(type -t _gsu_setup)" != "function" ]]; then
5 gsu_dir=${gsu_dir:-${BASH_SOURCE[0]%/*}}
6 . $gsu_dir/common || exit 1
7 _gsu_setup
8 fi
9
10 _gsu_usage()
11 {
12 gsu_short_msg "# Usage: $gsu_name command [options]"
13 }
14
15 # Return an extended regular expression to match against $0.
16 #
17 # When called without argument, the expression matches all lines which define a
18 # subcommand.
19 #
20 # If an argument is given, the returned expression matches only the subcommand
21 # passed as $1. This is useful to tell if a string is a valid subcommand.
22 #
23 # Regardless of whether an argument is given, the returned expression contains
24 # exactly one parenthesized subexpression for matching the command name.
25 _gsu_get_command_regex()
26 {
27 local cmd="${1:-[-a-zA-Z_0-9]+}"
28 result="^com_($cmd)\(\)"
29 }
30
31 _gsu_available_commands()
32 {
33 local ere
34
35 _gsu_get_command_regex
36 ere="$result"
37 result="$({
38 printf "help\nman\nprefs\ncomplete\n"
39 sed -Ee '
40 # if line matches, isolate command name
41 s/'"$ere"'/\1/g
42
43 # if there is a match, (print it and) start next cycle
44 t
45
46 # otherwise delete it
47 d
48 ' $0
49 } | sort | tr '\n' ' ')"
50 }
51
52 _gsu_print_available_commands()
53 {(
54 local i count=0
55 gsu_short_msg "Available commands:"
56 for i in $gsu_cmds; do
57 printf "$i"
58 count=$(($count + 1))
59 if test $(($count % 4)) -eq 0; then
60 echo
61 else
62 printf "\t"
63 if test ${#i} -lt 8; then
64 printf "\t"
65 fi
66 fi
67 done
68 echo
69 ) 2>&1
70 }
71
72 gsu_complete_options()
73 {
74 local opts="$1" cword="$2" cur opt
75 local -a words
76
77 shift 2
78 words=("$@")
79 cur="${words[$cword]}"
80 ret=0
81 [[ ! "$cur" == -* ]] && return
82
83 ret=0
84 for ((i=0; i < ${#opts}; i++)); do
85 opt="${opts:$i:1}"
86 [[ "$opt" == ":" ]] && continue
87 printf "%s" "-$opt "
88 let ret++
89 done
90 }
91
92 export gsu_prefs_txt="
93 Print the current preferences.
94
95 Usage: prefs [-e]
96
97 If -e is given, the config file is opened with the default editor. Without
98 options, the command prints out a list of all cmt config variables, together
99 with their current value and the default value."
100 _com_prefs()
101 {
102 local i conf="${gsu_config_file:=${HOME:-}/.$gsu_name.rc}"
103
104 gsu_getopts "e"
105 eval "$result"
106 (($ret < 0)) && return
107 gsu_check_arg_count $# 0 0
108 (($ret < 0)) && return
109
110 if [[ "$o_e" == "true" ]]; then
111 ret=-$E_GSU_MKDIR
112 result="${conf%/*}"
113 mkdir -p "$result"
114 (($? != 0)) && return
115 ret=-$E_GSU_EDITOR
116 result="${EDITOR:-vi}"
117 "$result" "$conf"
118 (($? != 0)) && return
119 ret=$GSU_SUCCESS
120 return
121 fi
122
123 for ((i=0; i < ${#gsu_options[@]}; i++)); do
124 local name= option_type= default_value= required=
125 local description= help_text=
126 eval "${gsu_options[$i]}"
127 eval val='"${'${gsu_config_var_prefix}_$name:-'}"'
128 case "$required" in
129 true|yes)
130 printf "# required"
131 ;;
132 *)
133 printf "# optional"
134 ;;
135 esac
136 printf " $option_type: $description"
137 if [[ "$required" != "yes" && "$required" != "true" ]]; then
138 printf " [$default_value]"
139 fi
140 echo
141 [[ -n "$help_text" ]] && sed -e '/^[ ]*$/d; s/^[ ]*/# /g' <<< "$help_text"
142 printf "$name=$val"
143 [[ "$val" == "$default_value" ]] && printf " # default"
144 echo
145 done
146 }
147
148 complete_prefs()
149 {
150 gsu_complete_options "e" "$@"
151 }
152
153 export gsu_man_txt="
154 Print the manual.
155
156 Usage: man"
157
158 _com_man()
159 {
160 local equal_signs="=================================================="
161 local minus_signs="--------------------------------------------------"
162 local com num
163
164 echo "$gsu_name (_${gsu_banner_txt}_) manual"
165 echo "${equal_signs:0:${#gsu_name} + ${#gsu_banner_txt} + 16}"
166 echo
167
168 sed -e '1,/^#\{70,\}/d' -e '/^#\{70,\}/,$d' $0 -e 's/^# *//'
169 echo "----"
170 echo
171 echo "$gsu_name usage"
172 echo "${minus_signs:0:${#gsu_name} + 6}"
173 printf "\t"
174 _gsu_usage 2>&1
175 echo "Each command has its own set of options as described below."
176 echo
177 echo "----"
178 echo
179 echo "Available commands:"
180
181 _gsu_available_commands
182 for com in $result; do
183 num=${#com}
184 (($num < 4)) && num=4
185 echo "${minus_signs:0:$num}"
186 echo "$com"
187 echo "${minus_signs:0:$num}"
188 $0 help $com
189 echo
190 done
191 ret=$GSU_SUCCESS
192 }
193
194 export gsu_help_txt="
195 Print online help.
196
197 Usage: help [command]
198
199 Without arguments, print the list of available commands. Otherwise,
200 print the help text for the given command."
201
202 export gsu_complete_txt="
203 Command line completion.
204
205 Usage: complete [<cword> <word>...]
206
207 When executed without argument the command writes bash code to
208 stdout. This code is suitable to be evaled from .bashrc to enable
209 completion.
210
211 If at least one argument is given, all possible completions are
212 written to stdout. This can be used from the completion function of
213 the subcommand.
214 "
215
216 _com_help()
217 {
218 local a b ere tab=' '
219
220 _gsu_get_command_regex
221 ere="$result"
222
223 if (($# == 0)); then
224 gsu_short_msg "### $gsu_name -- $gsu_banner_txt ###"
225 _gsu_usage 2>&1
226 {
227 printf "com_help()\n$gsu_help_txt" | head -n 4; echo "--"
228 printf "com_man()\n$gsu_man_txt" | head -n 4; echo "--"
229 printf "com_prefs()\n$gsu_prefs_txt" | head -n 4; echo "--"
230 printf "com_complete()\n$gsu_complete_txt" | head -n 4; echo "--"
231 grep -EA 2 "$ere" $0
232 } | grep -v -- '--' \
233 | sed -En "/$ere/"'!d
234 # remove everything but the command name
235 s/^com_(.*)\(\).*/\1/
236
237 # append tab after short commands (less than 8 chars)
238 s/^(.{1,7})$/\1'"$tab"'/g
239
240 # remove next line (should contain only ## anyway)
241 N
242 s/#.*//
243
244 # append next line, removing leading ##
245 N
246 s/#+ *//g
247
248 # replace newline by tab
249 y/\n/'"$tab"'/
250
251 # and print the sucker
252 p'
253 echo
254 echo "# Try $gsu_name help <command> for info on <command>."
255 ret=$GSU_SUCCESS
256 return
257 fi
258 if test "$1" = "help"; then
259 echo "$gsu_help_txt"
260 ret=$GSU_SUCCESS
261 return
262 fi
263 if test "$1" = "man"; then
264 echo "$gsu_man_txt"
265 ret=$GSU_SUCCESS
266 return
267 fi
268 if test "$1" = "prefs"; then
269 echo "$gsu_prefs_txt"
270 ret=$GSU_SUCCESS
271 return
272 fi
273 if test "$1" = "complete"; then
274 echo "$gsu_complete_txt"
275 ret=$GSU_SUCCESS
276 return
277 fi
278 ret=$GSU_SUCCESS
279 _gsu_get_command_regex "$1"
280 ere="$result"
281 if ! grep -Eq "$ere" $0; then
282 _gsu_print_available_commands
283 result="$1"
284 ret=-$E_GSU_BAD_COMMAND
285 return
286 fi
287 sed -nEe '
288 # only consider lines in the comment of the function
289 /'"$ere"'/,/^[^#]/ {
290
291 # remove leading ##
292 s/^## *//
293
294 # if it did start with ##, jump to label p and print it
295 tp
296
297 # otherwise, move on to next line
298 d
299
300 # print it
301 :p
302 p
303 }
304 ' $0
305 }
306
307 complete_help()
308 {
309 _gsu_available_commands
310 echo "$result"
311 }
312
313 # Wrapper for the bash getopts builtin.
314 #
315 # Aborts on programming errors such as missing or invalid option string. On
316 # success $result contains shell code that can be eval'ed. For each defined
317 # option x, the local variable o_x will be created when calling eval "$result".
318 # o_x contains true/false for options without argument and either the emtpy
319 # string or the given argument for options that take an argument.
320 #
321 # Example:
322 # gsu_getopts abc:x:y
323 # eval "$result"
324 # (($ret < 0)) && return
325 #
326 # [[ "$o_a" = 'true' ]] && echo 'The -a flag was given'
327 # [[ -n "$o_c" ]] && echo "The -c option was given with arg $o_c"
328 gsu_getopts()
329 {
330 local i c tab=' ' cr='
331 '
332
333 gsu_check_arg_count $# 1 1
334 if (($ret < 0)); then
335 gsu_err_msg
336 exit 1
337 fi
338
339 ret=-$E_GSU_GETOPTS
340 result="invalid optstring $1"
341 if [[ -z "$1" ]] || grep -q '::' <<< "$1" ; then
342 gsu_err_msg
343 exit 1
344 fi
345
346 for ((i=0; i < ${#1}; i++)); do
347 c=${1:$i:1}
348 case "$c" in
349 [a-zA-Z:]);;
350 *)
351 ret=-$E_GSU_GETOPTS
352 result="invalid character $c in optstring"
353 gsu_err_msg
354 exit 1
355 esac
356 done
357 result="local _gsu_getopts_opt"
358 for ((i=0; i < ${#1}; i++)); do
359 c1=${1:$i:1}
360 c2=${1:$(($i + 1)):1}
361 result+=" o_$c1="
362 if [[ "$c2" = ":" ]]; then
363 let i++
364 else
365 result+="false"
366 fi
367 done
368 result+="
369 OPTIND=1
370 while getopts $1 _gsu_getopts_opt \"\$@\"; do
371 case \"\$_gsu_getopts_opt\" in
372 "
373 for ((i=0; i < ${#1}; i++)); do
374 c1=${1:$i:1}
375 c2=${1:$(($i + 1)):1}
376 result+="$tab$tab$c1) o_$c1="
377 if [[ "$c2" = ":" ]]; then
378 result+="\"\$OPTARG\""
379 let i++
380 else
381 result+="true"
382 fi
383 result+=";;$cr"
384 done
385 result+="
386 *)
387 ret=-\$E_GSU_GETOPTS
388 result=\"invalid option given\"
389 return
390 ;;
391 esac
392 done
393 shift \$((\$OPTIND - 1))
394 "
395 ret=$GSU_SUCCESS
396 }
397
398 _com_complete()
399 {
400 local cmd n cword
401 local -a words
402
403 if (($# == 0)); then
404 cat <<EOF
405 local cur="\${COMP_WORDS[\$COMP_CWORD]}";
406 local -a candidates;
407
408 candidates=(\$($0 complete "\$COMP_CWORD" "\${COMP_WORDS[@]}"));
409 COMPREPLY=(\$(compgen -W "\${candidates[*]}" -- "\$cur"));
410 EOF
411 ret=$GSU_SUCCESS
412 return
413 fi
414
415 cword="$1"
416 gsu_is_a_number "$cword"
417 (($ret < 0)) && return
418 if (($cword <= 1)); then
419 _gsu_available_commands
420 echo "${result}"
421 ret=$GSU_SUCCESS
422 return
423 fi
424 shift
425 words=("$@")
426 cmd="${words[1]}"
427 ret=$GSU_SUCCESS # It's not an error if no completer was defined
428 [[ "$(type -t complete_$cmd)" != "function" ]] && return
429 complete_$cmd "$cword" "${words[@]}"
430 # ignore errors, they would only clutter the completion output
431 ret=$GSU_SUCCESS
432 }
433
434 # Find out if the current word is a parameter for an option.
435 #
436 # $1: usual getopts option string.
437 # $2: The current word number.
438 # $3..: All words of the current command line.
439 #
440 # return: If yes, $result contains the letter of the option for which the
441 # current word is a parameter. Otherwise, $result is empty.
442 #
443 gsu_cword_is_option_parameter()
444 {
445 local opts="$1" cword="$2" prev i n
446 local -a words
447
448 result=
449 (($cword == 0)) && return
450 ((${#opts} < 2)) && return
451
452 shift 2
453 words=("$@")
454 prev="${words[$(($cword - 1))]}"
455 [[ ! "$prev" == -* ]] && return
456
457 n=$((${#opts} - 1))
458 for ((i=0; i <= $n; i++)); do
459 opt="${opts:$i:1}"
460 [[ "${opts:$(($i + 1)):1}" != ":" ]] && continue
461 let i++
462 [[ "$prev" != "-$opt" ]] && continue
463 result="$opt"
464 return
465 done
466 ret=0
467 }
468
469 # Get the word number on which the cursor is, not counting options.
470 #
471 # This is useful for completing commands whose possible completions depend
472 # on the word number, for example mount.
473 #
474 # $1: Getopt option string.
475 # $2: The current word number.
476 # $3..: All words of the current command line.
477 #
478 # return: If the current word is an option, or a parameter to an option,
479 # this function sets $result to -1. Otherwise, the number of the non-option
480 # is returned in $result.
481 #
482 gsu_get_unnamed_arg_num()
483 {
484 local opts="$1" cword="$2" prev cur
485 local -i i n=0
486 local -a words
487
488 shift 2
489 words=("$@")
490 cur="${words[$cword]}"
491 prev="${words[$(($cword - 1))]}"
492 result=-1
493 [[ "$cur" == -* ]] && return
494 [[ "$prev" == -* ]] && [[ "$opts" == *${prev#-}:* ]] && return
495
496 for ((i=1; i <= $cword; i++)); do
497 prev="${words[$(($i - 1))]}"
498 cur="${words[$i]}"
499 [[ "$cur" == -* ]] && continue
500 if [[ "$prev" == -* ]]; then
501 opt=${prev#-}
502 [[ "$opts" != *$opt:* ]] && let n++
503 continue
504 fi
505 let n++
506 done
507 result="$(($n - 1))"
508 }
509
510 # Entry point for all gsu-based scripts.
511 #
512 # The startup part of the application script should source this file to load
513 # the functions defined here, and then call gsu(). Functions starting with com_
514 # are automatically recognized as subcommands.
515 #
516 # Minimal example:
517 #
518 # com_hello()
519 # {
520 # echo 'hello world'
521 # }
522 # gsu_dir=${gsu_dir:-/system/location/where/gsu/is/installed}
523 # . $gsu_dir/subcommand || exit 1
524 # gsu "$@"
525 gsu()
526 {
527 local i
528 _gsu_available_commands
529 gsu_cmds="$result"
530 if (($# == 0)); then
531 _gsu_usage
532 _gsu_print_available_commands
533 exit 1
534 fi
535 arg="$1"
536 shift
537 # check internal commands
538 if [[ "$arg" = "help" || "$arg" = "man" || "$arg" = "prefs" || "$arg" = "complete" ]]; then
539 _com_$arg "$@"
540 if (("$ret" < 0)); then
541 gsu_err_msg
542 exit 1
543 fi
544 exit 0
545 fi
546
547 # external commands
548 for i in $gsu_cmds; do
549 if test "$arg" = "$i"; then
550 com_$arg "$@"
551 if (("$ret" < 0)); then
552 gsu_err_msg
553 exit 1
554 fi
555 exit 0
556 fi
557 done
558
559 ret=-$E_GSU_BAD_COMMAND
560 result="$arg"
561 gsu_err_msg
562 _gsu_print_available_commands
563 exit 1
564 }
565
566 # Check number of arguments.
567 #
568 # Usage: gsu_check_arg_count <num_given> <num1> [<num2>]
569 #
570 # Check that <num_given> is between <num1> and <num2> inclusively.
571 # If only <num1> ist given, num2 is assumed to be infinity.
572 #
573 # Examples:
574 # 0 0 no argument allowed
575 # 1 1 exactly one argument required
576 # 0 2 at most two arguments admissible
577 # 2 at least two arguments reqired
578 #
579 gsu_check_arg_count()
580 {
581 ret=-$E_GSU_BAD_ARG_COUNT
582 if (($# == 2)); then # only num1 is given
583 result="at least $2 args required, $1 given"
584 (($1 < $2)) && return
585 ret=$GSU_SUCCESS
586 return
587 fi
588 # num1 and num2 given
589 result="need at least $2 args, $1 given"
590 (($1 < $2)) && return
591 result="need at most $3 args, $1 given"
592 (($1 > $3)) && return
593 ret=$GSU_SUCCESS
594 }