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.insert(prefix.chars().next().unwrap().to_string());
614 if prefix.len() == 2 {
615 self.opt_prefixes.insert(prefix.to_string());
616 }
617
618 if prefix.len() == 1 && value.len() == 1 {
620 short_opts.push(opt.clone());
621 } else {
622 long_opts.push(opt.clone());
623 }
624 }
625 }
626
627 let name = long_opts
628 .first()
629 .or(short_opts.first())
630 .cloned()
631 .unwrap_or_else(|| dest.to_string());
632
633 let parser_opt = ParserOption {
634 name,
635 short_opts: short_opts.clone(),
636 long_opts: long_opts.clone(),
637 action,
638 nargs,
639 const_value: const_value.map(String::from),
640 dest: dest.to_string(),
641 flag_needs_value,
642 };
643
644 for opt in &short_opts {
646 self.short_opt.insert(opt.clone(), parser_opt.clone());
647 }
648 for opt in &long_opts {
649 self.long_opt.insert(opt.clone(), parser_opt.clone());
650 }
651 }
652
653 pub fn add_argument(&mut self, dest: &str, nargs: i32) {
660 self.args.push(ParserArgument {
661 name: dest.to_string(),
662 nargs,
663 dest: dest.to_string(),
664 });
665 }
666
667 pub fn parse_args(&self, args: Vec<String>) -> ParseResult {
681 let mut state = ParsingState::new(args);
682
683 self.process_args_for_options(&mut state)?;
684 self.process_args_for_args(&mut state)?;
685
686 Ok((state.opts, state.largs, state.order))
687 }
688
689 fn process_args_for_options(&self, state: &mut ParsingState) -> Result<(), ClickError> {
691 while let Some(arg) = state.rargs.pop_front() {
692 let arglen = arg.len();
693
694 if arg == "--" {
696 return Ok(());
697 }
698
699 let first_char = arg.chars().next().unwrap_or(' ');
701 let is_option_like = self.opt_prefixes.contains(&first_char.to_string()) && arglen > 1;
702
703 if is_option_like {
704 self.process_opts(&arg, state)?;
705 } else if self.allow_interspersed_args {
706 state.largs.push(arg);
707 } else {
708 state.rargs.push_front(arg);
710 return Ok(());
711 }
712 }
713 Ok(())
714 }
715
716 fn process_args_for_args(&self, state: &mut ParsingState) -> Result<(), ClickError> {
718 let all_args: Vec<String> = state
720 .largs
721 .drain(..)
722 .chain(state.rargs.drain(..))
723 .collect();
724
725 let nargs_spec: Vec<i32> = self.args.iter().map(|a| a.nargs).collect();
727
728 let (parsed, remaining) = unpack_args(&all_args, &nargs_spec)?;
730
731 for (idx, arg_def) in self.args.iter().enumerate() {
733 if idx < parsed.len() {
734 let value = &parsed[idx];
735
736 if arg_def.nargs > 1 {
738 if let ParsedValue::Multiple(ref values) = value {
739 if values.len() < arg_def.nargs as usize {
740 return Err(ClickError::bad_argument_usage(
741 format!(
742 "Argument '{}' takes {} values.",
743 arg_def.dest, arg_def.nargs
744 ),
745 ));
746 }
747 }
748 }
749
750 state.opts.insert(arg_def.dest.clone(), value.clone());
751 if !value.is_unset() {
752 state.order.push(arg_def.dest.clone());
753 }
754 } else {
755 state.opts.insert(arg_def.dest.clone(), ParsedValue::Unset);
756 }
757 }
758
759 state.largs = remaining;
760 Ok(())
761 }
762
763 fn process_opts(&self, arg: &str, state: &mut ParsingState) -> Result<(), ClickError> {
765 let mut explicit_value = None;
766 let long_opt;
767
768 if let Some(eq_pos) = arg.find('=') {
770 long_opt = arg[..eq_pos].to_string();
771 explicit_value = Some(arg[eq_pos + 1..].to_string());
772 } else {
773 long_opt = arg.to_string();
774 }
775
776 let norm_long_opt = self.normalize_opt(&long_opt);
777
778 match self.match_long_opt(&norm_long_opt, explicit_value.clone(), state) {
780 Ok(()) => Ok(()),
781 Err(ClickError::NoSuchOption { .. }) => {
782 if let Some((prefix, _)) = split_opt(arg) {
784 if prefix.len() == 1 {
785 return self.match_short_opt(arg, state);
786 }
787 }
788
789 if self.ignore_unknown_options {
791 state.largs.push(arg.to_string());
792 return Ok(());
793 }
794
795 self.match_long_opt(&norm_long_opt, explicit_value, state)
797 }
798 Err(e) => Err(e),
799 }
800 }
801
802 fn match_long_opt(
804 &self,
805 opt: &str,
806 explicit_value: Option<String>,
807 state: &mut ParsingState,
808 ) -> Result<(), ClickError> {
809 let option = match self.long_opt.get(opt) {
810 Some(o) => o.clone(),
811 None => {
812 let all_opts: Vec<&str> = self.long_opt.keys().map(|s| s.as_str()).collect();
814 let possibilities = get_close_matches(opt, &all_opts, 3);
815
816 return Err(if possibilities.is_empty() {
817 ClickError::no_such_option(opt)
818 } else {
819 ClickError::no_such_option_with_suggestions(opt, possibilities)
820 });
821 }
822 };
823
824 if option.takes_value() {
825 if let Some(val) = explicit_value {
827 state.rargs.push_front(val);
828 }
829
830 let value = self.get_value_from_state(opt, &option, state)?;
831 self.process_option(&option, value, state);
832 } else if explicit_value.is_some() {
833 return Err(ClickError::bad_option_usage(
834 opt,
835 format!("Option '{}' does not take a value.", opt),
836 ));
837 } else {
838 self.process_option(&option, ParsedValue::Unset, state);
839 }
840
841 Ok(())
842 }
843
844 fn match_short_opt(&self, arg: &str, state: &mut ParsingState) -> Result<(), ClickError> {
846 let prefix = arg.chars().next().unwrap();
847 let mut i = 1;
848 let chars: Vec<char> = arg.chars().collect();
849 let mut unknown_options = Vec::new();
850
851 while i < chars.len() {
852 let ch = chars[i];
853 let opt = self.normalize_opt(&format!("{}{}", prefix, ch));
854 i += 1;
855
856 let option = match self.short_opt.get(&opt) {
857 Some(o) => o.clone(),
858 None => {
859 if self.ignore_unknown_options {
860 unknown_options.push(ch);
861 continue;
862 }
863 return Err(ClickError::no_such_option(&opt));
864 }
865 };
866
867 if option.takes_value() {
868 if i < chars.len() {
870 let value: String = chars[i..].iter().collect();
871 state.rargs.push_front(value);
872 }
873
874 let value = self.get_value_from_state(&opt, &option, state)?;
875 self.process_option(&option, value, state);
876
877 break;
879 } else {
880 self.process_option(&option, ParsedValue::Unset, state);
881 }
882 }
883
884 if self.ignore_unknown_options && !unknown_options.is_empty() {
886 let combined: String =
887 std::iter::once(prefix).chain(unknown_options).collect();
888 state.largs.push(combined);
889 }
890
891 Ok(())
892 }
893
894 fn get_value_from_state(
896 &self,
897 option_name: &str,
898 option: &ParserOption,
899 state: &mut ParsingState,
900 ) -> Result<ParsedValue, ClickError> {
901 let nargs = option.nargs;
902
903 if nargs == NARGS_OPTIONAL {
905 if let Some(next_arg) = state.rargs.front() {
907 let first_char = next_arg.chars().next().unwrap_or(' ');
909 let looks_like_option = self.opt_prefixes.contains(&first_char.to_string())
910 && next_arg.len() > 1;
911
912 if looks_like_option && option.flag_needs_value {
913 return Ok(ParsedValue::FlagNeedsValue);
915 }
916 } else if option.flag_needs_value {
917 return Ok(ParsedValue::FlagNeedsValue);
919 }
920
921 if let Some(value) = state.rargs.pop_front() {
923 return Ok(ParsedValue::Single(value));
924 } else {
925 return Ok(ParsedValue::Unset);
926 }
927 }
928
929 if nargs <= 0 {
930 return Ok(ParsedValue::Unset);
932 }
933
934 let rargs_len = state.rargs.len() as i32;
935
936 if rargs_len < nargs {
938 if option.flag_needs_value {
940 return Ok(ParsedValue::FlagNeedsValue);
941 }
942 return Err(ClickError::bad_option_usage(
943 option_name,
944 if nargs == 1 {
945 format!("Option '{}' requires an argument.", option_name)
946 } else {
947 format!("Option '{}' requires {} arguments.", option_name, nargs)
948 },
949 ));
950 }
951
952 if nargs == 1 {
953 if option.flag_needs_value {
955 if let Some(next_arg) = state.rargs.front() {
956 let first_char = next_arg.chars().next().unwrap_or(' ');
957 let looks_like_option = self.opt_prefixes.contains(&first_char.to_string())
958 && next_arg.len() > 1;
959
960 if looks_like_option {
961 return Ok(ParsedValue::FlagNeedsValue);
962 }
963 }
964 }
965
966 let value = state.rargs.pop_front().unwrap();
967 Ok(ParsedValue::Single(value))
968 } else {
969 let values: Vec<String> = (0..nargs)
970 .filter_map(|_| state.rargs.pop_front())
971 .collect();
972 Ok(ParsedValue::Multiple(values))
973 }
974 }
975
976 fn process_option(&self, option: &ParserOption, value: ParsedValue, state: &mut ParsingState) {
978 match option.action {
979 OptionAction::Store => {
980 state.opts.insert(option.dest.clone(), value);
981 }
982 OptionAction::StoreConst => {
983 let const_val = option
984 .const_value
985 .clone()
986 .map(ParsedValue::Single)
987 .unwrap_or(ParsedValue::Flag(true));
988 state.opts.insert(option.dest.clone(), const_val);
989 }
990 OptionAction::Append => {
991 let entry = state
992 .opts
993 .entry(option.dest.clone())
994 .or_insert_with(|| ParsedValue::Multiple(Vec::new()));
995 if let ParsedValue::Multiple(ref mut vec) = entry {
996 match value {
997 ParsedValue::Single(s) => vec.push(s),
998 ParsedValue::Multiple(v) => vec.extend(v),
999 ParsedValue::FlagNeedsValue => {
1000 state.opts.insert(
1004 format!("__click_internal_flag_needs_value_{}", option.dest),
1005 ParsedValue::Flag(true),
1006 );
1007 }
1008 _ => {}
1009 }
1010 }
1011 }
1012 OptionAction::AppendConst => {
1013 let entry = state
1014 .opts
1015 .entry(option.dest.clone())
1016 .or_insert_with(|| ParsedValue::Multiple(Vec::new()));
1017 if let ParsedValue::Multiple(ref mut vec) = entry {
1018 if let Some(ref const_val) = option.const_value {
1019 vec.push(const_val.clone());
1020 }
1021 }
1022 }
1023 OptionAction::Count => {
1024 let entry = state
1025 .opts
1026 .entry(option.dest.clone())
1027 .or_insert(ParsedValue::Count(0));
1028 if let ParsedValue::Count(ref mut n) = entry {
1029 *n += 1;
1030 }
1031 }
1032 }
1033
1034 state.order.push(option.dest.clone());
1035 }
1036}
1037
1038#[cfg(test)]
1043mod tests {
1044 use super::*;
1045
1046 #[test]
1051 fn test_split_opt_long() {
1052 assert_eq!(split_opt("--name"), Some(("--", "name")));
1053 assert_eq!(split_opt("--full-name"), Some(("--", "full-name")));
1054 }
1055
1056 #[test]
1057 fn test_split_opt_short() {
1058 assert_eq!(split_opt("-n"), Some(("-", "n")));
1059 assert_eq!(split_opt("-abc"), Some(("-", "abc")));
1060 }
1061
1062 #[test]
1063 fn test_split_opt_no_prefix() {
1064 assert_eq!(split_opt("name"), None);
1065 assert_eq!(split_opt(""), None);
1066 }
1067
1068 #[test]
1069 fn test_split_opt_just_dashes() {
1070 assert_eq!(split_opt("-"), None);
1071 assert_eq!(split_opt("--"), None);
1072 }
1073
1074 #[test]
1079 fn test_edit_distance() {
1080 assert_eq!(edit_distance("", ""), 0);
1081 assert_eq!(edit_distance("a", ""), 1);
1082 assert_eq!(edit_distance("", "b"), 1);
1083 assert_eq!(edit_distance("abc", "abc"), 0);
1084 assert_eq!(edit_distance("abc", "abd"), 1);
1085 assert_eq!(edit_distance("help", "hlep"), 2);
1086 assert_eq!(edit_distance("kitten", "sitting"), 3);
1087 }
1088
1089 #[test]
1090 fn test_get_close_matches() {
1091 let opts = vec!["--help", "--hello", "--version", "--verbose"];
1092 let matches = get_close_matches("--hlep", &opts, 3);
1093 assert!(matches.contains(&"--help".to_string()));
1094 }
1095
1096 #[test]
1101 fn test_short_option_with_space() {
1102 let mut parser = OptionParser::new();
1103 parser.add_option(&["-n"], "name", OptionAction::Store, 1, None);
1104
1105 let args = vec!["-n".to_string(), "value".to_string()];
1106 let (opts, remaining, _) = parser.parse_args(args).unwrap();
1107
1108 assert_eq!(
1109 opts.get("name"),
1110 Some(&ParsedValue::Single("value".to_string()))
1111 );
1112 assert!(remaining.is_empty());
1113 }
1114
1115 #[test]
1116 fn test_short_option_with_equals() {
1117 let mut parser = OptionParser::new();
1120 parser.add_option(&["-n"], "name", OptionAction::Store, 1, None);
1121
1122 let args = vec!["-n=value".to_string()];
1123 let (opts, remaining, _) = parser.parse_args(args).unwrap();
1124
1125 assert_eq!(
1127 opts.get("name"),
1128 Some(&ParsedValue::Single("=value".to_string()))
1129 );
1130 assert!(remaining.is_empty());
1131 }
1132
1133 #[test]
1134 fn test_short_option_attached_value() {
1135 let mut parser = OptionParser::new();
1136 parser.add_option(&["-n"], "name", OptionAction::Store, 1, None);
1137
1138 let args = vec!["-nvalue".to_string()];
1139 let (opts, remaining, _) = parser.parse_args(args).unwrap();
1140
1141 assert_eq!(
1142 opts.get("name"),
1143 Some(&ParsedValue::Single("value".to_string()))
1144 );
1145 assert!(remaining.is_empty());
1146 }
1147
1148 #[test]
1149 fn test_grouped_short_flags() {
1150 let mut parser = OptionParser::new();
1151 parser.add_option(&["-a"], "a", OptionAction::StoreConst, 0, Some("true"));
1152 parser.add_option(&["-b"], "b", OptionAction::StoreConst, 0, Some("true"));
1153 parser.add_option(&["-c"], "c", OptionAction::StoreConst, 0, Some("true"));
1154
1155 let args = vec!["-abc".to_string()];
1156 let (opts, remaining, _) = parser.parse_args(args).unwrap();
1157
1158 assert_eq!(
1159 opts.get("a"),
1160 Some(&ParsedValue::Single("true".to_string()))
1161 );
1162 assert_eq!(
1163 opts.get("b"),
1164 Some(&ParsedValue::Single("true".to_string()))
1165 );
1166 assert_eq!(
1167 opts.get("c"),
1168 Some(&ParsedValue::Single("true".to_string()))
1169 );
1170 assert!(remaining.is_empty());
1171 }
1172
1173 #[test]
1174 fn test_grouped_short_with_value() {
1175 let mut parser = OptionParser::new();
1176 parser.add_option(&["-a"], "a", OptionAction::StoreConst, 0, Some("true"));
1177 parser.add_option(&["-b"], "b", OptionAction::StoreConst, 0, Some("true"));
1178 parser.add_option(&["-n"], "name", OptionAction::Store, 1, None);
1179
1180 let args = vec!["-abn".to_string(), "value".to_string()];
1182 let (opts, remaining, _) = parser.parse_args(args).unwrap();
1183
1184 assert_eq!(
1185 opts.get("a"),
1186 Some(&ParsedValue::Single("true".to_string()))
1187 );
1188 assert_eq!(
1189 opts.get("b"),
1190 Some(&ParsedValue::Single("true".to_string()))
1191 );
1192 assert_eq!(
1193 opts.get("name"),
1194 Some(&ParsedValue::Single("value".to_string()))
1195 );
1196 assert!(remaining.is_empty());
1197 }
1198
1199 #[test]
1200 fn test_grouped_short_with_attached_value() {
1201 let mut parser = OptionParser::new();
1202 parser.add_option(&["-a"], "a", OptionAction::StoreConst, 0, Some("true"));
1203 parser.add_option(&["-n"], "name", OptionAction::Store, 1, None);
1204
1205 let args = vec!["-anVALUE".to_string()];
1207 let (opts, remaining, _) = parser.parse_args(args).unwrap();
1208
1209 assert_eq!(
1210 opts.get("a"),
1211 Some(&ParsedValue::Single("true".to_string()))
1212 );
1213 assert_eq!(
1214 opts.get("name"),
1215 Some(&ParsedValue::Single("VALUE".to_string()))
1216 );
1217 assert!(remaining.is_empty());
1218 }
1219
1220 #[test]
1225 fn test_long_option_with_space() {
1226 let mut parser = OptionParser::new();
1227 parser.add_option(&["--name"], "name", OptionAction::Store, 1, None);
1228
1229 let args = vec!["--name".to_string(), "value".to_string()];
1230 let (opts, remaining, _) = parser.parse_args(args).unwrap();
1231
1232 assert_eq!(
1233 opts.get("name"),
1234 Some(&ParsedValue::Single("value".to_string()))
1235 );
1236 assert!(remaining.is_empty());
1237 }
1238
1239 #[test]
1240 fn test_long_option_with_equals() {
1241 let mut parser = OptionParser::new();
1242 parser.add_option(&["--name"], "name", OptionAction::Store, 1, None);
1243
1244 let args = vec!["--name=value".to_string()];
1245 let (opts, remaining, _) = parser.parse_args(args).unwrap();
1246
1247 assert_eq!(
1248 opts.get("name"),
1249 Some(&ParsedValue::Single("value".to_string()))
1250 );
1251 assert!(remaining.is_empty());
1252 }
1253
1254 #[test]
1255 fn test_long_option_multiple_args() {
1256 let mut parser = OptionParser::new();
1257 parser.add_option(&["--point"], "point", OptionAction::Store, 2, None);
1258
1259 let args = vec!["--point".to_string(), "1".to_string(), "2".to_string()];
1260 let (opts, remaining, _) = parser.parse_args(args).unwrap();
1261
1262 assert_eq!(
1263 opts.get("point"),
1264 Some(&ParsedValue::Multiple(vec!["1".to_string(), "2".to_string()]))
1265 );
1266 assert!(remaining.is_empty());
1267 }
1268
1269 #[test]
1274 fn test_double_dash_terminator() {
1275 let mut parser = OptionParser::new();
1276 parser.add_option(&["--name"], "name", OptionAction::Store, 1, None);
1277 parser.add_argument("files", -1);
1278
1279 let args = vec![
1280 "--name".to_string(),
1281 "test".to_string(),
1282 "--".to_string(),
1283 "--not-an-option".to_string(),
1284 "file.txt".to_string(),
1285 ];
1286 let (opts, remaining, _) = parser.parse_args(args).unwrap();
1287
1288 assert_eq!(
1289 opts.get("name"),
1290 Some(&ParsedValue::Single("test".to_string()))
1291 );
1292 assert_eq!(
1293 opts.get("files"),
1294 Some(&ParsedValue::Multiple(vec![
1295 "--not-an-option".to_string(),
1296 "file.txt".to_string()
1297 ]))
1298 );
1299 assert!(remaining.is_empty());
1300 }
1301
1302 #[test]
1307 fn test_interspersed_args_allowed() {
1308 let mut parser = OptionParser::new().allow_interspersed_args(true);
1309 parser.add_option(&["--name"], "name", OptionAction::Store, 1, None);
1310 parser.add_argument("files", -1);
1311
1312 let args = vec![
1313 "file1.txt".to_string(),
1314 "--name".to_string(),
1315 "test".to_string(),
1316 "file2.txt".to_string(),
1317 ];
1318 let (opts, remaining, _) = parser.parse_args(args).unwrap();
1319
1320 assert_eq!(
1321 opts.get("name"),
1322 Some(&ParsedValue::Single("test".to_string()))
1323 );
1324 assert_eq!(
1325 opts.get("files"),
1326 Some(&ParsedValue::Multiple(vec![
1327 "file1.txt".to_string(),
1328 "file2.txt".to_string()
1329 ]))
1330 );
1331 assert!(remaining.is_empty());
1332 }
1333
1334 #[test]
1335 fn test_interspersed_args_not_allowed() {
1336 let mut parser = OptionParser::new().allow_interspersed_args(false);
1337 parser.add_option(&["--name"], "name", OptionAction::Store, 1, None);
1338 parser.add_argument("files", -1);
1339
1340 let args = vec![
1341 "file1.txt".to_string(),
1342 "--name".to_string(),
1343 "test".to_string(),
1344 "file2.txt".to_string(),
1345 ];
1346 let (opts, _remaining, _) = parser.parse_args(args).unwrap();
1347
1348 assert!(opts.get("name").is_none() || opts.get("name") == Some(&ParsedValue::Unset));
1350 assert_eq!(
1351 opts.get("files"),
1352 Some(&ParsedValue::Multiple(vec![
1353 "file1.txt".to_string(),
1354 "--name".to_string(),
1355 "test".to_string(),
1356 "file2.txt".to_string()
1357 ]))
1358 );
1359 }
1360
1361 #[test]
1366 fn test_unknown_option_error() {
1367 let parser = OptionParser::new();
1368
1369 let args = vec!["--unknown".to_string()];
1370 let result = parser.parse_args(args);
1371
1372 assert!(result.is_err());
1373 match result.unwrap_err() {
1374 ClickError::NoSuchOption { option_name, .. } => {
1375 assert_eq!(option_name, "--unknown");
1376 }
1377 _ => panic!("Expected NoSuchOption error"),
1378 }
1379 }
1380
1381 #[test]
1382 fn test_unknown_option_with_suggestion() {
1383 let mut parser = OptionParser::new();
1384 parser.add_option(&["--help"], "help", OptionAction::StoreConst, 0, Some("true"));
1385
1386 let args = vec!["--hlep".to_string()];
1387 let result = parser.parse_args(args);
1388
1389 assert!(result.is_err());
1390 match result.unwrap_err() {
1391 ClickError::NoSuchOption {
1392 option_name,
1393 possibilities,
1394 ..
1395 } => {
1396 assert_eq!(option_name, "--hlep");
1397 assert!(possibilities.is_some());
1398 assert!(possibilities.unwrap().contains(&"--help".to_string()));
1399 }
1400 _ => panic!("Expected NoSuchOption error with suggestions"),
1401 }
1402 }
1403
1404 #[test]
1405 fn test_ignore_unknown_options() {
1406 let mut parser = OptionParser::new().ignore_unknown_options(true);
1407 parser.add_option(&["--name"], "name", OptionAction::Store, 1, None);
1408 parser.add_argument("files", -1);
1409
1410 let args = vec![
1411 "--unknown".to_string(),
1412 "--name".to_string(),
1413 "test".to_string(),
1414 ];
1415 let (opts, _, _) = parser.parse_args(args).unwrap();
1416
1417 assert_eq!(
1418 opts.get("name"),
1419 Some(&ParsedValue::Single("test".to_string()))
1420 );
1421 if let Some(ParsedValue::Multiple(files)) = opts.get("files") {
1423 assert!(files.contains(&"--unknown".to_string()));
1424 }
1425 }
1426
1427 #[test]
1432 fn test_count_action() {
1433 let mut parser = OptionParser::new();
1434 parser.add_option(&["-v", "--verbose"], "verbose", OptionAction::Count, 0, None);
1435
1436 let args = vec![
1437 "-v".to_string(),
1438 "-v".to_string(),
1439 "--verbose".to_string(),
1440 ];
1441 let (opts, _, _) = parser.parse_args(args).unwrap();
1442
1443 assert_eq!(opts.get("verbose"), Some(&ParsedValue::Count(3)));
1444 }
1445
1446 #[test]
1447 fn test_append_action() {
1448 let mut parser = OptionParser::new();
1449 parser.add_option(&["-f", "--file"], "files", OptionAction::Append, 1, None);
1450
1451 let args = vec![
1452 "-f".to_string(),
1453 "a.txt".to_string(),
1454 "--file".to_string(),
1455 "b.txt".to_string(),
1456 "-f".to_string(),
1457 "c.txt".to_string(),
1458 ];
1459 let (opts, _, _) = parser.parse_args(args).unwrap();
1460
1461 assert_eq!(
1462 opts.get("files"),
1463 Some(&ParsedValue::Multiple(vec![
1464 "a.txt".to_string(),
1465 "b.txt".to_string(),
1466 "c.txt".to_string()
1467 ]))
1468 );
1469 }
1470
1471 #[test]
1472 fn test_append_const_action() {
1473 let mut parser = OptionParser::new();
1474 parser.add_option(
1475 &["--debug"],
1476 "flags",
1477 OptionAction::AppendConst,
1478 0,
1479 Some("debug"),
1480 );
1481 parser.add_option(
1482 &["--trace"],
1483 "flags",
1484 OptionAction::AppendConst,
1485 0,
1486 Some("trace"),
1487 );
1488
1489 let args = vec!["--debug".to_string(), "--trace".to_string()];
1490 let (opts, _, _) = parser.parse_args(args).unwrap();
1491
1492 assert_eq!(
1493 opts.get("flags"),
1494 Some(&ParsedValue::Multiple(vec![
1495 "debug".to_string(),
1496 "trace".to_string()
1497 ]))
1498 );
1499 }
1500
1501 #[test]
1502 fn test_store_const_action() {
1503 let mut parser = OptionParser::new();
1504 parser.add_option(
1505 &["--debug"],
1506 "debug",
1507 OptionAction::StoreConst,
1508 0,
1509 Some("true"),
1510 );
1511
1512 let args = vec!["--debug".to_string()];
1513 let (opts, _, _) = parser.parse_args(args).unwrap();
1514
1515 assert_eq!(
1516 opts.get("debug"),
1517 Some(&ParsedValue::Single("true".to_string()))
1518 );
1519 }
1520
1521 #[test]
1526 fn test_single_positional_argument() {
1527 let mut parser = OptionParser::new();
1528 parser.add_argument("file", 1);
1529
1530 let args = vec!["input.txt".to_string()];
1531 let (opts, remaining, _) = parser.parse_args(args).unwrap();
1532
1533 assert_eq!(
1534 opts.get("file"),
1535 Some(&ParsedValue::Single("input.txt".to_string()))
1536 );
1537 assert!(remaining.is_empty());
1538 }
1539
1540 #[test]
1541 fn test_variadic_positional_argument() {
1542 let mut parser = OptionParser::new();
1543 parser.add_argument("files", -1);
1544
1545 let args = vec!["a.txt".to_string(), "b.txt".to_string(), "c.txt".to_string()];
1546 let (opts, remaining, _) = parser.parse_args(args).unwrap();
1547
1548 assert_eq!(
1549 opts.get("files"),
1550 Some(&ParsedValue::Multiple(vec![
1551 "a.txt".to_string(),
1552 "b.txt".to_string(),
1553 "c.txt".to_string()
1554 ]))
1555 );
1556 assert!(remaining.is_empty());
1557 }
1558
1559 #[test]
1560 fn test_multiple_positional_arguments() {
1561 let mut parser = OptionParser::new();
1562 parser.add_argument("source", 1);
1563 parser.add_argument("dest", 1);
1564
1565 let args = vec!["input.txt".to_string(), "output.txt".to_string()];
1566 let (opts, remaining, _) = parser.parse_args(args).unwrap();
1567
1568 assert_eq!(
1569 opts.get("source"),
1570 Some(&ParsedValue::Single("input.txt".to_string()))
1571 );
1572 assert_eq!(
1573 opts.get("dest"),
1574 Some(&ParsedValue::Single("output.txt".to_string()))
1575 );
1576 assert!(remaining.is_empty());
1577 }
1578
1579 #[test]
1580 fn test_positional_with_variadic() {
1581 let mut parser = OptionParser::new();
1582 parser.add_argument("dest", 1);
1583 parser.add_argument("sources", -1);
1584
1585 let args = vec!["out.txt".to_string(), "a.txt".to_string(), "b.txt".to_string()];
1586 let (opts, remaining, _) = parser.parse_args(args).unwrap();
1587
1588 assert_eq!(
1589 opts.get("dest"),
1590 Some(&ParsedValue::Single("out.txt".to_string()))
1591 );
1592 assert_eq!(
1593 opts.get("sources"),
1594 Some(&ParsedValue::Multiple(vec![
1595 "a.txt".to_string(),
1596 "b.txt".to_string()
1597 ]))
1598 );
1599 assert!(remaining.is_empty());
1600 }
1601
1602 #[test]
1603 fn test_missing_required_positional() {
1604 let mut parser = OptionParser::new();
1605 parser.add_argument("file", 1);
1606
1607 let args: Vec<String> = vec![];
1608 let (opts, _, _) = parser.parse_args(args).unwrap();
1609
1610 assert_eq!(opts.get("file"), Some(&ParsedValue::Unset));
1612 }
1613
1614 #[test]
1619 fn test_options_and_arguments() {
1620 let mut parser = OptionParser::new();
1621 parser.add_option(&["-n", "--name"], "name", OptionAction::Store, 1, None);
1622 parser.add_option(&["-v", "--verbose"], "verbose", OptionAction::Count, 0, None);
1623 parser.add_argument("file", 1);
1624
1625 let args = vec![
1626 "-v".to_string(),
1627 "--name".to_string(),
1628 "test".to_string(),
1629 "-v".to_string(),
1630 "input.txt".to_string(),
1631 ];
1632 let (opts, remaining, order) = parser.parse_args(args).unwrap();
1633
1634 assert_eq!(opts.get("verbose"), Some(&ParsedValue::Count(2)));
1635 assert_eq!(
1636 opts.get("name"),
1637 Some(&ParsedValue::Single("test".to_string()))
1638 );
1639 assert_eq!(
1640 opts.get("file"),
1641 Some(&ParsedValue::Single("input.txt".to_string()))
1642 );
1643 assert!(remaining.is_empty());
1644
1645 assert!(order.contains(&"verbose".to_string()));
1647 assert!(order.contains(&"name".to_string()));
1648 assert!(order.contains(&"file".to_string()));
1649 }
1650
1651 #[test]
1656 fn test_missing_option_value() {
1657 let mut parser = OptionParser::new();
1658 parser.add_option(&["--name"], "name", OptionAction::Store, 1, None);
1659
1660 let args = vec!["--name".to_string()];
1661 let result = parser.parse_args(args);
1662
1663 assert!(result.is_err());
1664 match result.unwrap_err() {
1665 ClickError::BadOptionUsage { option_name, .. } => {
1666 assert_eq!(option_name, "--name");
1667 }
1668 _ => panic!("Expected BadOptionUsage error"),
1669 }
1670 }
1671
1672 #[test]
1673 fn test_option_takes_no_value() {
1674 let mut parser = OptionParser::new();
1675 parser.add_option(
1676 &["--debug"],
1677 "debug",
1678 OptionAction::StoreConst,
1679 0,
1680 Some("true"),
1681 );
1682
1683 let args = vec!["--debug=value".to_string()];
1684 let result = parser.parse_args(args);
1685
1686 assert!(result.is_err());
1687 match result.unwrap_err() {
1688 ClickError::BadOptionUsage { option_name, .. } => {
1689 assert_eq!(option_name, "--debug");
1690 }
1691 _ => panic!("Expected BadOptionUsage error"),
1692 }
1693 }
1694
1695 #[test]
1700 fn test_token_normalize_func() {
1701 let mut parser =
1702 OptionParser::new().token_normalize_func(|s| s.to_lowercase());
1703 parser.add_option(&["--name"], "name", OptionAction::Store, 1, None);
1704
1705 let args = vec!["--NAME".to_string(), "value".to_string()];
1706 let (opts, _, _) = parser.parse_args(args).unwrap();
1707
1708 assert_eq!(
1709 opts.get("name"),
1710 Some(&ParsedValue::Single("value".to_string()))
1711 );
1712 }
1713
1714 #[test]
1719 fn test_parsed_value_accessors() {
1720 let single = ParsedValue::Single("test".to_string());
1721 assert_eq!(single.as_single(), Some("test"));
1722 assert!(single.as_multiple().is_none());
1723 assert!(single.as_count().is_none());
1724 assert!(single.as_flag().is_none());
1725 assert!(!single.is_unset());
1726
1727 let multiple = ParsedValue::Multiple(vec!["a".to_string(), "b".to_string()]);
1728 assert!(multiple.as_single().is_none());
1729 assert_eq!(
1730 multiple.as_multiple(),
1731 Some(&["a".to_string(), "b".to_string()][..])
1732 );
1733
1734 let count = ParsedValue::Count(5);
1735 assert_eq!(count.as_count(), Some(5));
1736
1737 let flag = ParsedValue::Flag(true);
1738 assert_eq!(flag.as_flag(), Some(true));
1739
1740 let unset = ParsedValue::Unset;
1741 assert!(unset.is_unset());
1742 }
1743
1744 #[test]
1749 fn test_unpack_args_simple() {
1750 let args = vec!["a".to_string(), "b".to_string()];
1751 let specs = vec![1, 1];
1752 let (result, remaining) = unpack_args(&args, &specs).unwrap();
1753
1754 assert_eq!(result.len(), 2);
1755 assert_eq!(result[0], ParsedValue::Single("a".to_string()));
1756 assert_eq!(result[1], ParsedValue::Single("b".to_string()));
1757 assert!(remaining.is_empty());
1758 }
1759
1760 #[test]
1761 fn test_unpack_args_variadic() {
1762 let args = vec!["a".to_string(), "b".to_string(), "c".to_string()];
1763 let specs = vec![-1]; let (result, remaining) = unpack_args(&args, &specs).unwrap();
1765
1766 assert_eq!(result.len(), 1);
1767 assert_eq!(
1768 result[0],
1769 ParsedValue::Multiple(vec![
1770 "a".to_string(),
1771 "b".to_string(),
1772 "c".to_string()
1773 ])
1774 );
1775 assert!(remaining.is_empty());
1776 }
1777
1778 #[test]
1779 fn test_unpack_args_with_variadic_in_middle() {
1780 let args = vec![
1781 "dest".to_string(),
1782 "a".to_string(),
1783 "b".to_string(),
1784 "c".to_string(),
1785 ];
1786 let specs = vec![1, -1]; let (result, remaining) = unpack_args(&args, &specs).unwrap();
1788
1789 assert_eq!(result.len(), 2);
1790 assert_eq!(result[0], ParsedValue::Single("dest".to_string()));
1791 assert_eq!(
1792 result[1],
1793 ParsedValue::Multiple(vec![
1794 "a".to_string(),
1795 "b".to_string(),
1796 "c".to_string()
1797 ])
1798 );
1799 assert!(remaining.is_empty());
1800 }
1801
1802 #[test]
1803 fn test_unpack_args_missing() {
1804 let args = vec!["a".to_string()];
1805 let specs = vec![1, 1];
1806 let (result, remaining) = unpack_args(&args, &specs).unwrap();
1807
1808 assert_eq!(result.len(), 2);
1809 assert_eq!(result[0], ParsedValue::Single("a".to_string()));
1810 assert_eq!(result[1], ParsedValue::Unset);
1811 assert!(remaining.is_empty());
1812 }
1813
1814 #[test]
1815 fn test_unpack_args_empty_variadic() {
1816 let args: Vec<String> = vec![];
1817 let specs = vec![-1];
1818 let (result, remaining) = unpack_args(&args, &specs).unwrap();
1819
1820 assert_eq!(result.len(), 1);
1821 assert_eq!(result[0], ParsedValue::Multiple(vec![]));
1822 assert!(remaining.is_empty());
1823 }
1824
1825 #[test]
1830 fn test_unpack_args_optional_with_value() {
1831 let args = vec!["value".to_string()];
1832 let specs = vec![NARGS_OPTIONAL]; let (result, remaining) = unpack_args(&args, &specs).unwrap();
1834
1835 assert_eq!(result.len(), 1);
1836 assert_eq!(result[0], ParsedValue::Single("value".to_string()));
1837 assert!(remaining.is_empty());
1838 }
1839
1840 #[test]
1841 fn test_unpack_args_optional_without_value() {
1842 let args: Vec<String> = vec![];
1843 let specs = vec![NARGS_OPTIONAL]; let (result, remaining) = unpack_args(&args, &specs).unwrap();
1845
1846 assert_eq!(result.len(), 1);
1847 assert_eq!(result[0], ParsedValue::Unset);
1848 assert!(remaining.is_empty());
1849 }
1850
1851 #[test]
1852 fn test_unpack_args_optional_after_required() {
1853 let args = vec!["required".to_string(), "optional".to_string()];
1854 let specs = vec![1, NARGS_OPTIONAL]; let (result, remaining) = unpack_args(&args, &specs).unwrap();
1856
1857 assert_eq!(result.len(), 2);
1858 assert_eq!(result[0], ParsedValue::Single("required".to_string()));
1859 assert_eq!(result[1], ParsedValue::Single("optional".to_string()));
1860 assert!(remaining.is_empty());
1861 }
1862
1863 #[test]
1864 fn test_unpack_args_optional_missing_after_required() {
1865 let args = vec!["required".to_string()];
1866 let specs = vec![1, NARGS_OPTIONAL]; let (result, remaining) = unpack_args(&args, &specs).unwrap();
1868
1869 assert_eq!(result.len(), 2);
1870 assert_eq!(result[0], ParsedValue::Single("required".to_string()));
1871 assert_eq!(result[1], ParsedValue::Unset);
1872 assert!(remaining.is_empty());
1873 }
1874
1875 #[test]
1876 fn test_unpack_args_optional_before_required_preserves_required() {
1877 let args = vec!["x".to_string()];
1879 let specs = vec![NARGS_OPTIONAL, 1]; let (result, remaining) = unpack_args(&args, &specs).unwrap();
1881
1882 assert_eq!(result.len(), 2);
1883 assert_eq!(result[0], ParsedValue::Unset);
1885 assert_eq!(result[1], ParsedValue::Single("x".to_string()));
1887 assert!(remaining.is_empty());
1888 }
1889
1890 #[test]
1891 fn test_unpack_args_optional_before_required_with_both() {
1892 let args = vec!["opt".to_string(), "req".to_string()];
1894 let specs = vec![NARGS_OPTIONAL, 1]; let (result, remaining) = unpack_args(&args, &specs).unwrap();
1896
1897 assert_eq!(result.len(), 2);
1898 assert_eq!(result[0], ParsedValue::Single("opt".to_string()));
1899 assert_eq!(result[1], ParsedValue::Single("req".to_string()));
1900 assert!(remaining.is_empty());
1901 }
1902
1903 #[test]
1908 fn test_unpack_args_two_variadic_error() {
1909 let args = vec!["a".to_string(), "b".to_string()];
1910 let specs = vec![-1, -1]; let result = unpack_args(&args, &specs);
1912
1913 assert!(result.is_err());
1914 let err = result.unwrap_err();
1915 assert!(err.to_string().contains("variadic"));
1916 }
1917
1918 #[test]
1923 fn test_option_with_optional_value_explicit() {
1924 let mut parser = OptionParser::new();
1925 parser.add_option_ex(
1926 &["--opt"],
1927 "opt",
1928 OptionAction::Store,
1929 1,
1930 None,
1931 true, );
1933
1934 let args = vec!["--opt=value".to_string()];
1936 let (opts, _, _) = parser.parse_args(args).unwrap();
1937 assert_eq!(
1938 opts.get("opt"),
1939 Some(&ParsedValue::Single("value".to_string()))
1940 );
1941 }
1942
1943 #[test]
1944 fn test_option_with_optional_value_followed_by_option() {
1945 let mut parser = OptionParser::new();
1946 parser.add_option_ex(
1947 &["--opt"],
1948 "opt",
1949 OptionAction::Store,
1950 1,
1951 None,
1952 true, );
1954 parser.add_option(&["--other"], "other", OptionAction::StoreConst, 0, Some("true"));
1955
1956 let args = vec!["--opt".to_string(), "--other".to_string()];
1958 let (opts, _, _) = parser.parse_args(args).unwrap();
1959 assert_eq!(opts.get("opt"), Some(&ParsedValue::FlagNeedsValue));
1960 assert_eq!(
1961 opts.get("other"),
1962 Some(&ParsedValue::Single("true".to_string()))
1963 );
1964 }
1965
1966 #[test]
1967 fn test_option_with_optional_value_with_value() {
1968 let mut parser = OptionParser::new();
1969 parser.add_option_ex(
1970 &["--opt"],
1971 "opt",
1972 OptionAction::Store,
1973 1,
1974 None,
1975 true, );
1977
1978 let args = vec!["--opt".to_string(), "value".to_string()];
1980 let (opts, _, _) = parser.parse_args(args).unwrap();
1981 assert_eq!(
1982 opts.get("opt"),
1983 Some(&ParsedValue::Single("value".to_string()))
1984 );
1985 }
1986
1987 #[test]
1988 fn test_option_with_optional_value_at_end() {
1989 let mut parser = OptionParser::new();
1990 parser.add_option_ex(
1991 &["--opt"],
1992 "opt",
1993 OptionAction::Store,
1994 1,
1995 None,
1996 true, );
1998
1999 let args = vec!["--opt".to_string()];
2001 let (opts, _, _) = parser.parse_args(args).unwrap();
2002 assert_eq!(opts.get("opt"), Some(&ParsedValue::FlagNeedsValue));
2003 }
2004
2005 #[test]
2010 fn test_multi_value_argument_incomplete() {
2011 let mut parser = OptionParser::new();
2012 parser.add_argument("pair", 2); let args = vec!["first".to_string()];
2016 let result = parser.parse_args(args);
2017
2018 assert!(result.is_err());
2019 let err = result.unwrap_err();
2020 assert!(err.to_string().contains("takes 2 values"));
2021 }
2022
2023 #[test]
2024 fn test_multi_value_argument_complete() {
2025 let mut parser = OptionParser::new();
2026 parser.add_argument("pair", 2); let args = vec!["first".to_string(), "second".to_string()];
2030 let (opts, remaining, _) = parser.parse_args(args).unwrap();
2031
2032 assert_eq!(
2033 opts.get("pair"),
2034 Some(&ParsedValue::Multiple(vec![
2035 "first".to_string(),
2036 "second".to_string()
2037 ]))
2038 );
2039 assert!(remaining.is_empty());
2040 }
2041}