gsu: Add -e option to com_prefs.
[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