Merge branch 't/arithmetic_eval'
[gsu.git] / 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_name command [options]"
12 }
13
14 # Return an extended regular expression to match against $0.
15 #
16 # When called without argument, the expression matches all lines which define a
17 # subcommand.
18 #
19 # If an argument is given, the returned expression matches only the subcommand
20 # passed as $1. This is useful to tell if a string is a valid subcommand.
21 #
22 # Regardless of whether an argument is given, the returned expression contains
23 # exactly one parenthesized subexpression for matching the command name.
24 _gsu_get_command_regex()
25 {
26 local cmd="${1:-[-a-zA-Z_0-9]+}"
27 result="^com_($cmd)\(\)"
28 }
29
30 _gsu_available_commands()
31 {
32 local ere
33
34 _gsu_get_command_regex
35 ere="$result"
36 result="$({
37 printf "help\nman\nprefs\ncomplete\n"
38 sed -Ee '
39 # if line matches, isolate command name
40 s/'"$ere"'/\1/g
41
42 # if there is a match, (print it and) start next cycle
43 t
44
45 # otherwise delete it
46 d
47 ' $0
48 } | sort | tr '\n' ' ')"
49 }
50
51 _gsu_print_available_commands()
52 {(
53 local i count=0
54 gsu_short_msg "Available commands:"
55 for i in $gsu_cmds; do
56 printf "$i"
57 count=$(($count + 1))
58 if test $(($count % 4)) -eq 0; then
59 echo
60 else
61 printf "\t"
62 if test ${#i} -lt 8; then
63 printf "\t"
64 fi
65 fi
66 done
67 echo
68 ) 2>&1
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_setup
528 _gsu_available_commands
529 gsu_cmds="$result"
530 if (($# == 0)); then
531 _gsu_usage
532 _gsu_print_available_commands
533 exit 1
534 fi
535 arg="$1"
536 shift
537 # check internal commands
538 if [[ "$arg" = "help" || "$arg" = "man" || "$arg" = "prefs" || "$arg" = "complete" ]]; then
539 _com_$arg "$@"
540 if (("$ret" < 0)); then
541 gsu_err_msg
542 exit 1
543 fi
544 exit 0
545 fi
546
547 # external commands
548 for i in $gsu_cmds; do
549 if test "$arg" = "$i"; then
550 com_$arg "$@"
551 if (("$ret" < 0)); then
552 gsu_err_msg
553 exit 1
554 fi
555 exit 0
556 fi
557 done
558
559 ret=-$E_GSU_BAD_COMMAND
560 result="$arg"
561 gsu_err_msg
562 _gsu_print_available_commands
563 exit 1
564 }
565
566 # Check number of arguments.
567 #
568 # Usage: gsu_check_arg_count <num_given> <num1> [<num2>]
569 #
570 # Check that <num_given> is between <num1> and <num2> inclusively.
571 # If only <num1> ist given, num2 is assumed to be infinity.
572 #
573 # Examples:
574 # 0 0 no argument allowed
575 # 1 1 exactly one argument required
576 # 0 2 at most two arguments admissible
577 # 2 at least two arguments reqired
578 #
579 gsu_check_arg_count()
580 {
581 ret=-$E_GSU_BAD_ARG_COUNT
582 if (($# == 2)); then # only num1 is given
583 result="at least $2 args required, $1 given"
584 (($1 < $2)) && return
585 ret=$GSU_SUCCESS
586 return
587 fi
588 # num1 and num2 given
589 result="need at least $2 args, $1 given"
590 (($1 < $2)) && return
591 result="need at most $3 args, $1 given"
592 (($1 > $3)) && return
593 ret=$GSU_SUCCESS
594 }