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