-
Notifications
You must be signed in to change notification settings - Fork 2
/
chelli.sh
319 lines (254 loc) · 8.09 KB
/
chelli.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
#!/bin/zsh
declare -A OPTIONS
declare -A HELP_OPTION_INFO
declare -A COMMANDS
declare -A OPTION_VALUES
declare -A WAIT_FOR_EXEC_OPTION_ACTIONS
INIT_COMMAND_NAME="main"
WILDCARD_COMMAND_NAME="*"
CURRENT_COMMAND_NAME=$INIT_COMMAND_NAME
CURRENT_WALK_OPTION=()
HELP_COMMAND_INFO=()
version=1.0.0
raise_error() {
echo $1
exit 1
}
# error_type error_message
format_error() {
raise_error "[$1]: $2"
}
# error_message
parameter_error() {
format_error "Parameter Error" "${1}"
}
# error_message
option_error() {
format_error "Option Error" "${1}"
}
# error_message
command_error() {
format_error "Command Error" "${1}"
}
# error_message
argument_error() {
format_error "Argument Error" "${1}"
}
# has_value short_option long_option description fn specify_command
_option() {
local specify_command=${6:-"main"}
local key_name="$specify_command|$3"
OPTIONS[$key_name]="$2,$3,$5,$1"
HELP_OPTION_INFO[$key_name]="-$2, --$3 $4"
}
# name description fn arguments
command() {
# check arguments
for arg in ${@:4}
do
if ! [[ $arg =~ "(<.+>|\[.+\])" ]];then
argument_error "Please ensure the argument are define using [] or <>"
fi
done
arguments=`echo ${@:4} | sed -E 's/</__lt__/g' | sed -E 's/>/__gt__/g' | sed -E 's/\[/__lsb__/g' | sed -E 's/\]/__gsb__/g'`
COMMANDS[$1]="$3 $arguments"
HELP_COMMAND_INFO+=("$1 $2")
}
required_option() {
_option true $*
}
option() {
_option false $*
}
# CLI_name description version
set_metadata() {
CLI_name=$1
CLI_description=$2
CLI_version=${3:-"v1.0.0"}
}
# CLI_usage
set_usage() {
CLI_usage=$1
}
print_version() {
echo "$CLI_name version $CLI_version"
exit
}
# option_name
_exract_option() {
for key val in ${(kv)OPTIONS}
do
if [[ $key =~ "^$CURRENT_COMMAND_NAME\|" || $key =~ "^\\$WILDCARD_COMMAND_NAME\|" ]];then
eval local parsed_val="(${val//,/ })"
if [[ $1 == "-${parsed_val[1]}" || $1 == "--${parsed_val[2]}" ]];then
CURRENT_WALK_OPTION=($parsed_val)
return
fi
fi
done
if [[ $CURRENT_COMMAND_NAME == $INIT_COMMAND_NAME ]];then
option_error "Unknown option $1"
else
option_error "Unknown option $1 on command $CURRENT_COMMAND_NAME"
fi
}
# set result of action of the option
apply() {
OPTION_VALUES[$current_walk_option_name]=$1
}
# arguments
exec_options() {
for long_option_name val in ${(kv)WAIT_FOR_EXEC_OPTION_ACTIONS}
do
local exec_arr=(${val//|/ })
current_walk_option_name=$long_option_name
eval ${exec_arr[0]} ${exec_arr[1]} $CURRENT_COMMAND_NAME
done
eval local command_info="(${COMMANDS[$CURRENT_COMMAND_NAME]})"
$command_info[1] $@
# $COMMANDS[$CURRENT_COMMAND_NAME] $OPTION_VALUES
}
# command_name
print_help() {
echo
echo $CLI_description
echo
echo ${CLI_usage:-"Usage: $CLI_name [options] commands"}
local wait_for_print_options_help_info=('\nOptions:\n')
local wait_for_print_command_help_info=("\nCommands:\n")
local target_command_name=$1
collect_option_help_info() {
for command_name help_option in ${(kv)HELP_OPTION_INFO}
do
if [[ $command_name =~ "^$target_command_name\|" || $command_name =~ "^\\$WILDCARD_COMMAND_NAME\|" ]];then
wait_for_print_options_help_info+=($help_option)
fi
done
}
collect_command_help_info() {
for command_help_info in $HELP_COMMAND_INFO
do
if [[ $target_command_name == $INIT_COMMAND_NAME ]];then
wait_for_print_command_help_info+=($command_help_info)
fi
done
}
print_command_help_info() {
if [[ ${#wait_for_print_command_help_info[@]} == 1 ]];then
return
fi
for help_option in $wait_for_print_command_help_info
do
echo $help_option
done
}
print_option_help_info() {
if [[ ${#wait_for_print_options_help_info[@]} == 1 ]];then
return
fi
for help_option in $wait_for_print_options_help_info
do
echo $help_option
done
}
collect_option_help_info
collect_command_help_info
print_option_help_info
print_command_help_info
echo
exit
}
# build-in options
option "v" "version" "Print version information & quit" print_version
option "h" "help" "Print handbook & quit" print_help "*"
# args
cli_parse() {
local skip_args_count=0
local arguments=()
for i ({1..${#*[@]}})
do
if [[ $skip_args_count > 0 ]];then
skip_args_count=$((skip_args_count - 1))
continue
fi
local arg=${*[i]}
if [[ $arg == "" ]];then
continue
fi
if [[ $arg =~ "^(-|--)" ]];then
_exract_option ${*[i]}
local fn=${CURRENT_WALK_OPTION[3]}
local long_option=${CURRENT_WALK_OPTION[2]}
# is option
# the option have to accept a value
if [[ ${CURRENT_WALK_OPTION[4]} == true ]];then
skip_args_count=$((skip_args_count + 1))
local value=${*[i+1]}
if [[ $value == "" ]];then
option_error "Cannot find value of $arg"
fi
WAIT_FOR_EXEC_OPTION_ACTIONS[$long_option]="$fn|$value"
continue
fi
# no value option
WAIT_FOR_EXEC_OPTION_ACTIONS[$long_option]="$fn"
continue
elif [[ $i == 1 ]];then
local is_command=false
# walk commands
for command_name command_metadata in ${(kv)COMMANDS}
do
# exec next command
if [[ $command_name == $arg ]];then
CURRENT_COMMAND_NAME=$command_name
is_command=true
eval local command_info="(${COMMANDS[$CURRENT_COMMAND_NAME]})"
local receive_arguments=(${command_info:1})
local required_arguments=()
local option_arguments=()
for receive_arg in $receive_arguments
do
# required argument
if [[ $receive_arg =~ "__lt__(.+)__gt__" ]];then
required_arguments+=($match[1])
fi
if [[ $receive_arg =~ "__lsb__(.+)__gsb__" ]];then
option_arguments+=($match[1])
fi
done
local current_argument=()
for arg in ${*:2}
do
if [[ $arg =~ "^(-|--)" ]];then
break
fi
current_argument+=($arg)
done
# check required argument
if [[ ${#current_argument} < ${#required_arguments} ]];then
argument_error "The ${required_arguments[$((${#current_argument} + 1))]} argument is required"
# have optional argument
elif [[ ${#current_argument} -ge ${#required_arguments} ]];then
if [[ $((${#current_argument} - ${#required_arguments})) > ${#option_arguments} ]];then
argument_error "The ${current_argument[$((${#required_arguments} + ${#option_arguments}))]} argument is undefined"
else
arguments=($current_argument)
skip_args_count=${#current_argument}
fi
fi
break
fi
done
if [[ $is_command == true ]];then
continue
fi
fi
# boundary
if [[ $i == 1 ]];then
command_error "Unknown command $arg"
else
option_error "Unknown value $arg"
fi
done
exec_options $arguments
}