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 }