1# shellcheck shell=sh disable=SC1083,SC2004,SC2016
2
3Describe "getoptions()"
4	Include ./lib/getoptions_base.sh
5
6	parse() {
7		eval "$(getoptions parser_definition _parse)"
8		case $# in
9			0) _parse ;;
10			*) _parse "$@" ;;
11		esac
12	}
13
14	restargs() {
15		parse "$@"
16		eval "set -- $ARGS"
17		if [ $# -gt 0 ]; then
18			echo "$@"
19		fi
20	}
21
22	It "generates option parser"
23		parser_definition() { setup ARGS; echo 'called' >&2; }
24		When call parse
25		The word 1 of stderr should eq "called"
26		The status should be success
27	End
28
29	It "generates option parser with help"
30		parser_definition() { setup ARGS help:usage; }
31		getoptions_help() { echo 'getoptions_help called'; }
32		When call parse
33		The output should eq 'getoptions_help called'
34		The status should be success
35	End
36
37	It "parses options with default parser name"
38		parse() {
39			eval "$(getoptions parser_definition -)"
40			printf '%s ' "$@"
41		}
42		parser_definition() { setup ARGS; echo 'called' >&2; }
43		When call parse 1 2 3
44		The word 1 of stderr should eq "called"
45		The output should eq "1 2 3 "
46		The status should be success
47	End
48
49	Describe 'handling arguments'
50		Context 'when scanning mode is default'
51			parser_definition() {
52				setup ARGS -- 'foo bar'
53				flag FLAG_A -a
54			}
55			Specify "treats non-options as arguments"
56				When call restargs -a 1 -a 2 -a 3 - -- -a
57				The variable FLAG_A should eq 1
58				The output should eq "1 2 3 - -a"
59			End
60		End
61
62		Context "when scanning mode is '+'"
63			parser_definition() {
64				setup ARGS mode:'+'
65				flag FLAG_A -a
66			}
67			Specify "treats rest following a non-option as arguments"
68				When call restargs -a 1 -a 2 -a 3 -- -a
69				The variable FLAG_A should eq 1
70				The output should eq "1 -a 2 -a 3 -- -a"
71			End
72
73			Specify "treats -- as not arguments"
74				When call restargs -a -- -a
75				The variable FLAG_A should eq 1
76				The output should eq "-a"
77			End
78		End
79
80		Context "when scanning mode is '@'"
81			parser_definition() {
82				setup ARGS mode:'@'
83				flag FLAG_A -a
84			}
85			Specify "treats rest following a non-option as arguments"
86				When call restargs -a 1 -a 2 -a 3 -- -a
87				The variable FLAG_A should eq 1
88				The output should eq "1 -a 2 -a 3 -- -a"
89			End
90
91			Specify "treats -- as arguments"
92				When call restargs -a -- -a
93				The variable FLAG_A should eq 1
94				The output should eq "-- -a"
95			End
96		End
97
98		Context 'when the plus attribute disabled (default)'
99			parser_definition() { setup ARGS; }
100			Specify "treats as arguments"
101				When call restargs +o
102				The output should eq "+o"
103			End
104		End
105	End
106
107	Describe 'parser function'
108		Context 'when the option parser ends normally'
109			parser_definition() {
110				setup ARGS
111				flag FLAG_A -a
112			}
113			It "resets OPTIND and OPTARG"
114				# Workaround for ksh 88
115				foo() { [ "$OPTIND" -eq 1 ] || unset OPTIND; }
116				OPTIND=1 && foo
117
118				When call parse -a
119				The variable OPTIND should eq 1
120				The variable OPTARG should be undefined
121			End
122		End
123	End
124
125	Describe 'Default error handler'
126		Context "when specified unknown option"
127			parser_definition() { setup ARGS; }
128			It "displays error"
129				When run parse -x
130				The stderr should eq "Unrecognized option: -x"
131				The status should be failure
132			End
133		End
134
135		Context "when specified unknown long option"
136			parser_definition() { setup ARGS; }
137			It "displays error"
138				When run parse --long
139				The stderr should eq "Unrecognized option: --long"
140				The status should be failure
141			End
142		End
143
144		Context "when specified an argument to flag"
145			parser_definition() { setup ARGS; flag FLAG --flag; }
146			It "displays error"
147				When run parse --flag=value
148				The stderr should eq "Does not allow an argument: --flag"
149				The status should be failure
150			End
151		End
152
153		Context "when missing an argument for parameter"
154			parser_definition() { setup ARGS; param PARAM --param; }
155			It "displays error"
156				When run parse --param
157				The stderr should eq "Requires an argument: --param"
158				The status should be failure
159			End
160		End
161
162		Context 'when the plus attribute enabled'
163			parser_definition() { setup ARGS plus:true; }
164			It "displays error if unknown +option specified"
165				When run restargs +o
166				The stderr should eq "Unrecognized option: +o"
167				The status should be failure
168			End
169		End
170	End
171
172	Describe 'alternative mode'
173		parser_definition() {
174			setup ARGS alt:true
175			flag FLAG --flag
176			param PARAM --param
177			option OPTION --option
178		}
179		It "allow long options to start with a single '-'"
180			When call parse -flag -param p -option=o
181			The variable FLAG should eq 1
182			The variable PARAM should eq "p"
183			The variable OPTION should eq "o"
184		End
185	End
186
187	Describe 'prehook'
188		parser_definition() {
189			prehook() { echo "$@" >&2; invoke "$@"; }
190			setup ARGS alt:true
191			flag FLAG --flag
192			param PARAM --param
193			option OPTION --option
194			msg -- 'message'
195		}
196		It "called before helper functions is called"
197			When call parse -flag -param p -option=o
198			The line 1 of stderr should eq "setup ARGS alt:true"
199			The line 2 of stderr should eq "flag FLAG --flag"
200			The line 3 of stderr should eq "param PARAM --param"
201			The line 4 of stderr should eq "option OPTION --option"
202			The line 5 of stderr should eq "msg -- message"
203			The line 6 of stderr should eq "flag FLAG --flag"
204			The line 7 of stderr should eq "param PARAM --param"
205			The line 8 of stderr should eq "option OPTION --option"
206			The line 9 of stderr should eq "msg -- message"
207		End
208	End
209
210	Describe 'custom error handler'
211		parser_definition() {
212			setup RESTARGS error
213			param PARAM -p
214			param PARAM -q
215			param PARAM --pattern pattern:'foo | bar'
216			param VALID -v validate:'valid "$1"'
217			param ARG --arg validate:arg
218			flag  FLAG --flag
219		}
220		valid() { [ "$1" = "-v" ] && return 3; }
221		arg() { false; }
222		error() {
223			case $2 in
224				unknown) echo "custom $2: $3 [$OPTARG]"; return 20 ;;
225				valid:3) echo "valid $2: $3 [$OPTARG]"; return 30 ;;
226				pattern:'foo | bar') echo "pattern $2: $3 [$OPTARG]"; return 40 ;;
227				arg:*) echo "invalid argument [$OPTARG]"; return 1 ;;
228				noarg) echo "noarg [$OPTARG]"; return 1 ;;
229			esac
230			[ "$3" = "-q" ] && echo "$1 [$OPTARG]" && return 1
231			return 0
232		}
233
234		It "display custom error message"
235			When run parse -x
236			The stderr should eq "custom unknown: -x []"
237			The status should eq 20
238		End
239
240		It "display default error message when custom error handler succeeded"
241			When run parse -p
242			The stderr should eq "Requires an argument: -p"
243			The status should eq 1
244		End
245
246		It "receives default error message"
247			When run parse -q
248			The stderr should eq "Requires an argument: -q []"
249			The status should eq 1
250		End
251
252		It "receives exit status of custom validation"
253			When run parse -v value
254			The stderr should eq "valid valid:3: -v [value]"
255			The status should eq 30
256		End
257
258		It "receives pattern"
259			When run parse --pattern baz
260			The stderr should eq "pattern pattern:foo | bar: --pattern [baz]"
261			The status should eq 40
262		End
263
264		It "can refer to the OPTARG variable"
265			When run parse --arg argument
266			The stderr should eq "invalid argument [argument]"
267			The status should eq 1
268		End
269
270		It "can refer to the OPTARG variable"
271			When run parse --flag=argument
272			The stderr should eq "noarg [argument]"
273			The status should eq 1
274		End
275	End
276
277	Describe 'flag helper'
278		It "handles flags"
279			parser_definition() {
280				setup ARGS
281				flag FLAG_A -a
282				flag FLAG_B +b
283				flag FLAG_C --flag-c
284				flag FLAG_D --{no-}flag-d
285				flag FLAG_E --no-flag-e
286				flag FLAG_F --{no-}flag-f
287				flag FLAG_G --with{out}-flag-g
288				flag FLAG_H --without-flag-h
289				flag FLAG_I --with{out}-flag-i
290			}
291			When call parse -a +b --flag-c --flag-d --no-flag-e --no-flag-f --with-flag-g --without-flag-h --without-flag-i
292			The variable FLAG_A should eq 1
293			The variable FLAG_B should eq ""
294			The variable FLAG_C should eq 1
295			The variable FLAG_D should eq 1
296			The variable FLAG_E should eq ""
297			The variable FLAG_F should eq ""
298			The variable FLAG_G should eq 1
299			The variable FLAG_H should eq ""
300			The variable FLAG_I should eq ""
301		End
302
303		It "can change the set value"
304			parser_definition() {
305				setup ARGS
306				flag FLAG_A -a on:ON no:NO
307				flag FLAG_B +b on:ON no:NO
308			}
309			When call parse -a +b
310			The variable FLAG_A should eq "ON"
311			The variable FLAG_B should eq "NO"
312		End
313
314		It "set initial value when not specified flag"
315			BeforeCall FLAG_N=none FLAG_E=""
316			parser_definition() {
317				setup ARGS
318				flag FLAG_A -a on:ON no:NO init:@on
319				flag FLAG_B -b on:ON no:NO init:@no
320				flag FLAG_C -c on:ON no:NO init:'FLAG_C=func'
321				flag FLAG_D -d on:ON no:NO
322				flag FLAG_Q -q on:"a'b\""
323				flag FLAG_U -u init:@unset
324				flag FLAG_N -n init:@none
325				flag FLAG_E -n init:@export
326			}
327			When call parse -q
328			The variable FLAG_A should eq "ON"
329			The variable FLAG_B should eq "NO"
330			The variable FLAG_C should eq "func"
331			The variable FLAG_D should eq ""
332			The variable FLAG_Q should eq "a'b\""
333			The variable FLAG_U should be undefined
334			The variable FLAG_N should eq "none"
335			The variable FLAG_E should be exported
336		End
337
338		It "can be used combined short flags"
339			parser_definition() {
340				setup ARGS
341				flag FLAG_A -a
342				flag FLAG_B -b
343				flag FLAG_C -c
344				flag FLAG_D +d init:@on
345				flag FLAG_E +e init:@on
346				flag FLAG_F +f init:@on
347			}
348			When call parse -abc +def
349			The variable FLAG_A should be present
350			The variable FLAG_B should be present
351			The variable FLAG_C should be present
352			The variable FLAG_D should be blank
353			The variable FLAG_E should be blank
354			The variable FLAG_F should be blank
355		End
356
357		It "counts flags"
358			parser_definition() {
359				setup ARGS
360				flag COUNT -c +c counter:true
361			}
362			When call parse -c -c -c +c -c
363			The variable COUNT should eq 3
364		End
365
366		It "calls the function"
367			parser_definition() {
368				setup ARGS
369				flag :'foo "$1"' -f on:ON
370			}
371			foo() { echo "set [$OPTARG] : ${*:-}"; }
372			When run parse -f
373			The output should eq "set [ON] : -f"
374		End
375
376		It "calls the validator"
377			valid() { echo "$OPTARG" "$@"; }
378			parser_definition() {
379				setup ARGS
380				flag FLAG -f +f on:ON no:NO validate:'valid "$1"'
381			}
382			When call parse -f +f
383			The line 1 should eq "ON -f"
384			The line 2 should eq "NO +f"
385		End
386
387		Context 'when common flag value is specified'
388			parser_definition() {
389				setup ARGS on:ON no:NO
390				flag FLAG_A -a
391				flag FLAG_B +b
392			}
393			It "can change the set value"
394				When call parse -a +b
395				The variable FLAG_A should eq "ON"
396				The variable FLAG_B should eq "NO"
397			End
398		End
399	End
400
401	Describe 'param helper'
402		It "handles parameters"
403			parser_definition() {
404				setup ARGS
405				param PARAM_P -p
406				param PARAM_Q -q
407				param PARAM   --param
408			}
409			When call parse -p value1 -qvalue2 --param=value3
410			The variable PARAM_P should eq "value1"
411			The variable PARAM_Q should eq "value2"
412			The variable PARAM should eq "value3"
413		End
414
415		It "remains initial value when not specified parameter"
416			parser_definition() {
417				setup ARGS
418				param PARAM_P -p init:="initial"
419			}
420			When call parse
421			The variable PARAM_P should eq "initial"
422		End
423
424		It "calls the function"
425			parser_definition() {
426				setup ARGS
427				param :'foo "$1"' -p
428			}
429			foo() { echo "set [$OPTARG] : ${*:-}"; }
430			When run parse -p 123
431			The output should eq "set [123] : -p"
432		End
433
434		It "calls the validator"
435			valid() { echo "$OPTARG" "$@"; }
436			parser_definition() {
437				setup ARGS
438				param PARAM_P -p validate:'valid "$1"'
439				param PARAM_Q -q validate:'valid "$1"'
440				param PARAM   --param validate:'valid "$1"'
441			}
442			When call parse -p value1 -qvalue2 --param=value3
443			The line 1 should eq "value1 -p"
444			The line 2 should eq "value2 -q"
445			The line 3 should eq "value3 --param"
446		End
447
448		Context 'when specified pattern attribute'
449			Parameters
450				FOO success stdout ""
451				BAZ failure stderr "Does not match the pattern (FOO | BAR): -p"
452			End
453
454			It "checks if it matches the pattern"
455				parser_definition() {
456					setup ARGS
457					param PARAM -p pattern:'FOO | BAR'
458				}
459				When run parse -p "$1"
460				The status should be "$2"
461				The "$3" should eq "$4"
462			End
463		End
464	End
465
466	Describe 'option helper'
467		It "handles options"
468			parser_definition() {
469				setup ARGS
470				option OPTION   --option
471				option OPTION_O -o on:"default"
472				option OPTION_P -p
473				option OPTION_N --no-option no:"omission"
474			}
475			When call parse  --option=value1 -o -pvalue2 --no-option
476			The variable OPTION should eq "value1"
477			The variable OPTION_O should eq "default"
478			The variable OPTION_P should eq "value2"
479			The variable OPTION_N should eq "omission"
480		End
481
482		Context "when specified an argument to --no-option"
483			parser_definition() {
484				setup ARGS
485				option OPTION --no-option
486			}
487			It "displays error"
488				When run parse --no-option=value
489				The stderr should eq "Does not allow an argument: --no-option"
490				The status should be failure
491			End
492		End
493
494		Context "when specified an argument to --without-option"
495		    parser_definition() {
496				setup ARGS
497				option OPTION --without-option
498		    }
499			It "displays error"
500			    When run parse --without-option=value
501				The stderr should eq "Does not allow an argument: --without-option"
502				The status should be failure
503			End
504		End
505
506		It "remains initial value when not specified parameter"
507			parser_definition() {
508				setup ARGS
509				option OPTION_O -p init:="initial"
510			}
511			When call parse
512			The variable OPTION_O should eq "initial"
513		End
514
515		It "calls the function"
516			parser_definition() {
517				setup ARGS
518				option :'foo "$1"' -o
519			}
520			foo() { echo "set [$OPTARG] : ${*:-}"; }
521			When run parse -o123
522			The output should eq "set [123] : -o"
523		End
524
525		It "calls the validator"
526			valid() { echo "$OPTARG" "$@"; }
527			parser_definition() {
528				setup ARGS
529				option OPTION_O -o validate:'valid "$1"' on:"default"
530				option OPTION_P -p validate:'valid "$1"'
531				option OPTION   --option validate:'valid "$1"'
532			}
533			When call parse -o -pvalue1 --option=value2
534			The line 1 should eq "default -o"
535			The line 2 should eq "value1 -p"
536			The line 3 should eq "value2 --option"
537		End
538
539		Context 'when specified pattern attribute'
540			Parameters
541				foo success stdout ""
542				baz failure stderr "Does not match the pattern (foo | bar): -o"
543			End
544
545			It "checks if it matches the pattern"
546				parser_definition() {
547					setup ARGS
548					option OPTION -o pattern:'foo | bar'
549				}
550				When run parse -o"$1"
551				The status should be "$2"
552				The "$3" should eq "$4"
553			End
554		End
555	End
556
557	Describe 'disp helper'
558		BeforeRun VERSION=1.0
559
560		It "displays the variable"
561			parser_definition() {
562				setup ARGS
563				disp VERSION -v
564			}
565			When run parse -v
566			The output should eq "1.0"
567		End
568
569		It "calls the function"
570			version() { echo "func: $VERSION"; }
571			parser_definition() {
572				setup ARGS
573				disp :version -v
574			}
575			When run parse -v
576			The output should eq "func: 1.0"
577		End
578	End
579
580	Describe 'msg helper'
581		It "does nothing"
582			parser_definition() {
583				setup ARGS
584				msg -- 'test' 'foo bar'
585			}
586			When run parse
587			The output should be blank
588		End
589	End
590
591	Describe 'subcommand'
592		parser_definition() {
593			setup ARGS
594			flag FLAG -f
595			cmd list
596		}
597
598		Context "when specify a subcommand that exists"
599			Specify "treat subcommands and the rest as arguments"
600				When call restargs -f list -g 1 2
601				The output should eq "list -g 1 2"
602				The variable FLAG should eq "1"
603			End
604		End
605
606		Context "when not specify a subcommand"
607			Specify "parse global options only"
608				When call restargs -f
609				The output should eq ""
610				The variable FLAG should eq "1"
611			End
612		End
613
614		Context "when no subcommand and only arguments are passed"
615			Specify "the first argument is --"
616				When call restargs -f -- list 1 2
617				The output should eq "-- list 1 2"
618				The variable FLAG should eq "1"
619			End
620		End
621
622		Context "when specify a subcommand that not exists"
623			Specify "displays error"
624				When run restargs -f unknown -g 1 2
625				The error should eq "Not a command: unknown"
626				The status should be failure
627			End
628		End
629	End
630End
631