1use heck::ToSnakeCase;
2use indexmap::IndexMap;
3use itertools::Itertools;
4use log::trace;
5use miette::bail;
6use std::collections::{BTreeMap, HashMap, 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#[non_exhaustive]
69pub struct Parser<'a> {
70 spec: &'a Spec,
71 env: Option<HashMap<String, String>>,
72}
73
74impl<'a> Parser<'a> {
75 pub fn new(spec: &'a Spec) -> Self {
77 Self { spec, env: None }
78 }
79
80 pub fn with_env(mut self, env: HashMap<String, String>) -> Self {
85 self.env = Some(env);
86 self
87 }
88
89 pub fn parse(self, input: &[String]) -> Result<ParseOutput, miette::Error> {
93 let mut out = parse_partial_with_env(self.spec, input, self.env.as_ref())?;
94 trace!("{out:?}");
95
96 let get_env = |key: &str| -> Option<String> {
97 if let Some(ref env_map) = self.env {
98 env_map.get(key).cloned()
99 } else {
100 std::env::var(key).ok()
101 }
102 };
103
104 for arg in out.cmd.args.iter().skip(out.args.len()) {
106 if let Some(env_var) = arg.env.as_ref() {
107 if let Some(env_value) = get_env(env_var) {
108 out.args
109 .insert(Arc::new(arg.clone()), ParseValue::String(env_value));
110 continue;
111 }
112 }
113 if !arg.default.is_empty() {
114 if arg.var {
116 out.args.insert(
118 Arc::new(arg.clone()),
119 ParseValue::MultiString(arg.default.clone()),
120 );
121 } else {
122 out.args.insert(
124 Arc::new(arg.clone()),
125 ParseValue::String(arg.default[0].clone()),
126 );
127 }
128 }
129 }
130
131 for flag in out.available_flags.values() {
133 if out.flags.contains_key(flag) {
134 continue;
135 }
136 if let Some(env_var) = flag.env.as_ref() {
137 if let Some(env_value) = get_env(env_var) {
138 if flag.arg.is_some() {
139 out.flags
140 .insert(Arc::clone(flag), ParseValue::String(env_value));
141 } else {
142 let is_true = matches!(env_value.as_str(), "1" | "true" | "True" | "TRUE");
144 out.flags
145 .insert(Arc::clone(flag), ParseValue::Bool(is_true));
146 }
147 continue;
148 }
149 }
150 if !flag.default.is_empty() {
152 if flag.var {
154 if flag.arg.is_some() {
156 out.flags.insert(
157 Arc::clone(flag),
158 ParseValue::MultiString(flag.default.clone()),
159 );
160 } else {
161 let bools: Vec<bool> = flag
163 .default
164 .iter()
165 .map(|s| matches!(s.as_str(), "1" | "true" | "True" | "TRUE"))
166 .collect();
167 out.flags
168 .insert(Arc::clone(flag), ParseValue::MultiBool(bools));
169 }
170 } else {
171 if flag.arg.is_some() {
173 out.flags.insert(
174 Arc::clone(flag),
175 ParseValue::String(flag.default[0].clone()),
176 );
177 } else {
178 let is_true =
180 matches!(flag.default[0].as_str(), "1" | "true" | "True" | "TRUE");
181 out.flags
182 .insert(Arc::clone(flag), ParseValue::Bool(is_true));
183 }
184 }
185 }
186 if let Some(arg) = flag.arg.as_ref() {
188 if !out.flags.contains_key(flag) && !arg.default.is_empty() {
189 if flag.var {
190 out.flags.insert(
191 Arc::clone(flag),
192 ParseValue::MultiString(arg.default.clone()),
193 );
194 } else {
195 out.flags
196 .insert(Arc::clone(flag), ParseValue::String(arg.default[0].clone()));
197 }
198 }
199 }
200 }
201 if let Some(err) = out.errors.iter().find(|e| matches!(e, UsageErr::Help(_))) {
202 bail!("{err}");
203 }
204 if !out.errors.is_empty() {
205 bail!("{}", out.errors.iter().map(|e| e.to_string()).join("\n"));
206 }
207 Ok(out)
208 }
209}
210
211#[must_use = "parsing result should be used"]
218pub fn parse(spec: &Spec, input: &[String]) -> Result<ParseOutput, miette::Error> {
219 Parser::new(spec).parse(input)
220}
221
222#[must_use = "parsing result should be used"]
226pub fn parse_partial(spec: &Spec, input: &[String]) -> Result<ParseOutput, miette::Error> {
227 parse_partial_with_env(spec, input, None)
228}
229
230fn parse_partial_with_env(
232 spec: &Spec,
233 input: &[String],
234 custom_env: Option<&HashMap<String, String>>,
235) -> Result<ParseOutput, miette::Error> {
236 trace!("parse_partial: {input:?}");
237 let mut input = input.iter().cloned().collect::<VecDeque<_>>();
238 input.pop_front();
239
240 let gather_flags = |cmd: &SpecCommand| {
241 cmd.flags
242 .iter()
243 .flat_map(|f| {
244 let f = Arc::new(f.clone()); let mut flags = f
246 .long
247 .iter()
248 .map(|l| (format!("--{l}"), Arc::clone(&f)))
249 .chain(f.short.iter().map(|s| (format!("-{s}"), Arc::clone(&f))))
250 .collect::<Vec<_>>();
251 if let Some(negate) = &f.negate {
252 flags.push((negate.clone(), Arc::clone(&f)));
253 }
254 flags
255 })
256 .collect()
257 };
258
259 let mut out = ParseOutput {
260 cmd: spec.cmd.clone(),
261 cmds: vec![spec.cmd.clone()],
262 args: IndexMap::new(),
263 flags: IndexMap::new(),
264 available_flags: gather_flags(&spec.cmd),
265 flag_awaiting_value: vec![],
266 errors: vec![],
267 };
268
269 let mut prefix_words: Vec<String> = vec![];
282 let mut idx = 0;
283 let mut used_default_subcommand = false;
286
287 while idx < input.len() {
288 if let Some(subcommand) = out.cmd.find_subcommand(&input[idx]) {
289 let mut subcommand = subcommand.clone();
290 subcommand.mount(&prefix_words)?;
292 out.available_flags.retain(|_, f| f.global);
293 out.available_flags.extend(gather_flags(&subcommand));
294 input.remove(idx);
296 out.cmds.push(subcommand.clone());
297 out.cmd = subcommand.clone();
298 prefix_words.clear();
299 } else if input[idx].starts_with('-') {
302 let word = &input[idx];
304 let flag_key = get_flag_key(word);
305
306 if let Some(f) = out.available_flags.get(flag_key) {
307 if f.global {
309 prefix_words.push(input[idx].clone());
310 idx += 1;
311
312 if f.arg.is_some()
315 && !word.contains('=')
316 && idx < input.len()
317 && !input[idx].starts_with('-')
318 {
319 prefix_words.push(input[idx].clone());
320 idx += 1;
321 }
322 } else {
323 break;
327 }
328 } else {
329 break;
332 }
333 } else {
334 if !used_default_subcommand {
337 if let Some(default_name) = &spec.default_subcommand {
338 if let Some(subcommand) = out.cmd.find_subcommand(default_name) {
339 let mut subcommand = subcommand.clone();
340 subcommand.mount(&prefix_words)?;
342 out.available_flags.retain(|_, f| f.global);
343 out.available_flags.extend(gather_flags(&subcommand));
344 out.cmds.push(subcommand.clone());
345 out.cmd = subcommand.clone();
346 prefix_words.clear();
347 used_default_subcommand = true;
348 continue;
353 }
354 }
355 }
356 break;
358 }
359 }
360
361 let mut next_arg = out.cmd.args.first();
366 let mut enable_flags = true;
367 let mut grouped_flag = false;
368
369 while !input.is_empty() {
370 let mut w = input.pop_front().unwrap();
371
372 if let Some(ref restart_token) = out.cmd.restart_token {
375 if w == *restart_token {
376 out.args.clear();
378 next_arg = out.cmd.args.first();
379 out.flag_awaiting_value.clear(); enable_flags = true; continue;
383 }
384 }
385
386 if w == "--" {
387 enable_flags = false;
389
390 let should_preserve = next_arg
393 .map(|arg| arg.var && arg.double_dash == SpecDoubleDashChoices::Preserve)
394 .unwrap_or(false);
395
396 if should_preserve {
397 } else {
399 continue;
401 }
402 }
403
404 if enable_flags && w.starts_with("--") {
406 grouped_flag = false;
407 let (word, val) = w.split_once('=').unwrap_or_else(|| (&w, ""));
408 if !val.is_empty() {
409 input.push_front(val.to_string());
410 }
411 if let Some(f) = out.available_flags.get(word) {
412 if f.arg.is_some() {
413 out.flag_awaiting_value.push(Arc::clone(f));
414 } else if f.count {
415 let arr = out
416 .flags
417 .entry(Arc::clone(f))
418 .or_insert_with(|| ParseValue::MultiBool(vec![]))
419 .try_as_multi_bool_mut()
420 .unwrap();
421 arr.push(true);
422 } else {
423 let negate = f.negate.clone().unwrap_or_default();
424 out.flags
425 .insert(Arc::clone(f), ParseValue::Bool(w != negate));
426 }
427 continue;
428 }
429 if is_help_arg(spec, &w) {
430 out.errors
431 .push(render_help_err(spec, &out.cmd, w.len() > 2));
432 return Ok(out);
433 }
434 }
435
436 if enable_flags && w.starts_with('-') && w.len() > 1 {
438 let short = w.chars().nth(1).unwrap();
439 if let Some(f) = out.available_flags.get(&format!("-{short}")) {
440 if w.len() > 2 {
441 input.push_front(format!("-{}", &w[2..]));
442 grouped_flag = true;
443 }
444 if f.arg.is_some() {
445 out.flag_awaiting_value.push(Arc::clone(f));
446 } else if f.count {
447 let arr = out
448 .flags
449 .entry(Arc::clone(f))
450 .or_insert_with(|| ParseValue::MultiBool(vec![]))
451 .try_as_multi_bool_mut()
452 .unwrap();
453 arr.push(true);
454 } else {
455 let negate = f.negate.clone().unwrap_or_default();
456 out.flags
457 .insert(Arc::clone(f), ParseValue::Bool(w != negate));
458 }
459 continue;
460 }
461 if is_help_arg(spec, &w) {
462 out.errors
463 .push(render_help_err(spec, &out.cmd, w.len() > 2));
464 return Ok(out);
465 }
466 if grouped_flag {
467 grouped_flag = false;
468 w.remove(0);
469 }
470 }
471
472 if !out.flag_awaiting_value.is_empty() {
473 while let Some(flag) = out.flag_awaiting_value.pop() {
474 let arg = flag.arg.as_ref().unwrap();
475 if flag.var {
476 if let Some(choices) = &arg.choices {
477 if !choices.choices.contains(&w) {
478 if is_help_arg(spec, &w) {
479 out.errors
480 .push(render_help_err(spec, &out.cmd, w.len() > 2));
481 return Ok(out);
482 }
483 bail!(
484 "Invalid choice for option {}: {w}, expected one of {}",
485 flag.name,
486 choices.choices.join(", ")
487 );
488 }
489 }
490 let arr = out
491 .flags
492 .entry(flag)
493 .or_insert_with(|| ParseValue::MultiString(vec![]))
494 .try_as_multi_string_mut()
495 .unwrap();
496 arr.push(w);
497 } else {
498 if let Some(choices) = &arg.choices {
499 if !choices.choices.contains(&w) {
500 if is_help_arg(spec, &w) {
501 out.errors
502 .push(render_help_err(spec, &out.cmd, w.len() > 2));
503 return Ok(out);
504 }
505 bail!(
506 "Invalid choice for option {}: {w}, expected one of {}",
507 flag.name,
508 choices.choices.join(", ")
509 );
510 }
511 }
512 out.flags.insert(flag, ParseValue::String(w));
513 }
514 w = "".to_string();
515 }
516 continue;
517 }
518
519 if let Some(arg) = next_arg {
520 if arg.var {
521 if let Some(choices) = &arg.choices {
522 if !choices.choices.contains(&w) {
523 if is_help_arg(spec, &w) {
524 out.errors
525 .push(render_help_err(spec, &out.cmd, w.len() > 2));
526 return Ok(out);
527 }
528 bail!(
529 "Invalid choice for arg {}: {w}, expected one of {}",
530 arg.name,
531 choices.choices.join(", ")
532 );
533 }
534 }
535 let arr = out
536 .args
537 .entry(Arc::new(arg.clone()))
538 .or_insert_with(|| ParseValue::MultiString(vec![]))
539 .try_as_multi_string_mut()
540 .unwrap();
541 arr.push(w);
542 if arr.len() >= arg.var_max.unwrap_or(usize::MAX) {
543 next_arg = out.cmd.args.get(out.args.len());
544 }
545 } else {
546 if let Some(choices) = &arg.choices {
547 if !choices.choices.contains(&w) {
548 if is_help_arg(spec, &w) {
549 out.errors
550 .push(render_help_err(spec, &out.cmd, w.len() > 2));
551 return Ok(out);
552 }
553 bail!(
554 "Invalid choice for arg {}: {w}, expected one of {}",
555 arg.name,
556 choices.choices.join(", ")
557 );
558 }
559 }
560 out.args
561 .insert(Arc::new(arg.clone()), ParseValue::String(w));
562 next_arg = out.cmd.args.get(out.args.len());
563 }
564 continue;
565 }
566 if is_help_arg(spec, &w) {
567 out.errors
568 .push(render_help_err(spec, &out.cmd, w.len() > 2));
569 return Ok(out);
570 }
571 bail!("unexpected word: {w}");
572 }
573
574 for arg in out.cmd.args.iter().skip(out.args.len()) {
575 if arg.required && arg.default.is_empty() {
576 let has_env = arg.env.as_ref().is_some_and(|e| {
578 custom_env.map(|env| env.contains_key(e)).unwrap_or(false)
579 || std::env::var(e).is_ok()
580 });
581 if !has_env {
582 out.errors.push(UsageErr::MissingArg(arg.name.clone()));
583 }
584 }
585 }
586
587 for flag in out.available_flags.values() {
588 if out.flags.contains_key(flag) {
589 continue;
590 }
591 let has_default =
592 !flag.default.is_empty() || flag.arg.iter().any(|a| !a.default.is_empty());
593 let has_env = flag.env.as_ref().is_some_and(|e| {
595 custom_env.map(|env| env.contains_key(e)).unwrap_or(false) || std::env::var(e).is_ok()
596 });
597 if flag.required && !has_default && !has_env {
598 out.errors.push(UsageErr::MissingFlag(flag.name.clone()));
599 }
600 }
601
602 for (arg, value) in &out.args {
604 if arg.var {
605 if let ParseValue::MultiString(values) = value {
606 if let Some(min) = arg.var_min {
607 if values.len() < min {
608 out.errors.push(UsageErr::VarArgTooFew {
609 name: arg.name.clone(),
610 min,
611 got: values.len(),
612 });
613 }
614 }
615 if let Some(max) = arg.var_max {
616 if values.len() > max {
617 out.errors.push(UsageErr::VarArgTooMany {
618 name: arg.name.clone(),
619 max,
620 got: values.len(),
621 });
622 }
623 }
624 }
625 }
626 }
627
628 for (flag, value) in &out.flags {
630 if flag.var {
631 let count = match value {
632 ParseValue::MultiString(values) => values.len(),
633 ParseValue::MultiBool(values) => values.len(),
634 _ => continue,
635 };
636 if let Some(min) = flag.var_min {
637 if count < min {
638 out.errors.push(UsageErr::VarFlagTooFew {
639 name: flag.name.clone(),
640 min,
641 got: count,
642 });
643 }
644 }
645 if let Some(max) = flag.var_max {
646 if count > max {
647 out.errors.push(UsageErr::VarFlagTooMany {
648 name: flag.name.clone(),
649 max,
650 got: count,
651 });
652 }
653 }
654 }
655 }
656
657 Ok(out)
658}
659
660#[cfg(feature = "docs")]
661fn render_help_err(spec: &Spec, cmd: &SpecCommand, long: bool) -> UsageErr {
662 UsageErr::Help(docs::cli::render_help(spec, cmd, long))
663}
664
665#[cfg(not(feature = "docs"))]
666fn render_help_err(_spec: &Spec, _cmd: &SpecCommand, _long: bool) -> UsageErr {
667 UsageErr::Help("help".to_string())
668}
669
670fn is_help_arg(spec: &Spec, w: &str) -> bool {
671 spec.disable_help != Some(true)
672 && (w == "--help"
673 || w == "-h"
674 || w == "-?"
675 || (spec.cmd.subcommands.is_empty() && w == "help"))
676}
677
678impl ParseOutput {
679 pub fn as_env(&self) -> BTreeMap<String, String> {
680 let mut env = BTreeMap::new();
681 for (flag, val) in &self.flags {
682 let key = format!("usage_{}", flag.name.to_snake_case());
683 let val = match val {
684 ParseValue::Bool(b) => if *b { "true" } else { "false" }.to_string(),
685 ParseValue::String(s) => s.clone(),
686 ParseValue::MultiBool(b) => b.iter().filter(|b| **b).count().to_string(),
687 ParseValue::MultiString(s) => shell_words::join(s),
688 };
689 env.insert(key, val);
690 }
691 for (arg, val) in &self.args {
692 let key = format!("usage_{}", arg.name.to_snake_case());
693 env.insert(key, val.to_string());
694 }
695 env
696 }
697}
698
699impl Display for ParseValue {
700 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
701 match self {
702 ParseValue::Bool(b) => write!(f, "{b}"),
703 ParseValue::String(s) => write!(f, "{s}"),
704 ParseValue::MultiBool(b) => write!(f, "{}", b.iter().join(" ")),
705 ParseValue::MultiString(s) => write!(f, "{}", shell_words::join(s)),
706 }
707 }
708}
709
710impl Debug for ParseOutput {
711 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
712 f.debug_struct("ParseOutput")
713 .field("cmds", &self.cmds.iter().map(|c| &c.name).join(" ").trim())
714 .field(
715 "args",
716 &self
717 .args
718 .iter()
719 .map(|(a, w)| format!("{}: {w}", &a.name))
720 .collect_vec(),
721 )
722 .field(
723 "available_flags",
724 &self
725 .available_flags
726 .iter()
727 .map(|(f, w)| format!("{f}: {w}"))
728 .collect_vec(),
729 )
730 .field(
731 "flags",
732 &self
733 .flags
734 .iter()
735 .map(|(f, w)| format!("{}: {w}", &f.name))
736 .collect_vec(),
737 )
738 .field("flag_awaiting_value", &self.flag_awaiting_value)
739 .field("errors", &self.errors)
740 .finish()
741 }
742}
743
744#[cfg(test)]
745mod tests {
746 use super::*;
747
748 #[test]
749 fn test_parse() {
750 let cmd = SpecCommand::builder()
751 .name("test")
752 .arg(SpecArg::builder().name("arg").build())
753 .flag(SpecFlag::builder().long("flag").build())
754 .build();
755 let spec = Spec {
756 name: "test".to_string(),
757 bin: "test".to_string(),
758 cmd,
759 ..Default::default()
760 };
761 let input = vec!["test".to_string(), "arg1".to_string(), "--flag".to_string()];
762 let parsed = parse(&spec, &input).unwrap();
763 assert_eq!(parsed.cmds.len(), 1);
764 assert_eq!(parsed.cmds[0].name, "test");
765 assert_eq!(parsed.args.len(), 1);
766 assert_eq!(parsed.flags.len(), 1);
767 assert_eq!(parsed.available_flags.len(), 1);
768 }
769
770 #[test]
771 fn test_as_env() {
772 let cmd = SpecCommand::builder()
773 .name("test")
774 .arg(SpecArg::builder().name("arg").build())
775 .flag(SpecFlag::builder().long("flag").build())
776 .flag(
777 SpecFlag::builder()
778 .long("force")
779 .negate("--no-force")
780 .build(),
781 )
782 .build();
783 let spec = Spec {
784 name: "test".to_string(),
785 bin: "test".to_string(),
786 cmd,
787 ..Default::default()
788 };
789 let input = vec![
790 "test".to_string(),
791 "--flag".to_string(),
792 "--no-force".to_string(),
793 ];
794 let parsed = parse(&spec, &input).unwrap();
795 let env = parsed.as_env();
796 assert_eq!(env.len(), 2);
797 assert_eq!(env.get("usage_flag"), Some(&"true".to_string()));
798 assert_eq!(env.get("usage_force"), Some(&"false".to_string()));
799 }
800
801 #[test]
802 fn test_arg_env_var() {
803 let cmd = SpecCommand::builder()
804 .name("test")
805 .arg(
806 SpecArg::builder()
807 .name("input")
808 .env("TEST_ARG_INPUT")
809 .required(true)
810 .build(),
811 )
812 .build();
813 let spec = Spec {
814 name: "test".to_string(),
815 bin: "test".to_string(),
816 cmd,
817 ..Default::default()
818 };
819
820 std::env::set_var("TEST_ARG_INPUT", "test_file.txt");
822
823 let input = vec!["test".to_string()];
824 let parsed = parse(&spec, &input).unwrap();
825
826 assert_eq!(parsed.args.len(), 1);
827 let arg = parsed.args.keys().next().unwrap();
828 assert_eq!(arg.name, "input");
829 let value = parsed.args.values().next().unwrap();
830 assert_eq!(value.to_string(), "test_file.txt");
831
832 std::env::remove_var("TEST_ARG_INPUT");
834 }
835
836 #[test]
837 fn test_flag_env_var_with_arg() {
838 let cmd = SpecCommand::builder()
839 .name("test")
840 .flag(
841 SpecFlag::builder()
842 .long("output")
843 .env("TEST_FLAG_OUTPUT")
844 .arg(SpecArg::builder().name("file").build())
845 .build(),
846 )
847 .build();
848 let spec = Spec {
849 name: "test".to_string(),
850 bin: "test".to_string(),
851 cmd,
852 ..Default::default()
853 };
854
855 std::env::set_var("TEST_FLAG_OUTPUT", "output.txt");
857
858 let input = vec!["test".to_string()];
859 let parsed = parse(&spec, &input).unwrap();
860
861 assert_eq!(parsed.flags.len(), 1);
862 let flag = parsed.flags.keys().next().unwrap();
863 assert_eq!(flag.name, "output");
864 let value = parsed.flags.values().next().unwrap();
865 assert_eq!(value.to_string(), "output.txt");
866
867 std::env::remove_var("TEST_FLAG_OUTPUT");
869 }
870
871 #[test]
872 fn test_flag_env_var_boolean() {
873 let cmd = SpecCommand::builder()
874 .name("test")
875 .flag(
876 SpecFlag::builder()
877 .long("verbose")
878 .env("TEST_FLAG_VERBOSE")
879 .build(),
880 )
881 .build();
882 let spec = Spec {
883 name: "test".to_string(),
884 bin: "test".to_string(),
885 cmd,
886 ..Default::default()
887 };
888
889 std::env::set_var("TEST_FLAG_VERBOSE", "true");
891
892 let input = vec!["test".to_string()];
893 let parsed = parse(&spec, &input).unwrap();
894
895 assert_eq!(parsed.flags.len(), 1);
896 let flag = parsed.flags.keys().next().unwrap();
897 assert_eq!(flag.name, "verbose");
898 let value = parsed.flags.values().next().unwrap();
899 assert_eq!(value.to_string(), "true");
900
901 std::env::remove_var("TEST_FLAG_VERBOSE");
903 }
904
905 #[test]
906 fn test_env_var_precedence() {
907 let cmd = SpecCommand::builder()
909 .name("test")
910 .arg(
911 SpecArg::builder()
912 .name("input")
913 .env("TEST_PRECEDENCE_INPUT")
914 .required(true)
915 .build(),
916 )
917 .build();
918 let spec = Spec {
919 name: "test".to_string(),
920 bin: "test".to_string(),
921 cmd,
922 ..Default::default()
923 };
924
925 std::env::set_var("TEST_PRECEDENCE_INPUT", "env_file.txt");
927
928 let input = vec!["test".to_string(), "cli_file.txt".to_string()];
929 let parsed = parse(&spec, &input).unwrap();
930
931 assert_eq!(parsed.args.len(), 1);
932 let value = parsed.args.values().next().unwrap();
933 assert_eq!(value.to_string(), "cli_file.txt");
935
936 std::env::remove_var("TEST_PRECEDENCE_INPUT");
938 }
939
940 #[test]
941 fn test_flag_var_true_with_single_default() {
942 let cmd = SpecCommand::builder()
944 .name("test")
945 .flag(
946 SpecFlag::builder()
947 .long("foo")
948 .var(true)
949 .arg(SpecArg::builder().name("foo").build())
950 .default_value("bar")
951 .build(),
952 )
953 .build();
954 let spec = Spec {
955 name: "test".to_string(),
956 bin: "test".to_string(),
957 cmd,
958 ..Default::default()
959 };
960
961 let input = vec!["test".to_string()];
963 let parsed = parse(&spec, &input).unwrap();
964
965 assert_eq!(parsed.flags.len(), 1);
966 let flag = parsed.flags.keys().next().unwrap();
967 assert_eq!(flag.name, "foo");
968 let value = parsed.flags.values().next().unwrap();
969 match value {
971 ParseValue::MultiString(v) => {
972 assert_eq!(v.len(), 1);
973 assert_eq!(v[0], "bar");
974 }
975 _ => panic!("Expected MultiString, got {:?}", value),
976 }
977 }
978
979 #[test]
980 fn test_flag_var_true_with_multiple_defaults() {
981 let cmd = SpecCommand::builder()
983 .name("test")
984 .flag(
985 SpecFlag::builder()
986 .long("foo")
987 .var(true)
988 .arg(SpecArg::builder().name("foo").build())
989 .default_values(["xyz", "bar"])
990 .build(),
991 )
992 .build();
993 let spec = Spec {
994 name: "test".to_string(),
995 bin: "test".to_string(),
996 cmd,
997 ..Default::default()
998 };
999
1000 let input = vec!["test".to_string()];
1002 let parsed = parse(&spec, &input).unwrap();
1003
1004 assert_eq!(parsed.flags.len(), 1);
1005 let value = parsed.flags.values().next().unwrap();
1006 match value {
1008 ParseValue::MultiString(v) => {
1009 assert_eq!(v.len(), 2);
1010 assert_eq!(v[0], "xyz");
1011 assert_eq!(v[1], "bar");
1012 }
1013 _ => panic!("Expected MultiString, got {:?}", value),
1014 }
1015 }
1016
1017 #[test]
1018 fn test_flag_var_false_with_default_remains_string() {
1019 let cmd = SpecCommand::builder()
1021 .name("test")
1022 .flag(
1023 SpecFlag::builder()
1024 .long("foo")
1025 .var(false) .arg(SpecArg::builder().name("foo").build())
1027 .default_value("bar")
1028 .build(),
1029 )
1030 .build();
1031 let spec = Spec {
1032 name: "test".to_string(),
1033 bin: "test".to_string(),
1034 cmd,
1035 ..Default::default()
1036 };
1037
1038 let input = vec!["test".to_string()];
1040 let parsed = parse(&spec, &input).unwrap();
1041
1042 assert_eq!(parsed.flags.len(), 1);
1043 let value = parsed.flags.values().next().unwrap();
1044 match value {
1046 ParseValue::String(s) => {
1047 assert_eq!(s, "bar");
1048 }
1049 _ => panic!("Expected String, got {:?}", value),
1050 }
1051 }
1052
1053 #[test]
1054 fn test_arg_var_true_with_single_default() {
1055 let cmd = SpecCommand::builder()
1057 .name("test")
1058 .arg(
1059 SpecArg::builder()
1060 .name("files")
1061 .var(true)
1062 .default_value("default.txt")
1063 .required(false)
1064 .build(),
1065 )
1066 .build();
1067 let spec = Spec {
1068 name: "test".to_string(),
1069 bin: "test".to_string(),
1070 cmd,
1071 ..Default::default()
1072 };
1073
1074 let input = vec!["test".to_string()];
1076 let parsed = parse(&spec, &input).unwrap();
1077
1078 assert_eq!(parsed.args.len(), 1);
1079 let value = parsed.args.values().next().unwrap();
1080 match value {
1082 ParseValue::MultiString(v) => {
1083 assert_eq!(v.len(), 1);
1084 assert_eq!(v[0], "default.txt");
1085 }
1086 _ => panic!("Expected MultiString, got {:?}", value),
1087 }
1088 }
1089
1090 #[test]
1091 fn test_arg_var_true_with_multiple_defaults() {
1092 let cmd = SpecCommand::builder()
1094 .name("test")
1095 .arg(
1096 SpecArg::builder()
1097 .name("files")
1098 .var(true)
1099 .default_values(["file1.txt", "file2.txt"])
1100 .required(false)
1101 .build(),
1102 )
1103 .build();
1104 let spec = Spec {
1105 name: "test".to_string(),
1106 bin: "test".to_string(),
1107 cmd,
1108 ..Default::default()
1109 };
1110
1111 let input = vec!["test".to_string()];
1113 let parsed = parse(&spec, &input).unwrap();
1114
1115 assert_eq!(parsed.args.len(), 1);
1116 let value = parsed.args.values().next().unwrap();
1117 match value {
1119 ParseValue::MultiString(v) => {
1120 assert_eq!(v.len(), 2);
1121 assert_eq!(v[0], "file1.txt");
1122 assert_eq!(v[1], "file2.txt");
1123 }
1124 _ => panic!("Expected MultiString, got {:?}", value),
1125 }
1126 }
1127
1128 #[test]
1129 fn test_arg_var_false_with_default_remains_string() {
1130 let cmd = SpecCommand::builder()
1132 .name("test")
1133 .arg(
1134 SpecArg::builder()
1135 .name("file")
1136 .var(false)
1137 .default_value("default.txt")
1138 .required(false)
1139 .build(),
1140 )
1141 .build();
1142 let spec = Spec {
1143 name: "test".to_string(),
1144 bin: "test".to_string(),
1145 cmd,
1146 ..Default::default()
1147 };
1148
1149 let input = vec!["test".to_string()];
1151 let parsed = parse(&spec, &input).unwrap();
1152
1153 assert_eq!(parsed.args.len(), 1);
1154 let value = parsed.args.values().next().unwrap();
1155 match value {
1157 ParseValue::String(s) => {
1158 assert_eq!(s, "default.txt");
1159 }
1160 _ => panic!("Expected String, got {:?}", value),
1161 }
1162 }
1163
1164 #[test]
1165 fn test_default_subcommand() {
1166 let run_cmd = SpecCommand::builder()
1168 .name("run")
1169 .arg(SpecArg::builder().name("task").build())
1170 .build();
1171 let mut cmd = SpecCommand::builder().name("test").build();
1172 cmd.subcommands.insert("run".to_string(), run_cmd);
1173
1174 let spec = Spec {
1175 name: "test".to_string(),
1176 bin: "test".to_string(),
1177 cmd,
1178 default_subcommand: Some("run".to_string()),
1179 ..Default::default()
1180 };
1181
1182 let input = vec!["test".to_string(), "mytask".to_string()];
1184 let parsed = parse(&spec, &input).unwrap();
1185
1186 assert_eq!(parsed.cmds.len(), 2);
1188 assert_eq!(parsed.cmds[1].name, "run");
1189
1190 assert_eq!(parsed.args.len(), 1);
1192 let arg = parsed.args.keys().next().unwrap();
1193 assert_eq!(arg.name, "task");
1194 let value = parsed.args.values().next().unwrap();
1195 assert_eq!(value.to_string(), "mytask");
1196 }
1197
1198 #[test]
1199 fn test_default_subcommand_explicit_still_works() {
1200 let run_cmd = SpecCommand::builder()
1202 .name("run")
1203 .arg(SpecArg::builder().name("task").build())
1204 .build();
1205 let other_cmd = SpecCommand::builder()
1206 .name("other")
1207 .arg(SpecArg::builder().name("other_arg").build())
1208 .build();
1209 let mut cmd = SpecCommand::builder().name("test").build();
1210 cmd.subcommands.insert("run".to_string(), run_cmd);
1211 cmd.subcommands.insert("other".to_string(), other_cmd);
1212
1213 let spec = Spec {
1214 name: "test".to_string(),
1215 bin: "test".to_string(),
1216 cmd,
1217 default_subcommand: Some("run".to_string()),
1218 ..Default::default()
1219 };
1220
1221 let input = vec!["test".to_string(), "other".to_string(), "foo".to_string()];
1223 let parsed = parse(&spec, &input).unwrap();
1224
1225 assert_eq!(parsed.cmds.len(), 2);
1227 assert_eq!(parsed.cmds[1].name, "other");
1228 }
1229
1230 #[test]
1231 fn test_default_subcommand_with_nested_subcommands() {
1232 let say_cmd = SpecCommand::builder()
1236 .name("say")
1237 .arg(SpecArg::builder().name("name").build())
1238 .build();
1239 let mut run_cmd = SpecCommand::builder().name("run").build();
1240 run_cmd.subcommands.insert("say".to_string(), say_cmd);
1241
1242 let mut cmd = SpecCommand::builder().name("test").build();
1243 cmd.subcommands.insert("run".to_string(), run_cmd);
1244
1245 let spec = Spec {
1246 name: "test".to_string(),
1247 bin: "test".to_string(),
1248 cmd,
1249 default_subcommand: Some("run".to_string()),
1250 ..Default::default()
1251 };
1252
1253 let input = vec!["test".to_string(), "say".to_string(), "hello".to_string()];
1255 let parsed = parse(&spec, &input).unwrap();
1256
1257 assert_eq!(parsed.cmds.len(), 3);
1259 assert_eq!(parsed.cmds[0].name, "test");
1260 assert_eq!(parsed.cmds[1].name, "run");
1261 assert_eq!(parsed.cmds[2].name, "say");
1262
1263 assert_eq!(parsed.args.len(), 1);
1265 let arg = parsed.args.keys().next().unwrap();
1266 assert_eq!(arg.name, "name");
1267 let value = parsed.args.values().next().unwrap();
1268 assert_eq!(value.to_string(), "hello");
1269 }
1270
1271 #[test]
1272 fn test_default_subcommand_same_name_child() {
1273 let run_task = SpecCommand::builder()
1277 .name("run")
1278 .arg(SpecArg::builder().name("args").build())
1279 .build();
1280 let mut run_cmd = SpecCommand::builder().name("run").build();
1281 run_cmd.subcommands.insert("run".to_string(), run_task);
1282
1283 let mut cmd = SpecCommand::builder().name("test").build();
1284 cmd.subcommands.insert("run".to_string(), run_cmd);
1285
1286 let spec = Spec {
1287 name: "test".to_string(),
1288 bin: "test".to_string(),
1289 cmd,
1290 default_subcommand: Some("run".to_string()),
1291 ..Default::default()
1292 };
1293
1294 let input = vec!["test".to_string(), "run".to_string()];
1296 let parsed = parse(&spec, &input).unwrap();
1297
1298 assert_eq!(parsed.cmds.len(), 2);
1300 assert_eq!(parsed.cmds[0].name, "test");
1301 assert_eq!(parsed.cmds[1].name, "run");
1302
1303 let input = vec![
1305 "test".to_string(),
1306 "run".to_string(),
1307 "run".to_string(),
1308 "hello".to_string(),
1309 ];
1310 let parsed = parse(&spec, &input).unwrap();
1311
1312 assert_eq!(parsed.cmds.len(), 3);
1313 assert_eq!(parsed.cmds[0].name, "test");
1314 assert_eq!(parsed.cmds[1].name, "run");
1315 assert_eq!(parsed.cmds[2].name, "run");
1316 assert_eq!(parsed.args.len(), 1);
1317 let value = parsed.args.values().next().unwrap();
1318 assert_eq!(value.to_string(), "hello");
1319
1320 let mut run_cmd = SpecCommand::builder()
1324 .name("run")
1325 .arg(SpecArg::builder().name("task").build())
1326 .build();
1327 let run_task = SpecCommand::builder().name("run").build();
1328 run_cmd.subcommands.insert("run".to_string(), run_task);
1329
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_subcommand: Some("run".to_string()),
1338 ..Default::default()
1339 };
1340
1341 let input = vec!["test".to_string(), "other".to_string()];
1342 let parsed = parse(&spec, &input).unwrap();
1343
1344 assert_eq!(parsed.cmds.len(), 2);
1347 assert_eq!(parsed.cmds[0].name, "test");
1348 assert_eq!(parsed.cmds[1].name, "run");
1349
1350 assert_eq!(parsed.args.len(), 1);
1352 let value = parsed.args.values().next().unwrap();
1353 assert_eq!(value.to_string(), "other");
1354 }
1355
1356 #[test]
1357 fn test_restart_token() {
1358 let run_cmd = SpecCommand::builder()
1360 .name("run")
1361 .arg(SpecArg::builder().name("task").build())
1362 .restart_token(":::".to_string())
1363 .build();
1364 let mut cmd = SpecCommand::builder().name("test").build();
1365 cmd.subcommands.insert("run".to_string(), run_cmd);
1366
1367 let spec = Spec {
1368 name: "test".to_string(),
1369 bin: "test".to_string(),
1370 cmd,
1371 ..Default::default()
1372 };
1373
1374 let input = vec![
1376 "test".to_string(),
1377 "run".to_string(),
1378 "task1".to_string(),
1379 ":::".to_string(),
1380 "task2".to_string(),
1381 ];
1382 let parsed = parse(&spec, &input).unwrap();
1383
1384 assert_eq!(parsed.args.len(), 1);
1386 let value = parsed.args.values().next().unwrap();
1387 assert_eq!(value.to_string(), "task2");
1388 }
1389
1390 #[test]
1391 fn test_restart_token_multiple() {
1392 let run_cmd = SpecCommand::builder()
1394 .name("run")
1395 .arg(SpecArg::builder().name("task").build())
1396 .restart_token(":::".to_string())
1397 .build();
1398 let mut cmd = SpecCommand::builder().name("test").build();
1399 cmd.subcommands.insert("run".to_string(), run_cmd);
1400
1401 let spec = Spec {
1402 name: "test".to_string(),
1403 bin: "test".to_string(),
1404 cmd,
1405 ..Default::default()
1406 };
1407
1408 let input = vec![
1410 "test".to_string(),
1411 "run".to_string(),
1412 "task1".to_string(),
1413 ":::".to_string(),
1414 "task2".to_string(),
1415 ":::".to_string(),
1416 "task3".to_string(),
1417 ];
1418 let parsed = parse(&spec, &input).unwrap();
1419
1420 assert_eq!(parsed.args.len(), 1);
1422 let value = parsed.args.values().next().unwrap();
1423 assert_eq!(value.to_string(), "task3");
1424 }
1425
1426 #[test]
1427 fn test_restart_token_clears_flag_awaiting_value() {
1428 let run_cmd = SpecCommand::builder()
1430 .name("run")
1431 .arg(SpecArg::builder().name("task").build())
1432 .flag(
1433 SpecFlag::builder()
1434 .name("jobs")
1435 .long("jobs")
1436 .arg(SpecArg::builder().name("count").build())
1437 .build(),
1438 )
1439 .restart_token(":::".to_string())
1440 .build();
1441 let mut cmd = SpecCommand::builder().name("test").build();
1442 cmd.subcommands.insert("run".to_string(), run_cmd);
1443
1444 let spec = Spec {
1445 name: "test".to_string(),
1446 bin: "test".to_string(),
1447 cmd,
1448 ..Default::default()
1449 };
1450
1451 let input = vec![
1453 "test".to_string(),
1454 "run".to_string(),
1455 "task1".to_string(),
1456 "--jobs".to_string(),
1457 ":::".to_string(),
1458 "task2".to_string(),
1459 ];
1460 let parsed = parse(&spec, &input).unwrap();
1461
1462 assert_eq!(parsed.args.len(), 1);
1464 let value = parsed.args.values().next().unwrap();
1465 assert_eq!(value.to_string(), "task2");
1466 assert!(parsed.flag_awaiting_value.is_empty());
1468 }
1469
1470 #[test]
1471 fn test_restart_token_resets_double_dash() {
1472 let run_cmd = SpecCommand::builder()
1474 .name("run")
1475 .arg(SpecArg::builder().name("task").build())
1476 .arg(SpecArg::builder().name("extra_args").var(true).build())
1477 .flag(SpecFlag::builder().name("verbose").long("verbose").build())
1478 .restart_token(":::".to_string())
1479 .build();
1480 let mut cmd = SpecCommand::builder().name("test").build();
1481 cmd.subcommands.insert("run".to_string(), run_cmd);
1482
1483 let spec = Spec {
1484 name: "test".to_string(),
1485 bin: "test".to_string(),
1486 cmd,
1487 ..Default::default()
1488 };
1489
1490 let input = vec![
1492 "test".to_string(),
1493 "run".to_string(),
1494 "task1".to_string(),
1495 "--".to_string(),
1496 "extra".to_string(),
1497 ":::".to_string(),
1498 "--verbose".to_string(),
1499 "task2".to_string(),
1500 ];
1501 let parsed = parse(&spec, &input).unwrap();
1502
1503 assert!(parsed.flags.keys().any(|f| f.name == "verbose"));
1505 let task_arg = parsed.args.keys().find(|a| a.name == "task").unwrap();
1507 let value = parsed.args.get(task_arg).unwrap();
1508 assert_eq!(value.to_string(), "task2");
1509 }
1510
1511 #[test]
1512 fn test_double_dashes_without_preserve() {
1513 let run_cmd = SpecCommand::builder()
1515 .name("run")
1516 .arg(SpecArg::builder().name("args").var(true).build())
1517 .build();
1518 let mut cmd = SpecCommand::builder().name("test").build();
1519 cmd.subcommands.insert("run".to_string(), run_cmd);
1520
1521 let spec = Spec {
1522 name: "test".to_string(),
1523 bin: "test".to_string(),
1524 cmd,
1525 ..Default::default()
1526 };
1527
1528 let input = vec![
1530 "test".to_string(),
1531 "run".to_string(),
1532 "arg1".to_string(),
1533 "--".to_string(),
1534 "arg2".to_string(),
1535 "--".to_string(),
1536 "arg3".to_string(),
1537 ];
1538 let parsed = parse(&spec, &input).unwrap();
1539
1540 let args_arg = parsed.args.keys().find(|a| a.name == "args").unwrap();
1541 let value = parsed.args.get(args_arg).unwrap();
1542 assert_eq!(value.to_string(), "arg1 arg2 arg3");
1543 }
1544
1545 #[test]
1546 fn test_double_dashes_with_preserve() {
1547 let run_cmd = SpecCommand::builder()
1549 .name("run")
1550 .arg(
1551 SpecArg::builder()
1552 .name("args")
1553 .var(true)
1554 .double_dash(SpecDoubleDashChoices::Preserve)
1555 .build(),
1556 )
1557 .build();
1558 let mut cmd = SpecCommand::builder().name("test").build();
1559 cmd.subcommands.insert("run".to_string(), run_cmd);
1560
1561 let spec = Spec {
1562 name: "test".to_string(),
1563 bin: "test".to_string(),
1564 cmd,
1565 ..Default::default()
1566 };
1567
1568 let input = vec![
1570 "test".to_string(),
1571 "run".to_string(),
1572 "arg1".to_string(),
1573 "--".to_string(),
1574 "arg2".to_string(),
1575 "--".to_string(),
1576 "arg3".to_string(),
1577 ];
1578 let parsed = parse(&spec, &input).unwrap();
1579
1580 let args_arg = parsed.args.keys().find(|a| a.name == "args").unwrap();
1581 let value = parsed.args.get(args_arg).unwrap();
1582 assert_eq!(value.to_string(), "arg1 -- arg2 -- arg3");
1583 }
1584
1585 #[test]
1586 fn test_double_dashes_with_preserve_only_dashes() {
1587 let run_cmd = SpecCommand::builder()
1590 .name("run")
1591 .arg(
1592 SpecArg::builder()
1593 .name("args")
1594 .var(true)
1595 .double_dash(SpecDoubleDashChoices::Preserve)
1596 .build(),
1597 )
1598 .build();
1599 let mut cmd = SpecCommand::builder().name("test").build();
1600 cmd.subcommands.insert("run".to_string(), run_cmd);
1601
1602 let spec = Spec {
1603 name: "test".to_string(),
1604 bin: "test".to_string(),
1605 cmd,
1606 ..Default::default()
1607 };
1608
1609 let input = vec![
1611 "test".to_string(),
1612 "run".to_string(),
1613 "--".to_string(),
1614 "--".to_string(),
1615 ];
1616 let parsed = parse(&spec, &input).unwrap();
1617
1618 let args_arg = parsed.args.keys().find(|a| a.name == "args").unwrap();
1619 let value = parsed.args.get(args_arg).unwrap();
1620 assert_eq!(value.to_string(), "-- --");
1621 }
1622
1623 #[test]
1624 fn test_double_dashes_with_preserve_multiple_args() {
1625 let run_cmd = SpecCommand::builder()
1627 .name("run")
1628 .arg(SpecArg::builder().name("task").build())
1629 .arg(
1630 SpecArg::builder()
1631 .name("extra_args")
1632 .var(true)
1633 .double_dash(SpecDoubleDashChoices::Preserve)
1634 .build(),
1635 )
1636 .build();
1637 let mut cmd = SpecCommand::builder().name("test").build();
1638 cmd.subcommands.insert("run".to_string(), run_cmd);
1639
1640 let spec = Spec {
1641 name: "test".to_string(),
1642 bin: "test".to_string(),
1643 cmd,
1644 ..Default::default()
1645 };
1646
1647 let input = vec![
1650 "test".to_string(),
1651 "run".to_string(),
1652 "task1".to_string(),
1653 "--".to_string(),
1654 "arg1".to_string(),
1655 "--".to_string(),
1656 "--foo".to_string(),
1657 ];
1658 let parsed = parse(&spec, &input).unwrap();
1659
1660 let task_arg = parsed.args.keys().find(|a| a.name == "task").unwrap();
1661 let task_value = parsed.args.get(task_arg).unwrap();
1662 assert_eq!(task_value.to_string(), "task1");
1663
1664 let extra_arg = parsed.args.keys().find(|a| a.name == "extra_args").unwrap();
1665 let extra_value = parsed.args.get(extra_arg).unwrap();
1666 assert_eq!(extra_value.to_string(), "-- arg1 -- --foo");
1667 }
1668
1669 #[test]
1670 fn test_parser_with_custom_env_for_required_arg() {
1671 let cmd = SpecCommand::builder()
1674 .name("test")
1675 .arg(
1676 SpecArg::builder()
1677 .name("name")
1678 .env("NAME")
1679 .required(true)
1680 .build(),
1681 )
1682 .build();
1683 let spec = Spec {
1684 name: "test".to_string(),
1685 bin: "test".to_string(),
1686 cmd,
1687 ..Default::default()
1688 };
1689
1690 std::env::remove_var("NAME");
1692
1693 let mut env = HashMap::new();
1695 env.insert("NAME".to_string(), "john".to_string());
1696
1697 let input = vec!["test".to_string()];
1698 let result = Parser::new(&spec).with_env(env).parse(&input);
1699
1700 let parsed = result.expect("parse should succeed with custom env");
1702 assert_eq!(parsed.args.len(), 1);
1703 let value = parsed.args.values().next().unwrap();
1704 assert_eq!(value.to_string(), "john");
1705 }
1706
1707 #[test]
1708 fn test_parser_with_custom_env_for_required_flag() {
1709 let cmd = SpecCommand::builder()
1711 .name("test")
1712 .flag(
1713 SpecFlag::builder()
1714 .long("name")
1715 .env("NAME")
1716 .required(true)
1717 .arg(SpecArg::builder().name("name").build())
1718 .build(),
1719 )
1720 .build();
1721 let spec = Spec {
1722 name: "test".to_string(),
1723 bin: "test".to_string(),
1724 cmd,
1725 ..Default::default()
1726 };
1727
1728 std::env::remove_var("NAME");
1730
1731 let mut env = HashMap::new();
1733 env.insert("NAME".to_string(), "jane".to_string());
1734
1735 let input = vec!["test".to_string()];
1736 let result = Parser::new(&spec).with_env(env).parse(&input);
1737
1738 let parsed = result.expect("parse should succeed with custom env");
1740 assert_eq!(parsed.flags.len(), 1);
1741 let value = parsed.flags.values().next().unwrap();
1742 assert_eq!(value.to_string(), "jane");
1743 }
1744
1745 #[test]
1746 fn test_parser_with_custom_env_still_fails_when_missing() {
1747 let cmd = SpecCommand::builder()
1749 .name("test")
1750 .arg(
1751 SpecArg::builder()
1752 .name("name")
1753 .env("NAME")
1754 .required(true)
1755 .build(),
1756 )
1757 .build();
1758 let spec = Spec {
1759 name: "test".to_string(),
1760 bin: "test".to_string(),
1761 cmd,
1762 ..Default::default()
1763 };
1764
1765 std::env::remove_var("NAME");
1767
1768 let env = HashMap::new();
1770
1771 let input = vec!["test".to_string()];
1772 let result = Parser::new(&spec).with_env(env).parse(&input);
1773
1774 assert!(result.is_err());
1776 }
1777
1778 #[test]
1779 fn test_variadic_arg_captures_unknown_flags_from_spec_string() {
1780 let spec: Spec = r#"
1781 flag "-v --verbose" var=#true
1782 arg "[database]" default="myapp_dev"
1783 arg "[args...]"
1784 "#
1785 .parse()
1786 .unwrap();
1787 let input: Vec<String> = vec!["test", "mydb", "--host", "localhost"]
1788 .into_iter()
1789 .map(String::from)
1790 .collect();
1791 let parsed = parse(&spec, &input).unwrap();
1792 let env = parsed.as_env();
1793 assert_eq!(env.get("usage_database").unwrap(), "mydb");
1794 assert_eq!(env.get("usage_args").unwrap(), "--host localhost");
1795 }
1796
1797 #[test]
1798 fn test_variadic_arg_captures_unknown_flags() {
1799 let cmd = SpecCommand::builder()
1800 .name("test")
1801 .flag(SpecFlag::builder().short('v').long("verbose").build())
1802 .arg(SpecArg::builder().name("database").required(false).build())
1803 .arg(
1804 SpecArg::builder()
1805 .name("args")
1806 .required(false)
1807 .var(true)
1808 .build(),
1809 )
1810 .build();
1811 let spec = Spec {
1812 name: "test".to_string(),
1813 bin: "test".to_string(),
1814 cmd,
1815 ..Default::default()
1816 };
1817
1818 let input: Vec<String> = vec!["test", "mydb", "--host", "localhost"]
1820 .into_iter()
1821 .map(String::from)
1822 .collect();
1823 let parsed = parse(&spec, &input).unwrap();
1824 assert_eq!(parsed.args.len(), 2);
1825 let args_val = parsed
1826 .args
1827 .iter()
1828 .find(|(a, _)| a.name == "args")
1829 .unwrap()
1830 .1;
1831 match args_val {
1832 ParseValue::MultiString(v) => {
1833 assert_eq!(v, &vec!["--host".to_string(), "localhost".to_string()]);
1834 }
1835 _ => panic!("Expected MultiString, got {:?}", args_val),
1836 }
1837 }
1838
1839 #[test]
1840 fn test_variadic_arg_captures_unknown_flags_with_double_dash() {
1841 let cmd = SpecCommand::builder()
1842 .name("test")
1843 .flag(SpecFlag::builder().short('v').long("verbose").build())
1844 .arg(SpecArg::builder().name("database").required(false).build())
1845 .arg(
1846 SpecArg::builder()
1847 .name("args")
1848 .required(false)
1849 .var(true)
1850 .build(),
1851 )
1852 .build();
1853 let spec = Spec {
1854 name: "test".to_string(),
1855 bin: "test".to_string(),
1856 cmd,
1857 ..Default::default()
1858 };
1859
1860 let input: Vec<String> = vec!["test", "--", "mydb", "--host", "localhost"]
1862 .into_iter()
1863 .map(String::from)
1864 .collect();
1865 let parsed = parse(&spec, &input).unwrap();
1866 assert_eq!(parsed.args.len(), 2);
1867 let args_val = parsed
1868 .args
1869 .iter()
1870 .find(|(a, _)| a.name == "args")
1871 .unwrap()
1872 .1;
1873 match args_val {
1874 ParseValue::MultiString(v) => {
1875 assert_eq!(v, &vec!["--host".to_string(), "localhost".to_string()]);
1876 }
1877 _ => panic!("Expected MultiString, got {:?}", args_val),
1878 }
1879 }
1880}