gsu: Remove check on bash version.
[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