1use std::collections::{HashMap, HashSet, VecDeque};
19
20use crate::error::ClickError;
21
22type TokenNormalizeFunc = Box<dyn Fn(&str) -> String + Send + Sync>;
28
29pub type ParseResult = Result<(HashMap<String, ParsedValue>, Vec<String>, Vec<String>), ClickError>;
31
32pub const NARGS_OPTIONAL: i32 = -2;
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
42pub enum OptionAction {
43 #[default]
45 Store,
46 StoreConst,
48 Append,
50 AppendConst,
52 Count,
54}
55
56#[derive(Debug, Clone, PartialEq)]
62pub enum ParsedValue {
63 Single(String),
65 Multiple(Vec<String>),
67 Count(usize),
69 Flag(bool),
71 Unset,
73 FlagNeedsValue,
78}
79
80impl ParsedValue {
81 pub fn is_unset(&self) -> bool {
83 matches!(self, ParsedValue::Unset)
84 }
85
86 pub fn as_single(&self) -> Option<&str> {
88 match self {
89 ParsedValue::Single(s) => Some(s),
90 _ => None,
91 }
92 }
93
94 pub fn as_multiple(&self) -> Option<&[String]> {
96 match self {
97 ParsedValue::Multiple(v) => Some(v),
98 _ => None,
99 }
100 }
101
102 pub fn as_count(&self) -> Option<usize> {
104 match self {
105 ParsedValue::Count(n) => Some(*n),
106 _ => None,
107 }
108 }
109
110 pub fn as_flag(&self) -> Option<bool> {
112 match self {
113 ParsedValue::Flag(b) => Some(*b),
114 _ => None,
115 }
116 }
117}
118
119#[derive(Debug, Clone)]
125struct ParserOption {
126 #[allow(dead_code)]
128 name: String,
129 #[allow(dead_code)]
131 short_opts: Vec<String>,
132 #[allow(dead_code)]
134 long_opts: Vec<String>,
135 action: OptionAction,
137 nargs: i32,
139 const_value: Option<String>,
141 dest: String,
143 flag_needs_value: bool,
147}
148
149impl ParserOption {
150 fn takes_value(&self) -> bool {
152 matches!(self.action, OptionAction::Store | OptionAction::Append)
153 }
154}
155
156#[derive(Debug, Clone)]
162struct ParserArgument {
163 #[allow(dead_code)]
165 name: String,
166 nargs: i32,
168 dest: String,
170}
171
172#[derive(Debug)]
178struct ParsingState {
179 opts: HashMap<String, ParsedValue>,
181 largs: Vec<String>,
183 rargs: VecDeque<String>,
185 order: Vec<String>,
187}
188
189impl ParsingState {
190 fn new(args: Vec<String>) -> Self {
192 Self {
193 opts: HashMap::new(),
194 largs: Vec::new(),
195 rargs: VecDeque::from(args),
196 order: Vec::new(),
197 }
198 }
199}
200
201pub fn split_opt(opt: &str) -> Option<(&str, &str)> {
218 if opt.is_empty() {
219 return None;
220 }
221
222 let first = opt.chars().next().unwrap();
223 if first.is_alphanumeric() {
224 return None;
226 }
227
228 if opt.len() >= 2 && &opt[0..2] == "--" {
230 let name = &opt[2..];
231 if name.is_empty() {
232 return None; }
234 return Some(("--", name));
235 }
236
237 if opt.len() >= 2 && opt.starts_with('-') {
239 return Some(("-", &opt[1..]));
240 }
241
242 if opt.len() >= 2 {
244 let prefix_end = first.len_utf8();
245 Some((&opt[..prefix_end], &opt[prefix_end..]))
246 } else {
247 None
248 }
249}
250
251fn edit_distance(a: &str, b: &str) -> usize {
253 let a_chars: Vec<char> = a.chars().collect();
254 let b_chars: Vec<char> = b.chars().collect();
255 let m = a_chars.len();
256 let n = b_chars.len();
257
258 if m == 0 {
259 return n;
260 }
261 if n == 0 {
262 return m;
263 }
264
265 let mut prev = vec![0usize; n + 1];
267 let mut curr = vec![0usize; n + 1];
268
269 for (j, item) in prev.iter_mut().enumerate().take(n + 1) {
271 *item = j;
272 }
273
274 for i in 1..=m {
275 curr[0] = i;
276 for j in 1..=n {
277 let cost = if a_chars[i - 1] == b_chars[j - 1] {
278 0
279 } else {
280 1
281 };
282 curr[j] = (prev[j] + 1) .min(curr[j - 1] + 1) .min(prev[j - 1] + cost); }
286 std::mem::swap(&mut prev, &mut curr);
287 }
288
289 prev[n]
290}
291
292fn get_close_matches(word: &str, possibilities: &[&str], max_matches: usize) -> Vec<String> {
296 let mut scored: Vec<(usize, &str)> = possibilities
297 .iter()
298 .map(|&p| (edit_distance(word, p), p))
299 .filter(|(dist, _)| *dist <= 2)
300 .collect();
301
302 scored.sort_by_key(|(dist, _)| *dist);
303 scored
304 .into_iter()
305 .take(max_matches)
306 .map(|(_, s)| s.to_string())
307 .collect()
308}
309
310fn unpack_args(
325 args: &[String],
326 nargs_spec: &[i32],
327) -> Result<(Vec<ParsedValue>, Vec<String>), ClickError> {
328 let mut args_deque: VecDeque<String> = VecDeque::from(args.to_vec());
329 let mut nargs_deque: VecDeque<i32> = VecDeque::from(nargs_spec.to_vec());
330 let mut result: Vec<ParsedValue> = Vec::new();
331 let mut star_pos: Option<usize> = None;
332
333 fn fetch(deque: &mut VecDeque<String>, from_back: bool) -> Option<String> {
335 if from_back {
336 deque.pop_back()
337 } else {
338 deque.pop_front()
339 }
340 }
341
342 fn count_required_remaining(deque: &VecDeque<i32>) -> usize {
344 deque
345 .iter()
346 .filter(|&&n| n > 0) .map(|&n| n.max(1) as usize)
348 .sum()
349 }
350
351 while let Some(nargs) = if star_pos.is_none() {
352 nargs_deque.pop_front()
353 } else {
354 nargs_deque.pop_back()
355 } {
356 if nargs == 1 {
357 let value = fetch(&mut args_deque, star_pos.is_some());
359 result.push(match value {
360 Some(v) => ParsedValue::Single(v),
361 None => ParsedValue::Unset,
362 });
363 } else if nargs == NARGS_OPTIONAL {
364 let required_remaining = count_required_remaining(&nargs_deque);
366 let available = args_deque.len();
367
368 if available > required_remaining {
369 let value = fetch(&mut args_deque, star_pos.is_some());
371 result.push(match value {
372 Some(v) => ParsedValue::Single(v),
373 None => ParsedValue::Unset,
374 });
375 } else {
376 result.push(ParsedValue::Unset);
378 }
379 } else if nargs > 1 {
380 let mut values = Vec::new();
382 let mut missing_count = 0;
383 for _ in 0..nargs {
384 match fetch(&mut args_deque, star_pos.is_some()) {
385 Some(v) => {
386 values.push(v);
387 }
388 None => {
389 missing_count += 1;
390 }
391 }
392 }
393 if star_pos.is_some() {
395 values.reverse();
396 }
397 if missing_count == nargs as usize {
399 result.push(ParsedValue::Unset);
400 } else if missing_count > 0 {
401 result.push(ParsedValue::Multiple(values));
404 } else {
405 result.push(ParsedValue::Multiple(values));
406 }
407 } else if nargs == -1 {
408 if star_pos.is_some() {
410 return Err(ClickError::usage(
412 "Cannot have more than one variadic argument.".to_string(),
413 ));
414 }
415 star_pos = Some(result.len());
416 result.push(ParsedValue::Unset); }
418 }
420
421 if let Some(pos) = star_pos {
423 let remaining: Vec<String> = args_deque.drain(..).collect();
424 if remaining.is_empty() {
425 result[pos] = ParsedValue::Multiple(Vec::new());
426 } else {
427 result[pos] = ParsedValue::Multiple(remaining);
428 }
429 let after_star: Vec<_> = result.drain(pos + 1..).rev().collect();
431 result.extend(after_star);
432 }
433
434 Ok((result, args_deque.into_iter().collect()))
435}
436
437pub struct OptionParser {
465 allow_interspersed_args: bool,
467 ignore_unknown_options: bool,
469 short_opt: HashMap<String, ParserOption>,
471 long_opt: HashMap<String, ParserOption>,
473 opt_prefixes: HashSet<String>,
475 args: Vec<ParserArgument>,
477 token_normalize_func: Option<TokenNormalizeFunc>,
479}
480
481impl std::fmt::Debug for OptionParser {
482 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
483 f.debug_struct("OptionParser")
484 .field("allow_interspersed_args", &self.allow_interspersed_args)
485 .field("ignore_unknown_options", &self.ignore_unknown_options)
486 .field("short_opt", &self.short_opt)
487 .field("long_opt", &self.long_opt)
488 .field("opt_prefixes", &self.opt_prefixes)
489 .field("args", &self.args)
490 .field(
491 "token_normalize_func",
492 &self.token_normalize_func.as_ref().map(|_| "<function>"),
493 )
494 .finish()
495 }
496}
497
498impl Default for OptionParser {
499 fn default() -> Self {
500 Self::new()
501 }
502}
503
504impl OptionParser {
505 pub fn new() -> Self {
507 let mut opt_prefixes = HashSet::new();
508 opt_prefixes.insert("-".to_string());
509 opt_prefixes.insert("--".to_string());
510
511 Self {
512 allow_interspersed_args: true,
513 ignore_unknown_options: false,
514 short_opt: HashMap::new(),
515 long_opt: HashMap::new(),
516 opt_prefixes,
517 args: Vec::new(),
518 token_normalize_func: None,
519 }
520 }
521
522 pub fn allow_interspersed_args(mut self, allow: bool) -> Self {
527 self.allow_interspersed_args = allow;
528 self
529 }
530
531 pub fn ignore_unknown_options(mut self, ignore: bool) -> Self {
536 self.ignore_unknown_options = ignore;
537 self
538 }
539
540 pub fn token_normalize_func<F>(mut self, func: F) -> Self
545 where
546 F: Fn(&str) -> String + Send + Sync + 'static,
547 {
548 self.token_normalize_func = Some(Box::new(func));
549 self
550 }
551
552 fn normalize_opt(&self, opt: &str) -> String {
554 if let Some(ref func) = self.token_normalize_func {
555 if let Some((prefix, name)) = split_opt(opt) {
556 format!("{}{}", prefix, func(name))
557 } else {
558 opt.to_string()
559 }
560 } else {
561 opt.to_string()
562 }
563 }
564
565 pub fn add_option(
575 &mut self,
576 opts: &[&str],
577 dest: &str,
578 action: OptionAction,
579 nargs: i32,
580 const_value: Option<&str>,
581 ) {
582 self.add_option_ex(opts, dest, action, nargs, const_value, false);
583 }
584
585 pub fn add_option_ex(
597 &mut self,
598 opts: &[&str],
599 dest: &str,
600 action: OptionAction,
601 nargs: i32,
602 const_value: Option<&str>,
603 flag_needs_value: bool,
604 ) {
605 let opts: Vec<String> = opts.iter().map(|o| self.normalize_opt(o)).collect();
606
607 let mut short_opts = Vec::new();
608 let mut long_opts = Vec::new();
609
610 for opt in &opts {
611 if let Some((prefix, value)) = split_opt(opt) {
612 self.opt_prefixes
614 .insert(prefix.chars().next().unwrap().to_string());
615 if prefix.len() == 2 {
616 self.opt_prefixes.insert(prefix.to_string());
617 }
618
619 if prefix.len() == 1 && value.len() == 1 {
621 short_opts.push(opt.clone());
622 } else {
623 long_opts.push(opt.clone());
624 }
625 }
626 }
627
628 let name = long_opts
629 .first()
630 .or(short_opts.first())
631 .cloned()
632 .unwrap_or_else(|| dest.to_string());
633
634 let parser_opt = ParserOption {
635 name,
636 short_opts: short_opts.clone(),
637 long_opts: long_opts.clone(),
638 action,
639 nargs,
640 const_value: const_value.map(String::from),
641 dest: dest.to_string(),
642 flag_needs_value,
643 };
644
645 for opt in &short_opts {
647 self.short_opt.insert(opt.clone(), parser_opt.clone());
648 }
649 for opt in &long_opts {
650 self.long_opt.insert(opt.clone(), parser_opt.clone());
651 }
652 }
653
654 pub fn add_argument(&mut self, dest: &str, nargs: i32) {
661 self.args.push(ParserArgument {
662 name: dest.to_string(),
663 nargs,
664 dest: dest.to_string(),
665 });
666 }
667
668 pub fn parse_args(&self, args: Vec<String>) -> ParseResult {
682 let mut state = ParsingState::new(args);
683
684 self.process_args_for_options(&mut state)?;
685 self.process_args_for_args(&mut state)?;
686
687 Ok((state.opts, state.largs, state.order))
688 }
689
690 fn process_args_for_options(&self, state: &mut ParsingState) -> Result<(), ClickError> {
692 while let Some(arg) = state.rargs.pop_front() {
693 let arglen = arg.len();
694
695 if arg == "--" {
697 return Ok(());
698 }
699
700 let first_char = arg.chars().next().unwrap_or(' ');
702 let is_option_like = self.opt_prefixes.contains(&first_char.to_string()) && arglen > 1;
703
704 if is_option_like {
705 self.process_opts(&arg, state)?;
706 } else if self.allow_interspersed_args {
707 state.largs.push(arg);
708 } else {
709 state.rargs.push_front(arg);
711 return Ok(());
712 }
713 }
714 Ok(())
715 }
716
717 fn process_args_for_args(&self, state: &mut ParsingState) -> Result<(), ClickError> {
719 let all_args: Vec<String> = state.largs.drain(..).chain(state.rargs.drain(..)).collect();
721
722 let nargs_spec: Vec<i32> = self.args.iter().map(|a| a.nargs).collect();
724
725 let (parsed, remaining) = unpack_args(&all_args, &nargs_spec)?;
727
728 for (idx, arg_def) in self.args.iter().enumerate() {
730 if idx < parsed.len() {
731 let value = &parsed[idx];
732
733 if arg_def.nargs > 1 {
735 if let ParsedValue::Multiple(ref values) = value {
736 if values.len() < arg_def.nargs as usize {
737 return Err(ClickError::bad_argument_usage(format!(
738 "Argument '{}' takes {} values.",
739 arg_def.dest, arg_def.nargs
740 )));
741 }
742 }
743 }
744
745 state.opts.insert(arg_def.dest.clone(), value.clone());
746 if !value.is_unset() {
747 state.order.push(arg_def.dest.clone());
748 }
749 } else {
750 state.opts.insert(arg_def.dest.clone(), ParsedValue::Unset);
751 }
752 }
753
754 state.largs = remaining;
755 Ok(())
756 }
757
758 fn process_opts(&self, arg: &str, state: &mut ParsingState) -> Result<(), ClickError> {
760 let mut explicit_value = None;
761 let long_opt;
762
763 if let Some(eq_pos) = arg.find('=') {
765 long_opt = arg[..eq_pos].to_string();
766 explicit_value = Some(arg[eq_pos + 1..].to_string());
767 } else {
768 long_opt = arg.to_string();
769 }
770
771 let norm_long_opt = self.normalize_opt(&long_opt);
772
773 match self.match_long_opt(&norm_long_opt, explicit_value.clone(), state) {
775 Ok(()) => Ok(()),
776 Err(ClickError::NoSuchOption { .. }) => {
777 if let Some((prefix, _)) = split_opt(arg) {
779 if prefix.len() == 1 {
780 return self.match_short_opt(arg, state);
781 }
782 }
783
784 if self.ignore_unknown_options {
786 state.largs.push(arg.to_string());
787 return Ok(());
788 }
789
790 self.match_long_opt(&norm_long_opt, explicit_value, state)
792 }
793 Err(e) => Err(e),
794 }
795 }
796
797 fn match_long_opt(
799 &self,
800 opt: &str,
801 explicit_value: Option<String>,
802 state: &mut ParsingState,
803 ) -> Result<(), ClickError> {
804 let option = match self.long_opt.get(opt) {
805 Some(o) => o.clone(),
806 None => {
807 let all_opts: Vec<&str> = self.long_opt.keys().map(|s| s.as_str()).collect();
809 let possibilities = get_close_matches(opt, &all_opts, 3);
810
811 return Err(if possibilities.is_empty() {
812 ClickError::no_such_option(opt)
813 } else {
814 ClickError::no_such_option_with_suggestions(opt, possibilities)
815 });
816 }
817 };
818
819 if option.takes_value() {
820 if let Some(val) = explicit_value {
822 state.rargs.push_front(val);
823 }
824
825 let value = self.get_value_from_state(opt, &option, state)?;
826 self.process_option(&option, value, state);
827 } else if explicit_value.is_some() {
828 return Err(ClickError::bad_option_usage(
829 opt,
830 format!("Option '{}' does not take a value.", opt),
831 ));
832 } else {
833 self.process_option(&option, ParsedValue::Unset, state);
834 }
835
836 Ok(())
837 }
838
839 fn match_short_opt(&self, arg: &str, state: &mut ParsingState) -> Result<(), ClickError> {
841 let prefix = arg.chars().next().unwrap();
842 let mut i = 1;
843 let chars: Vec<char> = arg.chars().collect();
844 let mut unknown_options = Vec::new();
845
846 while i < chars.len() {
847 let ch = chars[i];
848 let opt = self.normalize_opt(&format!("{}{}", prefix, ch));
849 i += 1;
850
851 let option = match self.short_opt.get(&opt) {
852 Some(o) => o.clone(),
853 None => {
854 if self.ignore_unknown_options {
855 unknown_options.push(ch);
856 continue;
857 }
858 return Err(ClickError::no_such_option(&opt));
859 }
860 };
861
862 if option.takes_value() {
863 if i < chars.len() {
865 let value: String = chars[i..].iter().collect();
866 state.rargs.push_front(value);
867 }
868
869 let value = self.get_value_from_state(&opt, &option, state)?;
870 self.process_option(&option, value, state);
871
872 break;
874 } else {
875 self.process_option(&option, ParsedValue::Unset, state);
876 }
877 }
878
879 if self.ignore_unknown_options && !unknown_options.is_empty() {
881 let combined: String = std::iter::once(prefix).chain(unknown_options).collect();
882 state.largs.push(combined);
883 }
884
885 Ok(())
886 }
887
888 fn get_value_from_state(
890 &self,
891 option_name: &str,
892 option: &ParserOption,
893 state: &mut ParsingState,
894 ) -> Result<ParsedValue, ClickError> {
895 let nargs = option.nargs;
896
897 if nargs == NARGS_OPTIONAL {
899 if let Some(next_arg) = state.rargs.front() {
901 let first_char = next_arg.chars().next().unwrap_or(' ');
903 let looks_like_option =
904 self.opt_prefixes.contains(&first_char.to_string()) && next_arg.len() > 1;
905
906 if looks_like_option && option.flag_needs_value {
907 return Ok(ParsedValue::FlagNeedsValue);
909 }
910 } else if option.flag_needs_value {
911 return Ok(ParsedValue::FlagNeedsValue);
913 }
914
915 if let Some(value) = state.rargs.pop_front() {
917 return Ok(ParsedValue::Single(value));
918 } else {
919 return Ok(ParsedValue::Unset);
920 }
921 }
922
923 if nargs <= 0 {
924 return Ok(ParsedValue::Unset);
926 }
927
928 let rargs_len = state.rargs.len() as i32;
929
930 if rargs_len < nargs {
932 if option.flag_needs_value {
934 return Ok(ParsedValue::FlagNeedsValue);
935 }
936 return Err(ClickError::bad_option_usage(
937 option_name,
938 if nargs == 1 {
939 format!("Option '{}' requires an argument.", option_name)
940 } else {
941 format!("Option '{}' requires {} arguments.", option_name, nargs)
942 },
943 ));
944 }
945
946 if nargs == 1 {
947 if option.flag_needs_value {
949 if let Some(next_arg) = state.rargs.front() {
950 let first_char = next_arg.chars().next().unwrap_or(' ');
951 let looks_like_option =
952 self.opt_prefixes.contains(&first_char.to_string()) && next_arg.len() > 1;
953
954 if looks_like_option {
955 return Ok(ParsedValue::FlagNeedsValue);
956 }
957 }
958 }
959
960 let value = state.rargs.pop_front().unwrap();
961 Ok(ParsedValue::Single(value))
962 } else {
963 let values: Vec<String> = (0..nargs).filter_map(|_| state.rargs.pop_front()).collect();
964 Ok(ParsedValue::Multiple(values))
965 }
966 }
967
968 fn process_option(&self, option: &ParserOption, value: ParsedValue, state: &mut ParsingState) {
970 match option.action {
971 OptionAction::Store => {
972 state.opts.insert(option.dest.clone(), value);
973 }
974 OptionAction::StoreConst => {
975 let const_val = option
976 .const_value
977 .clone()
978 .map(ParsedValue::Single)
979 .unwrap_or(ParsedValue::Flag(true));
980 state.opts.insert(option.dest.clone(), const_val);
981 }
982 OptionAction::Append => {
983 let entry = state
984 .opts
985 .entry(option.dest.clone())
986 .or_insert_with(|| ParsedValue::Multiple(Vec::new()));
987 if let ParsedValue::Multiple(ref mut vec) = entry {
988 match value {
989 ParsedValue::Single(s) => vec.push(s),
990 ParsedValue::Multiple(v) => vec.extend(v),
991 ParsedValue::FlagNeedsValue => {
992 state.opts.insert(
996 format!("__click_internal_flag_needs_value_{}", option.dest),
997 ParsedValue::Flag(true),
998 );
999 }
1000 _ => {}
1001 }
1002 }
1003 }
1004 OptionAction::AppendConst => {
1005 let entry = state
1006 .opts
1007 .entry(option.dest.clone())
1008 .or_insert_with(|| ParsedValue::Multiple(Vec::new()));
1009 if let ParsedValue::Multiple(ref mut vec) = entry {
1010 if let Some(ref const_val) = option.const_value {
1011 vec.push(const_val.clone());
1012 }
1013 }
1014 }
1015 OptionAction::Count => {
1016 let entry = state
1017 .opts
1018 .entry(option.dest.clone())
1019 .or_insert(ParsedValue::Count(0));
1020 if let ParsedValue::Count(ref mut n) = entry {
1021 *n += 1;
1022 }
1023 }
1024 }
1025
1026 state.order.push(option.dest.clone());
1027 }
1028}
1029
1030#[cfg(test)]
1035mod tests {
1036 use super::*;
1037
1038 #[test]
1043 fn test_split_opt_long() {
1044 assert_eq!(split_opt("--name"), Some(("--", "name")));
1045 assert_eq!(split_opt("--full-name"), Some(("--", "full-name")));
1046 }
1047
1048 #[test]
1049 fn test_split_opt_short() {
1050 assert_eq!(split_opt("-n"), Some(("-", "n")));
1051 assert_eq!(split_opt("-abc"), Some(("-", "abc")));
1052 }
1053
1054 #[test]
1055 fn test_split_opt_no_prefix() {
1056 assert_eq!(split_opt("name"), None);
1057 assert_eq!(split_opt(""), None);
1058 }
1059
1060 #[test]
1061 fn test_split_opt_just_dashes() {
1062 assert_eq!(split_opt("-"), None);
1063 assert_eq!(split_opt("--"), None);
1064 }
1065
1066 #[test]
1071 fn test_edit_distance() {
1072 assert_eq!(edit_distance("", ""), 0);
1073 assert_eq!(edit_distance("a", ""), 1);
1074 assert_eq!(edit_distance("", "b"), 1);
1075 assert_eq!(edit_distance("abc", "abc"), 0);
1076 assert_eq!(edit_distance("abc", "abd"), 1);
1077 assert_eq!(edit_distance("help", "hlep"), 2);
1078 assert_eq!(edit_distance("kitten", "sitting"), 3);
1079 }
1080
1081 #[test]
1082 fn test_get_close_matches() {
1083 let opts = vec!["--help", "--hello", "--version", "--verbose"];
1084 let matches = get_close_matches("--hlep", &opts, 3);
1085 assert!(matches.contains(&"--help".to_string()));
1086 }
1087
1088 #[test]
1093 fn test_short_option_with_space() {
1094 let mut parser = OptionParser::new();
1095 parser.add_option(&["-n"], "name", OptionAction::Store, 1, None);
1096
1097 let args = vec!["-n".to_string(), "value".to_string()];
1098 let (opts, remaining, _) = parser.parse_args(args).unwrap();
1099
1100 assert_eq!(
1101 opts.get("name"),
1102 Some(&ParsedValue::Single("value".to_string()))
1103 );
1104 assert!(remaining.is_empty());
1105 }
1106
1107 #[test]
1108 fn test_short_option_with_equals() {
1109 let mut parser = OptionParser::new();
1112 parser.add_option(&["-n"], "name", OptionAction::Store, 1, None);
1113
1114 let args = vec!["-n=value".to_string()];
1115 let (opts, remaining, _) = parser.parse_args(args).unwrap();
1116
1117 assert_eq!(
1119 opts.get("name"),
1120 Some(&ParsedValue::Single("=value".to_string()))
1121 );
1122 assert!(remaining.is_empty());
1123 }
1124
1125 #[test]
1126 fn test_short_option_attached_value() {
1127 let mut parser = OptionParser::new();
1128 parser.add_option(&["-n"], "name", OptionAction::Store, 1, None);
1129
1130 let args = vec!["-nvalue".to_string()];
1131 let (opts, remaining, _) = parser.parse_args(args).unwrap();
1132
1133 assert_eq!(
1134 opts.get("name"),
1135 Some(&ParsedValue::Single("value".to_string()))
1136 );
1137 assert!(remaining.is_empty());
1138 }
1139
1140 #[test]
1141 fn test_grouped_short_flags() {
1142 let mut parser = OptionParser::new();
1143 parser.add_option(&["-a"], "a", OptionAction::StoreConst, 0, Some("true"));
1144 parser.add_option(&["-b"], "b", OptionAction::StoreConst, 0, Some("true"));
1145 parser.add_option(&["-c"], "c", OptionAction::StoreConst, 0, Some("true"));
1146
1147 let args = vec!["-abc".to_string()];
1148 let (opts, remaining, _) = parser.parse_args(args).unwrap();
1149
1150 assert_eq!(
1151 opts.get("a"),
1152 Some(&ParsedValue::Single("true".to_string()))
1153 );
1154 assert_eq!(
1155 opts.get("b"),
1156 Some(&ParsedValue::Single("true".to_string()))
1157 );
1158 assert_eq!(
1159 opts.get("c"),
1160 Some(&ParsedValue::Single("true".to_string()))
1161 );
1162 assert!(remaining.is_empty());
1163 }
1164
1165 #[test]
1166 fn test_grouped_short_with_value() {
1167 let mut parser = OptionParser::new();
1168 parser.add_option(&["-a"], "a", OptionAction::StoreConst, 0, Some("true"));
1169 parser.add_option(&["-b"], "b", OptionAction::StoreConst, 0, Some("true"));
1170 parser.add_option(&["-n"], "name", OptionAction::Store, 1, None);
1171
1172 let args = vec!["-abn".to_string(), "value".to_string()];
1174 let (opts, remaining, _) = parser.parse_args(args).unwrap();
1175
1176 assert_eq!(
1177 opts.get("a"),
1178 Some(&ParsedValue::Single("true".to_string()))
1179 );
1180 assert_eq!(
1181 opts.get("b"),
1182 Some(&ParsedValue::Single("true".to_string()))
1183 );
1184 assert_eq!(
1185 opts.get("name"),
1186 Some(&ParsedValue::Single("value".to_string()))
1187 );
1188 assert!(remaining.is_empty());
1189 }
1190
1191 #[test]
1192 fn test_grouped_short_with_attached_value() {
1193 let mut parser = OptionParser::new();
1194 parser.add_option(&["-a"], "a", OptionAction::StoreConst, 0, Some("true"));
1195 parser.add_option(&["-n"], "name", OptionAction::Store, 1, None);
1196
1197 let args = vec!["-anVALUE".to_string()];
1199 let (opts, remaining, _) = parser.parse_args(args).unwrap();
1200
1201 assert_eq!(
1202 opts.get("a"),
1203 Some(&ParsedValue::Single("true".to_string()))
1204 );
1205 assert_eq!(
1206 opts.get("name"),
1207 Some(&ParsedValue::Single("VALUE".to_string()))
1208 );
1209 assert!(remaining.is_empty());
1210 }
1211
1212 #[test]
1217 fn test_long_option_with_space() {
1218 let mut parser = OptionParser::new();
1219 parser.add_option(&["--name"], "name", OptionAction::Store, 1, None);
1220
1221 let args = vec!["--name".to_string(), "value".to_string()];
1222 let (opts, remaining, _) = parser.parse_args(args).unwrap();
1223
1224 assert_eq!(
1225 opts.get("name"),
1226 Some(&ParsedValue::Single("value".to_string()))
1227 );
1228 assert!(remaining.is_empty());
1229 }
1230
1231 #[test]
1232 fn test_long_option_with_equals() {
1233 let mut parser = OptionParser::new();
1234 parser.add_option(&["--name"], "name", OptionAction::Store, 1, None);
1235
1236 let args = vec!["--name=value".to_string()];
1237 let (opts, remaining, _) = parser.parse_args(args).unwrap();
1238
1239 assert_eq!(
1240 opts.get("name"),
1241 Some(&ParsedValue::Single("value".to_string()))
1242 );
1243 assert!(remaining.is_empty());
1244 }
1245
1246 #[test]
1247 fn test_long_option_multiple_args() {
1248 let mut parser = OptionParser::new();
1249 parser.add_option(&["--point"], "point", OptionAction::Store, 2, None);
1250
1251 let args = vec!["--point".to_string(), "1".to_string(), "2".to_string()];
1252 let (opts, remaining, _) = parser.parse_args(args).unwrap();
1253
1254 assert_eq!(
1255 opts.get("point"),
1256 Some(&ParsedValue::Multiple(vec![
1257 "1".to_string(),
1258 "2".to_string()
1259 ]))
1260 );
1261 assert!(remaining.is_empty());
1262 }
1263
1264 #[test]
1269 fn test_double_dash_terminator() {
1270 let mut parser = OptionParser::new();
1271 parser.add_option(&["--name"], "name", OptionAction::Store, 1, None);
1272 parser.add_argument("files", -1);
1273
1274 let args = vec![
1275 "--name".to_string(),
1276 "test".to_string(),
1277 "--".to_string(),
1278 "--not-an-option".to_string(),
1279 "file.txt".to_string(),
1280 ];
1281 let (opts, remaining, _) = parser.parse_args(args).unwrap();
1282
1283 assert_eq!(
1284 opts.get("name"),
1285 Some(&ParsedValue::Single("test".to_string()))
1286 );
1287 assert_eq!(
1288 opts.get("files"),
1289 Some(&ParsedValue::Multiple(vec![
1290 "--not-an-option".to_string(),
1291 "file.txt".to_string()
1292 ]))
1293 );
1294 assert!(remaining.is_empty());
1295 }
1296
1297 #[test]
1302 fn test_interspersed_args_allowed() {
1303 let mut parser = OptionParser::new().allow_interspersed_args(true);
1304 parser.add_option(&["--name"], "name", OptionAction::Store, 1, None);
1305 parser.add_argument("files", -1);
1306
1307 let args = vec![
1308 "file1.txt".to_string(),
1309 "--name".to_string(),
1310 "test".to_string(),
1311 "file2.txt".to_string(),
1312 ];
1313 let (opts, remaining, _) = parser.parse_args(args).unwrap();
1314
1315 assert_eq!(
1316 opts.get("name"),
1317 Some(&ParsedValue::Single("test".to_string()))
1318 );
1319 assert_eq!(
1320 opts.get("files"),
1321 Some(&ParsedValue::Multiple(vec![
1322 "file1.txt".to_string(),
1323 "file2.txt".to_string()
1324 ]))
1325 );
1326 assert!(remaining.is_empty());
1327 }
1328
1329 #[test]
1330 fn test_interspersed_args_not_allowed() {
1331 let mut parser = OptionParser::new().allow_interspersed_args(false);
1332 parser.add_option(&["--name"], "name", OptionAction::Store, 1, None);
1333 parser.add_argument("files", -1);
1334
1335 let args = vec![
1336 "file1.txt".to_string(),
1337 "--name".to_string(),
1338 "test".to_string(),
1339 "file2.txt".to_string(),
1340 ];
1341 let (opts, _remaining, _) = parser.parse_args(args).unwrap();
1342
1343 assert!(opts.get("name").is_none() || opts.get("name") == Some(&ParsedValue::Unset));
1345 assert_eq!(
1346 opts.get("files"),
1347 Some(&ParsedValue::Multiple(vec![
1348 "file1.txt".to_string(),
1349 "--name".to_string(),
1350 "test".to_string(),
1351 "file2.txt".to_string()
1352 ]))
1353 );
1354 }
1355
1356 #[test]
1361 fn test_unknown_option_error() {
1362 let parser = OptionParser::new();
1363
1364 let args = vec!["--unknown".to_string()];
1365 let result = parser.parse_args(args);
1366
1367 assert!(result.is_err());
1368 match result.unwrap_err() {
1369 ClickError::NoSuchOption { option_name, .. } => {
1370 assert_eq!(option_name, "--unknown");
1371 }
1372 _ => panic!("Expected NoSuchOption error"),
1373 }
1374 }
1375
1376 #[test]
1377 fn test_unknown_option_with_suggestion() {
1378 let mut parser = OptionParser::new();
1379 parser.add_option(
1380 &["--help"],
1381 "help",
1382 OptionAction::StoreConst,
1383 0,
1384 Some("true"),
1385 );
1386
1387 let args = vec!["--hlep".to_string()];
1388 let result = parser.parse_args(args);
1389
1390 assert!(result.is_err());
1391 match result.unwrap_err() {
1392 ClickError::NoSuchOption {
1393 option_name,
1394 possibilities,
1395 ..
1396 } => {
1397 assert_eq!(option_name, "--hlep");
1398 assert!(possibilities.is_some());
1399 assert!(possibilities.unwrap().contains(&"--help".to_string()));
1400 }
1401 _ => panic!("Expected NoSuchOption error with suggestions"),
1402 }
1403 }
1404
1405 #[test]
1406 fn test_ignore_unknown_options() {
1407 let mut parser = OptionParser::new().ignore_unknown_options(true);
1408 parser.add_option(&["--name"], "name", OptionAction::Store, 1, None);
1409 parser.add_argument("files", -1);
1410
1411 let args = vec![
1412 "--unknown".to_string(),
1413 "--name".to_string(),
1414 "test".to_string(),
1415 ];
1416 let (opts, _, _) = parser.parse_args(args).unwrap();
1417
1418 assert_eq!(
1419 opts.get("name"),
1420 Some(&ParsedValue::Single("test".to_string()))
1421 );
1422 if let Some(ParsedValue::Multiple(files)) = opts.get("files") {
1424 assert!(files.contains(&"--unknown".to_string()));
1425 }
1426 }
1427
1428 #[test]
1433 fn test_count_action() {
1434 let mut parser = OptionParser::new();
1435 parser.add_option(
1436 &["-v", "--verbose"],
1437 "verbose",
1438 OptionAction::Count,
1439 0,
1440 None,
1441 );
1442
1443 let args = vec!["-v".to_string(), "-v".to_string(), "--verbose".to_string()];
1444 let (opts, _, _) = parser.parse_args(args).unwrap();
1445
1446 assert_eq!(opts.get("verbose"), Some(&ParsedValue::Count(3)));
1447 }
1448
1449 #[test]
1450 fn test_append_action() {
1451 let mut parser = OptionParser::new();
1452 parser.add_option(&["-f", "--file"], "files", OptionAction::Append, 1, None);
1453
1454 let args = vec![
1455 "-f".to_string(),
1456 "a.txt".to_string(),
1457 "--file".to_string(),
1458 "b.txt".to_string(),
1459 "-f".to_string(),
1460 "c.txt".to_string(),
1461 ];
1462 let (opts, _, _) = parser.parse_args(args).unwrap();
1463
1464 assert_eq!(
1465 opts.get("files"),
1466 Some(&ParsedValue::Multiple(vec![
1467 "a.txt".to_string(),
1468 "b.txt".to_string(),
1469 "c.txt".to_string()
1470 ]))
1471 );
1472 }
1473
1474 #[test]
1475 fn test_append_const_action() {
1476 let mut parser = OptionParser::new();
1477 parser.add_option(
1478 &["--debug"],
1479 "flags",
1480 OptionAction::AppendConst,
1481 0,
1482 Some("debug"),
1483 );
1484 parser.add_option(
1485 &["--trace"],
1486 "flags",
1487 OptionAction::AppendConst,
1488 0,
1489 Some("trace"),
1490 );
1491
1492 let args = vec!["--debug".to_string(), "--trace".to_string()];
1493 let (opts, _, _) = parser.parse_args(args).unwrap();
1494
1495 assert_eq!(
1496 opts.get("flags"),
1497 Some(&ParsedValue::Multiple(vec![
1498 "debug".to_string(),
1499 "trace".to_string()
1500 ]))
1501 );
1502 }
1503
1504 #[test]
1505 fn test_store_const_action() {
1506 let mut parser = OptionParser::new();
1507 parser.add_option(
1508 &["--debug"],
1509 "debug",
1510 OptionAction::StoreConst,
1511 0,
1512 Some("true"),
1513 );
1514
1515 let args = vec!["--debug".to_string()];
1516 let (opts, _, _) = parser.parse_args(args).unwrap();
1517
1518 assert_eq!(
1519 opts.get("debug"),
1520 Some(&ParsedValue::Single("true".to_string()))
1521 );
1522 }
1523
1524 #[test]
1529 fn test_single_positional_argument() {
1530 let mut parser = OptionParser::new();
1531 parser.add_argument("file", 1);
1532
1533 let args = vec!["input.txt".to_string()];
1534 let (opts, remaining, _) = parser.parse_args(args).unwrap();
1535
1536 assert_eq!(
1537 opts.get("file"),
1538 Some(&ParsedValue::Single("input.txt".to_string()))
1539 );
1540 assert!(remaining.is_empty());
1541 }
1542
1543 #[test]
1544 fn test_variadic_positional_argument() {
1545 let mut parser = OptionParser::new();
1546 parser.add_argument("files", -1);
1547
1548 let args = vec![
1549 "a.txt".to_string(),
1550 "b.txt".to_string(),
1551 "c.txt".to_string(),
1552 ];
1553 let (opts, remaining, _) = parser.parse_args(args).unwrap();
1554
1555 assert_eq!(
1556 opts.get("files"),
1557 Some(&ParsedValue::Multiple(vec![
1558 "a.txt".to_string(),
1559 "b.txt".to_string(),
1560 "c.txt".to_string()
1561 ]))
1562 );
1563 assert!(remaining.is_empty());
1564 }
1565
1566 #[test]
1567 fn test_multiple_positional_arguments() {
1568 let mut parser = OptionParser::new();
1569 parser.add_argument("source", 1);
1570 parser.add_argument("dest", 1);
1571
1572 let args = vec!["input.txt".to_string(), "output.txt".to_string()];
1573 let (opts, remaining, _) = parser.parse_args(args).unwrap();
1574
1575 assert_eq!(
1576 opts.get("source"),
1577 Some(&ParsedValue::Single("input.txt".to_string()))
1578 );
1579 assert_eq!(
1580 opts.get("dest"),
1581 Some(&ParsedValue::Single("output.txt".to_string()))
1582 );
1583 assert!(remaining.is_empty());
1584 }
1585
1586 #[test]
1587 fn test_positional_with_variadic() {
1588 let mut parser = OptionParser::new();
1589 parser.add_argument("dest", 1);
1590 parser.add_argument("sources", -1);
1591
1592 let args = vec![
1593 "out.txt".to_string(),
1594 "a.txt".to_string(),
1595 "b.txt".to_string(),
1596 ];
1597 let (opts, remaining, _) = parser.parse_args(args).unwrap();
1598
1599 assert_eq!(
1600 opts.get("dest"),
1601 Some(&ParsedValue::Single("out.txt".to_string()))
1602 );
1603 assert_eq!(
1604 opts.get("sources"),
1605 Some(&ParsedValue::Multiple(vec![
1606 "a.txt".to_string(),
1607 "b.txt".to_string()
1608 ]))
1609 );
1610 assert!(remaining.is_empty());
1611 }
1612
1613 #[test]
1614 fn test_missing_required_positional() {
1615 let mut parser = OptionParser::new();
1616 parser.add_argument("file", 1);
1617
1618 let args: Vec<String> = vec![];
1619 let (opts, _, _) = parser.parse_args(args).unwrap();
1620
1621 assert_eq!(opts.get("file"), Some(&ParsedValue::Unset));
1623 }
1624
1625 #[test]
1630 fn test_options_and_arguments() {
1631 let mut parser = OptionParser::new();
1632 parser.add_option(&["-n", "--name"], "name", OptionAction::Store, 1, None);
1633 parser.add_option(
1634 &["-v", "--verbose"],
1635 "verbose",
1636 OptionAction::Count,
1637 0,
1638 None,
1639 );
1640 parser.add_argument("file", 1);
1641
1642 let args = vec![
1643 "-v".to_string(),
1644 "--name".to_string(),
1645 "test".to_string(),
1646 "-v".to_string(),
1647 "input.txt".to_string(),
1648 ];
1649 let (opts, remaining, order) = parser.parse_args(args).unwrap();
1650
1651 assert_eq!(opts.get("verbose"), Some(&ParsedValue::Count(2)));
1652 assert_eq!(
1653 opts.get("name"),
1654 Some(&ParsedValue::Single("test".to_string()))
1655 );
1656 assert_eq!(
1657 opts.get("file"),
1658 Some(&ParsedValue::Single("input.txt".to_string()))
1659 );
1660 assert!(remaining.is_empty());
1661
1662 assert!(order.contains(&"verbose".to_string()));
1664 assert!(order.contains(&"name".to_string()));
1665 assert!(order.contains(&"file".to_string()));
1666 }
1667
1668 #[test]
1673 fn test_missing_option_value() {
1674 let mut parser = OptionParser::new();
1675 parser.add_option(&["--name"], "name", OptionAction::Store, 1, None);
1676
1677 let args = vec!["--name".to_string()];
1678 let result = parser.parse_args(args);
1679
1680 assert!(result.is_err());
1681 match result.unwrap_err() {
1682 ClickError::BadOptionUsage { option_name, .. } => {
1683 assert_eq!(option_name, "--name");
1684 }
1685 _ => panic!("Expected BadOptionUsage error"),
1686 }
1687 }
1688
1689 #[test]
1690 fn test_option_takes_no_value() {
1691 let mut parser = OptionParser::new();
1692 parser.add_option(
1693 &["--debug"],
1694 "debug",
1695 OptionAction::StoreConst,
1696 0,
1697 Some("true"),
1698 );
1699
1700 let args = vec!["--debug=value".to_string()];
1701 let result = parser.parse_args(args);
1702
1703 assert!(result.is_err());
1704 match result.unwrap_err() {
1705 ClickError::BadOptionUsage { option_name, .. } => {
1706 assert_eq!(option_name, "--debug");
1707 }
1708 _ => panic!("Expected BadOptionUsage error"),
1709 }
1710 }
1711
1712 #[test]
1717 fn test_token_normalize_func() {
1718 let mut parser = OptionParser::new().token_normalize_func(|s| s.to_lowercase());
1719 parser.add_option(&["--name"], "name", OptionAction::Store, 1, None);
1720
1721 let args = vec!["--NAME".to_string(), "value".to_string()];
1722 let (opts, _, _) = parser.parse_args(args).unwrap();
1723
1724 assert_eq!(
1725 opts.get("name"),
1726 Some(&ParsedValue::Single("value".to_string()))
1727 );
1728 }
1729
1730 #[test]
1735 fn test_parsed_value_accessors() {
1736 let single = ParsedValue::Single("test".to_string());
1737 assert_eq!(single.as_single(), Some("test"));
1738 assert!(single.as_multiple().is_none());
1739 assert!(single.as_count().is_none());
1740 assert!(single.as_flag().is_none());
1741 assert!(!single.is_unset());
1742
1743 let multiple = ParsedValue::Multiple(vec!["a".to_string(), "b".to_string()]);
1744 assert!(multiple.as_single().is_none());
1745 assert_eq!(
1746 multiple.as_multiple(),
1747 Some(&["a".to_string(), "b".to_string()][..])
1748 );
1749
1750 let count = ParsedValue::Count(5);
1751 assert_eq!(count.as_count(), Some(5));
1752
1753 let flag = ParsedValue::Flag(true);
1754 assert_eq!(flag.as_flag(), Some(true));
1755
1756 let unset = ParsedValue::Unset;
1757 assert!(unset.is_unset());
1758 }
1759
1760 #[test]
1765 fn test_unpack_args_simple() {
1766 let args = vec!["a".to_string(), "b".to_string()];
1767 let specs = vec![1, 1];
1768 let (result, remaining) = unpack_args(&args, &specs).unwrap();
1769
1770 assert_eq!(result.len(), 2);
1771 assert_eq!(result[0], ParsedValue::Single("a".to_string()));
1772 assert_eq!(result[1], ParsedValue::Single("b".to_string()));
1773 assert!(remaining.is_empty());
1774 }
1775
1776 #[test]
1777 fn test_unpack_args_variadic() {
1778 let args = vec!["a".to_string(), "b".to_string(), "c".to_string()];
1779 let specs = vec![-1]; let (result, remaining) = unpack_args(&args, &specs).unwrap();
1781
1782 assert_eq!(result.len(), 1);
1783 assert_eq!(
1784 result[0],
1785 ParsedValue::Multiple(vec!["a".to_string(), "b".to_string(), "c".to_string()])
1786 );
1787 assert!(remaining.is_empty());
1788 }
1789
1790 #[test]
1791 fn test_unpack_args_with_variadic_in_middle() {
1792 let args = vec![
1793 "dest".to_string(),
1794 "a".to_string(),
1795 "b".to_string(),
1796 "c".to_string(),
1797 ];
1798 let specs = vec![1, -1]; let (result, remaining) = unpack_args(&args, &specs).unwrap();
1800
1801 assert_eq!(result.len(), 2);
1802 assert_eq!(result[0], ParsedValue::Single("dest".to_string()));
1803 assert_eq!(
1804 result[1],
1805 ParsedValue::Multiple(vec!["a".to_string(), "b".to_string(), "c".to_string()])
1806 );
1807 assert!(remaining.is_empty());
1808 }
1809
1810 #[test]
1811 fn test_unpack_args_missing() {
1812 let args = vec!["a".to_string()];
1813 let specs = vec![1, 1];
1814 let (result, remaining) = unpack_args(&args, &specs).unwrap();
1815
1816 assert_eq!(result.len(), 2);
1817 assert_eq!(result[0], ParsedValue::Single("a".to_string()));
1818 assert_eq!(result[1], ParsedValue::Unset);
1819 assert!(remaining.is_empty());
1820 }
1821
1822 #[test]
1823 fn test_unpack_args_empty_variadic() {
1824 let args: Vec<String> = vec![];
1825 let specs = vec![-1];
1826 let (result, remaining) = unpack_args(&args, &specs).unwrap();
1827
1828 assert_eq!(result.len(), 1);
1829 assert_eq!(result[0], ParsedValue::Multiple(vec![]));
1830 assert!(remaining.is_empty());
1831 }
1832
1833 #[test]
1838 fn test_unpack_args_optional_with_value() {
1839 let args = vec!["value".to_string()];
1840 let specs = vec![NARGS_OPTIONAL]; let (result, remaining) = unpack_args(&args, &specs).unwrap();
1842
1843 assert_eq!(result.len(), 1);
1844 assert_eq!(result[0], ParsedValue::Single("value".to_string()));
1845 assert!(remaining.is_empty());
1846 }
1847
1848 #[test]
1849 fn test_unpack_args_optional_without_value() {
1850 let args: Vec<String> = vec![];
1851 let specs = vec![NARGS_OPTIONAL]; let (result, remaining) = unpack_args(&args, &specs).unwrap();
1853
1854 assert_eq!(result.len(), 1);
1855 assert_eq!(result[0], ParsedValue::Unset);
1856 assert!(remaining.is_empty());
1857 }
1858
1859 #[test]
1860 fn test_unpack_args_optional_after_required() {
1861 let args = vec!["required".to_string(), "optional".to_string()];
1862 let specs = vec![1, NARGS_OPTIONAL]; let (result, remaining) = unpack_args(&args, &specs).unwrap();
1864
1865 assert_eq!(result.len(), 2);
1866 assert_eq!(result[0], ParsedValue::Single("required".to_string()));
1867 assert_eq!(result[1], ParsedValue::Single("optional".to_string()));
1868 assert!(remaining.is_empty());
1869 }
1870
1871 #[test]
1872 fn test_unpack_args_optional_missing_after_required() {
1873 let args = vec!["required".to_string()];
1874 let specs = vec![1, NARGS_OPTIONAL]; let (result, remaining) = unpack_args(&args, &specs).unwrap();
1876
1877 assert_eq!(result.len(), 2);
1878 assert_eq!(result[0], ParsedValue::Single("required".to_string()));
1879 assert_eq!(result[1], ParsedValue::Unset);
1880 assert!(remaining.is_empty());
1881 }
1882
1883 #[test]
1884 fn test_unpack_args_optional_before_required_preserves_required() {
1885 let args = vec!["x".to_string()];
1887 let specs = vec![NARGS_OPTIONAL, 1]; let (result, remaining) = unpack_args(&args, &specs).unwrap();
1889
1890 assert_eq!(result.len(), 2);
1891 assert_eq!(result[0], ParsedValue::Unset);
1893 assert_eq!(result[1], ParsedValue::Single("x".to_string()));
1895 assert!(remaining.is_empty());
1896 }
1897
1898 #[test]
1899 fn test_unpack_args_optional_before_required_with_both() {
1900 let args = vec!["opt".to_string(), "req".to_string()];
1902 let specs = vec![NARGS_OPTIONAL, 1]; let (result, remaining) = unpack_args(&args, &specs).unwrap();
1904
1905 assert_eq!(result.len(), 2);
1906 assert_eq!(result[0], ParsedValue::Single("opt".to_string()));
1907 assert_eq!(result[1], ParsedValue::Single("req".to_string()));
1908 assert!(remaining.is_empty());
1909 }
1910
1911 #[test]
1916 fn test_unpack_args_two_variadic_error() {
1917 let args = vec!["a".to_string(), "b".to_string()];
1918 let specs = vec![-1, -1]; let result = unpack_args(&args, &specs);
1920
1921 assert!(result.is_err());
1922 let err = result.unwrap_err();
1923 assert!(err.to_string().contains("variadic"));
1924 }
1925
1926 #[test]
1931 fn test_option_with_optional_value_explicit() {
1932 let mut parser = OptionParser::new();
1933 parser.add_option_ex(
1934 &["--opt"],
1935 "opt",
1936 OptionAction::Store,
1937 1,
1938 None,
1939 true, );
1941
1942 let args = vec!["--opt=value".to_string()];
1944 let (opts, _, _) = parser.parse_args(args).unwrap();
1945 assert_eq!(
1946 opts.get("opt"),
1947 Some(&ParsedValue::Single("value".to_string()))
1948 );
1949 }
1950
1951 #[test]
1952 fn test_option_with_optional_value_followed_by_option() {
1953 let mut parser = OptionParser::new();
1954 parser.add_option_ex(
1955 &["--opt"],
1956 "opt",
1957 OptionAction::Store,
1958 1,
1959 None,
1960 true, );
1962 parser.add_option(
1963 &["--other"],
1964 "other",
1965 OptionAction::StoreConst,
1966 0,
1967 Some("true"),
1968 );
1969
1970 let args = vec!["--opt".to_string(), "--other".to_string()];
1972 let (opts, _, _) = parser.parse_args(args).unwrap();
1973 assert_eq!(opts.get("opt"), Some(&ParsedValue::FlagNeedsValue));
1974 assert_eq!(
1975 opts.get("other"),
1976 Some(&ParsedValue::Single("true".to_string()))
1977 );
1978 }
1979
1980 #[test]
1981 fn test_option_with_optional_value_with_value() {
1982 let mut parser = OptionParser::new();
1983 parser.add_option_ex(
1984 &["--opt"],
1985 "opt",
1986 OptionAction::Store,
1987 1,
1988 None,
1989 true, );
1991
1992 let args = vec!["--opt".to_string(), "value".to_string()];
1994 let (opts, _, _) = parser.parse_args(args).unwrap();
1995 assert_eq!(
1996 opts.get("opt"),
1997 Some(&ParsedValue::Single("value".to_string()))
1998 );
1999 }
2000
2001 #[test]
2002 fn test_option_with_optional_value_at_end() {
2003 let mut parser = OptionParser::new();
2004 parser.add_option_ex(
2005 &["--opt"],
2006 "opt",
2007 OptionAction::Store,
2008 1,
2009 None,
2010 true, );
2012
2013 let args = vec!["--opt".to_string()];
2015 let (opts, _, _) = parser.parse_args(args).unwrap();
2016 assert_eq!(opts.get("opt"), Some(&ParsedValue::FlagNeedsValue));
2017 }
2018
2019 #[test]
2024 fn test_multi_value_argument_incomplete() {
2025 let mut parser = OptionParser::new();
2026 parser.add_argument("pair", 2); let args = vec!["first".to_string()];
2030 let result = parser.parse_args(args);
2031
2032 assert!(result.is_err());
2033 let err = result.unwrap_err();
2034 assert!(err.to_string().contains("takes 2 values"));
2035 }
2036
2037 #[test]
2038 fn test_multi_value_argument_complete() {
2039 let mut parser = OptionParser::new();
2040 parser.add_argument("pair", 2); let args = vec!["first".to_string(), "second".to_string()];
2044 let (opts, remaining, _) = parser.parse_args(args).unwrap();
2045
2046 assert_eq!(
2047 opts.get("pair"),
2048 Some(&ParsedValue::Multiple(vec![
2049 "first".to_string(),
2050 "second".to_string()
2051 ]))
2052 );
2053 assert!(remaining.is_empty());
2054 }
2055}