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 let arr = out
477 .flags
478 .entry(flag)
479 .or_insert_with(|| ParseValue::MultiString(vec![]))
480 .try_as_multi_string_mut()
481 .unwrap();
482 arr.push(w);
483 } else {
484 if let Some(choices) = &arg.choices {
485 if !choices.choices.contains(&w) {
486 if is_help_arg(spec, &w) {
487 out.errors
488 .push(render_help_err(spec, &out.cmd, w.len() > 2));
489 return Ok(out);
490 }
491 bail!(
492 "Invalid choice for option {}: {w}, expected one of {}",
493 flag.name,
494 choices.choices.join(", ")
495 );
496 }
497 }
498 out.flags.insert(flag, ParseValue::String(w));
499 }
500 w = "".to_string();
501 }
502 continue;
503 }
504
505 if let Some(arg) = next_arg {
506 if arg.var {
507 let arr = out
508 .args
509 .entry(Arc::new(arg.clone()))
510 .or_insert_with(|| ParseValue::MultiString(vec![]))
511 .try_as_multi_string_mut()
512 .unwrap();
513 arr.push(w);
514 if arr.len() >= arg.var_max.unwrap_or(usize::MAX) {
515 next_arg = out.cmd.args.get(out.args.len());
516 }
517 } else {
518 if let Some(choices) = &arg.choices {
519 if !choices.choices.contains(&w) {
520 if is_help_arg(spec, &w) {
521 out.errors
522 .push(render_help_err(spec, &out.cmd, w.len() > 2));
523 return Ok(out);
524 }
525 bail!(
526 "Invalid choice for arg {}: {w}, expected one of {}",
527 arg.name,
528 choices.choices.join(", ")
529 );
530 }
531 }
532 out.args
533 .insert(Arc::new(arg.clone()), ParseValue::String(w));
534 next_arg = out.cmd.args.get(out.args.len());
535 }
536 continue;
537 }
538 if is_help_arg(spec, &w) {
539 out.errors
540 .push(render_help_err(spec, &out.cmd, w.len() > 2));
541 return Ok(out);
542 }
543 bail!("unexpected word: {w}");
544 }
545
546 for arg in out.cmd.args.iter().skip(out.args.len()) {
547 if arg.required && arg.default.is_empty() {
548 let has_env = arg.env.as_ref().is_some_and(|e| {
550 custom_env.map(|env| env.contains_key(e)).unwrap_or(false)
551 || std::env::var(e).is_ok()
552 });
553 if !has_env {
554 out.errors.push(UsageErr::MissingArg(arg.name.clone()));
555 }
556 }
557 }
558
559 for flag in out.available_flags.values() {
560 if out.flags.contains_key(flag) {
561 continue;
562 }
563 let has_default =
564 !flag.default.is_empty() || flag.arg.iter().any(|a| !a.default.is_empty());
565 let has_env = flag.env.as_ref().is_some_and(|e| {
567 custom_env.map(|env| env.contains_key(e)).unwrap_or(false) || std::env::var(e).is_ok()
568 });
569 if flag.required && !has_default && !has_env {
570 out.errors.push(UsageErr::MissingFlag(flag.name.clone()));
571 }
572 }
573
574 for (arg, value) in &out.args {
576 if arg.var {
577 if let ParseValue::MultiString(values) = value {
578 if let Some(min) = arg.var_min {
579 if values.len() < min {
580 out.errors.push(UsageErr::VarArgTooFew {
581 name: arg.name.clone(),
582 min,
583 got: values.len(),
584 });
585 }
586 }
587 if let Some(max) = arg.var_max {
588 if values.len() > max {
589 out.errors.push(UsageErr::VarArgTooMany {
590 name: arg.name.clone(),
591 max,
592 got: values.len(),
593 });
594 }
595 }
596 }
597 }
598 }
599
600 for (flag, value) in &out.flags {
602 if flag.var {
603 let count = match value {
604 ParseValue::MultiString(values) => values.len(),
605 ParseValue::MultiBool(values) => values.len(),
606 _ => continue,
607 };
608 if let Some(min) = flag.var_min {
609 if count < min {
610 out.errors.push(UsageErr::VarFlagTooFew {
611 name: flag.name.clone(),
612 min,
613 got: count,
614 });
615 }
616 }
617 if let Some(max) = flag.var_max {
618 if count > max {
619 out.errors.push(UsageErr::VarFlagTooMany {
620 name: flag.name.clone(),
621 max,
622 got: count,
623 });
624 }
625 }
626 }
627 }
628
629 Ok(out)
630}
631
632#[cfg(feature = "docs")]
633fn render_help_err(spec: &Spec, cmd: &SpecCommand, long: bool) -> UsageErr {
634 UsageErr::Help(docs::cli::render_help(spec, cmd, long))
635}
636
637#[cfg(not(feature = "docs"))]
638fn render_help_err(_spec: &Spec, _cmd: &SpecCommand, _long: bool) -> UsageErr {
639 UsageErr::Help("help".to_string())
640}
641
642fn is_help_arg(spec: &Spec, w: &str) -> bool {
643 spec.disable_help != Some(true)
644 && (w == "--help"
645 || w == "-h"
646 || w == "-?"
647 || (spec.cmd.subcommands.is_empty() && w == "help"))
648}
649
650impl ParseOutput {
651 pub fn as_env(&self) -> BTreeMap<String, String> {
652 let mut env = BTreeMap::new();
653 for (flag, val) in &self.flags {
654 let key = format!("usage_{}", flag.name.to_snake_case());
655 let val = match val {
656 ParseValue::Bool(b) => if *b { "true" } else { "false" }.to_string(),
657 ParseValue::String(s) => s.clone(),
658 ParseValue::MultiBool(b) => b.iter().filter(|b| **b).count().to_string(),
659 ParseValue::MultiString(s) => shell_words::join(s),
660 };
661 env.insert(key, val);
662 }
663 for (arg, val) in &self.args {
664 let key = format!("usage_{}", arg.name.to_snake_case());
665 env.insert(key, val.to_string());
666 }
667 env
668 }
669}
670
671impl Display for ParseValue {
672 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
673 match self {
674 ParseValue::Bool(b) => write!(f, "{b}"),
675 ParseValue::String(s) => write!(f, "{s}"),
676 ParseValue::MultiBool(b) => write!(f, "{}", b.iter().join(" ")),
677 ParseValue::MultiString(s) => write!(f, "{}", shell_words::join(s)),
678 }
679 }
680}
681
682impl Debug for ParseOutput {
683 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
684 f.debug_struct("ParseOutput")
685 .field("cmds", &self.cmds.iter().map(|c| &c.name).join(" ").trim())
686 .field(
687 "args",
688 &self
689 .args
690 .iter()
691 .map(|(a, w)| format!("{}: {w}", &a.name))
692 .collect_vec(),
693 )
694 .field(
695 "available_flags",
696 &self
697 .available_flags
698 .iter()
699 .map(|(f, w)| format!("{f}: {w}"))
700 .collect_vec(),
701 )
702 .field(
703 "flags",
704 &self
705 .flags
706 .iter()
707 .map(|(f, w)| format!("{}: {w}", &f.name))
708 .collect_vec(),
709 )
710 .field("flag_awaiting_value", &self.flag_awaiting_value)
711 .field("errors", &self.errors)
712 .finish()
713 }
714}
715
716#[cfg(test)]
717mod tests {
718 use super::*;
719
720 #[test]
721 fn test_parse() {
722 let cmd = SpecCommand::builder()
723 .name("test")
724 .arg(SpecArg::builder().name("arg").build())
725 .flag(SpecFlag::builder().long("flag").build())
726 .build();
727 let spec = Spec {
728 name: "test".to_string(),
729 bin: "test".to_string(),
730 cmd,
731 ..Default::default()
732 };
733 let input = vec!["test".to_string(), "arg1".to_string(), "--flag".to_string()];
734 let parsed = parse(&spec, &input).unwrap();
735 assert_eq!(parsed.cmds.len(), 1);
736 assert_eq!(parsed.cmds[0].name, "test");
737 assert_eq!(parsed.args.len(), 1);
738 assert_eq!(parsed.flags.len(), 1);
739 assert_eq!(parsed.available_flags.len(), 1);
740 }
741
742 #[test]
743 fn test_as_env() {
744 let cmd = SpecCommand::builder()
745 .name("test")
746 .arg(SpecArg::builder().name("arg").build())
747 .flag(SpecFlag::builder().long("flag").build())
748 .flag(
749 SpecFlag::builder()
750 .long("force")
751 .negate("--no-force")
752 .build(),
753 )
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![
762 "test".to_string(),
763 "--flag".to_string(),
764 "--no-force".to_string(),
765 ];
766 let parsed = parse(&spec, &input).unwrap();
767 let env = parsed.as_env();
768 assert_eq!(env.len(), 2);
769 assert_eq!(env.get("usage_flag"), Some(&"true".to_string()));
770 assert_eq!(env.get("usage_force"), Some(&"false".to_string()));
771 }
772
773 #[test]
774 fn test_arg_env_var() {
775 let cmd = SpecCommand::builder()
776 .name("test")
777 .arg(
778 SpecArg::builder()
779 .name("input")
780 .env("TEST_ARG_INPUT")
781 .required(true)
782 .build(),
783 )
784 .build();
785 let spec = Spec {
786 name: "test".to_string(),
787 bin: "test".to_string(),
788 cmd,
789 ..Default::default()
790 };
791
792 std::env::set_var("TEST_ARG_INPUT", "test_file.txt");
794
795 let input = vec!["test".to_string()];
796 let parsed = parse(&spec, &input).unwrap();
797
798 assert_eq!(parsed.args.len(), 1);
799 let arg = parsed.args.keys().next().unwrap();
800 assert_eq!(arg.name, "input");
801 let value = parsed.args.values().next().unwrap();
802 assert_eq!(value.to_string(), "test_file.txt");
803
804 std::env::remove_var("TEST_ARG_INPUT");
806 }
807
808 #[test]
809 fn test_flag_env_var_with_arg() {
810 let cmd = SpecCommand::builder()
811 .name("test")
812 .flag(
813 SpecFlag::builder()
814 .long("output")
815 .env("TEST_FLAG_OUTPUT")
816 .arg(SpecArg::builder().name("file").build())
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 std::env::set_var("TEST_FLAG_OUTPUT", "output.txt");
829
830 let input = vec!["test".to_string()];
831 let parsed = parse(&spec, &input).unwrap();
832
833 assert_eq!(parsed.flags.len(), 1);
834 let flag = parsed.flags.keys().next().unwrap();
835 assert_eq!(flag.name, "output");
836 let value = parsed.flags.values().next().unwrap();
837 assert_eq!(value.to_string(), "output.txt");
838
839 std::env::remove_var("TEST_FLAG_OUTPUT");
841 }
842
843 #[test]
844 fn test_flag_env_var_boolean() {
845 let cmd = SpecCommand::builder()
846 .name("test")
847 .flag(
848 SpecFlag::builder()
849 .long("verbose")
850 .env("TEST_FLAG_VERBOSE")
851 .build(),
852 )
853 .build();
854 let spec = Spec {
855 name: "test".to_string(),
856 bin: "test".to_string(),
857 cmd,
858 ..Default::default()
859 };
860
861 std::env::set_var("TEST_FLAG_VERBOSE", "true");
863
864 let input = vec!["test".to_string()];
865 let parsed = parse(&spec, &input).unwrap();
866
867 assert_eq!(parsed.flags.len(), 1);
868 let flag = parsed.flags.keys().next().unwrap();
869 assert_eq!(flag.name, "verbose");
870 let value = parsed.flags.values().next().unwrap();
871 assert_eq!(value.to_string(), "true");
872
873 std::env::remove_var("TEST_FLAG_VERBOSE");
875 }
876
877 #[test]
878 fn test_env_var_precedence() {
879 let cmd = SpecCommand::builder()
881 .name("test")
882 .arg(
883 SpecArg::builder()
884 .name("input")
885 .env("TEST_PRECEDENCE_INPUT")
886 .required(true)
887 .build(),
888 )
889 .build();
890 let spec = Spec {
891 name: "test".to_string(),
892 bin: "test".to_string(),
893 cmd,
894 ..Default::default()
895 };
896
897 std::env::set_var("TEST_PRECEDENCE_INPUT", "env_file.txt");
899
900 let input = vec!["test".to_string(), "cli_file.txt".to_string()];
901 let parsed = parse(&spec, &input).unwrap();
902
903 assert_eq!(parsed.args.len(), 1);
904 let value = parsed.args.values().next().unwrap();
905 assert_eq!(value.to_string(), "cli_file.txt");
907
908 std::env::remove_var("TEST_PRECEDENCE_INPUT");
910 }
911
912 #[test]
913 fn test_flag_var_true_with_single_default() {
914 let cmd = SpecCommand::builder()
916 .name("test")
917 .flag(
918 SpecFlag::builder()
919 .long("foo")
920 .var(true)
921 .arg(SpecArg::builder().name("foo").build())
922 .default_value("bar")
923 .build(),
924 )
925 .build();
926 let spec = Spec {
927 name: "test".to_string(),
928 bin: "test".to_string(),
929 cmd,
930 ..Default::default()
931 };
932
933 let input = vec!["test".to_string()];
935 let parsed = parse(&spec, &input).unwrap();
936
937 assert_eq!(parsed.flags.len(), 1);
938 let flag = parsed.flags.keys().next().unwrap();
939 assert_eq!(flag.name, "foo");
940 let value = parsed.flags.values().next().unwrap();
941 match value {
943 ParseValue::MultiString(v) => {
944 assert_eq!(v.len(), 1);
945 assert_eq!(v[0], "bar");
946 }
947 _ => panic!("Expected MultiString, got {:?}", value),
948 }
949 }
950
951 #[test]
952 fn test_flag_var_true_with_multiple_defaults() {
953 let cmd = SpecCommand::builder()
955 .name("test")
956 .flag(
957 SpecFlag::builder()
958 .long("foo")
959 .var(true)
960 .arg(SpecArg::builder().name("foo").build())
961 .default_values(["xyz", "bar"])
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.flags.len(), 1);
977 let value = parsed.flags.values().next().unwrap();
978 match value {
980 ParseValue::MultiString(v) => {
981 assert_eq!(v.len(), 2);
982 assert_eq!(v[0], "xyz");
983 assert_eq!(v[1], "bar");
984 }
985 _ => panic!("Expected MultiString, got {:?}", value),
986 }
987 }
988
989 #[test]
990 fn test_flag_var_false_with_default_remains_string() {
991 let cmd = SpecCommand::builder()
993 .name("test")
994 .flag(
995 SpecFlag::builder()
996 .long("foo")
997 .var(false) .arg(SpecArg::builder().name("foo").build())
999 .default_value("bar")
1000 .build(),
1001 )
1002 .build();
1003 let spec = Spec {
1004 name: "test".to_string(),
1005 bin: "test".to_string(),
1006 cmd,
1007 ..Default::default()
1008 };
1009
1010 let input = vec!["test".to_string()];
1012 let parsed = parse(&spec, &input).unwrap();
1013
1014 assert_eq!(parsed.flags.len(), 1);
1015 let value = parsed.flags.values().next().unwrap();
1016 match value {
1018 ParseValue::String(s) => {
1019 assert_eq!(s, "bar");
1020 }
1021 _ => panic!("Expected String, got {:?}", value),
1022 }
1023 }
1024
1025 #[test]
1026 fn test_arg_var_true_with_single_default() {
1027 let cmd = SpecCommand::builder()
1029 .name("test")
1030 .arg(
1031 SpecArg::builder()
1032 .name("files")
1033 .var(true)
1034 .default_value("default.txt")
1035 .required(false)
1036 .build(),
1037 )
1038 .build();
1039 let spec = Spec {
1040 name: "test".to_string(),
1041 bin: "test".to_string(),
1042 cmd,
1043 ..Default::default()
1044 };
1045
1046 let input = vec!["test".to_string()];
1048 let parsed = parse(&spec, &input).unwrap();
1049
1050 assert_eq!(parsed.args.len(), 1);
1051 let value = parsed.args.values().next().unwrap();
1052 match value {
1054 ParseValue::MultiString(v) => {
1055 assert_eq!(v.len(), 1);
1056 assert_eq!(v[0], "default.txt");
1057 }
1058 _ => panic!("Expected MultiString, got {:?}", value),
1059 }
1060 }
1061
1062 #[test]
1063 fn test_arg_var_true_with_multiple_defaults() {
1064 let cmd = SpecCommand::builder()
1066 .name("test")
1067 .arg(
1068 SpecArg::builder()
1069 .name("files")
1070 .var(true)
1071 .default_values(["file1.txt", "file2.txt"])
1072 .required(false)
1073 .build(),
1074 )
1075 .build();
1076 let spec = Spec {
1077 name: "test".to_string(),
1078 bin: "test".to_string(),
1079 cmd,
1080 ..Default::default()
1081 };
1082
1083 let input = vec!["test".to_string()];
1085 let parsed = parse(&spec, &input).unwrap();
1086
1087 assert_eq!(parsed.args.len(), 1);
1088 let value = parsed.args.values().next().unwrap();
1089 match value {
1091 ParseValue::MultiString(v) => {
1092 assert_eq!(v.len(), 2);
1093 assert_eq!(v[0], "file1.txt");
1094 assert_eq!(v[1], "file2.txt");
1095 }
1096 _ => panic!("Expected MultiString, got {:?}", value),
1097 }
1098 }
1099
1100 #[test]
1101 fn test_arg_var_false_with_default_remains_string() {
1102 let cmd = SpecCommand::builder()
1104 .name("test")
1105 .arg(
1106 SpecArg::builder()
1107 .name("file")
1108 .var(false)
1109 .default_value("default.txt")
1110 .required(false)
1111 .build(),
1112 )
1113 .build();
1114 let spec = Spec {
1115 name: "test".to_string(),
1116 bin: "test".to_string(),
1117 cmd,
1118 ..Default::default()
1119 };
1120
1121 let input = vec!["test".to_string()];
1123 let parsed = parse(&spec, &input).unwrap();
1124
1125 assert_eq!(parsed.args.len(), 1);
1126 let value = parsed.args.values().next().unwrap();
1127 match value {
1129 ParseValue::String(s) => {
1130 assert_eq!(s, "default.txt");
1131 }
1132 _ => panic!("Expected String, got {:?}", value),
1133 }
1134 }
1135
1136 #[test]
1137 fn test_default_subcommand() {
1138 let run_cmd = SpecCommand::builder()
1140 .name("run")
1141 .arg(SpecArg::builder().name("task").build())
1142 .build();
1143 let mut cmd = SpecCommand::builder().name("test").build();
1144 cmd.subcommands.insert("run".to_string(), run_cmd);
1145
1146 let spec = Spec {
1147 name: "test".to_string(),
1148 bin: "test".to_string(),
1149 cmd,
1150 default_subcommand: Some("run".to_string()),
1151 ..Default::default()
1152 };
1153
1154 let input = vec!["test".to_string(), "mytask".to_string()];
1156 let parsed = parse(&spec, &input).unwrap();
1157
1158 assert_eq!(parsed.cmds.len(), 2);
1160 assert_eq!(parsed.cmds[1].name, "run");
1161
1162 assert_eq!(parsed.args.len(), 1);
1164 let arg = parsed.args.keys().next().unwrap();
1165 assert_eq!(arg.name, "task");
1166 let value = parsed.args.values().next().unwrap();
1167 assert_eq!(value.to_string(), "mytask");
1168 }
1169
1170 #[test]
1171 fn test_default_subcommand_explicit_still_works() {
1172 let run_cmd = SpecCommand::builder()
1174 .name("run")
1175 .arg(SpecArg::builder().name("task").build())
1176 .build();
1177 let other_cmd = SpecCommand::builder()
1178 .name("other")
1179 .arg(SpecArg::builder().name("other_arg").build())
1180 .build();
1181 let mut cmd = SpecCommand::builder().name("test").build();
1182 cmd.subcommands.insert("run".to_string(), run_cmd);
1183 cmd.subcommands.insert("other".to_string(), other_cmd);
1184
1185 let spec = Spec {
1186 name: "test".to_string(),
1187 bin: "test".to_string(),
1188 cmd,
1189 default_subcommand: Some("run".to_string()),
1190 ..Default::default()
1191 };
1192
1193 let input = vec!["test".to_string(), "other".to_string(), "foo".to_string()];
1195 let parsed = parse(&spec, &input).unwrap();
1196
1197 assert_eq!(parsed.cmds.len(), 2);
1199 assert_eq!(parsed.cmds[1].name, "other");
1200 }
1201
1202 #[test]
1203 fn test_default_subcommand_with_nested_subcommands() {
1204 let say_cmd = SpecCommand::builder()
1208 .name("say")
1209 .arg(SpecArg::builder().name("name").build())
1210 .build();
1211 let mut run_cmd = SpecCommand::builder().name("run").build();
1212 run_cmd.subcommands.insert("say".to_string(), say_cmd);
1213
1214 let mut cmd = SpecCommand::builder().name("test").build();
1215 cmd.subcommands.insert("run".to_string(), run_cmd);
1216
1217 let spec = Spec {
1218 name: "test".to_string(),
1219 bin: "test".to_string(),
1220 cmd,
1221 default_subcommand: Some("run".to_string()),
1222 ..Default::default()
1223 };
1224
1225 let input = vec!["test".to_string(), "say".to_string(), "hello".to_string()];
1227 let parsed = parse(&spec, &input).unwrap();
1228
1229 assert_eq!(parsed.cmds.len(), 3);
1231 assert_eq!(parsed.cmds[0].name, "test");
1232 assert_eq!(parsed.cmds[1].name, "run");
1233 assert_eq!(parsed.cmds[2].name, "say");
1234
1235 assert_eq!(parsed.args.len(), 1);
1237 let arg = parsed.args.keys().next().unwrap();
1238 assert_eq!(arg.name, "name");
1239 let value = parsed.args.values().next().unwrap();
1240 assert_eq!(value.to_string(), "hello");
1241 }
1242
1243 #[test]
1244 fn test_default_subcommand_same_name_child() {
1245 let run_task = SpecCommand::builder()
1249 .name("run")
1250 .arg(SpecArg::builder().name("args").build())
1251 .build();
1252 let mut run_cmd = SpecCommand::builder().name("run").build();
1253 run_cmd.subcommands.insert("run".to_string(), run_task);
1254
1255 let mut cmd = SpecCommand::builder().name("test").build();
1256 cmd.subcommands.insert("run".to_string(), run_cmd);
1257
1258 let spec = Spec {
1259 name: "test".to_string(),
1260 bin: "test".to_string(),
1261 cmd,
1262 default_subcommand: Some("run".to_string()),
1263 ..Default::default()
1264 };
1265
1266 let input = vec!["test".to_string(), "run".to_string()];
1268 let parsed = parse(&spec, &input).unwrap();
1269
1270 assert_eq!(parsed.cmds.len(), 2);
1272 assert_eq!(parsed.cmds[0].name, "test");
1273 assert_eq!(parsed.cmds[1].name, "run");
1274
1275 let input = vec![
1277 "test".to_string(),
1278 "run".to_string(),
1279 "run".to_string(),
1280 "hello".to_string(),
1281 ];
1282 let parsed = parse(&spec, &input).unwrap();
1283
1284 assert_eq!(parsed.cmds.len(), 3);
1285 assert_eq!(parsed.cmds[0].name, "test");
1286 assert_eq!(parsed.cmds[1].name, "run");
1287 assert_eq!(parsed.cmds[2].name, "run");
1288 assert_eq!(parsed.args.len(), 1);
1289 let value = parsed.args.values().next().unwrap();
1290 assert_eq!(value.to_string(), "hello");
1291
1292 let mut run_cmd = SpecCommand::builder()
1296 .name("run")
1297 .arg(SpecArg::builder().name("task").build())
1298 .build();
1299 let run_task = SpecCommand::builder().name("run").build();
1300 run_cmd.subcommands.insert("run".to_string(), run_task);
1301
1302 let mut cmd = SpecCommand::builder().name("test").build();
1303 cmd.subcommands.insert("run".to_string(), run_cmd);
1304
1305 let spec = Spec {
1306 name: "test".to_string(),
1307 bin: "test".to_string(),
1308 cmd,
1309 default_subcommand: Some("run".to_string()),
1310 ..Default::default()
1311 };
1312
1313 let input = vec!["test".to_string(), "other".to_string()];
1314 let parsed = parse(&spec, &input).unwrap();
1315
1316 assert_eq!(parsed.cmds.len(), 2);
1319 assert_eq!(parsed.cmds[0].name, "test");
1320 assert_eq!(parsed.cmds[1].name, "run");
1321
1322 assert_eq!(parsed.args.len(), 1);
1324 let value = parsed.args.values().next().unwrap();
1325 assert_eq!(value.to_string(), "other");
1326 }
1327
1328 #[test]
1329 fn test_restart_token() {
1330 let run_cmd = SpecCommand::builder()
1332 .name("run")
1333 .arg(SpecArg::builder().name("task").build())
1334 .restart_token(":::".to_string())
1335 .build();
1336 let mut cmd = SpecCommand::builder().name("test").build();
1337 cmd.subcommands.insert("run".to_string(), run_cmd);
1338
1339 let spec = Spec {
1340 name: "test".to_string(),
1341 bin: "test".to_string(),
1342 cmd,
1343 ..Default::default()
1344 };
1345
1346 let input = vec![
1348 "test".to_string(),
1349 "run".to_string(),
1350 "task1".to_string(),
1351 ":::".to_string(),
1352 "task2".to_string(),
1353 ];
1354 let parsed = parse(&spec, &input).unwrap();
1355
1356 assert_eq!(parsed.args.len(), 1);
1358 let value = parsed.args.values().next().unwrap();
1359 assert_eq!(value.to_string(), "task2");
1360 }
1361
1362 #[test]
1363 fn test_restart_token_multiple() {
1364 let run_cmd = SpecCommand::builder()
1366 .name("run")
1367 .arg(SpecArg::builder().name("task").build())
1368 .restart_token(":::".to_string())
1369 .build();
1370 let mut cmd = SpecCommand::builder().name("test").build();
1371 cmd.subcommands.insert("run".to_string(), run_cmd);
1372
1373 let spec = Spec {
1374 name: "test".to_string(),
1375 bin: "test".to_string(),
1376 cmd,
1377 ..Default::default()
1378 };
1379
1380 let input = vec![
1382 "test".to_string(),
1383 "run".to_string(),
1384 "task1".to_string(),
1385 ":::".to_string(),
1386 "task2".to_string(),
1387 ":::".to_string(),
1388 "task3".to_string(),
1389 ];
1390 let parsed = parse(&spec, &input).unwrap();
1391
1392 assert_eq!(parsed.args.len(), 1);
1394 let value = parsed.args.values().next().unwrap();
1395 assert_eq!(value.to_string(), "task3");
1396 }
1397
1398 #[test]
1399 fn test_restart_token_clears_flag_awaiting_value() {
1400 let run_cmd = SpecCommand::builder()
1402 .name("run")
1403 .arg(SpecArg::builder().name("task").build())
1404 .flag(
1405 SpecFlag::builder()
1406 .name("jobs")
1407 .long("jobs")
1408 .arg(SpecArg::builder().name("count").build())
1409 .build(),
1410 )
1411 .restart_token(":::".to_string())
1412 .build();
1413 let mut cmd = SpecCommand::builder().name("test").build();
1414 cmd.subcommands.insert("run".to_string(), run_cmd);
1415
1416 let spec = Spec {
1417 name: "test".to_string(),
1418 bin: "test".to_string(),
1419 cmd,
1420 ..Default::default()
1421 };
1422
1423 let input = vec![
1425 "test".to_string(),
1426 "run".to_string(),
1427 "task1".to_string(),
1428 "--jobs".to_string(),
1429 ":::".to_string(),
1430 "task2".to_string(),
1431 ];
1432 let parsed = parse(&spec, &input).unwrap();
1433
1434 assert_eq!(parsed.args.len(), 1);
1436 let value = parsed.args.values().next().unwrap();
1437 assert_eq!(value.to_string(), "task2");
1438 assert!(parsed.flag_awaiting_value.is_empty());
1440 }
1441
1442 #[test]
1443 fn test_restart_token_resets_double_dash() {
1444 let run_cmd = SpecCommand::builder()
1446 .name("run")
1447 .arg(SpecArg::builder().name("task").build())
1448 .arg(SpecArg::builder().name("extra_args").var(true).build())
1449 .flag(SpecFlag::builder().name("verbose").long("verbose").build())
1450 .restart_token(":::".to_string())
1451 .build();
1452 let mut cmd = SpecCommand::builder().name("test").build();
1453 cmd.subcommands.insert("run".to_string(), run_cmd);
1454
1455 let spec = Spec {
1456 name: "test".to_string(),
1457 bin: "test".to_string(),
1458 cmd,
1459 ..Default::default()
1460 };
1461
1462 let input = vec![
1464 "test".to_string(),
1465 "run".to_string(),
1466 "task1".to_string(),
1467 "--".to_string(),
1468 "extra".to_string(),
1469 ":::".to_string(),
1470 "--verbose".to_string(),
1471 "task2".to_string(),
1472 ];
1473 let parsed = parse(&spec, &input).unwrap();
1474
1475 assert!(parsed.flags.keys().any(|f| f.name == "verbose"));
1477 let task_arg = parsed.args.keys().find(|a| a.name == "task").unwrap();
1479 let value = parsed.args.get(task_arg).unwrap();
1480 assert_eq!(value.to_string(), "task2");
1481 }
1482
1483 #[test]
1484 fn test_double_dashes_without_preserve() {
1485 let run_cmd = SpecCommand::builder()
1487 .name("run")
1488 .arg(SpecArg::builder().name("args").var(true).build())
1489 .build();
1490 let mut cmd = SpecCommand::builder().name("test").build();
1491 cmd.subcommands.insert("run".to_string(), run_cmd);
1492
1493 let spec = Spec {
1494 name: "test".to_string(),
1495 bin: "test".to_string(),
1496 cmd,
1497 ..Default::default()
1498 };
1499
1500 let input = vec![
1502 "test".to_string(),
1503 "run".to_string(),
1504 "arg1".to_string(),
1505 "--".to_string(),
1506 "arg2".to_string(),
1507 "--".to_string(),
1508 "arg3".to_string(),
1509 ];
1510 let parsed = parse(&spec, &input).unwrap();
1511
1512 let args_arg = parsed.args.keys().find(|a| a.name == "args").unwrap();
1513 let value = parsed.args.get(args_arg).unwrap();
1514 assert_eq!(value.to_string(), "arg1 arg2 arg3");
1515 }
1516
1517 #[test]
1518 fn test_double_dashes_with_preserve() {
1519 let run_cmd = SpecCommand::builder()
1521 .name("run")
1522 .arg(
1523 SpecArg::builder()
1524 .name("args")
1525 .var(true)
1526 .double_dash(SpecDoubleDashChoices::Preserve)
1527 .build(),
1528 )
1529 .build();
1530 let mut cmd = SpecCommand::builder().name("test").build();
1531 cmd.subcommands.insert("run".to_string(), run_cmd);
1532
1533 let spec = Spec {
1534 name: "test".to_string(),
1535 bin: "test".to_string(),
1536 cmd,
1537 ..Default::default()
1538 };
1539
1540 let input = vec![
1542 "test".to_string(),
1543 "run".to_string(),
1544 "arg1".to_string(),
1545 "--".to_string(),
1546 "arg2".to_string(),
1547 "--".to_string(),
1548 "arg3".to_string(),
1549 ];
1550 let parsed = parse(&spec, &input).unwrap();
1551
1552 let args_arg = parsed.args.keys().find(|a| a.name == "args").unwrap();
1553 let value = parsed.args.get(args_arg).unwrap();
1554 assert_eq!(value.to_string(), "arg1 -- arg2 -- arg3");
1555 }
1556
1557 #[test]
1558 fn test_double_dashes_with_preserve_only_dashes() {
1559 let run_cmd = SpecCommand::builder()
1562 .name("run")
1563 .arg(
1564 SpecArg::builder()
1565 .name("args")
1566 .var(true)
1567 .double_dash(SpecDoubleDashChoices::Preserve)
1568 .build(),
1569 )
1570 .build();
1571 let mut cmd = SpecCommand::builder().name("test").build();
1572 cmd.subcommands.insert("run".to_string(), run_cmd);
1573
1574 let spec = Spec {
1575 name: "test".to_string(),
1576 bin: "test".to_string(),
1577 cmd,
1578 ..Default::default()
1579 };
1580
1581 let input = vec![
1583 "test".to_string(),
1584 "run".to_string(),
1585 "--".to_string(),
1586 "--".to_string(),
1587 ];
1588 let parsed = parse(&spec, &input).unwrap();
1589
1590 let args_arg = parsed.args.keys().find(|a| a.name == "args").unwrap();
1591 let value = parsed.args.get(args_arg).unwrap();
1592 assert_eq!(value.to_string(), "-- --");
1593 }
1594
1595 #[test]
1596 fn test_double_dashes_with_preserve_multiple_args() {
1597 let run_cmd = SpecCommand::builder()
1599 .name("run")
1600 .arg(SpecArg::builder().name("task").build())
1601 .arg(
1602 SpecArg::builder()
1603 .name("extra_args")
1604 .var(true)
1605 .double_dash(SpecDoubleDashChoices::Preserve)
1606 .build(),
1607 )
1608 .build();
1609 let mut cmd = SpecCommand::builder().name("test").build();
1610 cmd.subcommands.insert("run".to_string(), run_cmd);
1611
1612 let spec = Spec {
1613 name: "test".to_string(),
1614 bin: "test".to_string(),
1615 cmd,
1616 ..Default::default()
1617 };
1618
1619 let input = vec![
1622 "test".to_string(),
1623 "run".to_string(),
1624 "task1".to_string(),
1625 "--".to_string(),
1626 "arg1".to_string(),
1627 "--".to_string(),
1628 "--foo".to_string(),
1629 ];
1630 let parsed = parse(&spec, &input).unwrap();
1631
1632 let task_arg = parsed.args.keys().find(|a| a.name == "task").unwrap();
1633 let task_value = parsed.args.get(task_arg).unwrap();
1634 assert_eq!(task_value.to_string(), "task1");
1635
1636 let extra_arg = parsed.args.keys().find(|a| a.name == "extra_args").unwrap();
1637 let extra_value = parsed.args.get(extra_arg).unwrap();
1638 assert_eq!(extra_value.to_string(), "-- arg1 -- --foo");
1639 }
1640
1641 #[test]
1642 fn test_parser_with_custom_env_for_required_arg() {
1643 let cmd = SpecCommand::builder()
1646 .name("test")
1647 .arg(
1648 SpecArg::builder()
1649 .name("name")
1650 .env("NAME")
1651 .required(true)
1652 .build(),
1653 )
1654 .build();
1655 let spec = Spec {
1656 name: "test".to_string(),
1657 bin: "test".to_string(),
1658 cmd,
1659 ..Default::default()
1660 };
1661
1662 std::env::remove_var("NAME");
1664
1665 let mut env = HashMap::new();
1667 env.insert("NAME".to_string(), "john".to_string());
1668
1669 let input = vec!["test".to_string()];
1670 let result = Parser::new(&spec).with_env(env).parse(&input);
1671
1672 let parsed = result.expect("parse should succeed with custom env");
1674 assert_eq!(parsed.args.len(), 1);
1675 let value = parsed.args.values().next().unwrap();
1676 assert_eq!(value.to_string(), "john");
1677 }
1678
1679 #[test]
1680 fn test_parser_with_custom_env_for_required_flag() {
1681 let cmd = SpecCommand::builder()
1683 .name("test")
1684 .flag(
1685 SpecFlag::builder()
1686 .long("name")
1687 .env("NAME")
1688 .required(true)
1689 .arg(SpecArg::builder().name("name").build())
1690 .build(),
1691 )
1692 .build();
1693 let spec = Spec {
1694 name: "test".to_string(),
1695 bin: "test".to_string(),
1696 cmd,
1697 ..Default::default()
1698 };
1699
1700 std::env::remove_var("NAME");
1702
1703 let mut env = HashMap::new();
1705 env.insert("NAME".to_string(), "jane".to_string());
1706
1707 let input = vec!["test".to_string()];
1708 let result = Parser::new(&spec).with_env(env).parse(&input);
1709
1710 let parsed = result.expect("parse should succeed with custom env");
1712 assert_eq!(parsed.flags.len(), 1);
1713 let value = parsed.flags.values().next().unwrap();
1714 assert_eq!(value.to_string(), "jane");
1715 }
1716
1717 #[test]
1718 fn test_parser_with_custom_env_still_fails_when_missing() {
1719 let cmd = SpecCommand::builder()
1721 .name("test")
1722 .arg(
1723 SpecArg::builder()
1724 .name("name")
1725 .env("NAME")
1726 .required(true)
1727 .build(),
1728 )
1729 .build();
1730 let spec = Spec {
1731 name: "test".to_string(),
1732 bin: "test".to_string(),
1733 cmd,
1734 ..Default::default()
1735 };
1736
1737 std::env::remove_var("NAME");
1739
1740 let env = HashMap::new();
1742
1743 let input = vec!["test".to_string()];
1744 let result = Parser::new(&spec).with_env(env).parse(&input);
1745
1746 assert!(result.is_err());
1748 }
1749}