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