332e5a0d43273f352e133f4a31507b061b4bb018
[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 export -f _gsu_usage
14
15 # Each line matching this is recognized as a subcommand. The name
16 # of the subcommand is the first subexpression.
17 export gsu_command_regex='^com_\([-a-zA-Z_0-9]\+\)()'
18
19 _gsu_available_commands()
20 {
21         result="$({
22                 printf "help\nman\nprefs\n"
23                 sed -ne "s/$gsu_command_regex/\1/g;T;p" $0
24                 } | sort | tr '\n' ' ')"
25 }
26 export -f _gsu_available_commands
27
28 _gsu_print_available_commands()
29 {(
30         local i count
31         gsu_short_msg "Available commands:"
32         for i in $gsu_cmds; do
33                 printf "$i"
34                 count=$(($count + 1))
35                 if test $(($count % 4)) -eq 0; then
36                         echo
37                 else
38                         printf "\t"
39                         if test ${#i} -lt 8; then
40                                 printf "\t"
41                         fi
42                 fi
43         done
44         echo
45 ) 2>&1
46 }
47 export -f _gsu_print_available_commands
48
49 export gsu_prefs_txt="
50 Print the current preferences.
51
52 Usage: prefs [-e]
53
54 If -e is given, the config file is opened with the default editor.  Without
55 options, the command prints out a list of all cmt config variables, together
56 with their current value and the default value."
57 _com_prefs()
58 {
59         local i conf="${gsu_config_file:=$HOME/.$gsu_name.rc}"
60
61         if [[ "$1" = "-e" ]]; then
62                 ret=-$E_GSU_MKDIR
63                 result="${conf%/*}"
64                 mkdir -p "$result"
65                 [[ $? -ne 0 ]] && return
66                 ret=-$E_GSU_EDITOR
67                 result="${EDITOR:-vi}"
68                 "$result" "$conf"
69                 [[ $? -ne 0 ]] && return
70                 ret=$GSU_SUCCESS
71                 return
72         fi
73
74         for ((i=0; i < ${#gsu_options[@]}; i++)); do
75                 local name= option_type= default_value= required=
76                 local description= help_text=
77                 eval "${gsu_options[$i]}"
78                 eval val='"$'${gsu_config_var_prefix}_$name'"'
79                 case "$required" in
80                 true|yes)
81                         printf "# required"
82                         ;;
83                 *)
84                         printf "# optional"
85                         ;;
86                 esac
87                 printf " $option_type: $description"
88                 if [[ "$required" != "yes" && "$required" != "true" ]]; then
89                         printf " [$default_value]"
90                 fi
91                 echo
92                 [[ -n "$help_text" ]] && sed -e '/^[    ]*$/d; s/^[     ]*/#    /g' <<< "$help_text"
93                 printf "$name=$val"
94                 [[ "$val" == "$default_value" ]] && printf " # default"
95                 echo
96         done
97 }
98 export -f _com_prefs
99
100 export gsu_man_txt="
101 Print the manual.
102
103 Usage: man"
104
105 _com_man()
106 {
107         local equal_signs="=================================================="
108         local minus_signs="--------------------------------------------------"
109         local com num
110
111         echo "$_gsu_self (_${gsu_banner_txt}_) manual"
112         echo "${equal_signs:0:${#_gsu_self} + ${#gsu_banner_txt} + 16}"
113         echo
114
115         sed -e '1,/^#\{70,\}/d' -e '/^#\{70,\}/,$d' $0 -e 's/^# *//'
116         echo "----"
117         echo
118         echo "$_gsu_self usage"
119         echo "${minus_signs:0:${#_gsu_self} + 6}"
120         printf "\t"
121         _gsu_usage 2>&1
122         echo "Each command has its own set of options as described below."
123         echo
124         echo "----"
125         echo
126         echo "Available commands:"
127
128         _gsu_available_commands
129         for com in $result; do
130                 num=${#com}
131                 if test $num -lt 4; then
132                         num=4
133                 fi
134                 echo "${minus_signs:0:$num}"
135                 echo "$com"
136                 echo "${minus_signs:0:$num}"
137                 $0 help $com
138                 echo
139         done
140         ret=$GSU_SUCCESS
141 }
142 export -f _com_man
143
144 _gsu_banner_msg()
145 {
146         local txt="### $_gsu_self --"
147         if test -z "$gsu_banner_txt"; then
148                 txt="$txt set \$gsu_banner_txt to customize this message"
149         else
150                 txt="$txt $gsu_banner_txt"
151         fi
152         gsu_short_msg "$txt ###"
153 }
154 export -f _gsu_banner_msg
155
156 export gsu_help_txt="
157 Print online help.
158
159 Usage: help [command]
160
161 Without arguments, print the list of available commands. Otherwise,
162 print the help text for the given command."
163
164 _com_help()
165 {
166         local a b
167         if test -z "$1"; then
168                 _gsu_banner_msg 2>&1
169                 _gsu_usage 2>&1
170                 {
171                         printf "com_help()\n$gsu_help_txt" | head -n 4; echo "--"
172                         printf "com_man()\n$gsu_man_txt" | head -n 4; echo "--"
173                         printf "com_prefs()\n$gsu_prefs_txt" | head -n 4; echo "--"
174                         grep -A 2 "$gsu_command_regex" $0
175                 } | grep -v -- '--' \
176                         | sed -e "/$gsu_command_regex/bs" \
177                                 -e 'H;$!d;x;s/\n//g;b' \
178                                 -e :s \
179                                 -e 'x;s/\n//g;${p;x;}' \
180                         | sed -e "s/${gsu_command_regex}#*/\1\t/" \
181                         | sort \
182                         | while read a b; do
183                                 printf "$a\t"
184                                 if test ${#a} -lt 8; then
185                                         printf "\t"
186                                 fi
187                                 echo "$b"
188                         done
189                 echo
190                 echo "# Try $_gsu_self help <command> for info on <command>."
191                 ret=$GSU_SUCCESS
192                 return
193         fi
194         if test "$1" = "help"; then
195                 echo "$gsu_help_txt"
196                 ret=$GSU_SUCCESS
197                 return
198         fi
199         if test "$1" = "man"; then
200                 echo "$gsu_man_txt"
201                 ret=$GSU_SUCCESS
202                 return
203         fi
204         if test "$1" = "prefs"; then
205                 echo "$gsu_prefs_txt"
206                 ret=$GSU_SUCCESS
207                 return
208         fi
209         ret=$GSU_SUCCESS
210         if grep -q "^com_$1()" $0; then
211                 sed -e "1,/^com_$1()$/d" -e '/^{/,$d' -e 's/^## *//' $0
212                 return
213         fi
214         _gsu_print_available_commands
215         result="$1"
216         ret=-$E_GSU_BAD_COMMAND
217 }
218 export -f _com_help
219
220 # Wrapper for bash's getopts.
221 #
222 # Aborts on programming errors such as missing or invalid option string.  On
223 # success $result contains shell code that can be eval'ed. For each defined
224 # option x, the local variable o_x will be created when calling eval "$result".
225 # o_x contains true/false for options without an argument or the emtpy string/the
226 # given argument, depending on whether this option was contained in the "$@"
227 # array.
228 #
229 # Example:
230 #       gsu_getopts abc:x:y
231 #       eval "$result"
232 #       [[ $ret -lt 0 ]] && return
233 #
234 #       [[ "$o_a" = "true ]] && echo "The -a flag was given"
235 #       [[ -n "$o_c" ]] && echo "The -c option was given with arg $o_c"
236 gsu_getopts()
237 {
238         local i c tab=' ' cr='
239 '
240
241         gsu_check_arg_count $# 1 1
242         if [[ $ret -lt 0 ]]; then
243                 gsu_err_msg
244                 exit 1
245         fi
246
247         ret=-$E_GSU_GETOPTS
248         result="invalid optstring $1"
249         if [[ -z "$1" ]] || grep -q '::' <<< "$1" ; then
250                 gsu_err_msg
251                 exit 1
252         fi
253
254         for ((i=0; i < ${#1}; i++)); do
255                 c=${1:$i:1}
256                 case "$c" in
257                 [a-zA-Z:]);;
258                 *)
259                         ret=-$E_GSU_GETOPTS
260                         result="invalid character $c in optstring"
261                         gsu_err_msg
262                         exit 1
263                 esac
264         done
265         result="local opt"
266         for ((i=0; i < ${#1}; i++)); do
267                 c1=${1:$i:1}
268                 c2=${1:$(($i + 1)):1}
269                 result+=" o_$c1"
270                 if [[ "$c2" = ":" ]]; then
271                         let i++
272                 else
273                         result+="=false"
274                 fi
275         done
276         result+="
277         OPTIND=1
278         while getopts $1 opt \"\$@\"; do
279                 case \"\$opt\" in
280 "
281         for ((i=0; i < ${#1}; i++)); do
282                 c1=${1:$i:1}
283                 c2=${1:$(($i + 1)):1}
284                 result+="$tab$tab$c1) o_$c1="
285                 if [[ "$c2" = ":" ]]; then
286                         result+="\"\$OPTARG\""
287                         let i++
288                 else
289                         result+="true"
290                 fi
291                 result+=";;$cr"
292         done
293         result+="
294                 *)
295                         ret=-\$E_GSU_GETOPTS
296                         result=\"invalid option given\"
297                         return
298                         ;;
299                 esac
300         done
301         shift \$((\$OPTIND - 1))
302 "
303         ret=$GSU_SUCCESS
304 }
305 export -f gsu_getopts
306
307 gsu()
308 {
309         local i
310         _gsu_setup
311         _gsu_available_commands
312         gsu_cmds="$result"
313         if test $# -eq 0; then
314                 _gsu_usage
315                 _gsu_print_available_commands
316                 exit 1
317         fi
318         arg="$1"
319         shift
320         # check internal commands
321         if [[ "$arg" = "help" || "$arg" = "man" || "$arg" = "prefs" ]]; then
322                 _com_$arg "$@"
323                 if [[ "$ret" -lt 0 ]]; then
324                         gsu_err_msg
325                         exit 1
326                 fi
327                 exit 0
328         fi
329
330         # external commands
331         for i in $gsu_cmds; do
332                 if test "$arg" = "$i"; then
333                         com_$arg "$@"
334                         if [[ "$ret" -lt 0 ]]; then
335                                 gsu_err_msg
336                                 exit 1
337                         fi
338                         exit 0
339                 fi
340         done
341
342         ret=-$E_GSU_BAD_COMMAND
343         result="$arg"
344         gsu_err_msg
345         _gsu_print_available_commands
346         exit 1
347 }
348 export -f gsu
349
350 # Check number of arguments.
351 #
352 # Usage: gsu_check_arg_count <num_given> <num1> [<num2>]
353 #
354 # Check that <num_given> is between <num1> and <num2> inclusively.
355 # If only <num1> ist given, num2 is assumed to be infinity.
356 #
357 # Examples:
358 #       0 0 no argument allowed
359 #       1 1 exactly one argument required
360 #       0 2 at most two arguments admissible
361 #       2   at least two arguments reqired
362 #
363 gsu_check_arg_count()
364 {
365         ret=-$E_GSU_BAD_ARG_COUNT
366         if [[ $# -eq 2 ]]; then # only num1 is given
367                 result="at least $2 args required, $1 given"
368                 [[ $1 -lt $2 ]] && return
369                 ret=$GSU_SUCCESS
370                 return
371         fi
372         # num1 and num2 given
373         result="need at least $2 args, $1 given"
374         [[ $1 -lt $2 ]] && return
375         result="need at most $3 args, $1 given"
376         [[ $1 -gt $3 ]] && return
377         ret=$GSU_SUCCESS
378 }
379 export -f gsu_check_arg_count
380