Name | Executed | Routines | % | Executed | Lines | % | Unexecuted |
/home/matt/eu/rds/include/std/cmdline.e | 6 | 8 | 75.00% | 317 | 441 | 71.88% | 124 |
Routine | Executed | Lines | Unexecuted | |
cmd_parse() | 117 | 161 | 72.67% | 44 |
local_help() | 59 | 98 | 60.20% | 39 |
standardize_opts() | 69 | 92 | 75.00% | 23 |
find_opt() | 39 | 49 | 79.59% | 10 |
local_abort() | 0 | 5 | 0.00% | 5 |
show_help() | 0 | 3 | 0.00% | 3 |
build_commandline() | 2 | 2 | 100.00% | 0 |
parse_commandline() | 2 | 2 | 100.00% | 0 |
# | Executed | |
1 | --**** | |
2 | -- == Command Line Handling | |
3 | -- | |
4 | -- < | |
5 | namespace cmdline | |
6 | ||
7 | include std/text.e | |
8 | include std/sequence.e as seq | |
9 | include std/map.e | |
10 | include std/error.e | |
11 | include std/os.e | |
12 | include std/io.e | |
13 | include std/console.e | |
14 | include std/types.e | |
15 | ||
16 | --**** | |
17 | -- === Constants | |
18 | ||
19 | public constant | |
20 | --** This option switch does not have a parameter. See [[:cmd_parse]] | |
21 | 3 | NO_PARAMETER = 'n', |
22 | ||
23 | --** This option switch does have a parameter. See [[:cmd_parse]] | |
24 | 3 | HAS_PARAMETER = 'p', |
25 | ||
26 | --** This option switch is not case sensitive. See [[:cmd_parse]] | |
27 | 3 | NO_CASE = 'i', |
28 | ||
29 | --** This option switch is case sensitive. See [[:cmd_parse]] | |
30 | 3 | HAS_CASE = 'c', |
31 | ||
32 | --** This option switch must be supplied on command line. See [[:cmd_parse]] | |
33 | 3 | MANDATORY = 'm', |
34 | ||
35 | --** This option switch does not have to be on command line. See [[:cmd_parse]] | |
36 | 3 | OPTIONAL = 'o', |
37 | ||
38 | --** This option switch must only occur once on the command line. See [[:cmd_parse]] | |
39 | 3 | ONCE = '1', |
40 | ||
41 | --** This option switch may occur multiple times on a command line. See [[:cmd_parse]] | |
42 | 3 | MULTIPLE = '*', |
43 | ||
44 | --** This option switch triggers the 'help' display. See [[:cmd_parse]] | |
45 | 3 | HELP = 'h' |
46 | ||
47 | public constant | |
48 | 3 | NO_HELP = -2 |
49 | ||
50 | public enum | |
51 | --** | |
52 | -- Additional help routine id. See [[:cmd_parse]] | |
53 | 3 | HELP_RID, |
54 | ||
55 | --** | |
56 | -- Validate all parameters (default). See [[:cmd_parse]] | |
57 | 3 | VALIDATE_ALL, |
58 | ||
59 | --** | |
60 | -- Do not cause an error for an invalid parameter. See [[:cmd_parse]] | |
61 | 3 | NO_VALIDATION, |
62 | ||
63 | --** | |
64 | -- Do not cause an error for an invalid parameter after the | |
65 | -- first extra item has been found. This can be helpful for | |
66 | -- processes such as the Interpreter itself that must deal | |
67 | -- with command line parameters that it is not meant to | |
68 | -- handle. At expansions after the first extra are also disabled. | |
69 | -- | |
70 | -- For instance: | |
71 | -- ##eui -D TEST greet.ex -name John -greeting Bye## | |
72 | -- -D TEST is meant for ##eui##, but -name and -greeting options | |
73 | -- are meant for ##greet.ex##. See [[:cmd_parse]] | |
74 | -- | |
75 | -- | |
76 | -- ##eui @euopts.txt greet.ex @hotmail.com## | |
77 | -- here 'hotmail.com' is not expanded into the command line but | |
78 | -- 'euopts.txt' is. | |
79 | 3 | NO_VALIDATION_AFTER_FIRST_EXTRA, |
80 | ||
81 | --** | |
82 | -- Only display the option list in show_help. Do not display other | |
83 | -- information such as program name, options, etc... See [[:cmd_parse]] | |
84 | 3 | SHOW_ONLY_OPTIONS, |
85 | ||
86 | --** | |
87 | -- Expand arguments that begin with '@' into the command line. (default) | |
88 | -- For example, @filename will expand the contents of file named 'filename' | |
89 | -- as if the file's contents were passed in on the command line. Arguments | |
90 | -- that come after the first extra will not be expanded when | |
91 | -- NO_VALIDATION_AFTER_FIRST_EXTRA is specified. | |
92 | 3 | AT_EXPANSION, |
93 | ||
94 | --** | |
95 | -- Do not expand arguments that begin with '@' into the command line. | |
96 | -- Normally @filename will expand the file names contents as if the | |
97 | -- file's contents were passed in on the command line. This option | |
98 | -- supresses this behavior. | |
99 | 3 | NO_AT_EXPANSION, |
100 | ||
101 | --** | |
102 | -- Supply a message to display and pause just prior to abort() being called. | |
103 | 3 | PAUSE_MSG, |
104 | ||
105 | $ | |
106 | ||
107 | -- | |
108 | ||
109 | public enum | |
110 | --** | |
111 | -- An index into the ##opts## list. See [[:cmd_parse]] | |
112 | 3 | OPT_IDX, |
113 | ||
114 | --** | |
115 | -- The number of times that the routine has been called | |
116 | -- by cmd_parse for this option. See [[:cmd_parse]] | |
117 | 3 | OPT_CNT, |
118 | ||
119 | --** | |
120 | -- The option's value as found on the command line. See [[:cmd_parse]] | |
121 | 3 | OPT_VAL, |
122 | ||
123 | --** | |
124 | -- The value ##1## if the command line indicates that this option is to remove | |
125 | -- any earlier occurrences of it. See [[:cmd_parse]] | |
126 | 3 | OPT_REV |
127 | ||
128 | ||
129 | -- Record fields in 'opts' argument. | |
130 | enum | |
131 | 3 | SHORTNAME = 1, |
132 | 3 | LONGNAME = 2, |
133 | 3 | DESCRIPTION = 3, |
134 | 3 | OPTIONS = 4, |
135 | 3 | CALLBACK = 5, |
136 | 3 | MAPNAME = 6 |
137 | ||
138 | 3 | sequence pause_msg = "" |
139 | ||
140 | 0 | |
141 | 0 | if length(pause_msg) != 0 then |
142 | 0 | any_key(pause_msg, 1) |
143 | end if | |
144 | ||
145 | 0 | abort(lvl) |
146 | 0 | end procedure |
147 | ||
148 | -- Local routine to validate and reformat option records if they are not in the standard format. | |
149 | 48 | |
150 | 48 | integer lExtras = 0 -- Ensure that there is zero or one 'extras' record only. |
151 | ||
152 | 48 | for i = 1 to length(opts) do |
153 | 104 | sequence opt = opts[i] |
154 | 104 | integer updated = 0 |
155 | ||
156 | 104 | if length(opt) < MAPNAME then |
157 | 76 | opt &= repeat(-1, MAPNAME - length(opt)) |
158 | 76 | updated = 1 |
159 | end if | |
160 | ||
161 | 104 | if sequence(opt[SHORTNAME]) and length(opt[SHORTNAME]) = 0 then |
162 | 0 | opt[SHORTNAME] = 0 |
163 | 0 | updated = 1 |
164 | end if | |
165 | ||
166 | 104 | if sequence(opt[LONGNAME]) and length(opt[LONGNAME]) = 0 then |
167 | 0 | opt[LONGNAME] = 0 |
168 | 0 | updated = 1 |
169 | end if | |
170 | ||
171 | 104 | if atom(opt[LONGNAME]) and atom(opt[SHORTNAME]) then |
172 | 2 | if lExtras != 0 then |
173 | 0 | crash("cmd_opts: There must be less than two 'extras' option records.\n") |
174 | else | |
175 | 2 | lExtras = i |
176 | 2 | if atom(opt[MAPNAME]) then |
177 | 1 | opt[MAPNAME] = "extras" |
178 | 1 | updated = 1 |
179 | end if | |
180 | end if | |
181 | end if | |
182 | ||
183 | 104 | if atom(opt[DESCRIPTION]) then |
184 | 0 | opt[DESCRIPTION] = "" |
185 | 0 | updated = 1 |
186 | end if | |
187 | ||
188 | ||
189 | 104 | if atom(opt[OPTIONS]) then |
190 | 1 | if equal(opt[OPTIONS], HAS_PARAMETER) then |
191 | 1 | opt[OPTIONS] = {HAS_PARAMETER,"x"} |
192 | else | |
193 | 0 | opt[OPTIONS] = {} |
194 | end if | |
195 | 1 | updated = 1 |
196 | else | |
197 | 103 | for j = 1 to length(opt[OPTIONS]) do |
198 | 72 | if find_from(opt[OPTIONS][j], opt[OPTIONS], j + 1) != 0 then |
199 | 0 | crash("cmd_opts: Duplicate processing options are not allowed in an option record.\n") |
200 | end if | |
201 | 72 | end for |
202 | ||
203 | 103 | if find(HAS_PARAMETER, opt[OPTIONS]) then |
204 | 7 | if find(NO_PARAMETER, opt[OPTIONS]) then |
205 | 0 | crash("cmd_opts: Cannot have both HAS_PARAMETER and NO_PARAMETER in an option record.\n") |
206 | end if | |
207 | end if | |
208 | ||
209 | 103 | if find(HAS_CASE, opt[OPTIONS]) then |
210 | 0 | if find(NO_CASE, opt[OPTIONS]) then |
211 | 0 | crash("cmd_opts: Cannot have both HAS_CASE and NO_CASE in an option record.\n") |
212 | end if | |
213 | end if | |
214 | ||
215 | 103 | if find(MANDATORY, opt[OPTIONS]) then |
216 | 4 | if find(OPTIONAL, opt[OPTIONS]) then |
217 | 0 | crash("cmd_opts: Cannot have both MANDATORY and OPTIONAL in an option record.\n") |
218 | end if | |
219 | end if | |
220 | ||
221 | 103 | if find(ONCE, opt[OPTIONS]) then |
222 | 2 | if find(MULTIPLE, opt[OPTIONS]) then |
223 | 0 | crash("cmd_opts: Cannot have both ONCE and MULTIPLE in an option record.\n") |
224 | end if | |
225 | end if | |
226 | ||
227 | end if | |
228 | ||
229 | 104 | if sequence(opt[CALLBACK]) then |
230 | 0 | opt[CALLBACK] = -1 |
231 | 0 | updated = 1 |
232 | 104 | elsif not integer(opt[CALLBACK]) then |
233 | 0 | opt[CALLBACK] = -1 |
234 | 0 | updated = 1 |
235 | 104 | elsif opt[CALLBACK] < 0 then |
236 | 102 | opt[CALLBACK] = -1 |
237 | 102 | updated = 1 |
238 | end if | |
239 | ||
240 | 104 | if sequence(opt[MAPNAME]) and length(opt[MAPNAME]) = 0 then |
241 | 0 | opt[MAPNAME] = 0 |
242 | 0 | updated = 1 |
243 | end if | |
244 | ||
245 | 104 | if atom(opt[MAPNAME]) then |
246 | 75 | if sequence(opt[LONGNAME]) then |
247 | 49 | opt[MAPNAME] = opt[LONGNAME] |
248 | 26 | elsif sequence(opt[SHORTNAME]) then |
249 | 26 | opt[MAPNAME] = opt[SHORTNAME] |
250 | else | |
251 | 0 | opt[MAPNAME] = "extras" |
252 | end if | |
253 | 75 | updated = 1 |
254 | end if | |
255 | ||
256 | 104 | if updated then |
257 | 103 | opts[i] = opt |
258 | end if | |
259 | 104 | end for |
260 | ||
261 | -- Check for duplicate option records. | |
262 | 48 | for i = 1 to length(opts) do |
263 | 104 | sequence opt |
264 | 104 | opt = opts[i] |
265 | 104 | if sequence(opt[SHORTNAME]) then |
266 | 98 | for j = i + 1 to length(opts) do |
267 | 96 | if equal(opt[SHORTNAME], opts[j][SHORTNAME]) then |
268 | 0 | crash("cmd_opts: Duplicate Short Names (%s) are not allowed in an option record.\n", |
269 | { opt[SHORTNAME]}) | |
270 | end if | |
271 | 96 | end for |
272 | end if | |
273 | ||
274 | 104 | if sequence(opt[LONGNAME]) then |
275 | 74 | for j = i + 1 to length(opts) do |
276 | 100 | if equal(opt[LONGNAME], opts[j][LONGNAME]) then |
277 | 0 | crash("cmd_opts: Duplicate Long Names (%s) are not allowed in an option record.\n", |
278 | {opt[LONGNAME]}) | |
279 | end if | |
280 | 100 | end for |
281 | end if | |
282 | 104 | end for |
283 | ||
284 | -- Insert the default 'help' option if one is not already there. | |
285 | 48 | integer has_help = 0 |
286 | 48 | for i = 1 to length(opts) do |
287 | 80 | if find(HELP, opts[i][OPTIONS]) then |
288 | 24 | has_help = 1 |
289 | 24 | exit |
290 | end if | |
291 | 56 | end for |
292 | ||
293 | 48 | if not has_help and add_help_options then |
294 | 24 | opts = append(opts, {"h", "help", "Display the command options", {HELP}, -1}) |
295 | 24 | opts = append(opts, {"?", 0, "Display the command options", {HELP}, -1}) |
296 | ||
297 | -- We have to standardize the above additions | |
298 | 24 | opts = standardize_opts(opts, 0) |
299 | end if | |
300 | ||
301 | 48 | return opts |
302 | end function | |
303 | ||
304 | 16 | |
305 | integer pad_size | |
306 | integer this_size | |
307 | sequence cmd | |
308 | sequence param_name | |
309 | integer has_param | |
310 | integer is_mandatory | |
311 | 16 | integer extras_mandatory = 0 |
312 | 16 | integer extras_opt = 0 |
313 | ||
314 | 16 | if std = 0 then |
315 | 0 | opts = standardize_opts(opts, not equal(add_help_rid, NO_HELP)) |
316 | end if | |
317 | ||
318 | -- Calculate the size of the padding required to keep option text aligned. | |
319 | 16 | pad_size = 0 |
320 | 16 | for i = 1 to length(opts) do |
321 | 47 | this_size = 0 |
322 | 47 | param_name = "" |
323 | ||
324 | 47 | if atom(opts[i][SHORTNAME]) and atom(opts[i][LONGNAME]) then |
325 | 0 | extras_opt = i |
326 | 0 | if find(MANDATORY, opts[i][OPTIONS]) then |
327 | 0 | extras_mandatory = 1 |
328 | end if | |
329 | -- Ignore 'extras' record | |
330 | 0 | continue |
331 | end if | |
332 | ||
333 | 47 | if sequence(opts[i][SHORTNAME]) then |
334 | 47 | this_size += length(opts[i][SHORTNAME]) + 1 -- Allow for "-" |
335 | 47 | if find(MANDATORY, opts[i][OPTIONS]) = 0 then |
336 | 47 | this_size += 2 -- Allow for '[' ']' |
337 | end if | |
338 | end if | |
339 | ||
340 | 47 | if sequence(opts[i][LONGNAME]) then |
341 | 31 | this_size += length(opts[i][LONGNAME]) + 2 -- Allow for "--" |
342 | 31 | if find(MANDATORY, opts[i][OPTIONS]) = 0 then |
343 | 31 | this_size += 2 -- Allow for '[' ']' |
344 | end if | |
345 | end if | |
346 | ||
347 | 47 | if sequence(opts[i][SHORTNAME]) and sequence(opts[i][LONGNAME]) then |
348 | 31 | this_size += 2 -- Allow for ", " between short and long names |
349 | end if | |
350 | ||
351 | 47 | has_param = find(HAS_PARAMETER, opts[i][OPTIONS]) |
352 | 47 | if has_param != 0 then |
353 | 0 | this_size += 1 -- Allow for " " |
354 | 0 | if has_param < length(opts[i][OPTIONS]) then |
355 | --has_param += 1 | |
356 | 0 | if sequence(opts[i][OPTIONS][has_param]) then |
357 | 0 | param_name = opts[i][OPTIONS][has_param] |
358 | else | |
359 | 0 | param_name = "x" |
360 | end if | |
361 | else | |
362 | 0 | param_name = "x" |
363 | end if | |
364 | 0 | this_size += 2 + length(param_name) |
365 | end if | |
366 | ||
367 | 47 | if pad_size < this_size then |
368 | 16 | pad_size = this_size |
369 | end if | |
370 | 47 | end for |
371 | 16 | pad_size += 3 -- Allow for minimum gap between cmd and its description |
372 | ||
373 | 16 | if not equal(add_help_rid, NO_HELP) then |
374 | 16 | printf(1, "%s options:\n", {cmds[2]}) |
375 | end if | |
376 | ||
377 | 16 | for i = 1 to length(opts) do |
378 | 47 | if atom(opts[i][SHORTNAME]) and atom(opts[i][LONGNAME]) then |
379 | -- Ignore 'extras' record | |
380 | 0 | continue |
381 | end if | |
382 | ||
383 | 47 | has_param = find(HAS_PARAMETER, opts[i][OPTIONS]) |
384 | 47 | if has_param != 0 then |
385 | 0 | if has_param < length(opts[i][OPTIONS]) then |
386 | 0 | has_param += 1 |
387 | 0 | if sequence(opts[i][OPTIONS][has_param]) then |
388 | 0 | param_name = opts[i][OPTIONS][has_param] |
389 | else | |
390 | 0 | param_name = "x" |
391 | end if | |
392 | else | |
393 | 0 | param_name = "x" |
394 | end if | |
395 | end if | |
396 | 47 | is_mandatory = (find(MANDATORY, opts[i][OPTIONS]) != 0) |
397 | 47 | cmd = "" |
398 | ||
399 | 47 | if sequence(opts[i][SHORTNAME]) then |
400 | 47 | if not is_mandatory then |
401 | 47 | cmd &= '[' |
402 | end if | |
403 | 47 | cmd &= '-' & opts[i][SHORTNAME] |
404 | 47 | if has_param != 0 then |
405 | 0 | cmd &= ' ' & param_name |
406 | end if | |
407 | 47 | if not is_mandatory then |
408 | 47 | cmd &= ']' |
409 | end if | |
410 | end if | |
411 | ||
412 | 47 | if sequence(opts[i][LONGNAME]) then |
413 | 31 | if length(cmd) > 0 then cmd &= ", " end if |
414 | 31 | if not is_mandatory then |
415 | 31 | cmd &= '[' |
416 | end if | |
417 | 31 | cmd &= "--" & opts[i][LONGNAME] |
418 | 31 | if has_param != 0 then |
419 | 0 | cmd &= '=' & param_name |
420 | end if | |
421 | 31 | if not is_mandatory then |
422 | 31 | cmd &= ']' |
423 | end if | |
424 | end if | |
425 | 47 | puts(1, " " & pad_tail(cmd, pad_size)) |
426 | 47 | puts(1, opts[i][DESCRIPTION] & '\n') |
427 | 47 | end for |
428 | ||
429 | 16 | if extras_mandatory != 0 then |
430 | 0 | if length(opts[extras_opt][DESCRIPTION]) > 0 then |
431 | 0 | puts(1, opts[extras_opt][DESCRIPTION]) |
432 | 0 | puts(1, '\n') |
433 | else | |
434 | 0 | puts(1, "One or more additional arguments are also required\n") |
435 | end if | |
436 | 16 | elsif extras_opt > 0 then |
437 | 0 | if length(opts[extras_opt][DESCRIPTION]) > 0 then |
438 | 0 | puts(1, opts[extras_opt][DESCRIPTION]) |
439 | 0 | puts(1, '\n') |
440 | else | |
441 | 0 | puts(1, "One or more additional arguments can be supplied.\n") |
442 | end if | |
443 | end if | |
444 | ||
445 | 16 | if atom(add_help_rid) then |
446 | 16 | if add_help_rid >= 0 then |
447 | 15 | puts(1, "\n") |
448 | 15 | call_proc(add_help_rid, {}) |
449 | 15 | puts(1, "\n") |
450 | end if | |
451 | else | |
452 | 0 | if length(add_help_rid) > 0 then |
453 | 0 | puts(1, "\n") |
454 | 0 | if t_display(add_help_rid) then |
455 | 0 | add_help_rid = {add_help_rid} |
456 | end if | |
457 | ||
458 | 0 | for i = 1 to length(add_help_rid) do |
459 | 0 | puts(1, add_help_rid[i]) |
460 | 0 | if add_help_rid[i][$] != '\n' then |
461 | 0 | puts(1, '\n') |
462 | end if | |
463 | 0 | end for |
464 | ||
465 | 0 | puts(1, "\n") |
466 | end if | |
467 | end if | |
468 | ||
469 | 16 | end procedure |
470 | ||
471 | ||
472 | --**** | |
473 | -- === Routines | |
474 | ||
475 | --**** | |
476 | -- Signature: | |
477 | -- | |
478 | -- | |
479 | -- Description: | |
480 | -- A **sequence**, of strings, where each string is a word from the command-line that started your program. | |
481 | -- | |
482 | -- Returns: | |
483 | -- # The ##path##, to either the Euphoria executable, (eui, eui.exe, euid.exe euiw.exe) or to your bound | |
484 | -- executable file. | |
485 | -- # The ##next word##, is either the name of your Euphoria main file, or | |
486 | -- (again) the path to your bound executable file. | |
487 | -- # Any ##extra words##, typed by the user. You can use these words in your program. | |
488 | -- | |
489 | -- There are as many entries as words, plus the two mentioned above. | |
490 | -- | |
491 | -- The Euphoria interpreter itself does not use any command-line options. You are free to use | |
492 | -- any options for your own program. It does have [[:command line switches]] though. | |
493 | -- | |
494 | -- The user can put quotes around a series of words to make them into a single argument. | |
495 | -- | |
496 | -- If you convert your program into an executable file, either by binding it, or translating it to C, | |
497 | -- you will find that all command-line arguments remain the same, except for the first two, | |
498 | -- even though your user no longer types "eui" on the command-line (see examples below). | |
499 | -- | |
500 | -- Example 1: | |
501 | -- | |
502 | -- -- The user types: eui myprog myfile.dat 12345 "the end" | |
503 | -- | |
504 | -- cmd = command_line() | |
505 | -- | |
506 | -- -- cmd will be: | |
507 | -- {"C:\EUPHORIA\BIN\EUI.EXE", | |
508 | -- "myprog", | |
509 | -- "myfile.dat", | |
510 | -- "12345", | |
511 | -- "the end"} | |
512 | -- | |
513 | -- | |
514 | -- Example 2: | |
515 | -- | |
516 | -- -- Your program is bound with the name "myprog.exe" | |
517 | -- -- and is stored in the directory c:\myfiles | |
518 | -- -- The user types: myprog myfile.dat 12345 "the end" | |
519 | -- | |
520 | -- cmd = command_line() | |
521 | -- | |
522 | -- -- cmd will be: | |
523 | -- {"C:\MYFILES\MYPROG.EXE", | |
524 | -- "C:\MYFILES\MYPROG.EXE", -- place holder | |
525 | -- "myfile.dat", | |
526 | -- "12345", | |
527 | -- "the end" | |
528 | -- } | |
529 | -- | |
530 | -- -- Note that all arguments remain the same as example 1 | |
531 | -- -- except for the first two. The second argument is always | |
532 | -- -- the same as the first and is inserted to keep the numbering | |
533 | -- -- of the subsequent arguments the same, whether your program | |
534 | -- -- is bound or translated as a .exe, or not. | |
535 | -- | |
536 | -- | |
537 | -- See Also: | |
538 | -- [[:build_commandline]], [[:option_switches]], [[:getenv]], [[:cmd_parse]], [[:show_help]] | |
539 | ||
540 | --**** | |
541 | -- Signature: | |
542 | -- | |
543 | -- | |
544 | -- Description: | |
545 | -- Retrieves the list of switches passed to the interpreter on the command line. | |
546 | -- | |
547 | -- Returns: | |
548 | -- A **sequence**, of strings, each containing a word related to switches. | |
549 | -- | |
550 | -- Comments: | |
551 | -- | |
552 | -- All switches are recorded in upper case. | |
553 | -- | |
554 | -- Example 1: | |
555 | -- | |
556 | -- euiw -d helLo | |
557 | -- -- will result in | |
558 | -- -- option_switches() being {"-D","helLo"} | |
559 | -- | |
560 | -- | |
561 | -- See Also: | |
562 | -- [[:Command line switches]] | |
563 | ||
564 | --** | |
565 | -- Show help message for the given opts. | |
566 | -- | |
567 | -- Parameters: | |
568 | -- # ##opts## : a sequence of options. See the [[:cmd_parse]] for details. | |
569 | -- # ##add_help_rid## : an object. Either a routine_id or a set of text strings. | |
570 | -- The default is -1 meaning that no additional help text will be used. | |
571 | -- # ##cmds## : a sequence of strings. By default this is the output from [[:command_line]]() | |
572 | -- | |
573 | -- Comments: | |
574 | -- * ##opts## is identical to the one used by [[:cmd_parse]] | |
575 | -- * ##add_help_rid## can be used to provide additional help text. By default, just | |
576 | -- the option switches and their descriptions will be displayed. However you can | |
577 | -- provide additional text by either supplying a routine_id of a procedure that | |
578 | -- accepts no parameters; this procedure is expected to write text to the stdout | |
579 | -- device. Or you can supply one or more lines of text that will be displayed. | |
580 | -- | |
581 | -- Example 1: | |
582 | -- | |
583 | -- -- in myfile.ex | |
584 | -- constant description = { | |
585 | -- "Creates a file containing an analysis of the weather.", | |
586 | -- "The analysis includes temperature and rainfall data", | |
587 | -- "for the past week." | |
588 | -- } | |
589 | -- | |
590 | -- show_help({ | |
591 | -- {"q", "silent", "Suppresses any output to console", NO_PARAMETER, -1}, | |
592 | -- {"r", 0, "Sets how many lines the console should display", {HAS_PARAMETER,"lines"}, -1}}, | |
593 | -- description) | |
594 | -- | |
595 | -- Outputs: | |
596 | -- {{{ | |
597 | -- myfile.ex options: | |
598 | -- -q, --silent Suppresses any output to console | |
599 | -- -r lines Sets how many lines the console should display | |
600 | -- | |
601 | -- Creates a file containing an analysis of the weather. | |
602 | -- The analysis includes temperature and rainfall data | |
603 | -- for the past week. | |
604 | -- }}} | |
605 | -- | |
606 | -- Example 2: | |
607 | -- | |
608 | -- -- in myfile.ex | |
609 | -- constant description = { | |
610 | -- "Creates a file containing an analysis of the weather.", | |
611 | -- "The analysis includes temperature and rainfall data", | |
612 | -- "for the past week." | |
613 | -- } | |
614 | -- procedure sh() | |
615 | -- for i = 1 to length(description) do | |
616 | -- printf(1, " >> %s <<\n", {description[i]}) | |
617 | -- end for | |
618 | -- end procedure | |
619 | -- | |
620 | -- show_help({ | |
621 | -- {"q", "silent", "Suppresses any output to console", NO_PARAMETER, -1}, | |
622 | -- {"r", 0, "Sets how many lines the console should display", {HAS_PARAMETER,"lines"}, -1}}, | |
623 | -- routine_id("sh")) | |
624 | -- | |
625 | -- Outputs: | |
626 | -- {{{ | |
627 | -- myfile.ex options: | |
628 | -- -q, --silent Suppresses any output to console | |
629 | -- -r lines Sets how many lines the console should display | |
630 | -- | |
631 | -- >> Creates a file containing an analysis of the weather. << | |
632 | -- >> The analysis includes temperature and rainfall data << | |
633 | -- >> for the past week. << | |
634 | -- }}} | |
635 | -- | |
636 | ||
637 | 0 | |
638 | 0 | local_help(opts, add_help_rid, cmds, 0) |
639 | 0 | end procedure |
640 | ||
641 | --- | |
642 | 8 | |
643 | integer slash | |
644 | sequence opt_name | |
645 | object opt_param | |
646 | 8 | integer param_found = 0 |
647 | 8 | integer reversed = 0 |
648 | ||
649 | 8 | if length(cmd_text) >= 2 then |
650 | -- Strip off any enclosing quotes | |
651 | 4 | if cmd_text[1] = '\'' or cmd_text[1] = '"' then |
652 | 0 | if cmd_text[$] = cmd_text[1] then |
653 | 0 | cmd_text = cmd_text[2 .. $-1] |
654 | end if | |
655 | end if | |
656 | end if | |
657 | ||
658 | 8 | if length(cmd_text) > 0 then |
659 | 8 | if find(cmd_text[1], "!-") then |
660 | 1 | reversed = 1 |
661 | 1 | cmd_text = cmd_text[2 .. $] |
662 | end if | |
663 | end if | |
664 | ||
665 | 8 | if length(cmd_text) < 1 then |
666 | 0 | return {-1, "Empty command text"} |
667 | end if | |
668 | ||
669 | 8 | opt_name = repeat(' ', length(cmd_text)) |
670 | 8 | opt_param = 0 |
671 | 8 | for i = 1 to length(cmd_text) do |
672 | 16 | if find(cmd_text[i], ":=") then |
673 | 1 | opt_name = opt_name[1 .. i - 1] |
674 | 1 | opt_param = cmd_text[i + 1 .. $] |
675 | 1 | if length(opt_param) >= 2 then |
676 | -- Strip off any enclosing quotes | |
677 | 1 | if opt_param[1] = '\'' or opt_param[1] = '"' then |
678 | 0 | if opt_param[$] = opt_param[1] then |
679 | 0 | opt_param = opt_param[2 .. $-1] |
680 | end if | |
681 | end if | |
682 | end if | |
683 | ||
684 | 1 | if length(opt_param) > 0 then |
685 | 1 | param_found = 1 |
686 | end if | |
687 | ||
688 | 1 | exit |
689 | else | |
690 | 15 | opt_name[i] = cmd_text[i] |
691 | end if | |
692 | 15 | end for |
693 | ||
694 | 8 | if param_found then |
695 | 1 | if find(lower(opt_param), {"1", "on", "yes", "y", "true", "ok", "+"}) then |
696 | 0 | opt_param = 1 |
697 | 1 | elsif find(lower(opt_param), {"0", "off", "no", "n", "false", "-"}) then |
698 | 0 | opt_param = 0 |
699 | end if | |
700 | end if | |
701 | ||
702 | 8 | for i = 1 to length(opts) do |
703 | 22 | if find(NO_CASE, opts[i][OPTIONS]) then |
704 | 8 | if not equal(lower(opt_name), lower(opts[i][opt_style[1]])) then |
705 | 6 | continue |
706 | end if | |
707 | else | |
708 | 14 | if not equal(opt_name, opts[i][opt_style[1]]) then |
709 | 8 | continue |
710 | end if | |
711 | end if | |
712 | ||
713 | 8 | if find(HAS_PARAMETER, opts[i][OPTIONS]) = 0 then |
714 | 4 | if param_found then |
715 | 0 | return {0, "Option should not have a parameter"} |
716 | end if | |
717 | end if | |
718 | ||
719 | 8 | if param_found then |
720 | 1 | return {i, opt_name, reversed, opt_param} |
721 | else | |
722 | 7 | if find(HAS_PARAMETER, opts[i][OPTIONS]) = 0 then |
723 | 4 | return {i, opt_name, reversed, 1 } |
724 | end if | |
725 | ||
726 | 3 | return {i, opt_name, reversed} |
727 | end if | |
728 | 0 | end for |
729 | ||
730 | 0 | return {0, "Unrecognised"} |
731 | end function | |
732 | ||
733 | --** | |
734 | -- Parse command line options, and optionally call procedures that relate to these options | |
735 | -- | |
736 | -- Parameters: | |
737 | -- # ##opts## : a sequence of valid option records: See Comments: section for details | |
738 | -- # ##parse_options## : an optional sequence of parse options: See Parse Options section for details | |
739 | -- # ##cmds## : an optional sequence of command line arguments. If omitted the output from | |
740 | -- ##command_line##() is used. | |
741 | -- | |
742 | -- Returns: | |
743 | -- A **map**, containing the options set. The returned map has one special key named "extras" | |
744 | -- which are values passed on the command line that are not part of any option, for instance | |
745 | -- a list of files ##myprog -verbose file1.txt file2.txt##. If any command element begins | |
746 | -- with an @ symbol then that file will be opened and its contents used to add to the command line. | |
747 | -- | |
748 | -- Parse Options: | |
749 | -- ##parse_options## can be a sequence of options that will affect the parsing of | |
750 | -- the command line options. Options can be: | |
751 | -- | |
752 | -- # ##VALIDATE_ALL## ~-- The default. All options will be validated for all possible errors. | |
753 | -- # ##NO_VALIDATION## ~-- Do not validate any parameter. | |
754 | -- # ##NO_VALIDATION_AFTER_FIRST_EXTRA## ~-- Do not validate any parameter after the first extra | |
755 | -- was encountered. This is helpful for programs such as the Interpreter itself: | |
756 | -- ##eui -D TEST greet.ex -name John##. -D TEST should be validated but anything after | |
757 | -- "greet.ex" should not as it is meant for greet.ex to handle, not eui. | |
758 | -- # ##HELP_RID## ~-- Specify a routine id to call in the event of a parse error (invalid option | |
759 | -- given, mandatory option not given, no parameter given for an option that requires a | |
760 | -- parameter, etc...) or a set of text strings. This can be used to provide additional | |
761 | -- help text. By default, just the option switches and their descriptions will be | |
762 | -- displayed. However you can provide additional text by either supplying a | |
763 | -- routine_id of a procedure that accepts no parameters; this procedure is expected | |
764 | -- to write text to the stdout device. Or you can supply one or more lines of text | |
765 | -- that will be displayed. | |
766 | -- # ##NO_AT_EXPANSION## ~-- Do not expand arguments that begin with '@.' | |
767 | -- # ##AT_EXPANSION## ~-- Expand arguments that begin with '@'. The name that follows @ will be | |
768 | -- opened as a file, read, and each trimmed non-empty line that does not begin with a | |
769 | -- '#' character will be inserted as arguments in the command line. These lines | |
770 | -- replace the original '@' argument as if they had been entered on the original | |
771 | -- command line. \\ | |
772 | -- ** If the name following the '@' begins with another '@', the extra | |
773 | -- '@' is removed and the remainder is the name of the file. However, if that | |
774 | -- file cannot be read, it is simply ignored. This allows //optional// files | |
775 | -- to be included on the command line. Normally, with just a single '@', if the | |
776 | -- file cannot be found the program aborts. | |
777 | -- ** Lines whose first non-whitespace character is '#' are treated as a comment | |
778 | -- and thus ignored. | |
779 | -- ** Lines enclosed with double quotes will have the quotes stripped off and the | |
780 | -- result is used as an argument. This can be used for arguments that begin with | |
781 | -- a '#' character, for example. | |
782 | -- ** Lines enclosed with single quotes will have the quotes stripped off and | |
783 | -- the line is then further split up use the space character as a delimiter. The | |
784 | -- resulting 'words' are then all treated as individual arguments on the command | |
785 | -- line. | |
786 | -- | |
787 | -- An example of parse options: | |
788 | -- | |
789 | -- { HELP_RID, routine_id("my_help"), NO_VALIDATION } | |
790 | -- | |
791 | -- | |
792 | -- Comments: | |
793 | -- Token types recognized on the command line: | |
794 | -- # a single '-'. Simply added to the 'extras' list | |
795 | -- # a single "~-~-". This signals the end of command line options. What remains of the command | |
796 | -- line is added to the 'extras' list, and the parsing terminates. | |
797 | -- # -shortName. The option will be looked up in the short name field of ##opts##. | |
798 | -- # /shortName. Same as -shortName. | |
799 | -- # -!shortName. If the 'shortName' has already been found the option is removed. | |
800 | -- # /!shortName. Same as -!shortName | |
801 | -- # ~-~-longName. The option will be looked up in the long name field of ##opts##. | |
802 | -- # ~-~-!longName. If the 'longName' has already been found the option is removed. | |
803 | -- # anything else. The word is simply added to the 'extras' list. | |
804 | -- | |
805 | -- For those options that require a parameter to also be supplied, the parameter | |
806 | -- can be given as either the next command line argument, or by appending '=' or ':' | |
807 | -- to the command option then appending the parameter data. \\ | |
808 | -- For example, **##-path=/usr/local##** or as **##-path /usr/local##**. | |
809 | -- | |
810 | -- On a failed lookup, the program shows the help by calling [[:show_help]](##opts##, | |
811 | -- ##add_help_rid##, ##cmds##) and terminates with status code 1. | |
812 | -- | |
813 | -- Option records have the following structure: | |
814 | -- # a sequence representing the (short name) text that will follow the "-" option format. | |
815 | -- Use an atom if not relevant | |
816 | -- # a sequence representing the (long name) text that will follow the "~-~-" option format. | |
817 | -- Use an atom if not relevant | |
818 | -- # a sequence, text that describes the option's purpose. Usually short as it is | |
819 | -- displayed when "-h"/"--help" is on the command line. Use an atom if not required. | |
820 | -- # An object ... | |
821 | -- ** If an **atom** then it can be either ##HAS_PARAMETER## or anything | |
822 | -- else if there is no parameter for this option. This format also implies that | |
823 | -- the option is optional, case-sensitive and can only occur once. | |
824 | -- ** If a **sequence**, it can containing zero or more processing flags in any order ... | |
825 | -- *** ##MANDATORY## to indicate that the option must always be supplied. | |
826 | -- *** ##HAS_PARAMETER## to indicate that the option must have a parameter following it. | |
827 | -- You can optionally have a name for the parameter immediately follow the ##HAS_PARAMETER## | |
828 | -- flag. If one isn't there, the help text will show "x" otherwise it shows the | |
829 | -- supplied name. | |
830 | -- *** ##NO_CASE## to indicate that the case of the supplied option is not significant. | |
831 | -- *** ##ONCE## to indicate that the option must only occur once on the command line. | |
832 | -- *** ##MULTIPLE## to indicate that the option can occur any number of times on the command line. | |
833 | -- ** If both ##ONCE## and ##MULTIPLE## are omitted then switches that also have | |
834 | -- ##HAS_PARAMETER## are only allowed once but switches without ##HAS_PARAMETER## | |
835 | -- can have multuple occurances but only one is recorded in the output map. | |
836 | -- # an integer; a [[:routine_id]]. This function will be called when the option is located | |
837 | -- on the command line and before it updates the map. \\ | |
838 | -- Use -1 if cmd_parse is not to invoke a function for this option.\\ | |
839 | -- The user defined function must accept a single sequence parameter containing four values. | |
840 | -- If the function returns ##1## then the command option does not update the map. | |
841 | -- You can use the predefined index values OPT_IDX, OPT_CNT, OPT_VAL, OPT_REV when | |
842 | -- referencing the function's parameter elements. | |
843 | -- ## An index into the ##opts## list. | |
844 | -- ## The number of times that the routine has been called | |
845 | -- by cmd_parse for this option | |
846 | -- ## The option's value as found on the command line | |
847 | -- ## 1 if the command line indicates that this option is to remove any earlier occurrences of it. | |
848 | -- | |
849 | -- When assigning a value to the resulting map, the key is the long name if present, | |
850 | -- otherwise it uses the short name. For options, you must supply a short name, | |
851 | -- a long name or both. | |
852 | -- | |
853 | -- If you want ##cmd_parse##() to call a user routine for the extra command line values, | |
854 | -- you need to specify an Option Record that has neither a short name or a long name, | |
855 | -- in which case only the routine_id field is used. | |
856 | -- | |
857 | -- For more details on how the command line is being pre-parsed, see [[:command_line]]. | |
858 | -- | |
859 | -- Example: | |
860 | -- | |
861 | -- sequence option_definition | |
862 | -- integer gVerbose = 0 | |
863 | -- sequence gOutFile = {} | |
864 | -- sequence gInFile = {} | |
865 | -- procedure opt_verbose( sequence value) | |
866 | -- if value[OPT_VAL] = -1 then -- (-!v used on command line) | |
867 | -- gVerbose = 0 | |
868 | -- else | |
869 | -- if value[OPT_CNT] = 1 then | |
870 | -- gVerbose = 1 | |
871 | -- else | |
872 | -- gVerbose += 1 | |
873 | -- end if | |
874 | -- end if | |
875 | -- end procedure | |
876 | -- | |
877 | -- procedure opt_output_filename( sequence value) | |
878 | -- gOutFile = value[OPT_VAL] | |
879 | -- end procedure | |
880 | -- | |
881 | -- procedure opt_extras( sequence value) | |
882 | -- if not file_exists(value[OPT_VAL]) then | |
883 | -- show_help(option_definitions, sprintf("Cannot find '%s'", value[OPT_VAL])) | |
884 | -- abort(1) | |
885 | -- end if | |
886 | -- gInFile = append(gInFile, value[OPT_VAL]) | |
887 | -- end procedure | |
888 | -- | |
889 | -- option_definition = { | |
890 | -- { "v", "verbose", "Verbose output",{NO_PARAMETER}, routine_id("opt_verbose")}, | |
891 | -- { "h", "hash", "Calculate hash values",{NO_PARAMETER}, -1}, | |
892 | -- { "o", "output", "Output filename",{MANDATORY, HAS_PARAMETER, ONCE} , routine_id("opt_output_filename") }, | |
893 | -- { "i", "import", "An import path", {HAS_PARAMETER, MULTIPLE}, -1 }, | |
894 | -- { 0, 0, 0, 0, routine_id("opt_extras")} | |
895 | -- } | |
896 | -- | |
897 | -- map:map opts = cmd_parse(option_definition) | |
898 | -- | |
899 | -- -- When run as: eui myprog.ex -v @output.txt -i /etc/app input1.txt input2.txt | |
900 | -- -- and the file "output.txt" contains the two lines ... | |
901 | -- -- --output=john.txt | |
902 | -- -- '-i /usr/local' | |
903 | -- -- | |
904 | -- -- map:get(opts, "verbose") --> 1 | |
905 | -- -- map:get(opts, "hash") --> 0 (not supplied on command line) | |
906 | -- -- map:get(opts, "output") --> "john.txt" | |
907 | -- -- map:get(opts, "import") --> {"/usr/local", "/etc/app"} | |
908 | -- -- map:get(opts, "extras") --> {"input1.txt", "input2.txt"} | |
909 | -- | |
910 | -- | |
911 | -- See Also: | |
912 | -- [[:show_help]], [[:command_line]] | |
913 | ||
914 | 24 | |
915 | integer arg_idx, opts_done | |
916 | sequence cmd | |
917 | object param | |
918 | sequence find_result | |
919 | sequence type_ | |
920 | integer from_ | |
921 | sequence help_opts | |
922 | sequence call_count | |
923 | 24 | integer add_help_rid = -1 |
924 | 24 | integer validation = VALIDATE_ALL |
925 | 24 | integer has_extra = 0 |
926 | 24 | integer use_at = 1 |
927 | ||
928 | 24 | if sequence(parse_options) then |
929 | 21 | integer i = 1 |
930 | ||
931 | 21 | while i <= length(parse_options) do |
932 | 40 | switch parse_options[i] do |
933 | case HELP_RID then | |
934 | 20 | if i < length(parse_options) then |
935 | 20 | i += 1 |
936 | 20 | add_help_rid = parse_options[i] |
937 | else | |
938 | 0 | crash("HELP_RID was given to cmd_parse with no routine_id") |
939 | end if | |
940 | ||
941 | case VALIDATE_ALL then | |
942 | 6 | validation = VALIDATE_ALL |
943 | ||
944 | case NO_VALIDATION then | |
945 | 6 | validation = NO_VALIDATION |
946 | ||
947 | case NO_VALIDATION_AFTER_FIRST_EXTRA then | |
948 | 8 | validation = NO_VALIDATION_AFTER_FIRST_EXTRA |
949 | ||
950 | case NO_AT_EXPANSION then | |
951 | 0 | use_at = 0 |
952 | ||
953 | case AT_EXPANSION then | |
954 | 0 | use_at = 1 |
955 | ||
956 | case PAUSE_MSG then | |
957 | 0 | if i < length(parse_options) then |
958 | 0 | i += 1 |
959 | 0 | pause_msg = parse_options[i] |
960 | else | |
961 | 0 | crash("PAUSE_MSG was given to cmd_parse with no actually message text") |
962 | end if | |
963 | ||
964 | end switch | |
965 | 40 | i += 1 |
966 | 40 | end while |
967 | ||
968 | 3 | elsif atom(parse_options) then |
969 | 3 | add_help_rid = parse_options |
970 | end if | |
971 | ||
972 | 24 | opts = standardize_opts(opts) |
973 | ||
974 | 24 | call_count = repeat(0, length(opts)) |
975 | ||
976 | 24 | map:map parsed_opts = map:new() |
977 | ||
978 | 24 | map:put(parsed_opts, "extras", {}) |
979 | ||
980 | 24 | arg_idx = 2 |
981 | 24 | opts_done = 0 |
982 | ||
983 | -- Find if there are any user-defined help options. | |
984 | 24 | help_opts = { "h", "?", "help" } |
985 | 24 | for i = 1 to length(opts) do |
986 | 76 | if find(HELP, opts[i][OPTIONS]) then |
987 | 48 | if sequence(opts[i][SHORTNAME]) then |
988 | 48 | help_opts = append(help_opts, opts[i][SHORTNAME]) |
989 | end if | |
990 | 48 | if sequence(opts[i][LONGNAME]) then |
991 | 24 | help_opts = append(help_opts, opts[i][LONGNAME]) |
992 | end if | |
993 | 48 | if find(NO_CASE, opts[i][OPTIONS]) then |
994 | 0 | help_opts = lower(help_opts) |
995 | 0 | arg_idx = length(help_opts) |
996 | 0 | for j = 1 to arg_idx do |
997 | 0 | help_opts = append(help_opts, upper(help_opts[j])) |
998 | 0 | end for |
999 | end if | |
1000 | end if | |
1001 | 76 | end for |
1002 | ||
1003 | 24 | while arg_idx < length(cmds) do |
1004 | 45 | arg_idx += 1 |
1005 | ||
1006 | 45 | cmd = cmds[arg_idx] |
1007 | 45 | if length(cmd) = 0 then |
1008 | 0 | continue |
1009 | end if | |
1010 | ||
1011 | 45 | if cmd[1] = '@' and use_at then |
1012 | 3 | object at_cmds |
1013 | 3 | sequence at_file |
1014 | 3 | integer j |
1015 | ||
1016 | 3 | if length(cmd) > 2 and cmd[2] = '@' then |
1017 | -- Read in the lines from the optional file. | |
1018 | 2 | at_cmds = io:read_lines(cmd[3..$]) |
1019 | 2 | if equal(at_cmds, -1) then |
1020 | -- File didn't exist but this is not an error, so just | |
1021 | -- remove it from the commands. | |
1022 | 1 | cmds = cmds[1..arg_idx-1] & cmds[arg_idx+1..$] |
1023 | 1 | arg_idx -= 1 |
1024 | 1 | continue |
1025 | end if | |
1026 | else | |
1027 | -- Read in the lines from the file. | |
1028 | 1 | at_cmds = io:read_lines(cmd[2..$]) |
1029 | 1 | if equal(at_cmds, -1) then |
1030 | 0 | printf(2, "Cannot access '@' argument file '%s'\n", {cmd[2..$]}) |
1031 | 0 | local_help(opts, add_help_rid, cmds, 1) |
1032 | 0 | local_abort(1) |
1033 | end if | |
1034 | end if | |
1035 | -- Parse the 'at' commands removing comment lines and empty lines, | |
1036 | -- and stripping off any enclosing quotes from lines. | |
1037 | 2 | j = 0 |
1038 | 2 | while j < length(at_cmds) do |
1039 | 12 | j += 1 |
1040 | 12 | at_cmds[j] = trim(at_cmds[j]) |
1041 | 12 | if length(at_cmds[j]) = 0 then |
1042 | 2 | at_cmds = at_cmds[1 .. j-1] & at_cmds[j+1 ..$] |
1043 | 2 | j -= 1 |
1044 | ||
1045 | 10 | elsif at_cmds[j][1] = '#' then |
1046 | 3 | at_cmds = at_cmds[1 .. j-1] & at_cmds[j+1 ..$] |
1047 | 3 | j -= 1 |
1048 | ||
1049 | 7 | elsif at_cmds[j][1] = '"' and at_cmds[j][$] = '"' and length(at_cmds[j]) >= 2 then |
1050 | 2 | at_cmds[j] = at_cmds[j][2 .. $-1] |
1051 | ||
1052 | 5 | elsif at_cmds[j][1] = '\'' and at_cmds[j][$] = '\'' and length(at_cmds[j]) >= 2 then |
1053 | 1 | sequence cmdex = split(' ', at_cmds[j][2 .. $-1], 0, 1) -- Empty words removed. |
1054 | ||
1055 | 1 | at_cmds = at_cmds[1..j-1] & cmdex & at_cmds[j+1 .. $] |
1056 | 1 | j = j + length(cmdex) - 1 |
1057 | ||
1058 | end if | |
1059 | 12 | end while |
1060 | ||
1061 | -- Replace the '@' argument with the contents of the file. | |
1062 | 2 | cmds = cmds[1..arg_idx-1] & at_cmds & cmds[arg_idx+1..$] |
1063 | 2 | arg_idx -= 1 |
1064 | 2 | continue |
1065 | end if | |
1066 | ||
1067 | 42 | if (opts_done or find(cmd[1], CMD_SWITCHES) = 0 or length(cmd) = 1) |
1068 | then | |
1069 | 18 | map:put(parsed_opts, "extras", cmd, map:APPEND) |
1070 | 18 | has_extra = 1 |
1071 | 18 | if validation = NO_VALIDATION_AFTER_FIRST_EXTRA then |
1072 | 5 | exit |
1073 | else | |
1074 | 13 | continue |
1075 | end if | |
1076 | end if | |
1077 | ||
1078 | 24 | if equal(cmd, "--") then |
1079 | 0 | opts_done = 1 |
1080 | 0 | continue |
1081 | end if | |
1082 | ||
1083 | 24 | if equal(cmd[1..2], "--") then -- found --opt-name |
1084 | 7 | type_ = {LONGNAME, "--"} |
1085 | 7 | from_ = 3 |
1086 | 17 | elsif cmd[1] = '-' then -- found -opt |
1087 | 17 | type_ = {SHORTNAME, "-"} |
1088 | 17 | from_ = 2 |
1089 | else -- found /opt | |
1090 | 0 | type_ = {SHORTNAME, "/"} |
1091 | 0 | from_ = 2 |
1092 | end if | |
1093 | ||
1094 | 24 | if find(cmd[from_..$], help_opts) then |
1095 | 16 | local_help(opts, add_help_rid, cmds, 1) |
1096 | 16 | ifdef UNITTEST then |
1097 | 16 | return 0 |
1098 | end ifdef | |
1099 | 0 | local_abort(0) |
1100 | end if | |
1101 | ||
1102 | 8 | find_result = find_opt(opts, type_, cmd[from_..$]) |
1103 | ||
1104 | 8 | if find_result[1] < 0 then |
1105 | 0 | continue -- Couldn't use this command argument for anything. |
1106 | end if | |
1107 | ||
1108 | 8 | if find_result[1] = 0 then |
1109 | 0 | if validation = VALIDATE_ALL or |
1110 | (validation = NO_VALIDATION_AFTER_FIRST_EXTRA and has_extra = 0) | |
1111 | then | |
1112 | -- something is wrong with the option | |
1113 | 0 | printf(1, "option '%s': %s\n\n", {cmd, find_result[2]}) |
1114 | 0 | local_help(opts, add_help_rid, cmds, 1) |
1115 | 0 | local_abort(1) |
1116 | end if | |
1117 | ||
1118 | 0 | continue |
1119 | end if | |
1120 | ||
1121 | 8 | sequence opt = opts[find_result[1]] |
1122 | ||
1123 | 8 | if find(HAS_PARAMETER, opt[OPTIONS]) != 0 then |
1124 | 4 | if length(find_result) < 4 then |
1125 | 3 | arg_idx += 1 |
1126 | 3 | if arg_idx <= length(cmds) then |
1127 | 3 | param = cmds[arg_idx] |
1128 | 3 | if length(param) = 2 and find(param[1], "-/") then |
1129 | 0 | param = "" |
1130 | end if | |
1131 | else | |
1132 | 0 | param = "" |
1133 | end if | |
1134 | ||
1135 | 3 | if length(param) = 0 and (validation = VALIDATE_ALL or ( |
1136 | validation = NO_VALIDATION_AFTER_FIRST_EXTRA)) | |
1137 | then | |
1138 | 0 | printf(1, "option '%s' must have a parameter\n\n", {find_result[2]}) |
1139 | 0 | local_help(opts, add_help_rid, cmds, 1) |
1140 | 0 | local_abort(1) |
1141 | end if | |
1142 | else | |
1143 | 1 | param = find_result[4] |
1144 | end if | |
1145 | else | |
1146 | 4 | param = find_result[4] |
1147 | end if | |
1148 | ||
1149 | 8 | if opt[CALLBACK] >= 0 then |
1150 | 2 | integer pos = find_result[1] |
1151 | 2 | call_count[pos] += 1 |
1152 | -- OPT_IDX OPT_CNT OPT_VAL OPT_REV | |
1153 | 2 | if call_func(opt[CALLBACK], {{find_result[1], call_count[pos], param, find_result[3]}}) = 0 then |
1154 | 0 | continue |
1155 | end if | |
1156 | end if | |
1157 | ||
1158 | 8 | if find_result[3] = 1 then |
1159 | 1 | map:remove(parsed_opts, opt[MAPNAME]) |
1160 | else | |
1161 | 7 | if find(MULTIPLE, opt[OPTIONS]) = 0 then |
1162 | 7 | if map:has(parsed_opts, opt[MAPNAME]) and (validation = VALIDATE_ALL or |
1163 | (validation = NO_VALIDATION_AFTER_FIRST_EXTRA)) | |
1164 | then | |
1165 | 0 | if find(HAS_PARAMETER, opt[OPTIONS]) or find(ONCE, opt[OPTIONS]) then |
1166 | 0 | printf(1, "option '%s' must not occur more than once in the command line.\n\n", {find_result[2]}) |
1167 | 0 | local_help(opts, add_help_rid, cmds, 1) |
1168 | 0 | local_abort(1) |
1169 | end if | |
1170 | else | |
1171 | 7 | map:put(parsed_opts, opt[MAPNAME], param) |
1172 | end if | |
1173 | else | |
1174 | 0 | map:put(parsed_opts, opt[MAPNAME], param, map:APPEND) |
1175 | end if | |
1176 | end if | |
1177 | 8 | end while |
1178 | ||
1179 | -- Check that all mandatory options have been supplied. | |
1180 | 8 | for i = 1 to length(opts) do |
1181 | 29 | if find(MANDATORY, opts[i][OPTIONS]) then |
1182 | 2 | if atom(opts[i][SHORTNAME]) and atom(opts[i][LONGNAME]) then |
1183 | 1 | if length(map:get(parsed_opts, opts[i][MAPNAME])) = 0 then |
1184 | 0 | puts(1, "Additional arguments were expected.\n\n") |
1185 | 0 | local_help(opts, add_help_rid, cmds, 1) |
1186 | 0 | local_abort(1) |
1187 | end if | |
1188 | else | |
1189 | 1 | if not map:has(parsed_opts, opts[i][MAPNAME]) then |
1190 | 0 | printf(1, "option '%s' is mandatory but was not supplied.\n\n", {opts[i][MAPNAME]}) |
1191 | 0 | local_help(opts, add_help_rid, cmds, 1) |
1192 | 0 | local_abort(1) |
1193 | end if | |
1194 | end if | |
1195 | end if | |
1196 | 29 | end for |
1197 | ||
1198 | 8 | return parsed_opts |
1199 | end function | |
1200 | ||
1201 | --** | |
1202 | -- Returns a text string based on the set of supplied strings. Typically, this | |
1203 | -- is used to ensure that arguments on a command line are properly formed | |
1204 | -- before submitting it to the shell. | |
1205 | -- | |
1206 | -- Parameters: | |
1207 | -- # ##cmds## : A sequence. Contains zero or more strings. | |
1208 | -- | |
1209 | -- Returns: | |
1210 | -- A **sequence**, which is a text string. Each of the strings in ##cmds## is | |
1211 | -- quoted if they contain spaces, and then concatenated to form a single | |
1212 | -- string. | |
1213 | -- | |
1214 | -- Comments: | |
1215 | -- Though this function does the quoting for you it is not going to protect | |
1216 | -- your programs from globing *, ?. And it is not specied here what happens if you | |
1217 | -- pass redirection or piping characters. | |
1218 | -- | |
1219 | -- Example 1: | |
1220 | -- | |
1221 | -- s = build_commandline( { "-d", "/usr/my docs/"} ) | |
1222 | -- -- s now contains '-d "/usr/my docs/"' | |
1223 | -- | |
1224 | -- | |
1225 | -- Example 2: | |
1226 | -- You can use this to run things that might be difficult to quote out: | |
1227 | -- Suppose you want to run a program that requires quotes on its | |
1228 | -- command line? Use this function to pass quotation marks: | |
1229 | -- | |
1230 | -- | |
1231 | -- s = build_commandline( { "awk", "-e", "'{ print $1"x"$2; }'" } ) | |
1232 | -- system(s,0) | |
1233 | -- | |
1234 | -- | |
1235 | -- See Also: | |
1236 | -- [[:parse_commandline]], [[:system]], [[:system_exec]], [[:command_line]] | |
1237 | ||
1238 | 13 | |
1239 | 13 | return flatten(quote( cmds,,'\\'," " ), " ") |
1240 | end function | |
1241 | ||
1242 | --** | |
1243 | -- Parse a command line string breaking it into a sequence of command line | |
1244 | -- options. | |
1245 | -- | |
1246 | -- Parameters: | |
1247 | -- # ##cmdline## : Command line sequence (string) | |
1248 | -- | |
1249 | -- Returns: | |
1250 | -- A **sequence**, of command line options | |
1251 | -- | |
1252 | -- Example 1: | |
1253 | -- | |
1254 | -- sequence opts = parse_commandline("-v -f '%Y-%m-%d %H:%M') | |
1255 | -- -- opts = { "-v", "-f", "%Y-%m-%d %H:%M" } | |
1256 | -- | |
1257 | -- | |
1258 | -- See Also: | |
1259 | -- [[:build_commandline]] | |
1260 | -- | |
1261 | ||
1262 | 1 | |
1263 | 1 | return keyvalues(cmdline, " ", ":=", "\"'`", " \t\r\n", 0) |
1264 | end function |