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