clipanion_core/
lib.rs

1mod actions;
2mod builder;
3mod errors;
4mod machine;
5mod node;
6mod runner;
7mod shared;
8mod transition;
9
10pub use builder::*;
11pub use errors::*;
12pub use machine::Machine;
13pub use runner::{OptionValue, Positional, RunState, run_machine, run_partial_machine};
14pub use shared::HELP_COMMAND_INDEX;
15
16#[test]
17fn it_should_select_the_default_command_when_using_no_arguments() {
18    let mut cli_builder = builder::CliBuilder::new();
19
20    cli_builder.add_command()
21        .make_default();
22
23    let machine = cli_builder.compile();
24
25    let result = runner::run_machine(&machine, &vec![]).unwrap();
26    assert_eq!(result.selected_index, Some(0));
27}
28
29#[test]
30fn it_should_select_the_default_command_when_using_mandatory_positional_arguments() {
31    let mut cli_builder = builder::CliBuilder::new();
32
33    cli_builder.add_command()
34        .make_default()
35        .add_positional(true, "foo").unwrap()
36        .add_positional(true, "bar").unwrap();
37
38    let machine = cli_builder.compile();
39
40    let result = runner::run_machine(&machine, &vec!["foo".to_string(), "bar".to_string()]).unwrap();
41    assert_eq!(result.selected_index, Some(0));
42}
43
44#[test]
45fn it_should_select_commands_by_their_path() {
46    let mut cli_builder = builder::CliBuilder::new();
47
48    cli_builder.add_command()
49        .add_path(vec!["foo".to_string()]);
50
51    cli_builder.add_command()
52        .add_path(vec!["bar".to_string()]);
53
54    let machine = cli_builder.compile();
55
56    let result = runner::run_machine(&machine, &vec!["foo".to_string()]).unwrap();
57    assert_eq!(result.selected_index, Some(0));
58
59    let result = runner::run_machine(&machine, &vec!["bar".to_string()]).unwrap();
60    assert_eq!(result.selected_index, Some(1));
61}
62
63#[test]
64fn it_should_select_commands_by_their_required_positional_arguments() {
65    let mut cli_builder = builder::CliBuilder::new();
66
67    cli_builder.add_command()
68        .make_default();
69
70    cli_builder.add_command()
71        .make_default()
72        .add_positional(true, "foo").unwrap();
73
74    let machine = cli_builder.compile();
75
76    let result = runner::run_machine(&machine, &vec![]).unwrap();
77    assert_eq!(result.selected_index, Some(0));
78
79    let result = runner::run_machine(&machine, &vec!["foo".to_string()]).unwrap();
80    assert_eq!(result.selected_index, Some(1));
81}
82
83#[test]
84fn it_should_select_options_by_their_simple_options() {
85    let mut cli_builder = builder::CliBuilder::new();
86
87    cli_builder.add_command()
88        .make_default()
89        .add_option(OptionDefinition {name_set: vec!["-x".to_string()], ..Default::default()}).unwrap();
90
91    cli_builder.add_command()
92        .make_default()
93        .add_option(OptionDefinition {name_set: vec!["-y".to_string()], ..Default::default()}).unwrap();
94
95    let machine = cli_builder.compile();
96
97    let result = runner::run_machine(&machine, &vec!["-x".to_string()]).unwrap();
98    assert_eq!(result.selected_index, Some(0));
99
100    let result = runner::run_machine(&machine, &vec!["-y".to_string()]).unwrap();
101    assert_eq!(result.selected_index, Some(1));
102}
103
104#[test]
105fn it_should_allow_options_to_precede_the_command_paths() {
106    let mut cli_builder = builder::CliBuilder::new();
107
108    cli_builder.add_command()
109        .add_path(vec!["foo".to_string()])
110        .add_option(OptionDefinition {name_set: vec!["-x".to_string()], ..Default::default()}).unwrap();
111
112    cli_builder.add_command()
113        .add_path(vec!["bar".to_string()])
114        .add_option(OptionDefinition {name_set: vec!["-y".to_string()], ..Default::default()}).unwrap();
115
116    let machine = cli_builder.compile();
117
118    let result = runner::run_machine(&machine, &vec!["-x".to_string(), "foo".to_string()]).unwrap();
119    assert_eq!(result.selected_index, Some(0));
120
121    let result = runner::run_machine(&machine, &vec!["-y".to_string(), "bar".to_string()]).unwrap();
122    assert_eq!(result.selected_index, Some(1));
123}
124
125#[test]
126fn it_should_select_commands_by_their_complex_values() {
127    let mut cli_builder = builder::CliBuilder::new();
128
129    cli_builder.add_command()
130        .make_default()
131        .add_option(OptionDefinition {name_set: vec!["-x".to_string()], arity: 1, ..Default::default()}).unwrap();
132
133    cli_builder.add_command()
134        .make_default()
135        .add_option(OptionDefinition {name_set: vec!["-y".to_string()], arity: 1, ..Default::default()}).unwrap();
136
137    let machine = cli_builder.compile();
138
139    let result = runner::run_machine(&machine, &vec!["-x".to_string(), "foo".to_string()]).unwrap();
140    assert_eq!(result.selected_index, Some(0));
141
142    let result = runner::run_machine(&machine, &vec!["-y".to_string(), "bar".to_string()]).unwrap();
143    assert_eq!(result.selected_index, Some(1));
144}
145
146#[test]
147fn it_should_prefer_longer_paths_over_mandatory_arguments() {
148    let mut cli_builder = builder::CliBuilder::new();
149
150    cli_builder.add_command()
151        .add_path(vec!["foo".to_string()]);
152
153    cli_builder.add_command()
154        .make_default()
155        .add_positional(true, "foo").unwrap();
156
157    let machine = cli_builder.compile();
158
159    let result = runner::run_machine(&machine, &vec!["foo".to_string()]).unwrap();
160    assert_eq!(result.selected_index, Some(0));
161}
162
163#[test]
164fn it_should_prefer_longer_paths_over_mandatory_arguments_reversed() {
165    let mut cli_builder = builder::CliBuilder::new();
166
167    cli_builder.add_command()
168        .make_default()
169        .add_positional(true, "foo").unwrap();
170
171    cli_builder.add_command()
172        .add_path(vec!["foo".to_string()]);
173
174    let machine = cli_builder.compile();
175
176    let result = runner::run_machine(&machine, &vec!["foo".to_string()]).unwrap();
177    assert_eq!(result.selected_index, Some(1));
178}
179
180#[test]
181fn it_should_prefer_longer_paths_over_mandatory_arguments_prefixed() {
182    let mut cli_builder = builder::CliBuilder::new();
183
184    cli_builder.add_command()
185        .add_path(vec!["prfx".to_string(), "foo".to_string()]);
186
187    cli_builder.add_command()
188        .add_path(vec!["prfx".to_string()])
189        .add_positional(true, "foo").unwrap();
190
191    let machine = cli_builder.compile();
192
193    let result = runner::run_machine(&machine, &vec!["prfx".to_string(), "foo".to_string()]).unwrap();
194    assert_eq!(result.selected_index, Some(0));
195}
196
197#[test]
198fn it_should_prefer_longer_paths_over_optional_arguments() {
199    let mut cli_builder = builder::CliBuilder::new();
200
201    cli_builder.add_command()
202        .add_path(vec!["foo".to_string()]);
203
204    cli_builder.add_command()
205        .make_default()
206        .add_positional(false, "foo").unwrap();
207
208    let machine = cli_builder.compile();
209
210    let result = runner::run_machine(&machine, &vec!["foo".to_string()]).unwrap();
211    assert_eq!(result.selected_index, Some(0));
212}
213
214#[test]
215fn it_should_prefer_longer_paths_over_optional_arguments_reversed() {
216    let mut cli_builder = builder::CliBuilder::new();
217
218    cli_builder.add_command()
219        .make_default()
220        .add_positional(false, "foo").unwrap();
221
222    cli_builder.add_command()
223        .add_path(vec!["foo".to_string()]);
224
225    let machine = cli_builder.compile();
226
227    let result = runner::run_machine(&machine, &vec!["foo".to_string()]).unwrap();
228    assert_eq!(result.selected_index, Some(1));
229}
230
231#[test]
232fn it_should_prefer_longer_paths_over_optional_arguments_prefixed() {
233    let mut cli_builder = builder::CliBuilder::new();
234
235    cli_builder.add_command()
236        .add_path(vec!["prfx".to_string(), "foo".to_string()]);
237
238    cli_builder.add_command()
239        .add_path(vec!["prfx".to_string()])
240        .add_positional(false, "foo").unwrap();
241
242    let machine = cli_builder.compile();
243
244    let result = runner::run_machine(&machine, &vec!["prfx".to_string(), "foo".to_string()]).unwrap();
245    assert_eq!(result.selected_index, Some(0));
246}
247
248#[test]
249fn it_should_prefer_required_arguments_over_optional_arguments() {
250    let mut cli_builder = builder::CliBuilder::new();
251
252    cli_builder.add_command()
253        .make_default()
254        .add_positional(true, "foo").unwrap();
255
256    cli_builder.add_command()
257        .make_default()
258        .add_positional(false, "foo").unwrap();
259
260    let machine = cli_builder.compile();
261
262    let result = runner::run_machine(&machine, &vec!["foo".to_string()]).unwrap();
263    assert_eq!(result.selected_index, Some(0));
264}
265
266#[test]
267fn it_should_prefer_required_arguments_over_optional_arguments_reversed() {
268    let mut cli_builder = builder::CliBuilder::new();
269
270    cli_builder.add_command()
271        .make_default()
272        .add_positional(false, "foo").unwrap();
273
274    cli_builder.add_command()
275        .make_default()
276        .add_positional(true, "foo").unwrap();
277
278    let machine = cli_builder.compile();
279
280    let result = runner::run_machine(&machine, &vec!["foo".to_string()]).unwrap();
281    assert_eq!(result.selected_index, Some(1));
282}
283
284#[test]
285fn it_should_fallback_from_path_to_required_arguments_if_needed() {
286    let mut cli_builder = builder::CliBuilder::new();
287
288    cli_builder.add_command()
289        .add_path(vec!["foo".to_string()]);
290
291    cli_builder.add_command()
292        .make_default()
293        .add_positional(true, "foo").unwrap();
294
295    let machine = cli_builder.compile();
296
297    let result = runner::run_machine(&machine, &vec!["bar".to_string()]).unwrap();
298    assert_eq!(result.selected_index, Some(1));
299}
300
301#[test]
302fn it_should_fallback_from_path_to_required_arguments_if_needed_reverse() {
303    let mut cli_builder = builder::CliBuilder::new();
304
305    cli_builder.add_command()
306        .make_default()
307        .add_positional(true, "foo").unwrap();
308
309    cli_builder.add_command()
310        .add_path(vec!["foo".to_string()]);
311
312    let machine = cli_builder.compile();
313
314    let result = runner::run_machine(&machine, &vec!["bar".to_string()]).unwrap();
315    assert_eq!(result.selected_index, Some(0));
316}
317
318#[test]
319fn it_should_fallback_from_path_to_required_arguments_if_needed_prefixed() {
320    let mut cli_builder = builder::CliBuilder::new();
321
322    cli_builder.add_command()
323        .add_path(vec!["prfx".to_string(), "foo".to_string()]);
324
325    cli_builder.add_command()
326        .add_path(vec!["prfx".to_string()])
327        .add_positional(true, "foo").unwrap();
328
329    let machine = cli_builder.compile();
330
331    let result = runner::run_machine(&machine, &vec!["prfx".to_string(), "bar".to_string()]).unwrap();
332    assert_eq!(result.selected_index, Some(1));
333}
334
335#[test]
336fn it_should_fallback_from_path_to_optional_arguments_if_needed() {
337    let mut cli_builder = builder::CliBuilder::new();
338
339    cli_builder.add_command()
340        .add_path(vec!["foo".to_string()]);
341
342    cli_builder.add_command()
343        .make_default()
344        .add_positional(false, "foo").unwrap();
345
346    let machine = cli_builder.compile();
347
348    let result = runner::run_machine(&machine, &vec!["bar".to_string()]).unwrap();
349    assert_eq!(result.selected_index, Some(1));
350}
351
352#[test]
353fn it_should_fallback_from_path_to_optional_arguments_if_needed_reverse() {
354    let mut cli_builder = builder::CliBuilder::new();
355
356    cli_builder.add_command()
357        .make_default()
358        .add_positional(false, "foo").unwrap();
359
360    cli_builder.add_command()
361        .add_path(vec!["foo".to_string()]);
362
363    let machine = cli_builder.compile();
364
365    let result = runner::run_machine(&machine, &vec!["bar".to_string()]).unwrap();
366    assert_eq!(result.selected_index, Some(0));
367}
368
369#[test]
370fn it_should_fallback_from_path_to_optional_arguments_if_needed_prefixed() {
371    let mut cli_builder = builder::CliBuilder::new();
372
373    cli_builder.add_command()
374        .add_path(vec!["prfx".to_string(), "foo".to_string()]);
375
376    cli_builder.add_command()
377        .add_path(vec!["prfx".to_string()])
378        .add_positional(false, "foo").unwrap();
379
380    let machine = cli_builder.compile();
381
382    let result = runner::run_machine(&machine, &vec!["prfx".to_string(), "bar".to_string()]).unwrap();
383    assert_eq!(result.selected_index, Some(1));
384}
385
386#[test]
387fn it_should_extract_booleans_from_simple_options() {
388    let mut cli_builder = builder::CliBuilder::new();
389
390    cli_builder.add_command()
391        .make_default()
392        .add_option(OptionDefinition {name_set: vec!["-x".to_string()], ..Default::default()}).unwrap();
393
394    let machine = cli_builder.compile();
395
396    let result = runner::run_machine(&machine, &vec!["-x".to_string()]).unwrap();
397    assert_eq!(result.options, vec![("-x".to_string(), runner::OptionValue::Bool(true))]);
398}
399
400#[test]
401fn it_should_extract_booleans_from_batch_options() {
402    let mut cli_builder = builder::CliBuilder::new();
403
404    cli_builder.add_command()
405        .make_default()
406        .add_option(OptionDefinition {name_set: vec!["-x".to_string()], ..Default::default()}).unwrap()
407        .add_option(OptionDefinition {name_set: vec!["-y".to_string()], ..Default::default()}).unwrap();
408
409    let machine = cli_builder.compile();
410
411    let result = runner::run_machine(&machine, &vec!["-xy".to_string()]).unwrap();
412    assert_eq!(result.options, vec![
413        ("-x".to_string(), runner::OptionValue::Bool(true)),
414        ("-y".to_string(), runner::OptionValue::Bool(true)),
415    ]);
416}
417
418#[test]
419fn it_should_invert_booleans_when_using_no() {
420    let mut cli_builder = builder::CliBuilder::new();
421
422    cli_builder.add_command()
423        .make_default()
424        .add_option(OptionDefinition {name_set: vec!["--foo".to_string()], ..Default::default()}).unwrap();
425
426    let machine = cli_builder.compile();
427
428    let result = runner::run_machine(&machine, &vec!["--no-foo".to_string()]).unwrap();
429    assert_eq!(result.options, vec![("--foo".to_string(), runner::OptionValue::Bool(false))]);
430}
431
432#[test]
433fn it_should_extract_strings_from_complex_options() {
434    let mut cli_builder = builder::CliBuilder::new();
435
436    cli_builder.add_command()
437        .make_default()
438        .add_option(OptionDefinition {name_set: vec!["-x".to_string()], arity: 1, ..Default::default()}).unwrap();
439
440    let machine = cli_builder.compile();
441
442    let result = runner::run_machine(&machine, &vec!["-x".to_string(), "foo".to_string()]).unwrap();
443    assert_eq!(result.options, vec![("-x".to_string(), runner::OptionValue::String("foo".to_string()))]);
444}
445
446#[test]
447fn it_should_extract_strings_from_complex_options_with_equals() {
448    let mut cli_builder = builder::CliBuilder::new();
449
450    cli_builder.add_command()
451        .make_default()
452        .add_option(OptionDefinition {name_set: vec!["--foo".to_string()], arity: 1, ..Default::default()}).unwrap();
453
454    let machine = cli_builder.compile();
455
456    let result = runner::run_machine(&machine, &vec!["--foo=foo".to_string()]).unwrap();
457    assert_eq!(result.options, vec![("--foo".to_string(), runner::OptionValue::String("foo".to_string()))]);
458}
459
460#[test]
461fn it_shouldnt_consider_dash_as_an_option() {
462    let mut cli_builder = builder::CliBuilder::new();
463
464    cli_builder.add_command()
465        .make_default()
466        .add_option(OptionDefinition {name_set: vec!["--foo".to_string()], arity: 1, ..Default::default()}).unwrap();
467
468    let machine = cli_builder.compile();
469
470    let result = runner::run_machine(&machine, &vec!["--foo".to_string(), "-".to_string()]).unwrap();
471    assert_eq!(result.options, vec![("--foo".to_string(), runner::OptionValue::String("-".to_string()))]);
472}
473
474#[test]
475fn it_should_extract_arrays_from_complex_options() {
476    let mut cli_builder = builder::CliBuilder::new();
477
478    cli_builder.add_command()
479        .make_default()
480        .add_option(OptionDefinition {name_set: vec!["--foo".to_string()], arity: 1, ..Default::default()}).unwrap();
481
482    let machine = cli_builder.compile();
483
484    let result = runner::run_machine(&machine, &vec!["--foo".to_string(), "bar".to_string(), "--foo".to_string(), "baz".to_string()]).unwrap();
485    assert_eq!(result.options, vec![
486        ("--foo".to_string(), runner::OptionValue::String("bar".to_string())),
487        ("--foo".to_string(), runner::OptionValue::String("baz".to_string())),
488    ]);
489}
490
491#[test]
492fn it_should_extract_arrays_from_complex_options_equal() {
493    let mut cli_builder = builder::CliBuilder::new();
494
495    cli_builder.add_command()
496        .make_default()
497        .add_option(OptionDefinition {name_set: vec!["--foo".to_string()], arity: 1, ..Default::default()}).unwrap();
498
499    let machine = cli_builder.compile();
500
501    let result = runner::run_machine(&machine, &vec!["--foo=bar".to_string(), "--foo=baz".to_string()]).unwrap();
502    assert_eq!(result.options, vec![
503        ("--foo".to_string(), runner::OptionValue::String("bar".to_string())),
504        ("--foo".to_string(), runner::OptionValue::String("baz".to_string())),
505    ]);
506}
507
508#[test]
509fn it_should_extract_arrays_from_complex_options_mixed() {
510    let mut cli_builder = builder::CliBuilder::new();
511
512    cli_builder.add_command()
513        .make_default()
514        .add_option(OptionDefinition {name_set: vec!["--foo".to_string()], arity: 1, ..Default::default()}).unwrap();
515
516    let machine = cli_builder.compile();
517
518    let result = runner::run_machine(&machine, &vec!["--foo".to_string(), "bar".to_string(), "--foo=baz".to_string()]).unwrap();
519    assert_eq!(result.options, vec![
520        ("--foo".to_string(), runner::OptionValue::String("bar".to_string())),
521        ("--foo".to_string(), runner::OptionValue::String("baz".to_string())),
522    ]);
523}
524
525#[test]
526fn it_should_support_rest_arguments() {
527    let mut cli_builder = builder::CliBuilder::new();
528
529    cli_builder.add_command()
530        .make_default()
531        .add_rest("rest").unwrap();
532
533    let machine = cli_builder.compile();
534
535    let result = runner::run_machine(&machine, &vec!["foo".to_string(), "bar".to_string(), "baz".to_string()]).unwrap();
536    assert_eq!(result.positionals, vec![
537        runner::Positional::Rest("foo".to_string()),
538        runner::Positional::Rest("bar".to_string()),
539        runner::Positional::Rest("baz".to_string()),
540    ]);
541}
542
543#[test]
544fn it_should_support_rest_arguments_followed_by_mandatory_arguments() {
545    let mut cli_builder = builder::CliBuilder::new();
546
547    cli_builder.add_command()
548        .make_default()
549        .add_rest("rest").unwrap()
550        .add_positional(true, "foo").unwrap();
551
552    let machine = cli_builder.compile();
553
554    let result = runner::run_machine(&machine, &vec!["src1".to_string(), "src2".to_string(), "src3".to_string(), "dest".to_string()]).unwrap();
555    assert_eq!(result.positionals, vec![
556        runner::Positional::Rest("src1".to_string()),
557        runner::Positional::Rest("src2".to_string()),
558        runner::Positional::Rest("src3".to_string()),
559        runner::Positional::Required("dest".to_string()),
560    ]);
561}
562
563#[test]
564fn it_should_support_rest_arguments_between_mandatory_arguments() {
565    let mut cli_builder = builder::CliBuilder::new();
566
567    cli_builder.add_command()
568        .make_default()
569        .add_positional(true, "foo").unwrap()
570        .add_rest("rest").unwrap()
571        .add_positional(true, "bar").unwrap();
572
573    let machine = cli_builder.compile();
574
575    let result = runner::run_machine(&machine, &vec!["foo".to_string(), "src1".to_string(), "src2".to_string(), "src3".to_string(), "dest".to_string()]).unwrap();
576    assert_eq!(result.positionals, vec![
577        runner::Positional::Required("foo".to_string()),
578        runner::Positional::Rest("src1".to_string()),
579        runner::Positional::Rest("src2".to_string()),
580        runner::Positional::Rest("src3".to_string()),
581        runner::Positional::Required("dest".to_string()),
582    ]);
583}
584
585#[test]
586fn it_should_support_option_arguments_in_between_rest_arguments() {
587    let mut cli_builder = builder::CliBuilder::new();
588
589    cli_builder.add_command()
590        .make_default()
591        .add_option(OptionDefinition {name_set: vec!["--foo".to_string()], ..Default::default()}).unwrap()
592        .add_option(OptionDefinition {name_set: vec!["--bar".to_string()], arity: 1, ..Default::default()}).unwrap()
593        .add_rest("rest").unwrap();
594
595    let machine = cli_builder.compile();
596
597    let result = runner::run_machine(&machine, &vec!["src1".to_string(), "--foo".to_string(), "src2".to_string(), "--bar".to_string(), "baz".to_string(), "src3".to_string()]).unwrap();
598
599    assert_eq!(result.options, vec![
600        ("--foo".to_string(), runner::OptionValue::Bool(true)),
601        ("--bar".to_string(), runner::OptionValue::String("baz".to_string())),
602    ]);
603
604    assert_eq!(result.positionals, vec![
605        runner::Positional::Rest("src1".to_string()),
606        runner::Positional::Rest("src2".to_string()),
607        runner::Positional::Rest("src3".to_string()),
608    ]);
609}
610
611#[test]
612fn it_should_ignore_options_when_they_follow_the_dash_dash_separator() {
613    let mut cli_builder = builder::CliBuilder::new();
614
615    cli_builder.add_command()
616        .add_path(vec!["foo".to_string()])
617        .add_option(OptionDefinition {name_set: vec!["-x".to_string()], ..Default::default()}).unwrap()
618        .add_positional(false, "foo").unwrap();
619
620    let machine = cli_builder.compile();
621
622    let result = runner::run_machine(&machine, &vec!["foo".to_string(), "--".to_string(), "-x".to_string()]).unwrap();
623
624    assert_eq!(result.options, vec![
625        // Must be empty
626    ]);
627
628    assert_eq!(result.positionals, vec![
629        runner::Positional::Optional("-x".to_string()),
630    ]);
631}
632
633#[test]
634fn it_should_ignore_options_when_they_appear_after_a_required_positional_from_a_proxy() {
635    let mut cli_builder = builder::CliBuilder::new();
636
637    cli_builder.add_command()
638        .add_path(vec!["foo".to_string()])
639        .add_option(OptionDefinition {name_set: vec!["-x".to_string()], ..Default::default()}).unwrap()
640        .add_positional(true, "foo").unwrap()
641        .add_positional(true, "bar").unwrap()
642        .add_proxy("proxy").unwrap();
643
644    let machine = cli_builder.compile();
645
646    let result = runner::run_machine(&machine, &vec!["foo".to_string(), "pos1".to_string(), "-x".to_string(), "pos2".to_string(), "proxy".to_string()]).unwrap();
647
648    assert_eq!(result.options, vec![
649        ("-x".to_string(), runner::OptionValue::Bool(true)),
650    ]);
651
652    assert_eq!(result.positionals, vec![
653        runner::Positional::Required("pos1".to_string()),
654        runner::Positional::Required("pos2".to_string()),
655        runner::Positional::Rest("proxy".to_string()),
656    ]);
657}
658
659#[test]
660fn it_should_ignore_options_when_they_appear_in_a_proxy_extra() {
661    let mut cli_builder = builder::CliBuilder::new();
662
663    cli_builder.add_command()
664        .add_path(vec!["foo".to_string()])
665        .add_option(OptionDefinition {name_set: vec!["-x".to_string()], ..Default::default()}).unwrap()
666        .add_proxy("proxy").unwrap();
667
668    let machine = cli_builder.compile();
669
670    let result = runner::run_machine(&machine, &vec!["foo".to_string(), "-x".to_string()]).unwrap();
671
672    assert_eq!(result.options, vec![
673        // Must be empty
674    ]);
675
676    assert_eq!(result.positionals, vec![
677        runner::Positional::Rest("-x".to_string()),
678    ]);
679}
680
681#[test]
682fn it_should_prefer_exact_commands_over_empty_proxies() {
683    let mut cli_builder = builder::CliBuilder::new();
684
685    cli_builder.add_command()
686        .add_path(vec!["foo".to_string()]);
687
688    cli_builder.add_command()
689        .add_path(vec!["foo".to_string()])
690        .add_positional(true, "foo").unwrap()
691        .add_proxy("proxy").unwrap();
692
693    let machine = cli_builder.compile();
694
695    let result = runner::run_machine(&machine, &vec!["foo".to_string()]).unwrap();
696    assert_eq!(result.selected_index, Some(0));
697}
698
699#[test]
700fn it_should_aggregate_the_options_as_they_are_found() {
701    let mut cli_builder = builder::CliBuilder::new();
702
703    cli_builder.add_command()
704        .make_default()
705        .add_option(OptionDefinition {name_set: vec!["-x".to_string()], ..Default::default()}).unwrap()
706        .add_option(OptionDefinition {name_set: vec!["-y".to_string()], ..Default::default()}).unwrap()
707        .add_option(OptionDefinition {name_set: vec!["-z".to_string()], ..Default::default()}).unwrap()
708        .add_option(OptionDefinition {name_set: vec!["-u".to_string()], arity: 1, ..Default::default()}).unwrap()
709        .add_option(OptionDefinition {name_set: vec!["-v".to_string()], arity: 1, ..Default::default()}).unwrap()
710        .add_option(OptionDefinition {name_set: vec!["-w".to_string()], arity: 1, ..Default::default()}).unwrap();
711
712    let machine = cli_builder.compile();
713
714    let result = runner::run_machine(&machine, &vec!["-x".to_string(), "-u".to_string(), "foo".to_string(), "-y".to_string(), "-v".to_string(), "bar".to_string(), "-y".to_string()]).unwrap();
715    assert_eq!(result.options, vec![
716        ("-x".to_string(), runner::OptionValue::Bool(true)),
717        ("-u".to_string(), runner::OptionValue::String("foo".to_string())),
718        ("-y".to_string(), runner::OptionValue::Bool(true)),
719        ("-v".to_string(), runner::OptionValue::String("bar".to_string())),
720        ("-y".to_string(), runner::OptionValue::Bool(true)),
721    ]);
722
723    let result = runner::run_machine(&machine, &vec!["-z".to_string(), "-y".to_string(), "-x".to_string()]).unwrap();
724    assert_eq!(result.options, vec![
725        ("-z".to_string(), runner::OptionValue::Bool(true)),
726        ("-y".to_string(), runner::OptionValue::Bool(true)),
727        ("-x".to_string(), runner::OptionValue::Bool(true)),
728    ]);
729}
730
731#[test]
732fn it_should_aggregate_the_mandatory_arguments() {
733    let mut cli_builder = builder::CliBuilder::new();
734
735    cli_builder.add_command()
736        .make_default()
737        .add_positional(true, "foo").unwrap()
738        .add_positional(true, "bar").unwrap();
739
740    let machine = cli_builder.compile();
741
742    let result = runner::run_machine(&machine, &vec!["foo".to_string(), "bar".to_string()]).unwrap();
743    assert_eq!(result.positionals, vec![
744        runner::Positional::Required("foo".to_string()),
745        runner::Positional::Required("bar".to_string()),
746    ]);
747}
748
749#[test]
750fn it_should_aggregate_the_optional_arguments() {
751    let mut cli_builder = builder::CliBuilder::new();
752
753    cli_builder.add_command()
754        .make_default()
755        .add_positional(false, "foo").unwrap()
756        .add_positional(false, "bar").unwrap();
757
758    let machine = cli_builder.compile();
759
760    let result = runner::run_machine(&machine, &vec!["foo".to_string(), "bar".to_string()]).unwrap();
761    assert_eq!(result.positionals, vec![
762        runner::Positional::Optional("foo".to_string()),
763        runner::Positional::Optional("bar".to_string()),
764    ]);
765}
766
767#[test]
768fn it_should_accept_as_few_optional_arguments_as_possible() {
769    let mut cli_builder = builder::CliBuilder::new();
770
771    cli_builder.add_command()
772        .make_default()
773        .add_positional(false, "foo").unwrap()
774        .add_positional(false, "bar").unwrap();
775
776    let machine = cli_builder.compile();
777
778    let result = runner::run_machine(&machine, &vec![]).unwrap();
779    assert_eq!(result.positionals, vec![]);
780
781    let result = runner::run_machine(&machine, &vec!["foo".to_string()]).unwrap();
782    assert_eq!(result.positionals, vec![
783        runner::Positional::Optional("foo".to_string()),
784    ]);
785}
786
787#[test]
788fn it_should_accept_a_mix_of_mandatory_and_optional_arguments() {
789    let mut cli_builder = builder::CliBuilder::new();
790
791    cli_builder.add_command()
792        .make_default()
793        .add_positional(true, "foo").unwrap()
794        .add_positional(false, "bar").unwrap();
795
796    let machine = cli_builder.compile();
797
798    let result = runner::run_machine(&machine, &vec!["foo".to_string()]).unwrap();
799    assert_eq!(result.positionals, vec![
800        runner::Positional::Required("foo".to_string()),
801    ]);
802
803    let result = runner::run_machine(&machine, &vec!["foo".to_string(), "bar".to_string()]).unwrap();
804    assert_eq!(result.positionals, vec![
805        runner::Positional::Required("foo".to_string()),
806        runner::Positional::Optional("bar".to_string()),
807    ]);
808}
809
810#[test]
811fn it_should_accept_any_option_as_positional_argument_when_proxies_are_enabled() {
812    let mut cli_builder = builder::CliBuilder::new();
813
814    cli_builder.add_command()
815        .make_default()
816        .add_proxy("proxy").unwrap();
817
818    let machine = cli_builder.compile();
819
820    let result = runner::run_machine(&machine, &vec!["--foo".to_string(), "--bar".to_string()]).unwrap();
821    assert_eq!(result.positionals, vec![
822        runner::Positional::Rest("--foo".to_string()),
823        runner::Positional::Rest("--bar".to_string()),
824    ]);
825}
826
827#[test]
828fn it_should_throw_acceptable_errors_when_passing_an_extraneous_option() {
829    let mut cli_builder = builder::CliBuilder::new();
830
831    cli_builder.add_command()
832        .make_default();
833
834    let machine = cli_builder.compile();
835
836    let result = runner::run_machine(&machine, &vec!["--foo".to_string()]);
837    assert!(matches!(result, Err(errors::Error::CommandError(_, errors::CommandError::UnknownOption))));
838}
839
840#[test]
841fn it_should_throw_acceptable_errors_when_passing_extraneous_arguments() {
842    let mut cli_builder = builder::CliBuilder::new();
843
844    cli_builder.add_command()
845        .make_default();
846
847    let machine = cli_builder.compile();
848
849    let result = runner::run_machine(&machine, &vec!["foo".to_string()]);
850    assert!(matches!(result, Err(errors::Error::CommandError(_, errors::CommandError::ExtraneousPositionalArguments))));
851}
852
853#[test]
854fn it_should_throw_acceptable_errors_when_writing_invalid_arguments() {
855    let mut cli_builder = builder::CliBuilder::new();
856
857    cli_builder.add_command()
858        .make_default();
859
860    let machine = cli_builder.compile();
861
862    let result = runner::run_machine(&machine, &vec!["-%#@$%#()@".to_string()]);
863    assert!(matches!(result, Err(errors::Error::CommandError(_, errors::CommandError::InvalidOption))));
864}
865
866#[test]
867fn it_should_throw_acceptable_errors_when_writing_bound_boolean_arguments() {
868    let mut cli_builder = builder::CliBuilder::new();
869
870    cli_builder.add_command()
871        .make_default()
872        .add_option(OptionDefinition {name_set: vec!["--foo".to_string()], allow_binding: false, ..Default::default()}).unwrap();
873
874    let machine = cli_builder.compile();
875
876    let result = runner::run_machine(&machine, &vec!["--foo=bar".to_string()]);
877    assert!(matches!(result, Err(errors::Error::CommandError(_, errors::CommandError::InvalidOption))));
878}
879
880#[test]
881fn it_should_rename_truthy_options_into_their_preferred_name() {
882    let mut cli_builder = builder::CliBuilder::new();
883
884    cli_builder.add_command()
885        .make_default()
886        .add_option(OptionDefinition {name_set: vec!["--value".to_string(), "-v".to_string()], ..Default::default()}).unwrap();
887
888    let machine = cli_builder.compile();
889
890    let result = runner::run_machine(&machine, &vec!["-v".to_string()]).unwrap();
891    assert_eq!(result.options, vec![("--value".to_string(), runner::OptionValue::Bool(true))]);
892}
893
894#[test]
895fn it_should_rename_falsy_options_into_their_preferred_name() {
896    let mut cli_builder = builder::CliBuilder::new();
897
898    cli_builder.add_command()
899        .make_default()
900        .add_option(OptionDefinition {name_set: vec!["--value".to_string(), "--v".to_string()], ..Default::default()}).unwrap();
901
902    let machine = cli_builder.compile();
903
904    let result = runner::run_machine(&machine, &vec!["--no-v".to_string()]).unwrap();
905    assert_eq!(result.options, vec![("--value".to_string(), runner::OptionValue::Bool(false))]);
906}
907
908#[test]
909fn it_should_rename_batch_options_into_their_preferred_name() {
910    let mut cli_builder = builder::CliBuilder::new();
911
912    cli_builder.add_command()
913        .make_default()
914        .add_option(OptionDefinition {name_set: vec!["--foo".to_string(), "-f".to_string()], ..Default::default()}).unwrap()
915        .add_option(OptionDefinition {name_set: vec!["--bar".to_string(), "-b".to_string()], ..Default::default()}).unwrap();
916
917    let machine = cli_builder.compile();
918
919    let result = runner::run_machine(&machine, &vec!["-fb".to_string()]).unwrap();
920    assert_eq!(result.options, vec![
921        ("--foo".to_string(), runner::OptionValue::Bool(true)),
922        ("--bar".to_string(), runner::OptionValue::Bool(true)),
923    ]);
924}
925
926#[test]
927fn it_should_rename_string_options_into_their_preferred_name() {
928    let mut cli_builder = builder::CliBuilder::new();
929
930    cli_builder.add_command()
931        .make_default()
932        .add_option(OptionDefinition {name_set: vec!["--value".to_string(), "-v".to_string()], arity: 1, ..Default::default()}).unwrap();
933
934    let machine = cli_builder.compile();
935
936    let result = runner::run_machine(&machine, &vec!["-v".to_string(), "foo".to_string()]).unwrap();
937    assert_eq!(result.options, vec![("--value".to_string(), runner::OptionValue::String("foo".to_string()))]);
938}
939
940#[test]
941fn it_should_rename_array_options_into_their_preferred_name() {
942    let mut cli_builder = builder::CliBuilder::new();
943
944    cli_builder.add_command()
945        .make_default()
946        .add_option(OptionDefinition {name_set: vec!["--value".to_string(), "-v".to_string()], arity: 2, ..Default::default()}).unwrap();
947
948    let machine = cli_builder.compile();
949
950    let result = runner::run_machine(&machine, &vec!["-v".to_string(), "foo".to_string(), "bar".to_string()]).unwrap();
951    assert_eq!(result.options, vec![
952        ("--value".to_string(), runner::OptionValue::Array(vec!["foo".to_string(), "bar".to_string()])),
953    ]);
954}