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