gsu_getopts: Rename local variable opt to _gsu_getopts_opt.
[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 if [[ "$1" = "-e" ]]; then
96 ret=-$E_GSU_MKDIR
97 result="${conf%/*}"
98 mkdir -p "$result"
99 [[ $? -ne 0 ]] && return
100 ret=-$E_GSU_EDITOR
101 result="${EDITOR:-vi}"
102 "$result" "$conf"
103 [[ $? -ne 0 ]] && return
104 ret=$GSU_SUCCESS
105 return
106 fi
107
108 for ((i=0; i < ${#gsu_options[@]}; i++)); do
109 local name= option_type= default_value= required=
110 local description= help_text=
111 eval "${gsu_options[$i]}"
112 eval val='"$'${gsu_config_var_prefix}_$name'"'
113 case "$required" in
114 true|yes)
115 printf "# required"
116 ;;
117 *)
118 printf "# optional"
119 ;;
120 esac
121 printf " $option_type: $description"
122 if [[ "$required" != "yes" && "$required" != "true" ]]; then
123 printf " [$default_value]"
124 fi
125 echo
126 [[ -n "$help_text" ]] && sed -e '/^[ ]*$/d; s/^[ ]*/# /g' <<< "$help_text"
127 printf "$name=$val"
128 [[ "$val" == "$default_value" ]] && printf " # default"
129 echo
130 done
131 }
132
133 complete_prefs()
134 {
135 gsu_complete_options "e" "$@"
136 }
137
138 export gsu_man_txt="
139 Print the manual.
140
141 Usage: man"
142
143 _com_man()
144 {
145 local equal_signs="=================================================="
146 local minus_signs="--------------------------------------------------"
147 local com num
148
149 echo "$_gsu_self (_${gsu_banner_txt}_) manual"
150 echo "${equal_signs:0:${#_gsu_self} + ${#gsu_banner_txt} + 16}"
151 echo
152
153 sed -e '1,/^#\{70,\}/d' -e '/^#\{70,\}/,$d' $0 -e 's/^# *//'
154 echo "----"
155 echo
156 echo "$_gsu_self usage"
157 echo "${minus_signs:0:${#_gsu_self} + 6}"
158 printf "\t"
159 _gsu_usage 2>&1
160 echo "Each command has its own set of options as described below."
161 echo
162 echo "----"
163 echo
164 echo "Available commands:"
165
166 _gsu_available_commands
167 for com in $result; do
168 num=${#com}
169 if test $num -lt 4; then
170 num=4
171 fi
172 echo "${minus_signs:0:$num}"
173 echo "$com"
174 echo "${minus_signs:0:$num}"
175 $0 help $com
176 echo
177 done
178 ret=$GSU_SUCCESS
179 }
180
181 _gsu_banner_msg()
182 {
183 local txt="### $_gsu_self --"
184 if test -z "$gsu_banner_txt"; then
185 txt="$txt set \$gsu_banner_txt to customize this message"
186 else
187 txt="$txt $gsu_banner_txt"
188 fi
189 gsu_short_msg "$txt ###"
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="$1"
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 fi
409
410 [[ -z "$cword" ]] && return
411 if (($cword <= 1)); then
412 _gsu_available_commands
413 echo "${result}"
414 ret=$GSU_SUCCESS
415 return
416 fi
417 shift
418 words=("$@")
419 cmd="${words[1]}"
420 ret=$GSU_SUCCESS # It's not an error if no completer was defined
421 [[ "$(type -t complete_$cmd)" != "function" ]] && return
422 complete_$cmd "$cword" "${words[@]}"
423 # ignore errors, they would only clutter the completion output
424 ret=$GSU_SUCCESS
425 }
426
427 gsu_cword_is_option_parameter()
428 {
429 local opts="$1" cword="$2" prev i n
430 local -a words
431
432 result=
433 (($cword == 0)) && return
434 ((${#opts} < 2)) && return
435
436 shift 2
437 words=("$@")
438 prev="${words[$(($cword - 1))]}"
439 [[ ! "$prev" == -* ]] && return
440
441 n=$((${#opts} - 1))
442 for ((i=0; i < $n; i++)); do
443 opt="${opts:$i:1}"
444 [[ "${opts:$(($i + 1)):1}" != ":" ]] && continue
445 let i++
446 [[ "$prev" != "-$opt" ]] && continue
447 result="$opt"
448 return
449 done
450 ret=0
451 }
452
453 gsu()
454 {
455 local i
456 _gsu_setup
457 _gsu_available_commands
458 gsu_cmds="$result"
459 if test $# -eq 0; then
460 _gsu_usage
461 _gsu_print_available_commands
462 exit 1
463 fi
464 arg="$1"
465 shift
466 # check internal commands
467 if [[ "$arg" = "help" || "$arg" = "man" || "$arg" = "prefs" || "$arg" = "complete" ]]; then
468 _com_$arg "$@"
469 if [[ "$ret" -lt 0 ]]; then
470 gsu_err_msg
471 exit 1
472 fi
473 exit 0
474 fi
475
476 # external commands
477 for i in $gsu_cmds; do
478 if test "$arg" = "$i"; then
479 com_$arg "$@"
480 if [[ "$ret" -lt 0 ]]; then
481 gsu_err_msg
482 exit 1
483 fi
484 exit 0
485 fi
486 done
487
488 ret=-$E_GSU_BAD_COMMAND
489 result="$arg"
490 gsu_err_msg
491 _gsu_print_available_commands
492 exit 1
493 }
494
495 # Check number of arguments.
496 #
497 # Usage: gsu_check_arg_count <num_given> <num1> [<num2>]
498 #
499 # Check that <num_given> is between <num1> and <num2> inclusively.
500 # If only <num1> ist given, num2 is assumed to be infinity.
501 #
502 # Examples:
503 # 0 0 no argument allowed
504 # 1 1 exactly one argument required
505 # 0 2 at most two arguments admissible
506 # 2 at least two arguments reqired
507 #
508 gsu_check_arg_count()
509 {
510 ret=-$E_GSU_BAD_ARG_COUNT
511 if [[ $# -eq 2 ]]; then # only num1 is given
512 result="at least $2 args required, $1 given"
513 [[ $1 -lt $2 ]] && return
514 ret=$GSU_SUCCESS
515 return
516 fi
517 # num1 and num2 given
518 result="need at least $2 args, $1 given"
519 [[ $1 -lt $2 ]] && return
520 result="need at most $3 args, $1 given"
521 [[ $1 -gt $3 ]] && return
522 ret=$GSU_SUCCESS
523 }