gsu: Use bash shebang.
[gsu.git] / funcs / gsu
1 #!/bin/bash
2 # gsu -- the global subcommand utility
3 # (C) 2006-2009 Andre Noll
4
5 _gsu_init_errors()
6 {
7         gsu_errors="
8 GSU_SUCCESS                     success
9 E_GSU_BAD_COMMAND               invalid command
10 E_GSU_NOT_A_NUMBER              not a number
11 E_GSU_SOURCE                    error in config file
12 E_GSU_CONFIG                    bad/missing config file option
13 E_GSU_BAD_CONFIG_VAR            invalid config variable
14 E_GSU_NEED_VALUE                value required but not given
15 E_GSU_BAD_BOOL                  bad value for boolian option
16 E_GSU_BAD_OPTION_TYPE           invalid option type
17 E_NO_DEFAULT                    missing default value
18 $gsu_errors
19 "
20         local a b i=0
21         while read a b; do
22                 if test -z "$a"; then
23                         continue
24                 fi
25                 #echo "a:$a,  b: $b"
26                 gsu_error_txt[i]="$b"
27                 eval $a=$i
28                 i=$(($i + 1))
29         done << EOF
30         $gsu_errors
31 EOF
32 }
33 export -f _gsu_init_errors
34
35 # check if $1 is a number
36 gsu_is_a_number()
37 {
38         result="$1"
39         if test "$1" -eq "$1" &> /dev/null; then
40                 ret=$GSU_SUCCESS
41         else
42                 ret=-$E_GSU_NOT_A_NUMBER
43         fi
44 }
45 export -f gsu_is_a_number
46
47 gsu_short_msg()
48 {
49         echo "$1" 1>&2
50 }
51 export -f gsu_short_msg
52
53 gsu_msg()
54 {
55         gsu_short_msg "$_gsu_self: $1"
56 }
57 export -f gsu_msg
58
59 gsu_date_msg()
60 {
61         gsu_short_msg "$_gsu_self $(date): $1"
62 }
63 export -f gsu_date_msg
64
65 _gsu_banner_msg()
66 {
67         local txt="*** $_gsu_self --"
68         if test -z "$gsu_banner_txt"; then
69                 txt="$txt set \$gsu_banner_txt to customize this message"
70         else
71                 txt="$txt $gsu_banner_txt"
72         fi
73         gsu_short_msg "$txt ***"
74 }
75 export -f _gsu_banner_msg
76
77 gsu_err_msg()
78 {
79         local txt="$result" err
80
81         gsu_is_a_number "$ret"
82         if test $ret -lt 0; then
83                 gsu_msg "unknown error ($ret:$txt)"
84                 exit 1
85         fi
86         if test $result -ge 0; then
87                 gsu_msg "unknown error ($result:$txt)"
88                 exit 1
89         fi
90         err=$((0 - $result))
91         if test -n "$txt"; then
92                 txt="$txt: ${gsu_error_txt[$err]}"
93         else
94                 txt="${gsu_error_txt[$err]}"
95         fi
96         gsu_msg "$txt"
97 }
98 export -f gsu_err_msg
99
100 _gsu_usage()
101 {
102         gsu_short_msg "Usage: $_gsu_self command [options]"
103 }
104 export -f _gsu_usage
105
106 _gsu_available_commands()
107 {
108         result="$( (printf "help\nman\nprefs\n"; grep "^com_[a-z_]\+()" $0) \
109                 | sed -e 's/^com_//' -e 's/()//' \
110                 | sort \
111                 | tr '\n' ' ')"
112         ret=$GSU_SUCCESS
113 }
114 export -f _gsu_available_commands
115
116 _gsu_print_available_commands()
117 {(
118         local i count
119         gsu_short_msg "Available commands:"
120         for i in $gsu_cmds; do
121                 printf "$i"
122                 count=$(($count + 1))
123                 if test $(($count % 4)) -eq 0; then
124                         echo
125                 else
126                         printf "\t"
127                         if test ${#i} -lt 8; then
128                                 printf "\t"
129                         fi
130                 fi
131         done
132         echo
133 ) 2>&1
134 }
135 export -f _gsu_print_available_commands
136
137 export gsu_prefs_txt="
138 Print the current preferences.
139
140 Usage: prefs
141
142 Print out a list of all cmt config variables, together with their current value
143 and the default value."
144 com_prefs()
145 {
146         local i
147
148         for ((i=0; i < ${#gsu_options[@]}; i++)); do
149                 local name= option_type= default_value= required=
150                 local description= help_text=
151                 eval "${gsu_options[$i]}"
152                 eval val='"$'${gsu_config_var_prefix}_$name'"'
153                 case "$required" in
154                 true|yes)
155                         printf "# required"
156                         ;;
157                 *)
158                         printf "# optional"
159                         ;;
160                 esac
161                 printf " $option_type: $description"
162                 if [[ "$required" != "yes" && "$required" != "true" ]]; then
163                         printf " [$default_value]"
164                 fi
165                 echo
166                 printf "$name=$val"
167                 [[ "$val" == "$default_value" ]] && printf " # default"
168                 echo
169         done
170 }
171 export -f com_prefs
172
173 export gsu_man_txt="
174 Print the manual.
175
176 Usage: man"
177
178 com_man()
179 {
180         local equal_signs="=================================================="
181         local minus_signs="--------------------------------------------------"
182         local com num
183
184         echo "$_gsu_self (_${gsu_banner_txt}_) manual"
185         echo "${equal_signs:0:${#_gsu_self} + ${#gsu_banner_txt} + 16}"
186         echo
187
188         sed -e '1,/^#\{70,\}/d' -e '/^#\{70,\}/,$d' $0 -e 's/^# *//'
189         echo "----"
190         echo
191         echo "$_gsu_self usage"
192         echo "${minus_signs:0:${#_gsu_self} + 6}"
193         printf "\t"
194         _gsu_usage 2>&1
195         echo "Each command has its own set of options as described below."
196         echo
197         echo "----"
198         echo
199         echo "Available commands:"
200
201         _gsu_available_commands
202         for com in $result; do
203                 num=${#com}
204                 if test $num -lt 4; then
205                         num=4
206                 fi
207                 echo "${minus_signs:0:$num}"
208                 echo "$com"
209                 echo "${minus_signs:0:$num}"
210                 $0 help $com
211                 echo
212         done
213         ret=$GSU_SUCCESS
214 }
215 export -f com_man
216
217 export gsu_help_txt="
218 Print online help.
219
220 Usage: help [command]
221
222 Without arguments, print the list of available commands. Otherwise,
223 print the help text for the given command."
224
225 com_help()
226 {
227         local a b
228         if test -z "$1"; then
229                 _gsu_banner_msg 2>&1
230                 _gsu_usage 2>&1
231                 # sed is magic, baby
232                 (printf "com_help()\n$gsu_help_txt" | head -n 4; echo "--"
233                 printf "com_man()\n$gsu_man_txt" | head -n 4; echo "--"
234                 printf "com_prefs()\n$gsu_prefs_txt" | head -n 4; echo "--"
235
236                 grep -A 2 "^com_\([a-zA-Z_0-9]\+\)()" $0) \
237                         | grep -v -- '--' \
238                         | sed -e '/^com_\([a-zA-Z_0-9]\+\)()/bs' \
239                                 -e 'H;$!d;x;s/\n//g;b' \
240                                 -e :s \
241                                 -e 'x;s/\n//g;${p;x;}' \
242                         | sed -e 's/^com_\([a-zA-Z_0-9]\+\)()#*/\1\t/' \
243                         | sort \
244                         | while read a b; do
245                                 printf "$a\t"
246                                 if test ${#a} -lt 8; then
247                                         printf "\t"
248                                 fi
249                                 echo "$b"
250                         done
251                 echo
252                 echo "Try $_gsu_self help <command> for info on <command>."
253                 ret=$GSU_SUCCESS
254                 return
255         fi
256         if test "$1" = "help"; then
257                 echo "$gsu_help_txt"
258                 ret=$GSU_SUCCESS
259                 return
260         fi
261         if test "$1" = "man"; then
262                 echo "$gsu_man_txt"
263                 ret=$GSU_SUCCESS
264                 return
265         fi
266         if test "$1" = "prefs"; then
267                 echo "$gsu_prefs_txt"
268                 ret=$GSU_SUCCESS
269                 return
270         fi
271         ret=$GSU_SUCCESS
272         if grep -q "^com_$1()" $0; then
273                 sed -e "1,/com_$1()/d" -e '/^{/,$d' -e 's/^## *//' $0
274                 return
275         fi
276         _gsu_print_available_commands
277         result="$1"
278         ret=-$E_GSU_BAD_COMMAND
279 }
280 export -f com_help
281
282 # internal gsu function that syntactically checks the gsu_options array
283 # for errors and parses the config file.
284 _gsu_check_options()
285 {
286         local i conf="${gsu_config_file:=$HOME/.$gsu_name.rc}"
287
288         [[ -r "$conf" ]] && source "$conf"
289
290         for ((i=0; i < ${#gsu_options[@]}; i++)); do
291                 local name= option_type= default_value= required=
292                 local description= help_text=
293                 local val
294
295                 eval "${gsu_options[$i]}"
296
297
298                 # Check name. It must be non-empty and consist of [a-zA-Z_0-9]
299                 # only.  Moreover it must not start with [a-zA-Z].
300
301                 ret=-$E_GSU_BAD_CONFIG_VAR
302                 result="$name"
303                 # bash's =~ works only for 3.2 and newer, so use grep
304                 echo "$name" | grep '^[a-zA-Z][a-zA-Z_0123456789]*$' &> /dev/null;
305                 [[ $? -ne 0 ]] && return
306
307                 eval val='"'\$$name'"'
308                 case "$required" in
309                 true|yes)
310                         ret=-$E_GSU_NEED_VALUE
311                         result="$name"
312                         [[ -z "$val" ]] && return
313                         ;;
314                 false|no)
315                         ;;
316                 *)
317                         ret=-$E_GSU_BAD_BOOL
318                         result="required: $required, name: $name, val: $val"
319                         return
320                 esac
321
322                 eval ${gsu_config_var_prefix}_$name='"'${val:=$default_value}'"'
323
324                 # Check option type. ATM, only num and string are supported
325                 # Other types may be added without breaking compatibility
326                 case "$option_type" in
327                 string)
328                         ;;
329                 num)
330                         gsu_is_a_number "$val"
331                         [[ $ret -lt 0 ]] && return
332                         ;;
333                 *)
334                         ret=-$E_GSU_BAD_OPTION_TYPE
335                         result="$name/$option_type"
336                         return
337                 esac
338         done
339         ret=$GSU_SUCCESS
340 }
341 export -f _gsu_check_options
342
343 gsu()
344 {
345         local i
346
347         _gsu_self="$(basename $0)"
348         gsu_name="${gsu_name:=$_gsu_self}"
349         gsu_config_var_prefix="${gsu_config_var_prefix:=$gsu_name}"
350         _gsu_init_errors
351         _gsu_check_options
352         if [[ "$ret" -lt 0 ]]; then
353                 if [[ "$1" != "help" && "$1" != "man" && "$1" != "prefs" ]]; then
354                         gsu_err_msg
355                         exit 1
356                 fi
357         fi
358         _gsu_available_commands
359         gsu_cmds="$result"
360         if test $# -eq 0; then
361                 _gsu_usage
362                 _gsu_print_available_commands
363                 exit 1
364         fi
365         arg="$1"
366         shift
367         for i in $gsu_cmds; do
368                 if test "$arg" = "$i"; then
369                         com_$arg "$@"
370                         if test $ret -lt 0; then
371                                 gsu_err_msg
372                                 exit 1
373                         fi
374                         exit 0
375                 fi
376         done
377         ret=-$E_GSU_BAD_COMMAND
378         result="$arg"
379         gsu_err_msg
380         _gsu_print_available_commands
381         exit 1
382 }
383 export -f gsu
384
385 # TODO: gsu_strerror: get error string