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