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