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 std::sync::Arc;
9use strum::EnumTryAs;
10
11#[cfg(feature = "docs")]
12use crate::docs;
13use crate::error::UsageErr;
14use crate::spec::arg::SpecDoubleDashChoices;
15use crate::{Spec, SpecArg, SpecCommand, SpecFlag};
16
17fn get_flag_key(word: &str) -> &str {
20 if word.starts_with("--") {
21 word.split_once('=').map(|(k, _)| k).unwrap_or(word)
23 } else if word.len() >= 2 {
24 &word[0..2]
26 } else {
27 word
28 }
29}
30
31pub struct ParseOutput {
32 pub cmd: SpecCommand,
33 pub cmds: Vec<SpecCommand>,
34 pub args: IndexMap<Arc<SpecArg>, ParseValue>,
35 pub flags: IndexMap<Arc<SpecFlag>, ParseValue>,
36 pub available_flags: BTreeMap<String, Arc<SpecFlag>>,
37 pub flag_awaiting_value: Vec<Arc<SpecFlag>>,
38 pub errors: Vec<UsageErr>,
39}
40
41#[derive(Debug, EnumTryAs, Clone)]
42pub enum ParseValue {
43 Bool(bool),
44 String(String),
45 MultiBool(Vec<bool>),
46 MultiString(Vec<String>),
47}
48
49#[must_use = "parsing result should be used"]
53pub fn parse(spec: &Spec, input: &[String]) -> Result<ParseOutput, miette::Error> {
54 let mut out = parse_partial(spec, input)?;
55 trace!("{out:?}");
56
57 for arg in out.cmd.args.iter().skip(out.args.len()) {
59 if let Some(env_var) = arg.env.as_ref() {
60 if let Ok(env_value) = std::env::var(env_var) {
61 out.args
62 .insert(Arc::new(arg.clone()), ParseValue::String(env_value));
63 continue;
64 }
65 }
66 if !arg.default.is_empty() {
67 if arg.var {
69 out.args.insert(
71 Arc::new(arg.clone()),
72 ParseValue::MultiString(arg.default.clone()),
73 );
74 } else {
75 out.args.insert(
77 Arc::new(arg.clone()),
78 ParseValue::String(arg.default[0].clone()),
79 );
80 }
81 }
82 }
83
84 for flag in out.available_flags.values() {
86 if out.flags.contains_key(flag) {
87 continue;
88 }
89 if let Some(env_var) = flag.env.as_ref() {
90 if let Ok(env_value) = std::env::var(env_var) {
91 if flag.arg.is_some() {
92 out.flags
93 .insert(Arc::clone(flag), ParseValue::String(env_value));
94 } else {
95 let is_true = matches!(env_value.as_str(), "1" | "true" | "True" | "TRUE");
97 out.flags
98 .insert(Arc::clone(flag), ParseValue::Bool(is_true));
99 }
100 continue;
101 }
102 }
103 if !flag.default.is_empty() {
105 if flag.var {
107 if flag.arg.is_some() {
109 out.flags.insert(
110 Arc::clone(flag),
111 ParseValue::MultiString(flag.default.clone()),
112 );
113 } else {
114 let bools: Vec<bool> = flag
116 .default
117 .iter()
118 .map(|s| matches!(s.as_str(), "1" | "true" | "True" | "TRUE"))
119 .collect();
120 out.flags
121 .insert(Arc::clone(flag), ParseValue::MultiBool(bools));
122 }
123 } else {
124 if flag.arg.is_some() {
126 out.flags.insert(
127 Arc::clone(flag),
128 ParseValue::String(flag.default[0].clone()),
129 );
130 } else {
131 let is_true =
133 matches!(flag.default[0].as_str(), "1" | "true" | "True" | "TRUE");
134 out.flags
135 .insert(Arc::clone(flag), ParseValue::Bool(is_true));
136 }
137 }
138 }
139 if let Some(arg) = flag.arg.as_ref() {
141 if !out.flags.contains_key(flag) && !arg.default.is_empty() {
142 if flag.var {
143 out.flags.insert(
144 Arc::clone(flag),
145 ParseValue::MultiString(arg.default.clone()),
146 );
147 } else {
148 out.flags
149 .insert(Arc::clone(flag), ParseValue::String(arg.default[0].clone()));
150 }
151 }
152 }
153 }
154 if let Some(err) = out.errors.iter().find(|e| matches!(e, UsageErr::Help(_))) {
155 bail!("{err}");
156 }
157 if !out.errors.is_empty() {
158 bail!("{}", out.errors.iter().map(|e| e.to_string()).join("\n"));
159 }
160 Ok(out)
161}
162
163#[must_use = "parsing result should be used"]
167pub fn parse_partial(spec: &Spec, input: &[String]) -> Result<ParseOutput, miette::Error> {
168 trace!("parse_partial: {input:?}");
169 let mut input = input.iter().cloned().collect::<VecDeque<_>>();
170 input.pop_front();
171
172 let gather_flags = |cmd: &SpecCommand| {
173 cmd.flags
174 .iter()
175 .flat_map(|f| {
176 let f = Arc::new(f.clone()); let mut flags = f
178 .long
179 .iter()
180 .map(|l| (format!("--{l}"), Arc::clone(&f)))
181 .chain(f.short.iter().map(|s| (format!("-{s}"), Arc::clone(&f))))
182 .collect::<Vec<_>>();
183 if let Some(negate) = &f.negate {
184 flags.push((negate.clone(), Arc::clone(&f)));
185 }
186 flags
187 })
188 .collect()
189 };
190
191 let mut out = ParseOutput {
192 cmd: spec.cmd.clone(),
193 cmds: vec![spec.cmd.clone()],
194 args: IndexMap::new(),
195 flags: IndexMap::new(),
196 available_flags: gather_flags(&spec.cmd),
197 flag_awaiting_value: vec![],
198 errors: vec![],
199 };
200
201 let mut prefix_words: Vec<String> = vec![];
214 let mut idx = 0;
215
216 while idx < input.len() {
217 if let Some(subcommand) = out.cmd.find_subcommand(&input[idx]) {
218 let mut subcommand = subcommand.clone();
219 subcommand.mount(&prefix_words)?;
221 out.available_flags.retain(|_, f| f.global);
222 out.available_flags.extend(gather_flags(&subcommand));
223 input.remove(idx);
225 out.cmds.push(subcommand.clone());
226 out.cmd = subcommand.clone();
227 prefix_words.clear();
228 } else if input[idx].starts_with('-') {
231 let word = &input[idx];
233 let flag_key = get_flag_key(word);
234
235 if let Some(f) = out.available_flags.get(flag_key) {
236 if f.global {
238 prefix_words.push(input[idx].clone());
239 idx += 1;
240
241 if f.arg.is_some()
244 && !word.contains('=')
245 && idx < input.len()
246 && !input[idx].starts_with('-')
247 {
248 prefix_words.push(input[idx].clone());
249 idx += 1;
250 }
251 } else {
252 break;
256 }
257 } else {
258 break;
261 }
262 } else {
263 if let Some(default_name) = &spec.default_subcommand {
266 if let Some(subcommand) = out.cmd.find_subcommand(default_name) {
267 let mut subcommand = subcommand.clone();
268 subcommand.mount(&prefix_words)?;
270 out.available_flags.retain(|_, f| f.global);
271 out.available_flags.extend(gather_flags(&subcommand));
272 out.cmds.push(subcommand.clone());
273 out.cmd = subcommand.clone();
274 prefix_words.clear();
275 break;
278 }
279 }
280 break;
282 }
283 }
284
285 let mut next_arg = out.cmd.args.first();
290 let mut enable_flags = true;
291 let mut grouped_flag = false;
292
293 while !input.is_empty() {
294 let mut w = input.pop_front().unwrap();
295
296 if let Some(ref restart_token) = out.cmd.restart_token {
299 if w == *restart_token {
300 out.args.clear();
302 next_arg = out.cmd.args.first();
303 out.flag_awaiting_value.clear(); enable_flags = true; continue;
307 }
308 }
309
310 if w == "--" {
311 enable_flags = false;
313
314 let should_preserve = next_arg
317 .map(|arg| arg.var && arg.double_dash == SpecDoubleDashChoices::Preserve)
318 .unwrap_or(false);
319
320 if should_preserve {
321 } else {
323 continue;
325 }
326 }
327
328 if enable_flags && w.starts_with("--") {
330 grouped_flag = false;
331 let (word, val) = w.split_once('=').unwrap_or_else(|| (&w, ""));
332 if !val.is_empty() {
333 input.push_front(val.to_string());
334 }
335 if let Some(f) = out.available_flags.get(word) {
336 if f.arg.is_some() {
337 out.flag_awaiting_value.push(Arc::clone(f));
338 } else if f.count {
339 let arr = out
340 .flags
341 .entry(Arc::clone(f))
342 .or_insert_with(|| ParseValue::MultiBool(vec![]))
343 .try_as_multi_bool_mut()
344 .unwrap();
345 arr.push(true);
346 } else {
347 let negate = f.negate.clone().unwrap_or_default();
348 out.flags
349 .insert(Arc::clone(f), ParseValue::Bool(w != negate));
350 }
351 continue;
352 }
353 if is_help_arg(spec, &w) {
354 out.errors
355 .push(render_help_err(spec, &out.cmd, w.len() > 2));
356 return Ok(out);
357 }
358 }
359
360 if enable_flags && w.starts_with('-') && w.len() > 1 {
362 let short = w.chars().nth(1).unwrap();
363 if let Some(f) = out.available_flags.get(&format!("-{short}")) {
364 if w.len() > 2 {
365 input.push_front(format!("-{}", &w[2..]));
366 grouped_flag = true;
367 }
368 if f.arg.is_some() {
369 out.flag_awaiting_value.push(Arc::clone(f));
370 } else if f.count {
371 let arr = out
372 .flags
373 .entry(Arc::clone(f))
374 .or_insert_with(|| ParseValue::MultiBool(vec![]))
375 .try_as_multi_bool_mut()
376 .unwrap();
377 arr.push(true);
378 } else {
379 let negate = f.negate.clone().unwrap_or_default();
380 out.flags
381 .insert(Arc::clone(f), ParseValue::Bool(w != negate));
382 }
383 continue;
384 }
385 if is_help_arg(spec, &w) {
386 out.errors
387 .push(render_help_err(spec, &out.cmd, w.len() > 2));
388 return Ok(out);
389 }
390 if grouped_flag {
391 grouped_flag = false;
392 w.remove(0);
393 }
394 }
395
396 if !out.flag_awaiting_value.is_empty() {
397 while let Some(flag) = out.flag_awaiting_value.pop() {
398 let arg = flag.arg.as_ref().unwrap();
399 if flag.var {
400 let arr = out
401 .flags
402 .entry(flag)
403 .or_insert_with(|| ParseValue::MultiString(vec![]))
404 .try_as_multi_string_mut()
405 .unwrap();
406 arr.push(w);
407 } else {
408 if let Some(choices) = &arg.choices {
409 if !choices.choices.contains(&w) {
410 if is_help_arg(spec, &w) {
411 out.errors
412 .push(render_help_err(spec, &out.cmd, w.len() > 2));
413 return Ok(out);
414 }
415 bail!(
416 "Invalid choice for option {}: {w}, expected one of {}",
417 flag.name,
418 choices.choices.join(", ")
419 );
420 }
421 }
422 out.flags.insert(flag, ParseValue::String(w));
423 }
424 w = "".to_string();
425 }
426 continue;
427 }
428
429 if let Some(arg) = next_arg {
430 if arg.var {
431 let arr = out
432 .args
433 .entry(Arc::new(arg.clone()))
434 .or_insert_with(|| ParseValue::MultiString(vec![]))
435 .try_as_multi_string_mut()
436 .unwrap();
437 arr.push(w);
438 if arr.len() >= arg.var_max.unwrap_or(usize::MAX) {
439 next_arg = out.cmd.args.get(out.args.len());
440 }
441 } else {
442 if let Some(choices) = &arg.choices {
443 if !choices.choices.contains(&w) {
444 if is_help_arg(spec, &w) {
445 out.errors
446 .push(render_help_err(spec, &out.cmd, w.len() > 2));
447 return Ok(out);
448 }
449 bail!(
450 "Invalid choice for arg {}: {w}, expected one of {}",
451 arg.name,
452 choices.choices.join(", ")
453 );
454 }
455 }
456 out.args
457 .insert(Arc::new(arg.clone()), ParseValue::String(w));
458 next_arg = out.cmd.args.get(out.args.len());
459 }
460 continue;
461 }
462 if is_help_arg(spec, &w) {
463 out.errors
464 .push(render_help_err(spec, &out.cmd, w.len() > 2));
465 return Ok(out);
466 }
467 bail!("unexpected word: {w}");
468 }
469
470 for arg in out.cmd.args.iter().skip(out.args.len()) {
471 if arg.required && arg.default.is_empty() {
472 let has_env = arg
474 .env
475 .as_ref()
476 .map(|e| std::env::var(e).is_ok())
477 .unwrap_or(false);
478 if !has_env {
479 out.errors.push(UsageErr::MissingArg(arg.name.clone()));
480 }
481 }
482 }
483
484 for flag in out.available_flags.values() {
485 if out.flags.contains_key(flag) {
486 continue;
487 }
488 let has_default =
489 !flag.default.is_empty() || flag.arg.iter().any(|a| !a.default.is_empty());
490 let has_env = flag
491 .env
492 .as_ref()
493 .map(|e| std::env::var(e).is_ok())
494 .unwrap_or(false);
495 if flag.required && !has_default && !has_env {
496 out.errors.push(UsageErr::MissingFlag(flag.name.clone()));
497 }
498 }
499
500 for (arg, value) in &out.args {
502 if arg.var {
503 if let ParseValue::MultiString(values) = value {
504 if let Some(min) = arg.var_min {
505 if values.len() < min {
506 out.errors.push(UsageErr::VarArgTooFew {
507 name: arg.name.clone(),
508 min,
509 got: values.len(),
510 });
511 }
512 }
513 if let Some(max) = arg.var_max {
514 if values.len() > max {
515 out.errors.push(UsageErr::VarArgTooMany {
516 name: arg.name.clone(),
517 max,
518 got: values.len(),
519 });
520 }
521 }
522 }
523 }
524 }
525
526 for (flag, value) in &out.flags {
528 if flag.var {
529 let count = match value {
530 ParseValue::MultiString(values) => values.len(),
531 ParseValue::MultiBool(values) => values.len(),
532 _ => continue,
533 };
534 if let Some(min) = flag.var_min {
535 if count < min {
536 out.errors.push(UsageErr::VarFlagTooFew {
537 name: flag.name.clone(),
538 min,
539 got: count,
540 });
541 }
542 }
543 if let Some(max) = flag.var_max {
544 if count > max {
545 out.errors.push(UsageErr::VarFlagTooMany {
546 name: flag.name.clone(),
547 max,
548 got: count,
549 });
550 }
551 }
552 }
553 }
554
555 Ok(out)
556}
557
558#[cfg(feature = "docs")]
559fn render_help_err(spec: &Spec, cmd: &SpecCommand, long: bool) -> UsageErr {
560 UsageErr::Help(docs::cli::render_help(spec, cmd, long))
561}
562
563#[cfg(not(feature = "docs"))]
564fn render_help_err(_spec: &Spec, _cmd: &SpecCommand, _long: bool) -> UsageErr {
565 UsageErr::Help("help".to_string())
566}
567
568fn is_help_arg(spec: &Spec, w: &str) -> bool {
569 spec.disable_help != Some(true)
570 && (w == "--help"
571 || w == "-h"
572 || w == "-?"
573 || (spec.cmd.subcommands.is_empty() && w == "help"))
574}
575
576impl ParseOutput {
577 pub fn as_env(&self) -> BTreeMap<String, String> {
578 let mut env = BTreeMap::new();
579 for (flag, val) in &self.flags {
580 let key = format!("usage_{}", flag.name.to_snake_case());
581 let val = match val {
582 ParseValue::Bool(b) => if *b { "true" } else { "false" }.to_string(),
583 ParseValue::String(s) => s.clone(),
584 ParseValue::MultiBool(b) => b.iter().filter(|b| **b).count().to_string(),
585 ParseValue::MultiString(s) => shell_words::join(s),
586 };
587 env.insert(key, val);
588 }
589 for (arg, val) in &self.args {
590 let key = format!("usage_{}", arg.name.to_snake_case());
591 env.insert(key, val.to_string());
592 }
593 env
594 }
595}
596
597impl Display for ParseValue {
598 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
599 match self {
600 ParseValue::Bool(b) => write!(f, "{b}"),
601 ParseValue::String(s) => write!(f, "{s}"),
602 ParseValue::MultiBool(b) => write!(f, "{}", b.iter().join(" ")),
603 ParseValue::MultiString(s) => write!(f, "{}", shell_words::join(s)),
604 }
605 }
606}
607
608impl Debug for ParseOutput {
609 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
610 f.debug_struct("ParseOutput")
611 .field("cmds", &self.cmds.iter().map(|c| &c.name).join(" ").trim())
612 .field(
613 "args",
614 &self
615 .args
616 .iter()
617 .map(|(a, w)| format!("{}: {w}", &a.name))
618 .collect_vec(),
619 )
620 .field(
621 "available_flags",
622 &self
623 .available_flags
624 .iter()
625 .map(|(f, w)| format!("{f}: {w}"))
626 .collect_vec(),
627 )
628 .field(
629 "flags",
630 &self
631 .flags
632 .iter()
633 .map(|(f, w)| format!("{}: {w}", &f.name))
634 .collect_vec(),
635 )
636 .field("flag_awaiting_value", &self.flag_awaiting_value)
637 .field("errors", &self.errors)
638 .finish()
639 }
640}
641
642#[cfg(test)]
643mod tests {
644 use super::*;
645
646 #[test]
647 fn test_parse() {
648 let cmd = SpecCommand::builder()
649 .name("test")
650 .arg(SpecArg::builder().name("arg").build())
651 .flag(SpecFlag::builder().long("flag").build())
652 .build();
653 let spec = Spec {
654 name: "test".to_string(),
655 bin: "test".to_string(),
656 cmd,
657 ..Default::default()
658 };
659 let input = vec!["test".to_string(), "arg1".to_string(), "--flag".to_string()];
660 let parsed = parse(&spec, &input).unwrap();
661 assert_eq!(parsed.cmds.len(), 1);
662 assert_eq!(parsed.cmds[0].name, "test");
663 assert_eq!(parsed.args.len(), 1);
664 assert_eq!(parsed.flags.len(), 1);
665 assert_eq!(parsed.available_flags.len(), 1);
666 }
667
668 #[test]
669 fn test_as_env() {
670 let cmd = SpecCommand::builder()
671 .name("test")
672 .arg(SpecArg::builder().name("arg").build())
673 .flag(SpecFlag::builder().long("flag").build())
674 .flag(
675 SpecFlag::builder()
676 .long("force")
677 .negate("--no-force")
678 .build(),
679 )
680 .build();
681 let spec = Spec {
682 name: "test".to_string(),
683 bin: "test".to_string(),
684 cmd,
685 ..Default::default()
686 };
687 let input = vec![
688 "test".to_string(),
689 "--flag".to_string(),
690 "--no-force".to_string(),
691 ];
692 let parsed = parse(&spec, &input).unwrap();
693 let env = parsed.as_env();
694 assert_eq!(env.len(), 2);
695 assert_eq!(env.get("usage_flag"), Some(&"true".to_string()));
696 assert_eq!(env.get("usage_force"), Some(&"false".to_string()));
697 }
698
699 #[test]
700 fn test_arg_env_var() {
701 let cmd = SpecCommand::builder()
702 .name("test")
703 .arg(
704 SpecArg::builder()
705 .name("input")
706 .env("TEST_ARG_INPUT")
707 .required(true)
708 .build(),
709 )
710 .build();
711 let spec = Spec {
712 name: "test".to_string(),
713 bin: "test".to_string(),
714 cmd,
715 ..Default::default()
716 };
717
718 std::env::set_var("TEST_ARG_INPUT", "test_file.txt");
720
721 let input = vec!["test".to_string()];
722 let parsed = parse(&spec, &input).unwrap();
723
724 assert_eq!(parsed.args.len(), 1);
725 let arg = parsed.args.keys().next().unwrap();
726 assert_eq!(arg.name, "input");
727 let value = parsed.args.values().next().unwrap();
728 assert_eq!(value.to_string(), "test_file.txt");
729
730 std::env::remove_var("TEST_ARG_INPUT");
732 }
733
734 #[test]
735 fn test_flag_env_var_with_arg() {
736 let cmd = SpecCommand::builder()
737 .name("test")
738 .flag(
739 SpecFlag::builder()
740 .long("output")
741 .env("TEST_FLAG_OUTPUT")
742 .arg(SpecArg::builder().name("file").build())
743 .build(),
744 )
745 .build();
746 let spec = Spec {
747 name: "test".to_string(),
748 bin: "test".to_string(),
749 cmd,
750 ..Default::default()
751 };
752
753 std::env::set_var("TEST_FLAG_OUTPUT", "output.txt");
755
756 let input = vec!["test".to_string()];
757 let parsed = parse(&spec, &input).unwrap();
758
759 assert_eq!(parsed.flags.len(), 1);
760 let flag = parsed.flags.keys().next().unwrap();
761 assert_eq!(flag.name, "output");
762 let value = parsed.flags.values().next().unwrap();
763 assert_eq!(value.to_string(), "output.txt");
764
765 std::env::remove_var("TEST_FLAG_OUTPUT");
767 }
768
769 #[test]
770 fn test_flag_env_var_boolean() {
771 let cmd = SpecCommand::builder()
772 .name("test")
773 .flag(
774 SpecFlag::builder()
775 .long("verbose")
776 .env("TEST_FLAG_VERBOSE")
777 .build(),
778 )
779 .build();
780 let spec = Spec {
781 name: "test".to_string(),
782 bin: "test".to_string(),
783 cmd,
784 ..Default::default()
785 };
786
787 std::env::set_var("TEST_FLAG_VERBOSE", "true");
789
790 let input = vec!["test".to_string()];
791 let parsed = parse(&spec, &input).unwrap();
792
793 assert_eq!(parsed.flags.len(), 1);
794 let flag = parsed.flags.keys().next().unwrap();
795 assert_eq!(flag.name, "verbose");
796 let value = parsed.flags.values().next().unwrap();
797 assert_eq!(value.to_string(), "true");
798
799 std::env::remove_var("TEST_FLAG_VERBOSE");
801 }
802
803 #[test]
804 fn test_env_var_precedence() {
805 let cmd = SpecCommand::builder()
807 .name("test")
808 .arg(
809 SpecArg::builder()
810 .name("input")
811 .env("TEST_PRECEDENCE_INPUT")
812 .required(true)
813 .build(),
814 )
815 .build();
816 let spec = Spec {
817 name: "test".to_string(),
818 bin: "test".to_string(),
819 cmd,
820 ..Default::default()
821 };
822
823 std::env::set_var("TEST_PRECEDENCE_INPUT", "env_file.txt");
825
826 let input = vec!["test".to_string(), "cli_file.txt".to_string()];
827 let parsed = parse(&spec, &input).unwrap();
828
829 assert_eq!(parsed.args.len(), 1);
830 let value = parsed.args.values().next().unwrap();
831 assert_eq!(value.to_string(), "cli_file.txt");
833
834 std::env::remove_var("TEST_PRECEDENCE_INPUT");
836 }
837
838 #[test]
839 fn test_flag_var_true_with_single_default() {
840 let cmd = SpecCommand::builder()
842 .name("test")
843 .flag(
844 SpecFlag::builder()
845 .long("foo")
846 .var(true)
847 .arg(SpecArg::builder().name("foo").build())
848 .default_value("bar")
849 .build(),
850 )
851 .build();
852 let spec = Spec {
853 name: "test".to_string(),
854 bin: "test".to_string(),
855 cmd,
856 ..Default::default()
857 };
858
859 let input = vec!["test".to_string()];
861 let parsed = parse(&spec, &input).unwrap();
862
863 assert_eq!(parsed.flags.len(), 1);
864 let flag = parsed.flags.keys().next().unwrap();
865 assert_eq!(flag.name, "foo");
866 let value = parsed.flags.values().next().unwrap();
867 match value {
869 ParseValue::MultiString(v) => {
870 assert_eq!(v.len(), 1);
871 assert_eq!(v[0], "bar");
872 }
873 _ => panic!("Expected MultiString, got {:?}", value),
874 }
875 }
876
877 #[test]
878 fn test_flag_var_true_with_multiple_defaults() {
879 let cmd = SpecCommand::builder()
881 .name("test")
882 .flag(
883 SpecFlag::builder()
884 .long("foo")
885 .var(true)
886 .arg(SpecArg::builder().name("foo").build())
887 .default_values(["xyz", "bar"])
888 .build(),
889 )
890 .build();
891 let spec = Spec {
892 name: "test".to_string(),
893 bin: "test".to_string(),
894 cmd,
895 ..Default::default()
896 };
897
898 let input = vec!["test".to_string()];
900 let parsed = parse(&spec, &input).unwrap();
901
902 assert_eq!(parsed.flags.len(), 1);
903 let value = parsed.flags.values().next().unwrap();
904 match value {
906 ParseValue::MultiString(v) => {
907 assert_eq!(v.len(), 2);
908 assert_eq!(v[0], "xyz");
909 assert_eq!(v[1], "bar");
910 }
911 _ => panic!("Expected MultiString, got {:?}", value),
912 }
913 }
914
915 #[test]
916 fn test_flag_var_false_with_default_remains_string() {
917 let cmd = SpecCommand::builder()
919 .name("test")
920 .flag(
921 SpecFlag::builder()
922 .long("foo")
923 .var(false) .arg(SpecArg::builder().name("foo").build())
925 .default_value("bar")
926 .build(),
927 )
928 .build();
929 let spec = Spec {
930 name: "test".to_string(),
931 bin: "test".to_string(),
932 cmd,
933 ..Default::default()
934 };
935
936 let input = vec!["test".to_string()];
938 let parsed = parse(&spec, &input).unwrap();
939
940 assert_eq!(parsed.flags.len(), 1);
941 let value = parsed.flags.values().next().unwrap();
942 match value {
944 ParseValue::String(s) => {
945 assert_eq!(s, "bar");
946 }
947 _ => panic!("Expected String, got {:?}", value),
948 }
949 }
950
951 #[test]
952 fn test_arg_var_true_with_single_default() {
953 let cmd = SpecCommand::builder()
955 .name("test")
956 .arg(
957 SpecArg::builder()
958 .name("files")
959 .var(true)
960 .default_value("default.txt")
961 .required(false)
962 .build(),
963 )
964 .build();
965 let spec = Spec {
966 name: "test".to_string(),
967 bin: "test".to_string(),
968 cmd,
969 ..Default::default()
970 };
971
972 let input = vec!["test".to_string()];
974 let parsed = parse(&spec, &input).unwrap();
975
976 assert_eq!(parsed.args.len(), 1);
977 let value = parsed.args.values().next().unwrap();
978 match value {
980 ParseValue::MultiString(v) => {
981 assert_eq!(v.len(), 1);
982 assert_eq!(v[0], "default.txt");
983 }
984 _ => panic!("Expected MultiString, got {:?}", value),
985 }
986 }
987
988 #[test]
989 fn test_arg_var_true_with_multiple_defaults() {
990 let cmd = SpecCommand::builder()
992 .name("test")
993 .arg(
994 SpecArg::builder()
995 .name("files")
996 .var(true)
997 .default_values(["file1.txt", "file2.txt"])
998 .required(false)
999 .build(),
1000 )
1001 .build();
1002 let spec = Spec {
1003 name: "test".to_string(),
1004 bin: "test".to_string(),
1005 cmd,
1006 ..Default::default()
1007 };
1008
1009 let input = vec!["test".to_string()];
1011 let parsed = parse(&spec, &input).unwrap();
1012
1013 assert_eq!(parsed.args.len(), 1);
1014 let value = parsed.args.values().next().unwrap();
1015 match value {
1017 ParseValue::MultiString(v) => {
1018 assert_eq!(v.len(), 2);
1019 assert_eq!(v[0], "file1.txt");
1020 assert_eq!(v[1], "file2.txt");
1021 }
1022 _ => panic!("Expected MultiString, got {:?}", value),
1023 }
1024 }
1025
1026 #[test]
1027 fn test_arg_var_false_with_default_remains_string() {
1028 let cmd = SpecCommand::builder()
1030 .name("test")
1031 .arg(
1032 SpecArg::builder()
1033 .name("file")
1034 .var(false)
1035 .default_value("default.txt")
1036 .required(false)
1037 .build(),
1038 )
1039 .build();
1040 let spec = Spec {
1041 name: "test".to_string(),
1042 bin: "test".to_string(),
1043 cmd,
1044 ..Default::default()
1045 };
1046
1047 let input = vec!["test".to_string()];
1049 let parsed = parse(&spec, &input).unwrap();
1050
1051 assert_eq!(parsed.args.len(), 1);
1052 let value = parsed.args.values().next().unwrap();
1053 match value {
1055 ParseValue::String(s) => {
1056 assert_eq!(s, "default.txt");
1057 }
1058 _ => panic!("Expected String, got {:?}", value),
1059 }
1060 }
1061
1062 #[test]
1063 fn test_default_subcommand() {
1064 let run_cmd = SpecCommand::builder()
1066 .name("run")
1067 .arg(SpecArg::builder().name("task").build())
1068 .build();
1069 let mut cmd = SpecCommand::builder().name("test").build();
1070 cmd.subcommands.insert("run".to_string(), run_cmd);
1071
1072 let spec = Spec {
1073 name: "test".to_string(),
1074 bin: "test".to_string(),
1075 cmd,
1076 default_subcommand: Some("run".to_string()),
1077 ..Default::default()
1078 };
1079
1080 let input = vec!["test".to_string(), "mytask".to_string()];
1082 let parsed = parse(&spec, &input).unwrap();
1083
1084 assert_eq!(parsed.cmds.len(), 2);
1086 assert_eq!(parsed.cmds[1].name, "run");
1087
1088 assert_eq!(parsed.args.len(), 1);
1090 let arg = parsed.args.keys().next().unwrap();
1091 assert_eq!(arg.name, "task");
1092 let value = parsed.args.values().next().unwrap();
1093 assert_eq!(value.to_string(), "mytask");
1094 }
1095
1096 #[test]
1097 fn test_default_subcommand_explicit_still_works() {
1098 let run_cmd = SpecCommand::builder()
1100 .name("run")
1101 .arg(SpecArg::builder().name("task").build())
1102 .build();
1103 let other_cmd = SpecCommand::builder()
1104 .name("other")
1105 .arg(SpecArg::builder().name("other_arg").build())
1106 .build();
1107 let mut cmd = SpecCommand::builder().name("test").build();
1108 cmd.subcommands.insert("run".to_string(), run_cmd);
1109 cmd.subcommands.insert("other".to_string(), other_cmd);
1110
1111 let spec = Spec {
1112 name: "test".to_string(),
1113 bin: "test".to_string(),
1114 cmd,
1115 default_subcommand: Some("run".to_string()),
1116 ..Default::default()
1117 };
1118
1119 let input = vec!["test".to_string(), "other".to_string(), "foo".to_string()];
1121 let parsed = parse(&spec, &input).unwrap();
1122
1123 assert_eq!(parsed.cmds.len(), 2);
1125 assert_eq!(parsed.cmds[1].name, "other");
1126 }
1127
1128 #[test]
1129 fn test_restart_token() {
1130 let run_cmd = SpecCommand::builder()
1132 .name("run")
1133 .arg(SpecArg::builder().name("task").build())
1134 .restart_token(":::".to_string())
1135 .build();
1136 let mut cmd = SpecCommand::builder().name("test").build();
1137 cmd.subcommands.insert("run".to_string(), run_cmd);
1138
1139 let spec = Spec {
1140 name: "test".to_string(),
1141 bin: "test".to_string(),
1142 cmd,
1143 ..Default::default()
1144 };
1145
1146 let input = vec![
1148 "test".to_string(),
1149 "run".to_string(),
1150 "task1".to_string(),
1151 ":::".to_string(),
1152 "task2".to_string(),
1153 ];
1154 let parsed = parse(&spec, &input).unwrap();
1155
1156 assert_eq!(parsed.args.len(), 1);
1158 let value = parsed.args.values().next().unwrap();
1159 assert_eq!(value.to_string(), "task2");
1160 }
1161
1162 #[test]
1163 fn test_restart_token_multiple() {
1164 let run_cmd = SpecCommand::builder()
1166 .name("run")
1167 .arg(SpecArg::builder().name("task").build())
1168 .restart_token(":::".to_string())
1169 .build();
1170 let mut cmd = SpecCommand::builder().name("test").build();
1171 cmd.subcommands.insert("run".to_string(), run_cmd);
1172
1173 let spec = Spec {
1174 name: "test".to_string(),
1175 bin: "test".to_string(),
1176 cmd,
1177 ..Default::default()
1178 };
1179
1180 let input = vec![
1182 "test".to_string(),
1183 "run".to_string(),
1184 "task1".to_string(),
1185 ":::".to_string(),
1186 "task2".to_string(),
1187 ":::".to_string(),
1188 "task3".to_string(),
1189 ];
1190 let parsed = parse(&spec, &input).unwrap();
1191
1192 assert_eq!(parsed.args.len(), 1);
1194 let value = parsed.args.values().next().unwrap();
1195 assert_eq!(value.to_string(), "task3");
1196 }
1197
1198 #[test]
1199 fn test_restart_token_clears_flag_awaiting_value() {
1200 let run_cmd = SpecCommand::builder()
1202 .name("run")
1203 .arg(SpecArg::builder().name("task").build())
1204 .flag(
1205 SpecFlag::builder()
1206 .name("jobs")
1207 .long("jobs")
1208 .arg(SpecArg::builder().name("count").build())
1209 .build(),
1210 )
1211 .restart_token(":::".to_string())
1212 .build();
1213 let mut cmd = SpecCommand::builder().name("test").build();
1214 cmd.subcommands.insert("run".to_string(), run_cmd);
1215
1216 let spec = Spec {
1217 name: "test".to_string(),
1218 bin: "test".to_string(),
1219 cmd,
1220 ..Default::default()
1221 };
1222
1223 let input = vec![
1225 "test".to_string(),
1226 "run".to_string(),
1227 "task1".to_string(),
1228 "--jobs".to_string(),
1229 ":::".to_string(),
1230 "task2".to_string(),
1231 ];
1232 let parsed = parse(&spec, &input).unwrap();
1233
1234 assert_eq!(parsed.args.len(), 1);
1236 let value = parsed.args.values().next().unwrap();
1237 assert_eq!(value.to_string(), "task2");
1238 assert!(parsed.flag_awaiting_value.is_empty());
1240 }
1241
1242 #[test]
1243 fn test_restart_token_resets_double_dash() {
1244 let run_cmd = SpecCommand::builder()
1246 .name("run")
1247 .arg(SpecArg::builder().name("task").build())
1248 .arg(SpecArg::builder().name("extra_args").var(true).build())
1249 .flag(SpecFlag::builder().name("verbose").long("verbose").build())
1250 .restart_token(":::".to_string())
1251 .build();
1252 let mut cmd = SpecCommand::builder().name("test").build();
1253 cmd.subcommands.insert("run".to_string(), run_cmd);
1254
1255 let spec = Spec {
1256 name: "test".to_string(),
1257 bin: "test".to_string(),
1258 cmd,
1259 ..Default::default()
1260 };
1261
1262 let input = vec![
1264 "test".to_string(),
1265 "run".to_string(),
1266 "task1".to_string(),
1267 "--".to_string(),
1268 "extra".to_string(),
1269 ":::".to_string(),
1270 "--verbose".to_string(),
1271 "task2".to_string(),
1272 ];
1273 let parsed = parse(&spec, &input).unwrap();
1274
1275 assert!(parsed.flags.keys().any(|f| f.name == "verbose"));
1277 let task_arg = parsed.args.keys().find(|a| a.name == "task").unwrap();
1279 let value = parsed.args.get(task_arg).unwrap();
1280 assert_eq!(value.to_string(), "task2");
1281 }
1282
1283 #[test]
1284 fn test_double_dashes_without_preserve() {
1285 let run_cmd = SpecCommand::builder()
1287 .name("run")
1288 .arg(SpecArg::builder().name("args").var(true).build())
1289 .build();
1290 let mut cmd = SpecCommand::builder().name("test").build();
1291 cmd.subcommands.insert("run".to_string(), run_cmd);
1292
1293 let spec = Spec {
1294 name: "test".to_string(),
1295 bin: "test".to_string(),
1296 cmd,
1297 ..Default::default()
1298 };
1299
1300 let input = vec![
1302 "test".to_string(),
1303 "run".to_string(),
1304 "arg1".to_string(),
1305 "--".to_string(),
1306 "arg2".to_string(),
1307 "--".to_string(),
1308 "arg3".to_string(),
1309 ];
1310 let parsed = parse(&spec, &input).unwrap();
1311
1312 let args_arg = parsed.args.keys().find(|a| a.name == "args").unwrap();
1313 let value = parsed.args.get(args_arg).unwrap();
1314 assert_eq!(value.to_string(), "arg1 arg2 arg3");
1315 }
1316
1317 #[test]
1318 fn test_double_dashes_with_preserve() {
1319 let run_cmd = SpecCommand::builder()
1321 .name("run")
1322 .arg(
1323 SpecArg::builder()
1324 .name("args")
1325 .var(true)
1326 .double_dash(SpecDoubleDashChoices::Preserve)
1327 .build(),
1328 )
1329 .build();
1330 let mut cmd = SpecCommand::builder().name("test").build();
1331 cmd.subcommands.insert("run".to_string(), run_cmd);
1332
1333 let spec = Spec {
1334 name: "test".to_string(),
1335 bin: "test".to_string(),
1336 cmd,
1337 ..Default::default()
1338 };
1339
1340 let input = vec![
1342 "test".to_string(),
1343 "run".to_string(),
1344 "arg1".to_string(),
1345 "--".to_string(),
1346 "arg2".to_string(),
1347 "--".to_string(),
1348 "arg3".to_string(),
1349 ];
1350 let parsed = parse(&spec, &input).unwrap();
1351
1352 let args_arg = parsed.args.keys().find(|a| a.name == "args").unwrap();
1353 let value = parsed.args.get(args_arg).unwrap();
1354 assert_eq!(value.to_string(), "arg1 -- arg2 -- arg3");
1355 }
1356
1357 #[test]
1358 fn test_double_dashes_with_preserve_only_dashes() {
1359 let run_cmd = SpecCommand::builder()
1362 .name("run")
1363 .arg(
1364 SpecArg::builder()
1365 .name("args")
1366 .var(true)
1367 .double_dash(SpecDoubleDashChoices::Preserve)
1368 .build(),
1369 )
1370 .build();
1371 let mut cmd = SpecCommand::builder().name("test").build();
1372 cmd.subcommands.insert("run".to_string(), run_cmd);
1373
1374 let spec = Spec {
1375 name: "test".to_string(),
1376 bin: "test".to_string(),
1377 cmd,
1378 ..Default::default()
1379 };
1380
1381 let input = vec![
1383 "test".to_string(),
1384 "run".to_string(),
1385 "--".to_string(),
1386 "--".to_string(),
1387 ];
1388 let parsed = parse(&spec, &input).unwrap();
1389
1390 let args_arg = parsed.args.keys().find(|a| a.name == "args").unwrap();
1391 let value = parsed.args.get(args_arg).unwrap();
1392 assert_eq!(value.to_string(), "-- --");
1393 }
1394
1395 #[test]
1396 fn test_double_dashes_with_preserve_multiple_args() {
1397 let run_cmd = SpecCommand::builder()
1399 .name("run")
1400 .arg(SpecArg::builder().name("task").build())
1401 .arg(
1402 SpecArg::builder()
1403 .name("extra_args")
1404 .var(true)
1405 .double_dash(SpecDoubleDashChoices::Preserve)
1406 .build(),
1407 )
1408 .build();
1409 let mut cmd = SpecCommand::builder().name("test").build();
1410 cmd.subcommands.insert("run".to_string(), run_cmd);
1411
1412 let spec = Spec {
1413 name: "test".to_string(),
1414 bin: "test".to_string(),
1415 cmd,
1416 ..Default::default()
1417 };
1418
1419 let input = vec![
1422 "test".to_string(),
1423 "run".to_string(),
1424 "task1".to_string(),
1425 "--".to_string(),
1426 "arg1".to_string(),
1427 "--".to_string(),
1428 "--foo".to_string(),
1429 ];
1430 let parsed = parse(&spec, &input).unwrap();
1431
1432 let task_arg = parsed.args.keys().find(|a| a.name == "task").unwrap();
1433 let task_value = parsed.args.get(task_arg).unwrap();
1434 assert_eq!(task_value.to_string(), "task1");
1435
1436 let extra_arg = parsed.args.keys().find(|a| a.name == "extra_args").unwrap();
1437 let extra_value = parsed.args.get(extra_arg).unwrap();
1438 assert_eq!(extra_value.to_string(), "-- arg1 -- --foo");
1439 }
1440}