Unify gsu_dir handling.
[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_self 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 [[ $? -ne 0 ]] && return
115 ret=-$E_GSU_EDITOR
116 result="${EDITOR:-vi}"
117 "$result" "$conf"
118 [[ $? -ne 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_self (_${gsu_banner_txt}_) manual"
165 echo "${equal_signs:0:${#_gsu_self} + ${#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_self usage"
172 echo "${minus_signs:0:${#_gsu_self} + 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 if test $num -lt 4; then
185 num=4
186 fi
187 echo "${minus_signs:0:$num}"
188 echo "$com"
189 echo "${minus_signs:0:$num}"
190 $0 help $com
191 echo
192 done
193 ret=$GSU_SUCCESS
194 }
195
196 _gsu_banner_msg()
197 {
198 gsu_short_msg "### $_gsu_self -- ###"
199 }
200
201 export gsu_help_txt="
202 Print online help.
203
204 Usage: help [command]
205
206 Without arguments, print the list of available commands. Otherwise,
207 print the help text for the given command."
208
209 export gsu_complete_txt="
210 Command line completion.
211
212 Usage: complete [<cword> <word>...]
213
214 When executed without argument the command writes bash code to
215 stdout. This code is suitable to be evaled from .bashrc to enable
216 completion.
217
218 If at least one argument is given, all possible completions are
219 written to stdout. This can be used from the completion function of
220 the subcommand.
221 "
222
223 _com_help()
224 {
225 local a b ere tab=' '
226
227 _gsu_get_command_regex
228 ere="$result"
229
230 if (($# == 0)); then
231 _gsu_banner_msg 2>&1
232 _gsu_usage 2>&1
233 {
234 printf "com_help()\n$gsu_help_txt" | head -n 4; echo "--"
235 printf "com_man()\n$gsu_man_txt" | head -n 4; echo "--"
236 printf "com_prefs()\n$gsu_prefs_txt" | head -n 4; echo "--"
237 printf "com_complete()\n$gsu_complete_txt" | head -n 4; echo "--"
238 grep -EA 2 "$ere" $0
239 } | grep -v -- '--' \
240 | sed -En "/$ere/"'!d
241 # remove everything but the command name
242 s/^com_(.*)\(\).*/\1/
243
244 # append tab after short commands (less than 8 chars)
245 s/^(.{1,7})$/\1'"$tab"'/g
246
247 # remove next line (should contain only ## anyway)
248 N
249 s/#.*//
250
251 # append next line, removing leading ##
252 N
253 s/#+ *//g
254
255 # replace newline by tab
256 y/\n/'"$tab"'/
257
258 # and print the sucker
259 p'
260 echo
261 echo "# Try $_gsu_self help <command> for info on <command>."
262 ret=$GSU_SUCCESS
263 return
264 fi
265 if test "$1" = "help"; then
266 echo "$gsu_help_txt"
267 ret=$GSU_SUCCESS
268 return
269 fi
270 if test "$1" = "man"; then
271 echo "$gsu_man_txt"
272 ret=$GSU_SUCCESS
273 return
274 fi
275 if test "$1" = "prefs"; then
276 echo "$gsu_prefs_txt"
277 ret=$GSU_SUCCESS
278 return
279 fi
280 if test "$1" = "complete"; then
281 echo "$gsu_complete_txt"
282 ret=$GSU_SUCCESS
283 return
284 fi
285 ret=$GSU_SUCCESS
286 _gsu_get_command_regex "$1"
287 ere="$result"
288 if ! grep -Eq "$ere" $0; then
289 _gsu_print_available_commands
290 result="$1"
291 ret=-$E_GSU_BAD_COMMAND
292 return
293 fi
294 sed -nEe '
295 # only consider lines in the comment of the function
296 /'"$ere"'/,/^[^#]/ {
297
298 # remove leading ##
299 s/^## *//
300
301 # if it did start with ##, jump to label p and print it
302 tp
303
304 # otherwise, move on to next line
305 d
306
307 # print it
308 :p
309 p
310 }
311 ' $0
312 }
313
314 complete_help()
315 {
316 _gsu_available_commands
317 echo "$result"
318 }
319
320 # Wrapper for the bash getopts builtin.
321 #
322 # Aborts on programming errors such as missing or invalid option string. On
323 # success $result contains shell code that can be eval'ed. For each defined
324 # option x, the local variable o_x will be created when calling eval "$result".
325 # o_x contains true/false for options without argument and either the emtpy
326 # string or the given argument for options that take an argument.
327 #
328 # Example:
329 # gsu_getopts abc:x:y
330 # eval "$result"
331 # (($ret < 0)) && return
332 #
333 # [[ "$o_a" = 'true' ]] && echo 'The -a flag was given'
334 # [[ -n "$o_c" ]] && echo "The -c option was given with arg $o_c"
335 gsu_getopts()
336 {
337 local i c tab=' ' cr='
338 '
339
340 gsu_check_arg_count $# 1 1
341 if [[ $ret -lt 0 ]]; then
342 gsu_err_msg
343 exit 1
344 fi
345
346 ret=-$E_GSU_GETOPTS
347 result="invalid optstring $1"
348 if [[ -z "$1" ]] || grep -q '::' <<< "$1" ; then
349 gsu_err_msg
350 exit 1
351 fi
352
353 for ((i=0; i < ${#1}; i++)); do
354 c=${1:$i:1}
355 case "$c" in
356 [a-zA-Z:]);;
357 *)
358 ret=-$E_GSU_GETOPTS
359 result="invalid character $c in optstring"
360 gsu_err_msg
361 exit 1
362 esac
363 done
364 result="local _gsu_getopts_opt"
365 for ((i=0; i < ${#1}; i++)); do
366 c1=${1:$i:1}
367 c2=${1:$(($i + 1)):1}
368 result+=" o_$c1="
369 if [[ "$c2" = ":" ]]; then
370 let i++
371 else
372 result+="false"
373 fi
374 done
375 result+="
376 OPTIND=1
377 while getopts $1 _gsu_getopts_opt \"\$@\"; do
378 case \"\$_gsu_getopts_opt\" in
379 "
380 for ((i=0; i < ${#1}; i++)); do
381 c1=${1:$i:1}
382 c2=${1:$(($i + 1)):1}
383 result+="$tab$tab$c1) o_$c1="
384 if [[ "$c2" = ":" ]]; then
385 result+="\"\$OPTARG\""
386 let i++
387 else
388 result+="true"
389 fi
390 result+=";;$cr"
391 done
392 result+="
393 *)
394 ret=-\$E_GSU_GETOPTS
395 result=\"invalid option given\"
396 return
397 ;;
398 esac
399 done
400 shift \$((\$OPTIND - 1))
401 "
402 ret=$GSU_SUCCESS
403 }
404
405 _com_complete()
406 {
407 local cmd n cword
408 local -a words
409
410 if (($# == 0)); then
411 cat <<EOF
412 local cur="\${COMP_WORDS[\$COMP_CWORD]}";
413 local -a candidates;
414
415 candidates=(\$($0 complete "\$COMP_CWORD" "\${COMP_WORDS[@]}"));
416 COMPREPLY=(\$(compgen -W "\${candidates[*]}" -- "\$cur"));
417 EOF
418 ret=$GSU_SUCCESS
419 return
420 fi
421
422 cword="$1"
423 gsu_is_a_number "$cword"
424 (($ret < 0)) && return
425 if (($cword <= 1)); then
426 _gsu_available_commands
427 echo "${result}"
428 ret=$GSU_SUCCESS
429 return
430 fi
431 shift
432 words=("$@")
433 cmd="${words[1]}"
434 ret=$GSU_SUCCESS # It's not an error if no completer was defined
435 [[ "$(type -t complete_$cmd)" != "function" ]] && return
436 complete_$cmd "$cword" "${words[@]}"
437 # ignore errors, they would only clutter the completion output
438 ret=$GSU_SUCCESS
439 }
440
441 # Find out if the current word is a parameter for an option.
442 #
443 # $1: usual getopts option string.
444 # $2: The current word number.
445 # $3..: All words of the current command line.
446 #
447 # return: If yes, $result contains the letter of the option for which the
448 # current word is a parameter. Otherwise, $result is empty.
449 #
450 gsu_cword_is_option_parameter()
451 {
452 local opts="$1" cword="$2" prev i n
453 local -a words
454
455 result=
456 (($cword == 0)) && return
457 ((${#opts} < 2)) && return
458
459 shift 2
460 words=("$@")
461 prev="${words[$(($cword - 1))]}"
462 [[ ! "$prev" == -* ]] && return
463
464 n=$((${#opts} - 1))
465 for ((i=0; i <= $n; i++)); do
466 opt="${opts:$i:1}"
467 [[ "${opts:$(($i + 1)):1}" != ":" ]] && continue
468 let i++
469 [[ "$prev" != "-$opt" ]] && continue
470 result="$opt"
471 return
472 done
473 ret=0
474 }
475
476 # Get the word number on which the cursor is, not counting options.
477 #
478 # This is useful for completing commands whose possible completions depend
479 # on the word number, for example mount.
480 #
481 # $1: Getopt option string.
482 # $2: The current word number.
483 # $3..: All words of the current command line.
484 #
485 # return: If the current word is an option, or a parameter to an option,
486 # this function sets $result to -1. Otherwise, the number of the non-option
487 # is returned in $result.
488 #
489 gsu_get_unnamed_arg_num()
490 {
491 local opts="$1" cword="$2" prev cur
492 local -i i n=0
493 local -a words
494
495 shift 2
496 words=("$@")
497 cur="${words[$cword]}"
498 prev="${words[$(($cword - 1))]}"
499 result=-1
500 [[ "$cur" == -* ]] && return
501 [[ "$prev" == -* ]] && [[ "$opts" == *${prev#-}:* ]] && return
502
503 for ((i=1; i <= $cword; i++)); do
504 prev="${words[$(($i - 1))]}"
505 cur="${words[$i]}"
506 [[ "$cur" == -* ]] && continue
507 if [[ "$prev" == -* ]]; then
508 opt=${prev#-}
509 [[ "$opts" != *$opt:* ]] && let n++
510 continue
511 fi
512 let n++
513 done
514 result="$(($n - 1))"
515 }
516
517 # Entry point for all gsu-based scripts.
518 #
519 # The startup part of the application script should source this file to load
520 # the functions defined here, and then call gsu(). Functions starting with com_
521 # are automatically recognized as subcommands.
522 #
523 # Minimal example:
524 #
525 # com_hello()
526 # {
527 # echo 'hello world'
528 # }
529 # gsu_dir=${gsu_dir:-/system/location/where/gsu/is/installed}
530 # . $gsu_dir/subcommand || exit 1
531 # gsu "$@"
532 gsu()
533 {
534 local i
535 _gsu_available_commands
536 gsu_cmds="$result"
537 if test $# -eq 0; then
538 _gsu_usage
539 _gsu_print_available_commands
540 exit 1
541 fi
542 arg="$1"
543 shift
544 # check internal commands
545 if [[ "$arg" = "help" || "$arg" = "man" || "$arg" = "prefs" || "$arg" = "complete" ]]; then
546 _com_$arg "$@"
547 if [[ "$ret" -lt 0 ]]; then
548 gsu_err_msg
549 exit 1
550 fi
551 exit 0
552 fi
553
554 # external commands
555 for i in $gsu_cmds; do
556 if test "$arg" = "$i"; then
557 com_$arg "$@"
558 if [[ "$ret" -lt 0 ]]; then
559 gsu_err_msg
560 exit 1
561 fi
562 exit 0
563 fi
564 done
565
566 ret=-$E_GSU_BAD_COMMAND
567 result="$arg"
568 gsu_err_msg
569 _gsu_print_available_commands
570 exit 1
571 }
572
573 # Check number of arguments.
574 #
575 # Usage: gsu_check_arg_count <num_given> <num1> [<num2>]
576 #
577 # Check that <num_given> is between <num1> and <num2> inclusively.
578 # If only <num1> ist given, num2 is assumed to be infinity.
579 #
580 # Examples:
581 # 0 0 no argument allowed
582 # 1 1 exactly one argument required
583 # 0 2 at most two arguments admissible
584 # 2 at least two arguments reqired
585 #
586 gsu_check_arg_count()
587 {
588 ret=-$E_GSU_BAD_ARG_COUNT
589 if [[ $# -eq 2 ]]; then # only num1 is given
590 result="at least $2 args required, $1 given"
591 [[ $1 -lt $2 ]] && return
592 ret=$GSU_SUCCESS
593 return
594 fi
595 # num1 and num2 given
596 result="need at least $2 args, $1 given"
597 [[ $1 -lt $2 ]] && return
598 result="need at most $3 args, $1 given"
599 [[ $1 -gt $3 ]] && return
600 ret=$GSU_SUCCESS
601 }