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