f09c66de613707c42794947e8b0b787c79f11f5e
[gsu.git] / misc / gsu / subcommand
1 #!/bin/bash
2 # (C) 2006-2011 Andre Noll
3
4 if [[ $(type -t gsu_is_a_number) != "function" ]]; then
5         GSU_DIR=${GSU_DIR:=$HOME/.gsu}
6         . $GSU_DIR/common || exit 1
7 fi
8
9 _gsu_usage()
10 {
11         gsu_short_msg "# Usage: $_gsu_self command [options]"
12 }
13
14 # Each line matching this is recognized as a subcommand. The name
15 # of the subcommand is the first subexpression.
16 export gsu_command_regex='^com_\([-a-zA-Z_0-9]\+\)()'
17
18 _gsu_available_commands()
19 {
20         result="$({
21                 printf "help\nman\nprefs\n"
22                 sed -ne "s/$gsu_command_regex/\1/g;T;p" $0
23                 } | sort | tr '\n' ' ')"
24 }
25
26 _gsu_print_available_commands()
27 {(
28         local i count
29         gsu_short_msg "Available commands:"
30         for i in $gsu_cmds; do
31                 printf "$i"
32                 count=$(($count + 1))
33                 if test $(($count % 4)) -eq 0; then
34                         echo
35                 else
36                         printf "\t"
37                         if test ${#i} -lt 8; then
38                                 printf "\t"
39                         fi
40                 fi
41         done
42         echo
43 ) 2>&1
44 }
45
46 export gsu_prefs_txt="
47 Print the current preferences.
48
49 Usage: prefs [-e]
50
51 If -e is given, the config file is opened with the default editor.  Without
52 options, the command prints out a list of all cmt config variables, together
53 with their current value and the default value."
54 _com_prefs()
55 {
56         local i conf="${gsu_config_file:=$HOME/.$gsu_name.rc}"
57
58         if [[ "$1" = "-e" ]]; then
59                 ret=-$E_GSU_MKDIR
60                 result="${conf%/*}"
61                 mkdir -p "$result"
62                 [[ $? -ne 0 ]] && return
63                 ret=-$E_GSU_EDITOR
64                 result="${EDITOR:-vi}"
65                 "$result" "$conf"
66                 [[ $? -ne 0 ]] && return
67                 ret=$GSU_SUCCESS
68                 return
69         fi
70
71         for ((i=0; i < ${#gsu_options[@]}; i++)); do
72                 local name= option_type= default_value= required=
73                 local description= help_text=
74                 eval "${gsu_options[$i]}"
75                 eval val='"$'${gsu_config_var_prefix}_$name'"'
76                 case "$required" in
77                 true|yes)
78                         printf "# required"
79                         ;;
80                 *)
81                         printf "# optional"
82                         ;;
83                 esac
84                 printf " $option_type: $description"
85                 if [[ "$required" != "yes" && "$required" != "true" ]]; then
86                         printf " [$default_value]"
87                 fi
88                 echo
89                 [[ -n "$help_text" ]] && sed -e '/^[    ]*$/d; s/^[     ]*/#    /g' <<< "$help_text"
90                 printf "$name=$val"
91                 [[ "$val" == "$default_value" ]] && printf " # default"
92                 echo
93         done
94 }
95
96 export gsu_man_txt="
97 Print the manual.
98
99 Usage: man"
100
101 _com_man()
102 {
103         local equal_signs="=================================================="
104         local minus_signs="--------------------------------------------------"
105         local com num
106
107         echo "$_gsu_self (_${gsu_banner_txt}_) manual"
108         echo "${equal_signs:0:${#_gsu_self} + ${#gsu_banner_txt} + 16}"
109         echo
110
111         sed -e '1,/^#\{70,\}/d' -e '/^#\{70,\}/,$d' $0 -e 's/^# *//'
112         echo "----"
113         echo
114         echo "$_gsu_self usage"
115         echo "${minus_signs:0:${#_gsu_self} + 6}"
116         printf "\t"
117         _gsu_usage 2>&1
118         echo "Each command has its own set of options as described below."
119         echo
120         echo "----"
121         echo
122         echo "Available commands:"
123
124         _gsu_available_commands
125         for com in $result; do
126                 num=${#com}
127                 if test $num -lt 4; then
128                         num=4
129                 fi
130                 echo "${minus_signs:0:$num}"
131                 echo "$com"
132                 echo "${minus_signs:0:$num}"
133                 $0 help $com
134                 echo
135         done
136         ret=$GSU_SUCCESS
137 }
138
139 _gsu_banner_msg()
140 {
141         local txt="### $_gsu_self --"
142         if test -z "$gsu_banner_txt"; then
143                 txt="$txt set \$gsu_banner_txt to customize this message"
144         else
145                 txt="$txt $gsu_banner_txt"
146         fi
147         gsu_short_msg "$txt ###"
148 }
149
150 export gsu_help_txt="
151 Print online help.
152
153 Usage: help [command]
154
155 Without arguments, print the list of available commands. Otherwise,
156 print the help text for the given command."
157
158 _com_help()
159 {
160         local a b
161         if test -z "$1"; then
162                 _gsu_banner_msg 2>&1
163                 _gsu_usage 2>&1
164                 {
165                         printf "com_help()\n$gsu_help_txt" | head -n 4; echo "--"
166                         printf "com_man()\n$gsu_man_txt" | head -n 4; echo "--"
167                         printf "com_prefs()\n$gsu_prefs_txt" | head -n 4; echo "--"
168                         grep -A 2 "$gsu_command_regex" $0
169                 } | grep -v -- '--' \
170                         | sed -e "/$gsu_command_regex/bs" \
171                                 -e 'H;$!d;x;s/\n//g;b' \
172                                 -e :s \
173                                 -e 'x;s/\n//g;${p;x;}' \
174                         | sed -e "s/${gsu_command_regex}#*/\1\t/" \
175                         | sort \
176                         | while read a b; do
177                                 printf "$a\t"
178                                 if test ${#a} -lt 8; then
179                                         printf "\t"
180                                 fi
181                                 echo "$b"
182                         done
183                 echo
184                 echo "# Try $_gsu_self help <command> for info on <command>."
185                 ret=$GSU_SUCCESS
186                 return
187         fi
188         if test "$1" = "help"; then
189                 echo "$gsu_help_txt"
190                 ret=$GSU_SUCCESS
191                 return
192         fi
193         if test "$1" = "man"; then
194                 echo "$gsu_man_txt"
195                 ret=$GSU_SUCCESS
196                 return
197         fi
198         if test "$1" = "prefs"; then
199                 echo "$gsu_prefs_txt"
200                 ret=$GSU_SUCCESS
201                 return
202         fi
203         ret=$GSU_SUCCESS
204         if grep -q "^com_$1()" $0; then
205                 sed -e "1,/^com_$1()$/d" -e '/^{/,$d' -e 's/^## *//' $0
206                 return
207         fi
208         _gsu_print_available_commands
209         result="$1"
210         ret=-$E_GSU_BAD_COMMAND
211 }
212
213 # Wrapper for bash's getopts.
214 #
215 # Aborts on programming errors such as missing or invalid option string.  On
216 # success $result contains shell code that can be eval'ed. For each defined
217 # option x, the local variable o_x will be created when calling eval "$result".
218 # o_x contains true/false for options without an argument or the emtpy string/the
219 # given argument, depending on whether this option was contained in the "$@"
220 # array.
221 #
222 # Example:
223 #       gsu_getopts abc:x:y
224 #       eval "$result"
225 #       [[ $ret -lt 0 ]] && return
226 #
227 #       [[ "$o_a" = "true ]] && echo "The -a flag was given"
228 #       [[ -n "$o_c" ]] && echo "The -c option was given with arg $o_c"
229 gsu_getopts()
230 {
231         local i c tab=' ' cr='
232 '
233
234         gsu_check_arg_count $# 1 1
235         if [[ $ret -lt 0 ]]; then
236                 gsu_err_msg
237                 exit 1
238         fi
239
240         ret=-$E_GSU_GETOPTS
241         result="invalid optstring $1"
242         if [[ -z "$1" ]] || grep -q '::' <<< "$1" ; then
243                 gsu_err_msg
244                 exit 1
245         fi
246
247         for ((i=0; i < ${#1}; i++)); do
248                 c=${1:$i:1}
249                 case "$c" in
250                 [a-zA-Z:]);;
251                 *)
252                         ret=-$E_GSU_GETOPTS
253                         result="invalid character $c in optstring"
254                         gsu_err_msg
255                         exit 1
256                 esac
257         done
258         result="local opt"
259         for ((i=0; i < ${#1}; i++)); do
260                 c1=${1:$i:1}
261                 c2=${1:$(($i + 1)):1}
262                 result+=" o_$c1"
263                 if [[ "$c2" = ":" ]]; then
264                         let i++
265                 else
266                         result+="=false"
267                 fi
268         done
269         result+="
270         OPTIND=1
271         while getopts $1 opt \"\$@\"; do
272                 case \"\$opt\" in
273 "
274         for ((i=0; i < ${#1}; i++)); do
275                 c1=${1:$i:1}
276                 c2=${1:$(($i + 1)):1}
277                 result+="$tab$tab$c1) o_$c1="
278                 if [[ "$c2" = ":" ]]; then
279                         result+="\"\$OPTARG\""
280                         let i++
281                 else
282                         result+="true"
283                 fi
284                 result+=";;$cr"
285         done
286         result+="
287                 *)
288                         ret=-\$E_GSU_GETOPTS
289                         result=\"invalid option given\"
290                         return
291                         ;;
292                 esac
293         done
294         shift \$((\$OPTIND - 1))
295 "
296         ret=$GSU_SUCCESS
297 }
298
299 gsu()
300 {
301         local i
302         _gsu_setup
303         _gsu_available_commands
304         gsu_cmds="$result"
305         if test $# -eq 0; then
306                 _gsu_usage
307                 _gsu_print_available_commands
308                 exit 1
309         fi
310         arg="$1"
311         shift
312         # check internal commands
313         if [[ "$arg" = "help" || "$arg" = "man" || "$arg" = "prefs" ]]; then
314                 _com_$arg "$@"
315                 if [[ "$ret" -lt 0 ]]; then
316                         gsu_err_msg
317                         exit 1
318                 fi
319                 exit 0
320         fi
321
322         # external commands
323         for i in $gsu_cmds; do
324                 if test "$arg" = "$i"; then
325                         com_$arg "$@"
326                         if [[ "$ret" -lt 0 ]]; then
327                                 gsu_err_msg
328                                 exit 1
329                         fi
330                         exit 0
331                 fi
332         done
333
334         ret=-$E_GSU_BAD_COMMAND
335         result="$arg"
336         gsu_err_msg
337         _gsu_print_available_commands
338         exit 1
339 }
340
341 # Check number of arguments.
342 #
343 # Usage: gsu_check_arg_count <num_given> <num1> [<num2>]
344 #
345 # Check that <num_given> is between <num1> and <num2> inclusively.
346 # If only <num1> ist given, num2 is assumed to be infinity.
347 #
348 # Examples:
349 #       0 0 no argument allowed
350 #       1 1 exactly one argument required
351 #       0 2 at most two arguments admissible
352 #       2   at least two arguments reqired
353 #
354 gsu_check_arg_count()
355 {
356         ret=-$E_GSU_BAD_ARG_COUNT
357         if [[ $# -eq 2 ]]; then # only num1 is given
358                 result="at least $2 args required, $1 given"
359                 [[ $1 -lt $2 ]] && return
360                 ret=$GSU_SUCCESS
361                 return
362         fi
363         # num1 and num2 given
364         result="need at least $2 args, $1 given"
365         [[ $1 -lt $2 ]] && return
366         result="need at most $3 args, $1 given"
367         [[ $1 -gt $3 ]] && return
368         ret=$GSU_SUCCESS
369 }