1use heck::ToSnakeCase;
2use indexmap::IndexMap;
3use itertools::Itertools;
4use log::trace;
5use miette::bail;
6use std::collections::{BTreeMap, VecDeque};
7use std::fmt::{Debug, Display, Formatter};
8use strum::EnumTryAs;
9
10#[cfg(feature = "docs")]
11use crate::docs;
12use crate::error::UsageErr;
13use crate::{Spec, SpecArg, SpecCommand, SpecFlag};
14
15fn get_flag_key(word: &str) -> &str {
18 if word.starts_with("--") {
19 word.split_once('=').map(|(k, _)| k).unwrap_or(word)
21 } else if word.len() >= 2 {
22 &word[0..2]
24 } else {
25 word
26 }
27}
28
29pub struct ParseOutput {
30 pub cmd: SpecCommand,
31 pub cmds: Vec<SpecCommand>,
32 pub args: IndexMap<SpecArg, ParseValue>,
33 pub flags: IndexMap<SpecFlag, ParseValue>,
34 pub available_flags: BTreeMap<String, SpecFlag>,
35 pub flag_awaiting_value: Vec<SpecFlag>,
36 pub errors: Vec<UsageErr>,
37}
38
39#[derive(Debug, EnumTryAs, Clone)]
40pub enum ParseValue {
41 Bool(bool),
42 String(String),
43 MultiBool(Vec<bool>),
44 MultiString(Vec<String>),
45}
46
47pub fn parse(spec: &Spec, input: &[String]) -> Result<ParseOutput, miette::Error> {
48 let mut out = parse_partial(spec, input)?;
49 trace!("{out:?}");
50
51 for arg in out.cmd.args.iter().skip(out.args.len()) {
53 if let Some(env_var) = arg.env.as_ref() {
54 if let Ok(env_value) = std::env::var(env_var) {
55 out.args.insert(arg.clone(), ParseValue::String(env_value));
56 continue;
57 }
58 }
59 if !arg.default.is_empty() {
60 if arg.var {
62 out.args
64 .insert(arg.clone(), ParseValue::MultiString(arg.default.clone()));
65 } else {
66 out.args
68 .insert(arg.clone(), ParseValue::String(arg.default[0].clone()));
69 }
70 }
71 }
72
73 for flag in out.available_flags.values() {
75 if out.flags.contains_key(flag) {
76 continue;
77 }
78 if let Some(env_var) = flag.env.as_ref() {
79 if let Ok(env_value) = std::env::var(env_var) {
80 if flag.arg.is_some() {
81 out.flags
82 .insert(flag.clone(), ParseValue::String(env_value));
83 } else {
84 let is_true = matches!(env_value.as_str(), "1" | "true" | "True" | "TRUE");
86 out.flags.insert(flag.clone(), ParseValue::Bool(is_true));
87 }
88 continue;
89 }
90 }
91 if !flag.default.is_empty() {
93 if flag.var {
95 if flag.arg.is_some() {
97 out.flags
98 .insert(flag.clone(), ParseValue::MultiString(flag.default.clone()));
99 } else {
100 let bools: Vec<bool> = flag
102 .default
103 .iter()
104 .map(|s| matches!(s.as_str(), "1" | "true" | "True" | "TRUE"))
105 .collect();
106 out.flags.insert(flag.clone(), ParseValue::MultiBool(bools));
107 }
108 } else {
109 if flag.arg.is_some() {
111 out.flags
112 .insert(flag.clone(), ParseValue::String(flag.default[0].clone()));
113 } else {
114 let is_true =
116 matches!(flag.default[0].as_str(), "1" | "true" | "True" | "TRUE");
117 out.flags.insert(flag.clone(), ParseValue::Bool(is_true));
118 }
119 }
120 }
121 if let Some(arg) = flag.arg.as_ref() {
123 if !out.flags.contains_key(flag) && !arg.default.is_empty() {
124 if flag.var {
125 out.flags
126 .insert(flag.clone(), ParseValue::MultiString(arg.default.clone()));
127 } else {
128 out.flags
129 .insert(flag.clone(), ParseValue::String(arg.default[0].clone()));
130 }
131 }
132 }
133 }
134 if let Some(err) = out.errors.iter().find(|e| matches!(e, UsageErr::Help(_))) {
135 bail!("{err}");
136 }
137 if !out.errors.is_empty() {
138 bail!("{}", out.errors.iter().map(|e| e.to_string()).join("\n"));
139 }
140 Ok(out)
141}
142
143pub fn parse_partial(spec: &Spec, input: &[String]) -> Result<ParseOutput, miette::Error> {
144 trace!("parse_partial: {input:?}");
145 let mut input = input.iter().cloned().collect::<VecDeque<_>>();
146 input.pop_front();
147
148 let gather_flags = |cmd: &SpecCommand| {
149 cmd.flags
150 .iter()
151 .flat_map(|f| {
152 let mut flags = f
153 .long
154 .iter()
155 .map(|l| (format!("--{l}"), f.clone()))
156 .chain(f.short.iter().map(|s| (format!("-{s}"), f.clone())))
157 .collect::<Vec<_>>();
158 if let Some(negate) = &f.negate {
159 flags.push((negate.clone(), f.clone()));
160 }
161 flags
162 })
163 .collect()
164 };
165
166 let mut out = ParseOutput {
167 cmd: spec.cmd.clone(),
168 cmds: vec![spec.cmd.clone()],
169 args: IndexMap::new(),
170 flags: IndexMap::new(),
171 available_flags: gather_flags(&spec.cmd),
172 flag_awaiting_value: vec![],
173 errors: vec![],
174 };
175
176 let mut prefix_words: Vec<String> = vec![];
189 let mut idx = 0;
190
191 while idx < input.len() {
192 if let Some(subcommand) = out.cmd.find_subcommand(&input[idx]) {
193 let mut subcommand = subcommand.clone();
194 subcommand.mount(&prefix_words)?;
196 out.available_flags.retain(|_, f| f.global);
197 out.available_flags.extend(gather_flags(&subcommand));
198 input.remove(idx);
200 out.cmds.push(subcommand.clone());
201 out.cmd = subcommand.clone();
202 prefix_words.clear();
203 } else if input[idx].starts_with('-') {
206 let word = &input[idx];
208 let flag_key = get_flag_key(word);
209
210 if let Some(f) = out.available_flags.get(flag_key) {
211 if f.global {
213 prefix_words.push(input[idx].clone());
214 idx += 1;
215
216 if f.arg.is_some()
219 && !word.contains('=')
220 && idx < input.len()
221 && !input[idx].starts_with('-')
222 {
223 prefix_words.push(input[idx].clone());
224 idx += 1;
225 }
226 } else {
227 break;
231 }
232 } else {
233 break;
236 }
237 } else {
238 break;
241 }
242 }
243
244 let mut next_arg = out.cmd.args.first();
249 let mut enable_flags = true;
250 let mut grouped_flag = false;
251
252 while !input.is_empty() {
253 let mut w = input.pop_front().unwrap();
254
255 if w == "--" {
256 enable_flags = false;
257 continue;
258 }
259
260 if enable_flags && w.starts_with("--") {
262 grouped_flag = false;
263 let (word, val) = w.split_once('=').unwrap_or_else(|| (&w, ""));
264 if !val.is_empty() {
265 input.push_front(val.to_string());
266 }
267 if let Some(f) = out.available_flags.get(word) {
268 if f.arg.is_some() {
269 out.flag_awaiting_value.push(f.clone());
270 } else if f.count {
271 let arr = out
272 .flags
273 .entry(f.clone())
274 .or_insert_with(|| ParseValue::MultiBool(vec![]))
275 .try_as_multi_bool_mut()
276 .unwrap();
277 arr.push(true);
278 } else {
279 let negate = f.negate.clone().unwrap_or_default();
280 out.flags.insert(f.clone(), ParseValue::Bool(w != negate));
281 }
282 continue;
283 }
284 if is_help_arg(spec, &w) {
285 out.errors
286 .push(render_help_err(spec, &out.cmd, w.len() > 2));
287 return Ok(out);
288 }
289 }
290
291 if enable_flags && w.starts_with('-') && w.len() > 1 {
293 let short = w.chars().nth(1).unwrap();
294 if let Some(f) = out.available_flags.get(&format!("-{short}")) {
295 if w.len() > 2 {
296 input.push_front(format!("-{}", &w[2..]));
297 grouped_flag = true;
298 }
299 if f.arg.is_some() {
300 out.flag_awaiting_value.push(f.clone());
301 } else if f.count {
302 let arr = out
303 .flags
304 .entry(f.clone())
305 .or_insert_with(|| ParseValue::MultiBool(vec![]))
306 .try_as_multi_bool_mut()
307 .unwrap();
308 arr.push(true);
309 } else {
310 let negate = f.negate.clone().unwrap_or_default();
311 out.flags.insert(f.clone(), ParseValue::Bool(w != negate));
312 }
313 continue;
314 }
315 if is_help_arg(spec, &w) {
316 out.errors
317 .push(render_help_err(spec, &out.cmd, w.len() > 2));
318 return Ok(out);
319 }
320 if grouped_flag {
321 grouped_flag = false;
322 w.remove(0);
323 }
324 }
325
326 if !out.flag_awaiting_value.is_empty() {
327 while let Some(flag) = out.flag_awaiting_value.pop() {
328 let arg = flag.arg.as_ref().unwrap();
329 if flag.var {
330 let arr = out
331 .flags
332 .entry(flag)
333 .or_insert_with(|| ParseValue::MultiString(vec![]))
334 .try_as_multi_string_mut()
335 .unwrap();
336 arr.push(w);
337 } else {
338 if let Some(choices) = &arg.choices {
339 if !choices.choices.contains(&w) {
340 if is_help_arg(spec, &w) {
341 out.errors
342 .push(render_help_err(spec, &out.cmd, w.len() > 2));
343 return Ok(out);
344 }
345 bail!(
346 "Invalid choice for option {}: {w}, expected one of {}",
347 flag.name,
348 choices.choices.join(", ")
349 );
350 }
351 }
352 out.flags.insert(flag, ParseValue::String(w));
353 }
354 w = "".to_string();
355 }
356 continue;
357 }
358
359 if let Some(arg) = next_arg {
360 if arg.var {
361 let arr = out
362 .args
363 .entry(arg.clone())
364 .or_insert_with(|| ParseValue::MultiString(vec![]))
365 .try_as_multi_string_mut()
366 .unwrap();
367 arr.push(w);
368 if arr.len() >= arg.var_max.unwrap_or(usize::MAX) {
369 next_arg = out.cmd.args.get(out.args.len());
370 }
371 } else {
372 if let Some(choices) = &arg.choices {
373 if !choices.choices.contains(&w) {
374 if is_help_arg(spec, &w) {
375 out.errors
376 .push(render_help_err(spec, &out.cmd, w.len() > 2));
377 return Ok(out);
378 }
379 bail!(
380 "Invalid choice for arg {}: {w}, expected one of {}",
381 arg.name,
382 choices.choices.join(", ")
383 );
384 }
385 }
386 out.args.insert(arg.clone(), ParseValue::String(w));
387 next_arg = out.cmd.args.get(out.args.len());
388 }
389 continue;
390 }
391 if is_help_arg(spec, &w) {
392 out.errors
393 .push(render_help_err(spec, &out.cmd, w.len() > 2));
394 return Ok(out);
395 }
396 bail!("unexpected word: {w}");
397 }
398
399 for arg in out.cmd.args.iter().skip(out.args.len()) {
400 if arg.required && arg.default.is_empty() {
401 let has_env = arg
403 .env
404 .as_ref()
405 .map(|e| std::env::var(e).is_ok())
406 .unwrap_or(false);
407 if !has_env {
408 out.errors.push(UsageErr::MissingArg(arg.name.clone()));
409 }
410 }
411 }
412
413 for flag in out.available_flags.values() {
414 if out.flags.contains_key(flag) {
415 continue;
416 }
417 let has_default =
418 !flag.default.is_empty() || flag.arg.iter().any(|a| !a.default.is_empty());
419 let has_env = flag
420 .env
421 .as_ref()
422 .map(|e| std::env::var(e).is_ok())
423 .unwrap_or(false);
424 if flag.required && !has_default && !has_env {
425 out.errors.push(UsageErr::MissingFlag(flag.name.clone()));
426 }
427 }
428
429 for (arg, value) in &out.args {
431 if arg.var {
432 if let ParseValue::MultiString(values) = value {
433 if let Some(min) = arg.var_min {
434 if values.len() < min {
435 out.errors.push(UsageErr::VarArgTooFew {
436 name: arg.name.clone(),
437 min,
438 got: values.len(),
439 });
440 }
441 }
442 if let Some(max) = arg.var_max {
443 if values.len() > max {
444 out.errors.push(UsageErr::VarArgTooMany {
445 name: arg.name.clone(),
446 max,
447 got: values.len(),
448 });
449 }
450 }
451 }
452 }
453 }
454
455 for (flag, value) in &out.flags {
457 if flag.var {
458 let count = match value {
459 ParseValue::MultiString(values) => values.len(),
460 ParseValue::MultiBool(values) => values.len(),
461 _ => continue,
462 };
463 if let Some(min) = flag.var_min {
464 if count < min {
465 out.errors.push(UsageErr::VarFlagTooFew {
466 name: flag.name.clone(),
467 min,
468 got: count,
469 });
470 }
471 }
472 if let Some(max) = flag.var_max {
473 if count > max {
474 out.errors.push(UsageErr::VarFlagTooMany {
475 name: flag.name.clone(),
476 max,
477 got: count,
478 });
479 }
480 }
481 }
482 }
483
484 Ok(out)
485}
486
487#[cfg(feature = "docs")]
488fn render_help_err(spec: &Spec, cmd: &SpecCommand, long: bool) -> UsageErr {
489 UsageErr::Help(docs::cli::render_help(spec, cmd, long))
490}
491
492#[cfg(not(feature = "docs"))]
493fn render_help_err(_spec: &Spec, _cmd: &SpecCommand, _long: bool) -> UsageErr {
494 UsageErr::Help("help".to_string())
495}
496
497fn is_help_arg(spec: &Spec, w: &str) -> bool {
498 spec.disable_help != Some(true)
499 && (w == "--help"
500 || w == "-h"
501 || w == "-?"
502 || (spec.cmd.subcommands.is_empty() && w == "help"))
503}
504
505impl ParseOutput {
506 pub fn as_env(&self) -> BTreeMap<String, String> {
507 let mut env = BTreeMap::new();
508 for (flag, val) in &self.flags {
509 let key = format!("usage_{}", flag.name.to_snake_case());
510 let val = match val {
511 ParseValue::Bool(b) => if *b { "true" } else { "false" }.to_string(),
512 ParseValue::String(s) => s.clone(),
513 ParseValue::MultiBool(b) => b.iter().filter(|b| **b).count().to_string(),
514 ParseValue::MultiString(s) => shell_words::join(s),
515 };
516 env.insert(key, val);
517 }
518 for (arg, val) in &self.args {
519 let key = format!("usage_{}", arg.name.to_snake_case());
520 env.insert(key, val.to_string());
521 }
522 env
523 }
524}
525
526impl Display for ParseValue {
527 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
528 match self {
529 ParseValue::Bool(b) => write!(f, "{b}"),
530 ParseValue::String(s) => write!(f, "{s}"),
531 ParseValue::MultiBool(b) => write!(f, "{}", b.iter().join(" ")),
532 ParseValue::MultiString(s) => write!(f, "{}", shell_words::join(s)),
533 }
534 }
535}
536
537impl Debug for ParseOutput {
538 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
539 f.debug_struct("ParseOutput")
540 .field("cmds", &self.cmds.iter().map(|c| &c.name).join(" ").trim())
541 .field(
542 "args",
543 &self
544 .args
545 .iter()
546 .map(|(a, w)| format!("{}: {w}", &a.name))
547 .collect_vec(),
548 )
549 .field(
550 "available_flags",
551 &self
552 .available_flags
553 .iter()
554 .map(|(f, w)| format!("{f}: {w}"))
555 .collect_vec(),
556 )
557 .field(
558 "flags",
559 &self
560 .flags
561 .iter()
562 .map(|(f, w)| format!("{}: {w}", &f.name))
563 .collect_vec(),
564 )
565 .field("flag_awaiting_value", &self.flag_awaiting_value)
566 .field("errors", &self.errors)
567 .finish()
568 }
569}
570
571#[cfg(test)]
572mod tests {
573 use super::*;
574
575 #[test]
576 fn test_parse() {
577 let cmd = SpecCommand::builder()
578 .name("test")
579 .arg(SpecArg::builder().name("arg").build())
580 .flag(SpecFlag::builder().long("flag").build())
581 .build();
582 let spec = Spec {
583 name: "test".to_string(),
584 bin: "test".to_string(),
585 cmd,
586 ..Default::default()
587 };
588 let input = vec!["test".to_string(), "arg1".to_string(), "--flag".to_string()];
589 let parsed = parse(&spec, &input).unwrap();
590 assert_eq!(parsed.cmds.len(), 1);
591 assert_eq!(parsed.cmds[0].name, "test");
592 assert_eq!(parsed.args.len(), 1);
593 assert_eq!(parsed.flags.len(), 1);
594 assert_eq!(parsed.available_flags.len(), 1);
595 }
596
597 #[test]
598 fn test_as_env() {
599 let cmd = SpecCommand::builder()
600 .name("test")
601 .arg(SpecArg::builder().name("arg").build())
602 .flag(SpecFlag::builder().long("flag").build())
603 .flag(
604 SpecFlag::builder()
605 .long("force")
606 .negate("--no-force")
607 .build(),
608 )
609 .build();
610 let spec = Spec {
611 name: "test".to_string(),
612 bin: "test".to_string(),
613 cmd,
614 ..Default::default()
615 };
616 let input = vec![
617 "test".to_string(),
618 "--flag".to_string(),
619 "--no-force".to_string(),
620 ];
621 let parsed = parse(&spec, &input).unwrap();
622 let env = parsed.as_env();
623 assert_eq!(env.len(), 2);
624 assert_eq!(env.get("usage_flag"), Some(&"true".to_string()));
625 assert_eq!(env.get("usage_force"), Some(&"false".to_string()));
626 }
627
628 #[test]
629 fn test_arg_env_var() {
630 let cmd = SpecCommand::builder()
631 .name("test")
632 .arg(
633 SpecArg::builder()
634 .name("input")
635 .env("TEST_ARG_INPUT")
636 .required(true)
637 .build(),
638 )
639 .build();
640 let spec = Spec {
641 name: "test".to_string(),
642 bin: "test".to_string(),
643 cmd,
644 ..Default::default()
645 };
646
647 std::env::set_var("TEST_ARG_INPUT", "test_file.txt");
649
650 let input = vec!["test".to_string()];
651 let parsed = parse(&spec, &input).unwrap();
652
653 assert_eq!(parsed.args.len(), 1);
654 let arg = parsed.args.keys().next().unwrap();
655 assert_eq!(arg.name, "input");
656 let value = parsed.args.values().next().unwrap();
657 assert_eq!(value.to_string(), "test_file.txt");
658
659 std::env::remove_var("TEST_ARG_INPUT");
661 }
662
663 #[test]
664 fn test_flag_env_var_with_arg() {
665 let cmd = SpecCommand::builder()
666 .name("test")
667 .flag(
668 SpecFlag::builder()
669 .long("output")
670 .env("TEST_FLAG_OUTPUT")
671 .arg(SpecArg::builder().name("file").build())
672 .build(),
673 )
674 .build();
675 let spec = Spec {
676 name: "test".to_string(),
677 bin: "test".to_string(),
678 cmd,
679 ..Default::default()
680 };
681
682 std::env::set_var("TEST_FLAG_OUTPUT", "output.txt");
684
685 let input = vec!["test".to_string()];
686 let parsed = parse(&spec, &input).unwrap();
687
688 assert_eq!(parsed.flags.len(), 1);
689 let flag = parsed.flags.keys().next().unwrap();
690 assert_eq!(flag.name, "output");
691 let value = parsed.flags.values().next().unwrap();
692 assert_eq!(value.to_string(), "output.txt");
693
694 std::env::remove_var("TEST_FLAG_OUTPUT");
696 }
697
698 #[test]
699 fn test_flag_env_var_boolean() {
700 let cmd = SpecCommand::builder()
701 .name("test")
702 .flag(
703 SpecFlag::builder()
704 .long("verbose")
705 .env("TEST_FLAG_VERBOSE")
706 .build(),
707 )
708 .build();
709 let spec = Spec {
710 name: "test".to_string(),
711 bin: "test".to_string(),
712 cmd,
713 ..Default::default()
714 };
715
716 std::env::set_var("TEST_FLAG_VERBOSE", "true");
718
719 let input = vec!["test".to_string()];
720 let parsed = parse(&spec, &input).unwrap();
721
722 assert_eq!(parsed.flags.len(), 1);
723 let flag = parsed.flags.keys().next().unwrap();
724 assert_eq!(flag.name, "verbose");
725 let value = parsed.flags.values().next().unwrap();
726 assert_eq!(value.to_string(), "true");
727
728 std::env::remove_var("TEST_FLAG_VERBOSE");
730 }
731
732 #[test]
733 fn test_env_var_precedence() {
734 let cmd = SpecCommand::builder()
736 .name("test")
737 .arg(
738 SpecArg::builder()
739 .name("input")
740 .env("TEST_PRECEDENCE_INPUT")
741 .required(true)
742 .build(),
743 )
744 .build();
745 let spec = Spec {
746 name: "test".to_string(),
747 bin: "test".to_string(),
748 cmd,
749 ..Default::default()
750 };
751
752 std::env::set_var("TEST_PRECEDENCE_INPUT", "env_file.txt");
754
755 let input = vec!["test".to_string(), "cli_file.txt".to_string()];
756 let parsed = parse(&spec, &input).unwrap();
757
758 assert_eq!(parsed.args.len(), 1);
759 let value = parsed.args.values().next().unwrap();
760 assert_eq!(value.to_string(), "cli_file.txt");
762
763 std::env::remove_var("TEST_PRECEDENCE_INPUT");
765 }
766
767 #[test]
768 fn test_flag_var_true_with_single_default() {
769 let cmd = SpecCommand::builder()
771 .name("test")
772 .flag(
773 SpecFlag::builder()
774 .long("foo")
775 .var(true)
776 .arg(SpecArg::builder().name("foo").build())
777 .default_value("bar")
778 .build(),
779 )
780 .build();
781 let spec = Spec {
782 name: "test".to_string(),
783 bin: "test".to_string(),
784 cmd,
785 ..Default::default()
786 };
787
788 let input = vec!["test".to_string()];
790 let parsed = parse(&spec, &input).unwrap();
791
792 assert_eq!(parsed.flags.len(), 1);
793 let flag = parsed.flags.keys().next().unwrap();
794 assert_eq!(flag.name, "foo");
795 let value = parsed.flags.values().next().unwrap();
796 match value {
798 ParseValue::MultiString(v) => {
799 assert_eq!(v.len(), 1);
800 assert_eq!(v[0], "bar");
801 }
802 _ => panic!("Expected MultiString, got {:?}", value),
803 }
804 }
805
806 #[test]
807 fn test_flag_var_true_with_multiple_defaults() {
808 let cmd = SpecCommand::builder()
810 .name("test")
811 .flag(
812 SpecFlag::builder()
813 .long("foo")
814 .var(true)
815 .arg(SpecArg::builder().name("foo").build())
816 .default_values(["xyz", "bar"])
817 .build(),
818 )
819 .build();
820 let spec = Spec {
821 name: "test".to_string(),
822 bin: "test".to_string(),
823 cmd,
824 ..Default::default()
825 };
826
827 let input = vec!["test".to_string()];
829 let parsed = parse(&spec, &input).unwrap();
830
831 assert_eq!(parsed.flags.len(), 1);
832 let value = parsed.flags.values().next().unwrap();
833 match value {
835 ParseValue::MultiString(v) => {
836 assert_eq!(v.len(), 2);
837 assert_eq!(v[0], "xyz");
838 assert_eq!(v[1], "bar");
839 }
840 _ => panic!("Expected MultiString, got {:?}", value),
841 }
842 }
843
844 #[test]
845 fn test_flag_var_false_with_default_remains_string() {
846 let cmd = SpecCommand::builder()
848 .name("test")
849 .flag(
850 SpecFlag::builder()
851 .long("foo")
852 .var(false) .arg(SpecArg::builder().name("foo").build())
854 .default_value("bar")
855 .build(),
856 )
857 .build();
858 let spec = Spec {
859 name: "test".to_string(),
860 bin: "test".to_string(),
861 cmd,
862 ..Default::default()
863 };
864
865 let input = vec!["test".to_string()];
867 let parsed = parse(&spec, &input).unwrap();
868
869 assert_eq!(parsed.flags.len(), 1);
870 let value = parsed.flags.values().next().unwrap();
871 match value {
873 ParseValue::String(s) => {
874 assert_eq!(s, "bar");
875 }
876 _ => panic!("Expected String, got {:?}", value),
877 }
878 }
879
880 #[test]
881 fn test_arg_var_true_with_single_default() {
882 let cmd = SpecCommand::builder()
884 .name("test")
885 .arg(
886 SpecArg::builder()
887 .name("files")
888 .var(true)
889 .default_value("default.txt")
890 .required(false)
891 .build(),
892 )
893 .build();
894 let spec = Spec {
895 name: "test".to_string(),
896 bin: "test".to_string(),
897 cmd,
898 ..Default::default()
899 };
900
901 let input = vec!["test".to_string()];
903 let parsed = parse(&spec, &input).unwrap();
904
905 assert_eq!(parsed.args.len(), 1);
906 let value = parsed.args.values().next().unwrap();
907 match value {
909 ParseValue::MultiString(v) => {
910 assert_eq!(v.len(), 1);
911 assert_eq!(v[0], "default.txt");
912 }
913 _ => panic!("Expected MultiString, got {:?}", value),
914 }
915 }
916
917 #[test]
918 fn test_arg_var_true_with_multiple_defaults() {
919 let cmd = SpecCommand::builder()
921 .name("test")
922 .arg(
923 SpecArg::builder()
924 .name("files")
925 .var(true)
926 .default_values(["file1.txt", "file2.txt"])
927 .required(false)
928 .build(),
929 )
930 .build();
931 let spec = Spec {
932 name: "test".to_string(),
933 bin: "test".to_string(),
934 cmd,
935 ..Default::default()
936 };
937
938 let input = vec!["test".to_string()];
940 let parsed = parse(&spec, &input).unwrap();
941
942 assert_eq!(parsed.args.len(), 1);
943 let value = parsed.args.values().next().unwrap();
944 match value {
946 ParseValue::MultiString(v) => {
947 assert_eq!(v.len(), 2);
948 assert_eq!(v[0], "file1.txt");
949 assert_eq!(v[1], "file2.txt");
950 }
951 _ => panic!("Expected MultiString, got {:?}", value),
952 }
953 }
954
955 #[test]
956 fn test_arg_var_false_with_default_remains_string() {
957 let cmd = SpecCommand::builder()
959 .name("test")
960 .arg(
961 SpecArg::builder()
962 .name("file")
963 .var(false)
964 .default_value("default.txt")
965 .required(false)
966 .build(),
967 )
968 .build();
969 let spec = Spec {
970 name: "test".to_string(),
971 bin: "test".to_string(),
972 cmd,
973 ..Default::default()
974 };
975
976 let input = vec!["test".to_string()];
978 let parsed = parse(&spec, &input).unwrap();
979
980 assert_eq!(parsed.args.len(), 1);
981 let value = parsed.args.values().next().unwrap();
982 match value {
984 ParseValue::String(s) => {
985 assert_eq!(s, "default.txt");
986 }
987 _ => panic!("Expected String, got {:?}", value),
988 }
989 }
990}