]> git.tuebingen.mpg.de Git - gsu.git/blob - funcs/gsu
448d551180ee0dee11896ac23746f7704186f9ad
[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_GSU_BAD_ARG_COUNT             invalid number of arguments
18 E_GSU_EDITOR                    failed to execute editor
19 E_NO_DEFAULT                    missing default value
20 $gsu_errors
21 "
22         local a b i=0
23         while read a b; do
24                 if test -z "$a"; then
25                         continue
26                 fi
27                 #echo "a:$a,  b: $b"
28                 gsu_error_txt[i]="$b"
29                 eval $a=$i
30                 i=$(($i + 1))
31         done << EOF
32         $gsu_errors
33 EOF
34 }
35 export -f _gsu_init_errors
36
37 # check if $1 is a number
38 gsu_is_a_number()
39 {
40         result="$1"
41         if test "$1" -eq "$1" &> /dev/null; then
42                 ret=$GSU_SUCCESS
43         else
44                 ret=-$E_GSU_NOT_A_NUMBER
45         fi
46 }
47 export -f gsu_is_a_number
48
49 # Check number of arguments.
50 #
51 # Usage: gsu_check_arg_count <num_given> <num1> [<num2>]
52 #
53 # Check that <num_given> is between <num1> and <num2> inclusively.
54 # If only <num1> ist given, num2 is assumed to be infinity.
55 #
56 # Examples:
57 #       0 0 no argument allowed
58 #       1 1 exactly one argument required
59 #       0 2 at most two arguments admissible
60 #       2   at least two arguments reqired
61 #
62 gsu_check_arg_count()
63 {
64         ret=-$E_GSU_BAD_ARG_COUNT
65         if [[ $# -eq 2 ]]; then # only num1 is given
66                 result="at least $2 args required, $1 given"
67                 [[ $1 -lt $2 ]] && return
68                 ret=$GSU_SUCCESS
69                 return
70         fi
71         # num1 and num2 given
72         result="need at least $2 args, $1 given"
73         [[ $1 -lt $2 ]] && return
74         result="need at most $3 args, $1 given"
75         [[ $1 -gt $3 ]] && return
76         ret=$GSU_SUCCESS
77 }
78 export -f gsu_check_arg_count
79
80 gsu_short_msg()
81 {
82         echo "$1" 1>&2
83 }
84 export -f gsu_short_msg
85
86 gsu_msg()
87 {
88         gsu_short_msg "$_gsu_self: $1"
89 }
90 export -f gsu_msg
91
92 gsu_date_msg()
93 {
94         gsu_short_msg "$_gsu_self $(date): $1"
95 }
96 export -f gsu_date_msg
97
98
99
100 _gsu_banner_msg()
101 {
102         local txt="### $_gsu_self --"
103         if test -z "$gsu_banner_txt"; then
104                 txt="$txt set \$gsu_banner_txt to customize this message"
105         else
106                 txt="$txt $gsu_banner_txt"
107         fi
108         gsu_short_msg "$txt ###"
109 }
110 export -f _gsu_banner_msg
111
112 gsu_err_msg()
113 {
114         local txt="$result" err
115
116         gsu_is_a_number "$ret"
117         if test $ret -lt 0; then
118                 gsu_msg "unknown error ($ret:$txt)"
119                 exit 1
120         fi
121         if test $result -ge 0; then
122                 gsu_msg "unknown error ($result:$txt)"
123                 exit 1
124         fi
125         err=$((0 - $result))
126         if test -n "$txt"; then
127                 txt="$txt: ${gsu_error_txt[$err]}"
128         else
129                 txt="${gsu_error_txt[$err]}"
130         fi
131         gsu_msg "$txt"
132 }
133 export -f gsu_err_msg
134
135 _gsu_usage()
136 {
137         gsu_short_msg "# Usage: $_gsu_self command [options]"
138 }
139 export -f _gsu_usage
140
141 _gsu_available_commands()
142 {
143         result="$( (printf "help\nman\nprefs\n"; grep "^com_[a-z_]\+()" $0) \
144                 | sed -e 's/^com_//' -e 's/()//' \
145                 | sort \
146                 | tr '\n' ' ')"
147         ret=$GSU_SUCCESS
148 }
149 export -f _gsu_available_commands
150
151 _gsu_print_available_commands()
152 {(
153         local i count
154         gsu_short_msg "Available commands:"
155         for i in $gsu_cmds; do
156                 printf "$i"
157                 count=$(($count + 1))
158                 if test $(($count % 4)) -eq 0; then
159                         echo
160                 else
161                         printf "\t"
162                         if test ${#i} -lt 8; then
163                                 printf "\t"
164                         fi
165                 fi
166         done
167         echo
168 ) 2>&1
169 }
170 export -f _gsu_print_available_commands
171
172 export gsu_prefs_txt="
173 Print the current preferences.
174
175 Usage: prefs [-e]
176
177 If -e is given, the config file is opened with the default editor.  Without
178 options, the command prints out a list of all cmt config variables, together
179 with their current value and the default value."
180 _com_prefs()
181 {
182         local i conf="${gsu_config_file:=$HOME/.$gsu_name.rc}"
183
184         if [[ "$1" = "-e" ]]; then
185                 ret=-$E_GSU_EDITOR
186                 result="${EDITOR:-vi}"
187                 "$result" "$conf"
188                 ret=$GSU_SUCCESS
189                 return
190         fi
191
192         for ((i=0; i < ${#gsu_options[@]}; i++)); do
193                 local name= option_type= default_value= required=
194                 local description= help_text=
195                 eval "${gsu_options[$i]}"
196                 eval val='"$'${gsu_config_var_prefix}_$name'"'
197                 case "$required" in
198                 true|yes)
199                         printf "# required"
200                         ;;
201                 *)
202                         printf "# optional"
203                         ;;
204                 esac
205                 printf " $option_type: $description"
206                 if [[ "$required" != "yes" && "$required" != "true" ]]; then
207                         printf " [$default_value]"
208                 fi
209                 echo
210                 [[ -n "$help_text" ]] && sed -e '/^[    ]*$/d; s/^[     ]*/#    /g' <<< "$help_text"
211                 printf "$name=$val"
212                 [[ "$val" == "$default_value" ]] && printf " # default"
213                 echo
214         done
215 }
216 export -f _com_prefs
217
218 export gsu_man_txt="
219 Print the manual.
220
221 Usage: man"
222
223 _com_man()
224 {
225         local equal_signs="=================================================="
226         local minus_signs="--------------------------------------------------"
227         local com num
228
229         echo "$_gsu_self (_${gsu_banner_txt}_) manual"
230         echo "${equal_signs:0:${#_gsu_self} + ${#gsu_banner_txt} + 16}"
231         echo
232
233         sed -e '1,/^#\{70,\}/d' -e '/^#\{70,\}/,$d' $0 -e 's/^# *//'
234         echo "----"
235         echo
236         echo "$_gsu_self usage"
237         echo "${minus_signs:0:${#_gsu_self} + 6}"
238         printf "\t"
239         _gsu_usage 2>&1
240         echo "Each command has its own set of options as described below."
241         echo
242         echo "----"
243         echo
244         echo "Available commands:"
245
246         _gsu_available_commands
247         for com in $result; do
248                 num=${#com}
249                 if test $num -lt 4; then
250                         num=4
251                 fi
252                 echo "${minus_signs:0:$num}"
253                 echo "$com"
254                 echo "${minus_signs:0:$num}"
255                 $0 help $com
256                 echo
257         done
258         ret=$GSU_SUCCESS
259 }
260 export -f _com_man
261
262 export gsu_help_txt="
263 Print online help.
264
265 Usage: help [command]
266
267 Without arguments, print the list of available commands. Otherwise,
268 print the help text for the given command."
269
270 _com_help()
271 {
272         local a b
273         if test -z "$1"; then
274                 _gsu_banner_msg 2>&1
275                 _gsu_usage 2>&1
276                 {
277                         printf "com_help()\n$gsu_help_txt" | head -n 4; echo "--"
278                         printf "com_man()\n$gsu_man_txt" | head -n 4; echo "--"
279                         printf "com_prefs()\n$gsu_prefs_txt" | head -n 4; echo "--"
280                         grep -A 2 "^com_\([a-zA-Z_0-9]\+\)()" $0
281                 } | grep -v -- '--' \
282                         | sed -e '/^com_\([a-zA-Z_0-9]\+\)()/bs' \
283                                 -e 'H;$!d;x;s/\n//g;b' \
284                                 -e :s \
285                                 -e 'x;s/\n//g;${p;x;}' \
286                         | sed -e 's/^com_\([a-zA-Z_0-9]\+\)()#*/\1\t/' \
287                         | sort \
288                         | while read a b; do
289                                 printf "$a\t"
290                                 if test ${#a} -lt 8; then
291                                         printf "\t"
292                                 fi
293                                 echo "$b"
294                         done
295                 echo
296                 echo "# Try $_gsu_self help <command> for info on <command>."
297                 ret=$GSU_SUCCESS
298                 return
299         fi
300         if test "$1" = "help"; then
301                 echo "$gsu_help_txt"
302                 ret=$GSU_SUCCESS
303                 return
304         fi
305         if test "$1" = "man"; then
306                 echo "$gsu_man_txt"
307                 ret=$GSU_SUCCESS
308                 return
309         fi
310         if test "$1" = "prefs"; then
311                 echo "$gsu_prefs_txt"
312                 ret=$GSU_SUCCESS
313                 return
314         fi
315         ret=$GSU_SUCCESS
316         if grep -q "^com_$1()" $0; then
317                 sed -e "1,/^com_$1()$/d" -e '/^{/,$d' -e 's/^## *//' $0
318                 return
319         fi
320         _gsu_print_available_commands
321         result="$1"
322         ret=-$E_GSU_BAD_COMMAND
323 }
324 export -f _com_help
325
326 # internal gsu function that syntactically checks the gsu_options array
327 # for errors and parses the config file.
328 _gsu_check_options()
329 {
330         local i conf="${gsu_config_file:=$HOME/.$gsu_name.rc}" val
331
332         for ((i=0; i < ${#gsu_options[@]}; i++)); do
333                 eval "${gsu_options[$i]}"
334                 eval val='"'\$$name'"'
335                 eval orig_${gsu_config_var_prefix}_$name='"'${val}'"'
336         done
337
338         [[ -r "$conf" ]] && source "$conf"
339
340         for ((i=0; i < ${#gsu_options[@]}; i++)); do
341                 local name= option_type= default_value= required=
342                 local description= help_text=
343                 local val orig_val
344
345                 eval "${gsu_options[$i]}"
346
347
348                 # Check name. It must be non-empty and consist of [a-zA-Z_0-9]
349                 # only.  Moreover it must not start with [a-zA-Z].
350
351                 ret=-$E_GSU_BAD_CONFIG_VAR
352                 result="name: '$name'"
353                 # bash's =~ works only for 3.2 and newer, so use grep
354                 echo "$name" | grep '^[a-zA-Z][a-zA-Z_0123456789]*$' &> /dev/null;
355                 [[ $? -ne 0 ]] && return
356
357                 eval orig_val='"'\$orig_${gsu_config_var_prefix}_$name'"'
358                 if [[ -z "$orig_val" ]]; then
359                         eval val='"'\$$name'"'
360                 else
361                         val="$orig_val"
362                 fi
363                 case "$required" in
364                 true|yes)
365                         ret=-$E_GSU_NEED_VALUE
366                         result="$name"
367                         [[ -z "$val" ]] && return
368                         ;;
369                 false|no)
370                         ;;
371                 *)
372                         ret=-$E_GSU_BAD_BOOL
373                         result="required: $required, name: $name, val: $val"
374                         return
375                 esac
376
377                 eval ${gsu_config_var_prefix}_$name='"'${val:=$default_value}'"'
378                 # Check option type. ATM, only num and string are supported
379                 # Other types may be added without breaking compatibility
380                 case "$option_type" in
381                 string)
382                         ;;
383                 num)
384                         gsu_is_a_number "$val"
385                         [[ $ret -lt 0 ]] && return
386                         ;;
387                 *)
388                         ret=-$E_GSU_BAD_OPTION_TYPE
389                         result="$name/$option_type"
390                         return
391                 esac
392         done
393         ret=$GSU_SUCCESS
394 }
395 export -f _gsu_check_options
396
397 gsu()
398 {
399         local i
400
401         gsu_is_a_number "${BASH_VERSINFO[0]}"
402         if [[ $ret -lt 0 ]]; then
403                 gsu_msg "fatal: failed to determine bash version"
404                 exit 1
405         fi
406
407         if [[ "${BASH_VERSINFO[0]}" -lt 4 ]]; then
408                 gsu_msg "fatal: This script requires at least bash 4.0"
409                 exit 1
410         fi
411         _gsu_self="$(basename $0)"
412         gsu_name="${gsu_name:=$_gsu_self}"
413         gsu_config_var_prefix="${gsu_config_var_prefix:=$gsu_name}"
414         _gsu_init_errors
415         _gsu_check_options
416         if [[ "$ret" -lt 0 ]]; then
417                 if [[ "$1" != "help" && "$1" != "man" ]]; then
418                         gsu_err_msg
419                         exit 1
420                 fi
421         fi
422         _gsu_available_commands
423         gsu_cmds="$result"
424         if test $# -eq 0; then
425                 _gsu_usage
426                 _gsu_print_available_commands
427                 exit 1
428         fi
429         arg="$1"
430         shift
431         # check internal commands
432         if [[ "$arg" = "help" || "$arg" = "man" || "$arg" = "prefs" ]]; then
433                 _com_$arg "$@"
434                 if [[ "$ret" -lt 0 ]]; then
435                         gsu_err_msg
436                         exit 1
437                 fi
438                 exit 0
439         fi
440
441         # external commands
442         for i in $gsu_cmds; do
443                 if test "$arg" = "$i"; then
444                         com_$arg "$@"
445                         if [[ "$ret" -lt 0 ]]; then
446                                 gsu_err_msg
447                                 exit 1
448                         fi
449                         exit 0
450                 fi
451         done
452
453         ret=-$E_GSU_BAD_COMMAND
454         result="$arg"
455         gsu_err_msg
456         _gsu_print_available_commands
457         exit 1
458 }
459 export -f gsu
460
461 # TODO: gsu_strerror: get error string