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