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