Skip to main content

click/
parser.rs

1//! Low-level argument parsing for click-rs.
2//!
3//! This module provides the internal option parser that handles the actual
4//! parsing of command-line arguments. It is modeled after Python Click's
5//! `parser.py` and brings a similar but simplified API.
6//!
7//! # Reference
8//!
9//! Based on Python Click's `parser.py`.
10//!
11//! # Key Behaviors
12//!
13//! - `--` ends option parsing; remaining args are positional
14//! - Short options can be grouped: `-abc` = `-a -b -c`
15//! - Short option with value: `-nfoo` or `-n foo`
16//! - Long option with value: `--name=foo` or `--name foo`
17
18use std::collections::{HashMap, HashSet, VecDeque};
19
20use crate::error::ClickError;
21
22// =============================================================================
23// Type Aliases
24// =============================================================================
25
26/// Type alias for token normalization function.
27type TokenNormalizeFunc = Box<dyn Fn(&str) -> String + Send + Sync>;
28
29/// Result type for parsing: (options, remaining args, parameter order).
30pub type ParseResult = Result<(HashMap<String, ParsedValue>, Vec<String>, Vec<String>), ClickError>;
31
32/// Special nargs value indicating optional (? in Python Click).
33/// This is used internally to distinguish optional from 0.
34pub const NARGS_OPTIONAL: i32 = -2;
35
36// =============================================================================
37// OptionAction Enum
38// =============================================================================
39
40/// Action to perform when an option is encountered.
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
42pub enum OptionAction {
43    /// Store a single value (default).
44    #[default]
45    Store,
46    /// Store a constant value (for flags).
47    StoreConst,
48    /// Append value to a list.
49    Append,
50    /// Append a constant to a list.
51    AppendConst,
52    /// Count occurrences.
53    Count,
54}
55
56// =============================================================================
57// ParsedValue Enum
58// =============================================================================
59
60/// A parsed value from the command line.
61#[derive(Debug, Clone, PartialEq)]
62pub enum ParsedValue {
63    /// A single string value.
64    Single(String),
65    /// Multiple string values (for append or multi-valued options).
66    Multiple(Vec<String>),
67    /// A count of occurrences.
68    Count(usize),
69    /// A boolean flag value.
70    Flag(bool),
71    /// Value was not set (placeholder for missing optional values).
72    Unset,
73    /// Option was used as a flag without providing a value.
74    /// This happens when an optional-value option (like `--opt`) is used
75    /// without a value and the next token looks like another option.
76    /// The command layer should handle this by prompting or using a flag_value.
77    FlagNeedsValue,
78}
79
80impl ParsedValue {
81    /// Check if the value is unset.
82    pub fn is_unset(&self) -> bool {
83        matches!(self, ParsedValue::Unset)
84    }
85
86    /// Get as a single string, if applicable.
87    pub fn as_single(&self) -> Option<&str> {
88        match self {
89            ParsedValue::Single(s) => Some(s),
90            _ => None,
91        }
92    }
93
94    /// Get as multiple strings, if applicable.
95    pub fn as_multiple(&self) -> Option<&[String]> {
96        match self {
97            ParsedValue::Multiple(v) => Some(v),
98            _ => None,
99        }
100    }
101
102    /// Get as a count, if applicable.
103    pub fn as_count(&self) -> Option<usize> {
104        match self {
105            ParsedValue::Count(n) => Some(*n),
106            _ => None,
107        }
108    }
109
110    /// Get as a flag, if applicable.
111    pub fn as_flag(&self) -> Option<bool> {
112        match self {
113            ParsedValue::Flag(b) => Some(*b),
114            _ => None,
115        }
116    }
117}
118
119// =============================================================================
120// ParserOption Struct (Internal)
121// =============================================================================
122
123/// Internal representation of an option for the parser.
124#[derive(Debug, Clone)]
125struct ParserOption {
126    /// The canonical name for this option.
127    #[allow(dead_code)]
128    name: String,
129    /// Short option forms (e.g., ["-n", "-N"]).
130    #[allow(dead_code)]
131    short_opts: Vec<String>,
132    /// Long option forms (e.g., ["--name"]).
133    #[allow(dead_code)]
134    long_opts: Vec<String>,
135    /// Action to perform when this option is encountered.
136    action: OptionAction,
137    /// Number of arguments: 1 = single, -1 = variadic, 0 = flag, -2 = optional.
138    nargs: i32,
139    /// Constant value for StoreConst/AppendConst actions.
140    const_value: Option<String>,
141    /// Destination key in the parsed results.
142    dest: String,
143    /// Whether the option allows omitting the value (for optional options).
144    /// When true and no value is provided (next arg looks like an option),
145    /// the option uses a sentinel value instead of consuming the next arg.
146    flag_needs_value: bool,
147}
148
149impl ParserOption {
150    /// Check if this option takes a value.
151    fn takes_value(&self) -> bool {
152        matches!(self.action, OptionAction::Store | OptionAction::Append)
153    }
154}
155
156// =============================================================================
157// ParserArgument Struct (Internal)
158// =============================================================================
159
160/// Internal representation of a positional argument for the parser.
161#[derive(Debug, Clone)]
162struct ParserArgument {
163    /// The name of the argument.
164    #[allow(dead_code)]
165    name: String,
166    /// Number of values to consume: 1 = single, -1 = variadic.
167    nargs: i32,
168    /// Destination key in the parsed results.
169    dest: String,
170}
171
172// =============================================================================
173// ParsingState Struct
174// =============================================================================
175
176/// Mutable state during parsing.
177#[derive(Debug)]
178struct ParsingState {
179    /// Parsed option values.
180    opts: HashMap<String, ParsedValue>,
181    /// Left args (positional arguments accumulated so far).
182    largs: Vec<String>,
183    /// Right args (remaining arguments to process).
184    rargs: VecDeque<String>,
185    /// Order in which parameters were seen (dest names).
186    order: Vec<String>,
187}
188
189impl ParsingState {
190    /// Create a new parsing state from the argument list.
191    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
201// =============================================================================
202// Helper Functions
203// =============================================================================
204
205/// Split an option string into its prefix and name.
206///
207/// # Examples
208///
209/// ```
210/// use click::parser::split_opt;
211///
212/// assert_eq!(split_opt("--name"), Some(("--", "name")));
213/// assert_eq!(split_opt("-n"), Some(("-", "n")));
214/// assert_eq!(split_opt("name"), None);
215/// assert_eq!(split_opt("-"), None);
216/// ```
217pub 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        // No prefix - not an option
225        return None;
226    }
227
228    // Check for double-dash prefix
229    if opt.len() >= 2 && &opt[0..2] == "--" {
230        let name = &opt[2..];
231        if name.is_empty() {
232            return None; // Just "--" is not a valid option split
233        }
234        return Some(("--", name));
235    }
236
237    // Single-dash prefix
238    if opt.len() >= 2 && opt.starts_with('-') {
239        return Some(("-", &opt[1..]));
240    }
241
242    // Single character prefix (non-alphanumeric)
243    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
251/// Compute the Levenshtein edit distance between two strings.
252fn 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    // Use two rows for space efficiency
266    let mut prev = vec![0usize; n + 1];
267    let mut curr = vec![0usize; n + 1];
268
269    // Initialize first row
270    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) // deletion
283                .min(curr[j - 1] + 1) // insertion
284                .min(prev[j - 1] + cost); // substitution
285        }
286        std::mem::swap(&mut prev, &mut curr);
287    }
288
289    prev[n]
290}
291
292/// Find close matches to a string from a list of possibilities.
293///
294/// Returns options with edit distance <= 2, sorted by distance.
295fn 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
310/// Unpack arguments according to nargs specifications.
311///
312/// Given an iterable of arguments and nargs specs, returns (unpacked_values, remaining_args).
313/// Missing items are represented as ParsedValue::Unset.
314///
315/// # Nargs values:
316/// - `1` or `n > 1`: consume exactly that many values
317/// - `-1`: variadic, consume all remaining
318/// - `-2` (NARGS_OPTIONAL): optional, consume one if present
319/// - `0`: skip (no values consumed)
320///
321/// # Errors
322///
323/// Returns an error if there are two variadic specs (nargs < 0 and != NARGS_OPTIONAL).
324fn 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    // Helper to fetch from front or back depending on whether we've seen a star
334    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    // Count required args remaining in the nargs_deque (positive values, excluding optional)
343    fn count_required_remaining(deque: &VecDeque<i32>) -> usize {
344        deque
345            .iter()
346            .filter(|&&n| n > 0) // Positive = required (1 or more)
347            .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            // Single value
358            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            // Optional: consume one value if available AND we have enough for remaining required
365            let required_remaining = count_required_remaining(&nargs_deque);
366            let available = args_deque.len();
367
368            if available > required_remaining {
369                // We have more args than required, so optional can take one
370                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                // Not enough args for required, skip optional
377                result.push(ParsedValue::Unset);
378            }
379        } else if nargs > 1 {
380            // Multiple values - must get all of them
381            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 we're fetching from back, reverse to restore order
394            if star_pos.is_some() {
395                values.reverse();
396            }
397            // All missing = completely unset
398            if missing_count == nargs as usize {
399                result.push(ParsedValue::Unset);
400            } else if missing_count > 0 {
401                // Partial missing - mark with special value to indicate incomplete
402                // This will be caught later by process_args_for_args
403                result.push(ParsedValue::Multiple(values));
404            } else {
405                result.push(ParsedValue::Multiple(values));
406            }
407        } else if nargs == -1 {
408            // Variadic (star) - consumes all remaining
409            if star_pos.is_some() {
410                // Can't have two variadic specs
411                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); // Placeholder, will be filled later
417        }
418        // nargs == 0 means no arguments, skip
419    }
420
421    // If we had a star position, fill it with remaining args
422    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        // Reverse items after star position
430        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
437// =============================================================================
438// OptionParser Struct
439// =============================================================================
440
441/// The main option parser.
442///
443/// This parser handles the low-level parsing of command-line arguments.
444/// It supports both short and long options, with various actions like
445/// store, append, and count.
446///
447/// # Example
448///
449/// ```
450/// use click::parser::{OptionParser, OptionAction, ParsedValue};
451///
452/// let mut parser = OptionParser::new();
453/// parser.add_option(&["-n", "--name"], "name", OptionAction::Store, 1, None);
454/// parser.add_option(&["-v", "--verbose"], "verbose", OptionAction::Count, 0, None);
455/// parser.add_argument("file", 1);
456///
457/// let args = vec!["-v", "-v", "--name", "test", "input.txt"]
458///     .into_iter().map(String::from).collect();
459/// let (opts, remaining, order) = parser.parse_args(args).unwrap();
460///
461/// assert_eq!(opts.get("verbose"), Some(&ParsedValue::Count(2)));
462/// assert_eq!(opts.get("name"), Some(&ParsedValue::Single("test".to_string())));
463/// ```
464pub struct OptionParser {
465    /// Whether to allow positional args between options.
466    allow_interspersed_args: bool,
467    /// Whether to ignore unknown options (pass them through).
468    ignore_unknown_options: bool,
469    /// Map of short option strings to their definitions.
470    short_opt: HashMap<String, ParserOption>,
471    /// Map of long option strings to their definitions.
472    long_opt: HashMap<String, ParserOption>,
473    /// Set of valid option prefixes.
474    opt_prefixes: HashSet<String>,
475    /// List of positional argument definitions.
476    args: Vec<ParserArgument>,
477    /// Token normalize function (for case-insensitive matching, etc.).
478    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    /// Create a new option parser with default settings.
506    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    /// Set whether to allow interspersed positional arguments.
523    ///
524    /// When true (default), positional args can appear between options.
525    /// When false, the parser stops on the first non-option.
526    pub fn allow_interspersed_args(mut self, allow: bool) -> Self {
527        self.allow_interspersed_args = allow;
528        self
529    }
530
531    /// Set whether to ignore unknown options.
532    ///
533    /// When true, unknown options are passed through as positional args.
534    /// When false (default), unknown options cause an error.
535    pub fn ignore_unknown_options(mut self, ignore: bool) -> Self {
536        self.ignore_unknown_options = ignore;
537        self
538    }
539
540    /// Set a token normalization function.
541    ///
542    /// This function is applied to option names for matching.
543    /// Useful for case-insensitive option matching.
544    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    /// Normalize an option string using the token_normalize_func if set.
553    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    /// Add an option to the parser.
566    ///
567    /// # Arguments
568    ///
569    /// * `opts` - Option strings (e.g., `["-n", "--name"]`)
570    /// * `dest` - Destination key in the results
571    /// * `action` - Action to perform when option is encountered
572    /// * `nargs` - Number of arguments (1 = single, -1 = variadic, 0 = flag, -2 = optional)
573    /// * `const_value` - Constant value for StoreConst/AppendConst actions
574    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    /// Add an option to the parser with extended options.
586    ///
587    /// # Arguments
588    ///
589    /// * `opts` - Option strings (e.g., `["-n", "--name"]`)
590    /// * `dest` - Destination key in the results
591    /// * `action` - Action to perform when option is encountered
592    /// * `nargs` - Number of arguments (1 = single, -1 = variadic, 0 = flag, -2 = optional)
593    /// * `const_value` - Constant value for StoreConst/AppendConst actions
594    /// * `flag_needs_value` - If true, value is optional: if next arg looks like an option,
595    ///   returns FlagNeedsValue instead of consuming it
596    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                // Track the prefix
613                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                // Categorize as short or long
619                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        // Register in lookup maps
645        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    /// Add a positional argument to the parser.
654    ///
655    /// # Arguments
656    ///
657    /// * `dest` - Destination key in the results
658    /// * `nargs` - Number of values (1 = single, -1 = variadic)
659    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    /// Parse command-line arguments.
668    ///
669    /// # Returns
670    ///
671    /// A tuple of:
672    /// - Parsed option values (dest -> value)
673    /// - Remaining unparsed arguments
674    /// - Order of parameters as they were seen
675    ///
676    /// # Errors
677    ///
678    /// Returns a `ClickError` if parsing fails (e.g., unknown option,
679    /// missing value).
680    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    /// Process arguments for options.
690    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            // Double dashes always end option parsing
695            if arg == "--" {
696                return Ok(());
697            }
698
699            // Check if this looks like an option
700            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                // Stop processing options, put arg back
709                state.rargs.push_front(arg);
710                return Ok(());
711            }
712        }
713        Ok(())
714    }
715
716    /// Process arguments for positional arguments.
717    fn process_args_for_args(&self, state: &mut ParsingState) -> Result<(), ClickError> {
718        // Combine left args and remaining right args
719        let all_args: Vec<String> = state
720            .largs
721            .drain(..)
722            .chain(state.rargs.drain(..))
723            .collect();
724
725        // Get nargs specs for all arguments
726        let nargs_spec: Vec<i32> = self.args.iter().map(|a| a.nargs).collect();
727
728        // Unpack arguments
729        let (parsed, remaining) = unpack_args(&all_args, &nargs_spec)?;
730
731        // Store results and validate multi-value arguments
732        for (idx, arg_def) in self.args.iter().enumerate() {
733            if idx < parsed.len() {
734                let value = &parsed[idx];
735
736                // Check for incomplete multi-value arguments
737                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    /// Process a single option argument.
764    fn process_opts(&self, arg: &str, state: &mut ParsingState) -> Result<(), ClickError> {
765        let mut explicit_value = None;
766        let long_opt;
767
768        // Check for explicit value (--name=value)
769        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        // Try long option match first
779        match self.match_long_opt(&norm_long_opt, explicit_value.clone(), state) {
780            Ok(()) => Ok(()),
781            Err(ClickError::NoSuchOption { .. }) => {
782                // Long option not found - try short option if prefix is single char
783                if let Some((prefix, _)) = split_opt(arg) {
784                    if prefix.len() == 1 {
785                        return self.match_short_opt(arg, state);
786                    }
787                }
788
789                // Unknown long option
790                if self.ignore_unknown_options {
791                    state.largs.push(arg.to_string());
792                    return Ok(());
793                }
794
795                // Re-raise the error with suggestions
796                self.match_long_opt(&norm_long_opt, explicit_value, state)
797            }
798            Err(e) => Err(e),
799        }
800    }
801
802    /// Match a long option.
803    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                // Find close matches for suggestions
813                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            // Inject explicit value back into rargs if present
826            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    /// Match short options (potentially grouped).
845    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                // Remaining characters are the value
869                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                // Stop processing this arg
878                break;
879            } else {
880                self.process_option(&option, ParsedValue::Unset, state);
881            }
882        }
883
884        // Re-combine unknown options
885        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    /// Get a value from the parsing state for an option.
895    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        // Handle optional nargs (-2 / NARGS_OPTIONAL)
904        if nargs == NARGS_OPTIONAL {
905            // Check if there's a value available
906            if let Some(next_arg) = state.rargs.front() {
907                // If the next arg looks like an option, don't consume it
908                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                    // Option was used as a flag without a value
914                    return Ok(ParsedValue::FlagNeedsValue);
915                }
916            } else if option.flag_needs_value {
917                // No more args, option was used as flag
918                return Ok(ParsedValue::FlagNeedsValue);
919            }
920
921            // Consume the value if available
922            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            // Flag or count - no value needed
931            return Ok(ParsedValue::Unset);
932        }
933
934        let rargs_len = state.rargs.len() as i32;
935
936        // Check if we have enough arguments
937        if rargs_len < nargs {
938            // If flag_needs_value is set, allow omitting the value
939            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            // Check if next arg looks like an option when flag_needs_value is set
954            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    /// Process an option with its value.
977    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                            // Option used without value in append mode
1001                            // Mark as FlagNeedsValue to let command layer handle it
1002                            // Use internal namespace prefix to avoid collision with user options
1003                            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// =============================================================================
1039// Tests
1040// =============================================================================
1041
1042#[cfg(test)]
1043mod tests {
1044    use super::*;
1045
1046    // -------------------------------------------------------------------------
1047    // split_opt tests
1048    // -------------------------------------------------------------------------
1049
1050    #[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    // -------------------------------------------------------------------------
1075    // edit_distance tests
1076    // -------------------------------------------------------------------------
1077
1078    #[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    // -------------------------------------------------------------------------
1097    // Short option parsing tests
1098    // -------------------------------------------------------------------------
1099
1100    #[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        // For short options, the `=` is part of the value (not a separator like long options).
1118        // This matches POSIX behavior: -n=value means -n with value "=value".
1119        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        // The `=` is included in the value because short options take the rest of the arg as value
1126        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        // -abn means -a -b -n, where -n takes "value" as argument
1181        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        // -anVALUE means -a -nVALUE
1206        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    // -------------------------------------------------------------------------
1221    // Long option parsing tests
1222    // -------------------------------------------------------------------------
1223
1224    #[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    // -------------------------------------------------------------------------
1270    // Double-dash terminator tests
1271    // -------------------------------------------------------------------------
1272
1273    #[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    // -------------------------------------------------------------------------
1303    // Interspersed args tests
1304    // -------------------------------------------------------------------------
1305
1306    #[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        // --name should NOT be parsed as an option since we stopped at file1.txt
1349        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    // -------------------------------------------------------------------------
1362    // Unknown option handling tests
1363    // -------------------------------------------------------------------------
1364
1365    #[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        // --unknown should be in files
1422        if let Some(ParsedValue::Multiple(files)) = opts.get("files") {
1423            assert!(files.contains(&"--unknown".to_string()));
1424        }
1425    }
1426
1427    // -------------------------------------------------------------------------
1428    // Action tests
1429    // -------------------------------------------------------------------------
1430
1431    #[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    // -------------------------------------------------------------------------
1522    // Positional argument tests
1523    // -------------------------------------------------------------------------
1524
1525    #[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        // Missing argument results in Unset
1611        assert_eq!(opts.get("file"), Some(&ParsedValue::Unset));
1612    }
1613
1614    // -------------------------------------------------------------------------
1615    // Combined option and argument tests
1616    // -------------------------------------------------------------------------
1617
1618    #[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        // Check order
1646        assert!(order.contains(&"verbose".to_string()));
1647        assert!(order.contains(&"name".to_string()));
1648        assert!(order.contains(&"file".to_string()));
1649    }
1650
1651    // -------------------------------------------------------------------------
1652    // Error case tests
1653    // -------------------------------------------------------------------------
1654
1655    #[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    // -------------------------------------------------------------------------
1696    // Token normalization tests
1697    // -------------------------------------------------------------------------
1698
1699    #[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    // -------------------------------------------------------------------------
1715    // ParsedValue tests
1716    // -------------------------------------------------------------------------
1717
1718    #[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    // -------------------------------------------------------------------------
1745    // unpack_args tests
1746    // -------------------------------------------------------------------------
1747
1748    #[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]; // variadic
1764        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]; // dest, then variadic sources
1787        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    // -------------------------------------------------------------------------
1826    // Optional positional argument tests (Nargs::Optional -> NARGS_OPTIONAL)
1827    // -------------------------------------------------------------------------
1828
1829    #[test]
1830    fn test_unpack_args_optional_with_value() {
1831        let args = vec!["value".to_string()];
1832        let specs = vec![NARGS_OPTIONAL]; // optional
1833        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]; // optional
1844        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]; // required, then optional
1855        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]; // required, then optional
1867        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        // Optional before required: with only one arg, required gets it
1878        let args = vec!["x".to_string()];
1879        let specs = vec![NARGS_OPTIONAL, 1]; // optional, then required
1880        let (result, remaining) = unpack_args(&args, &specs).unwrap();
1881
1882        assert_eq!(result.len(), 2);
1883        // Optional should remain Unset since required needs the value
1884        assert_eq!(result[0], ParsedValue::Unset);
1885        // Required should get the value
1886        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        // Optional before required: with two args, both get values
1893        let args = vec!["opt".to_string(), "req".to_string()];
1894        let specs = vec![NARGS_OPTIONAL, 1]; // optional, then required
1895        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    // -------------------------------------------------------------------------
1904    // Two variadic specs error test
1905    // -------------------------------------------------------------------------
1906
1907    #[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]; // two variadic - error
1911        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    // -------------------------------------------------------------------------
1919    // Optional option value tests
1920    // -------------------------------------------------------------------------
1921
1922    #[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, // flag_needs_value
1932        );
1933
1934        // --opt=value should work normally
1935        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, // flag_needs_value
1953        );
1954        parser.add_option(&["--other"], "other", OptionAction::StoreConst, 0, Some("true"));
1955
1956        // --opt followed by another option should not consume the next option
1957        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, // flag_needs_value
1976        );
1977
1978        // --opt followed by a value should consume it
1979        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, // flag_needs_value
1997        );
1998
1999        // --opt at end of args should return FlagNeedsValue
2000        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    // -------------------------------------------------------------------------
2006    // Multi-value argument incomplete error tests
2007    // -------------------------------------------------------------------------
2008
2009    #[test]
2010    fn test_multi_value_argument_incomplete() {
2011        let mut parser = OptionParser::new();
2012        parser.add_argument("pair", 2); // requires 2 values
2013
2014        // Only providing 1 value should error
2015        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); // requires 2 values
2027
2028        // Providing 2 values should work
2029        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}