Skip to main content

click/
command.rs

1//! Command building block for click-rs.
2//!
3//! This module provides the [`Command`] struct, which is the basic building block
4//! of command-line interfaces in click-rs. A command handles argument parsing
5//! and invokes a callback function with the parsed values.
6//!
7//! # Reference
8//!
9//! Based on Python Click's `core.py:Command` class (line 873+).
10//!
11//! # Example
12//!
13//! ```
14//! use click::command::Command;
15//! use click::context::Context;
16//! use click::option::ClickOption;
17//! use click::argument::Argument;
18//!
19//! let cmd = Command::new("greet")
20//!     .help("Greet someone by name")
21//!     .option(
22//!         ClickOption::new(&["--name", "-n"])
23//!             .help("Name to greet")
24//!             .default("World")
25//!             .build()
26//!     )
27//!     .argument(
28//!         Argument::new("count")
29//!             .help("Number of times to greet")
30//!             .build()
31//!     )
32//!     .callback(|ctx| {
33//!         // Access parsed values from ctx.params()
34//!         Ok(())
35//!     })
36//!     .build();
37//!
38//! assert_eq!(cmd.name, Some("greet".to_string()));
39//! assert!(!cmd.hidden);
40//! ```
41
42use std::collections::HashMap;
43use std::sync::Arc;
44
45use crate::argument::Argument;
46use crate::context::{pop_context, push_context, Context, ContextBuilder};
47use crate::error::{ClickError, ErrorContext};
48use crate::option::ClickOption;
49use crate::parameter::{Nargs, Parameter};
50use crate::parser::{OptionAction, OptionParser, ParsedValue, NARGS_OPTIONAL};
51use crate::source::ParameterSource;
52use crate::termui;
53
54// =============================================================================
55// CommandCallback Type
56// =============================================================================
57
58/// Type for command callbacks.
59///
60/// The callback receives a reference to the [`Context`] containing parsed
61/// parameter values and returns a `Result` indicating success or failure.
62pub type CommandCallback = Box<dyn Fn(&Context) -> Result<(), ClickError> + Send + Sync>;
63
64// =============================================================================
65// Command Struct
66// =============================================================================
67
68/// A command is the basic building block of click-rs CLIs.
69///
70/// Commands handle argument parsing and invoke a callback with the parsed values.
71/// They can be nested within [`Group`]s to create subcommand hierarchies.
72///
73/// # Construction
74///
75/// Use [`Command::new`] to create a [`CommandBuilder`] for fluent construction:
76///
77/// ```
78/// use click::command::Command;
79///
80/// let cmd = Command::new("mycommand")
81///     .help("Description of the command")
82///     .build();
83/// ```
84pub struct Command {
85    /// The name of the command.
86    ///
87    /// When registered on a [`Group`], this becomes the subcommand name.
88    /// Use [`Context::info_name`] to get the actual invocation name.
89    pub name: Option<String>,
90
91    /// The callback to execute when the command is invoked.
92    ///
93    /// May be `None` if the command is just a container for subcommands.
94    pub callback: Option<CommandCallback>,
95
96    /// The options for this command.
97    pub options: Vec<ClickOption>,
98
99    /// The arguments for this command.
100    pub arguments: Vec<Argument>,
101
102    /// The help text for this command.
103    ///
104    /// Displayed in the help output after the usage line.
105    pub help: Option<String>,
106
107    /// Text printed at the end of the help page.
108    pub epilog: Option<String>,
109
110    /// Short help shown in parent command listings.
111    ///
112    /// If not set, derived from the first line of `help`.
113    pub short_help: Option<String>,
114
115    /// The metavar for options (default: "[OPTIONS]").
116    pub options_metavar: String,
117
118    /// Whether to add a --help option automatically.
119    pub add_help_option: bool,
120
121    /// Show help if no arguments are provided.
122    pub no_args_is_help: bool,
123
124    /// Hide this command from help output.
125    pub hidden: bool,
126
127    /// Mark this command as deprecated.
128    ///
129    /// If `Some`, the command shows a deprecation warning when invoked.
130    /// The string can be empty for a generic message, or contain custom text.
131    pub deprecated: Option<String>,
132
133    /// Whether to allow extra arguments (default: false).
134    pub allow_extra_args: bool,
135
136    /// Whether to allow interspersed arguments (default: true).
137    pub allow_interspersed_args: bool,
138
139    /// Whether to ignore unknown options (default: false).
140    pub ignore_unknown_options: bool,
141
142    /// Cached help option (created lazily).
143    help_option: Option<ClickOption>,
144}
145
146impl std::fmt::Debug for Command {
147    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148        f.debug_struct("Command")
149            .field("name", &self.name)
150            .field("options", &format!("<{} options>", self.options.len()))
151            .field(
152                "arguments",
153                &format!("<{} arguments>", self.arguments.len()),
154            )
155            .field("help", &self.help)
156            .field("epilog", &self.epilog)
157            .field("short_help", &self.short_help)
158            .field("options_metavar", &self.options_metavar)
159            .field("add_help_option", &self.add_help_option)
160            .field("no_args_is_help", &self.no_args_is_help)
161            .field("hidden", &self.hidden)
162            .field("deprecated", &self.deprecated)
163            .field("allow_extra_args", &self.allow_extra_args)
164            .field("allow_interspersed_args", &self.allow_interspersed_args)
165            .field("ignore_unknown_options", &self.ignore_unknown_options)
166            .finish()
167    }
168}
169
170impl Default for Command {
171    fn default() -> Self {
172        Self {
173            name: None,
174            callback: None,
175            options: Vec::new(),
176            arguments: Vec::new(),
177            help: None,
178            epilog: None,
179            short_help: None,
180            options_metavar: "[OPTIONS]".to_string(),
181            add_help_option: true,
182            no_args_is_help: false,
183            hidden: false,
184            deprecated: None,
185            allow_extra_args: false,
186            allow_interspersed_args: true,
187            ignore_unknown_options: false,
188            help_option: None,
189        }
190    }
191}
192
193impl Command {
194    const VERSION_METAVAR_PREFIX: &'static str = "__click_version__:";
195
196    /// Create a new command builder with the given name.
197    ///
198    /// # Example
199    ///
200    /// ```
201    /// use click::command::Command;
202    ///
203    /// let cmd = Command::new("hello")
204    ///     .help("Say hello")
205    ///     .build();
206    /// ```
207    #[allow(clippy::new_ret_no_self)]
208    pub fn new(name: &str) -> CommandBuilder {
209        CommandBuilder::new(name)
210    }
211
212    /// Get the help option if enabled.
213    ///
214    /// Returns `None` if `add_help_option` is false or if the help option
215    /// names conflict with existing parameters.
216    pub fn get_help_option(&self, ctx: &Context) -> Option<ClickOption> {
217        if !self.add_help_option {
218            return None;
219        }
220
221        let help_names = self.get_help_option_names(ctx);
222        if help_names.is_empty() {
223            return None;
224        }
225
226        // If we have a cached help option, return a clone
227        if let Some(ref help_opt) = self.help_option {
228            return Some(help_opt.clone());
229        }
230
231        // Create the help option
232        Some(make_help_option(&help_names))
233    }
234
235    /// Get the names available for the help option.
236    ///
237    /// Filters out any names that are already used by existing parameters.
238    fn get_help_option_names(&self, ctx: &Context) -> Vec<String> {
239        let mut all_names: std::collections::HashSet<String> =
240            ctx.help_option_names().iter().cloned().collect();
241
242        // Remove names used by existing options
243        for opt in &self.options {
244            for name in &opt.long {
245                all_names.remove(name);
246            }
247            for name in &opt.short {
248                all_names.remove(name);
249            }
250        }
251
252        all_names.into_iter().collect()
253    }
254
255    /// Get the total number of parameters (options + arguments).
256    pub fn param_count(&self) -> usize {
257        self.options.len() + self.arguments.len()
258    }
259
260    /// Get all parameters (options and arguments) for this command.
261    ///
262    /// Returns a vector of trait objects. Options are returned first,
263    /// followed by arguments, in the order they were added.
264    pub fn get_params(&self, _ctx: &Context) -> Vec<&dyn Parameter> {
265        let mut params: Vec<&dyn Parameter> = Vec::with_capacity(self.param_count());
266        for opt in &self.options {
267            params.push(opt);
268        }
269        for arg in &self.arguments {
270            params.push(arg);
271        }
272        params
273    }
274
275    /// Create a context for this command.
276    ///
277    /// This sets up parsing state and applies command defaults.
278    ///
279    /// # Arguments
280    ///
281    /// * `info_name` - The name to display in help/usage (usually the command name)
282    /// * `args` - The arguments to parse
283    /// * `parent` - Optional parent context for nested commands
284    ///
285    /// # Example
286    ///
287    /// ```
288    /// use click::command::Command;
289    ///
290    /// let cmd = Command::new("hello").build();
291    /// let ctx = cmd.make_context("hello", vec![], None).unwrap();
292    /// assert_eq!(ctx.info_name(), Some("hello"));
293    /// ```
294    pub fn make_context(
295        &self,
296        info_name: &str,
297        args: Vec<String>,
298        parent: Option<Arc<Context>>,
299    ) -> Result<Context, ClickError> {
300        let mut builder = ContextBuilder::new()
301            .info_name(info_name)
302            .allow_extra_args(self.allow_extra_args)
303            .allow_interspersed_args(self.allow_interspersed_args)
304            .ignore_unknown_options(self.ignore_unknown_options);
305
306        if let Some(parent) = parent {
307            builder = builder.parent(parent);
308        }
309
310        let mut ctx = builder.build();
311
312        // Parse arguments and populate context
313        self.parse_args(&mut ctx, args)?;
314
315        Ok(ctx)
316    }
317
318    /// Parse arguments and populate the context.
319    ///
320    /// This method creates a parser, adds all parameters to it, parses the
321    /// arguments, and stores the results in the context.
322    pub fn parse_args(&self, ctx: &mut Context, args: Vec<String>) -> Result<(), ClickError> {
323        // Check for no_args_is_help
324        if args.is_empty() && self.no_args_is_help && !ctx.resilient_parsing() {
325            // Return help instead of an error
326            return Err(ClickError::Exit { code: 0 });
327        }
328
329        // Create the parser
330        let mut parser = OptionParser::new()
331            .allow_interspersed_args(ctx.allow_interspersed_args())
332            .ignore_unknown_options(ctx.ignore_unknown_options());
333
334        // Add help option if enabled
335        let help_opt = self.get_help_option(ctx);
336
337        // Add options to parser
338        for opt in &self.options {
339            self.add_option_to_parser(&mut parser, opt);
340        }
341        if let Some(ref help) = help_opt {
342            self.add_option_to_parser(&mut parser, help);
343        }
344
345        // Add arguments to parser
346        for arg in &self.arguments {
347            self.add_argument_to_parser(&mut parser, arg);
348        }
349
350        // Parse arguments
351        let (opts, remaining, _order) = parser.parse_args(args)?;
352
353        // Process eager options first (like --help)
354        // This must happen before validation so --help works with missing required params
355        if let Some(ref help) = help_opt {
356            if let Some(ParsedValue::Single(val)) = opts.get(help.name()) {
357                if val == "true" {
358                    // Help was requested - print help and exit
359                    // Store in context for later use
360                    ctx.params_mut().insert(
361                        help.name().to_string(),
362                        Arc::new(true) as Arc<dyn std::any::Any + Send + Sync>,
363                    );
364                    // In Click, help triggers an eager exit. We signal this with Exit.
365                    // The caller (main) should catch this and print help.
366                    return Err(ClickError::Exit { code: 0 });
367                }
368            }
369        }
370
371        // Process other eager options
372        for opt in &self.options {
373            if opt.is_eager() {
374                if let Some(ParsedValue::Single(val)) = opts.get(opt.name()) {
375                    if val == "true" || !val.is_empty() {
376                        // Eager option was triggered - process and potentially exit
377                        // For now, just mark it in context
378                        ctx.params_mut().insert(
379                            opt.name().to_string(),
380                            Arc::new(val.clone()) as Arc<dyn std::any::Any + Send + Sync>,
381                        );
382
383                        // Special-case version option: eager print + exit, before validation.
384                        if opt
385                            .config
386                            .metavar
387                            .as_deref()
388                            .is_some_and(|m| m.starts_with(Self::VERSION_METAVAR_PREFIX))
389                        {
390                            return Err(ClickError::Exit { code: 0 });
391                        }
392                    }
393                }
394            }
395        }
396
397        // Process options (validates required options)
398        for opt in &self.options {
399            self.process_option_value(ctx, opt, &opts)?;
400        }
401        if let Some(ref help) = help_opt {
402            self.process_option_value(ctx, help, &opts)?;
403        }
404
405        // Process arguments (validates required arguments)
406        for arg in &self.arguments {
407            self.process_argument_value(ctx, arg, &opts)?;
408        }
409
410        // Handle extra arguments
411        if !remaining.is_empty() && !ctx.allow_extra_args() && !ctx.resilient_parsing() {
412            let extra_args = remaining.join(" ");
413            return Err(ClickError::usage(if remaining.len() == 1 {
414                format!("Got unexpected extra argument ({})", extra_args)
415            } else {
416                format!("Got unexpected extra arguments ({})", extra_args)
417            }));
418        }
419
420        // Store remaining args in context
421        ctx.args_mut().extend(remaining);
422
423        Ok(())
424    }
425
426    /// Add a ClickOption to the parser.
427    fn add_option_to_parser(&self, parser: &mut OptionParser, opt: &ClickOption) {
428        let mut opts: Vec<&str> = Vec::new();
429        for s in &opt.short {
430            opts.push(s.as_str());
431        }
432        for l in &opt.long {
433            opts.push(l.as_str());
434        }
435
436        let (action, nargs, const_value, flag_needs_value) = if opt.count {
437            (OptionAction::Count, 0, None, false)
438        } else if opt.is_flag {
439            let const_val = opt.flag_value.as_deref().unwrap_or("true");
440            (OptionAction::StoreConst, 0, Some(const_val), false)
441        } else if opt.multiple() {
442            let (nargs_val, fnv) = match opt.nargs() {
443                Nargs::Count(n) => (n as i32, false),
444                Nargs::Variadic => (-1, false),
445                Nargs::Optional => (1, true), // Optional option values use flag_needs_value
446            };
447            (OptionAction::Append, nargs_val, None, fnv)
448        } else {
449            let (nargs_val, fnv) = match opt.nargs() {
450                Nargs::Count(n) => (n as i32, false),
451                Nargs::Variadic => (-1, false),
452                Nargs::Optional => (1, true), // Optional option values use flag_needs_value
453            };
454            (OptionAction::Store, nargs_val, None, fnv)
455        };
456
457        parser.add_option_ex(
458            &opts,
459            opt.name(),
460            action,
461            nargs,
462            const_value,
463            flag_needs_value,
464        );
465    }
466
467    /// Add an Argument to the parser.
468    fn add_argument_to_parser(&self, parser: &mut OptionParser, arg: &Argument) {
469        let nargs_val = match arg.nargs() {
470            Nargs::Count(n) => n as i32,
471            Nargs::Variadic => -1,
472            Nargs::Optional => NARGS_OPTIONAL, // Use special marker for optional args
473        };
474
475        parser.add_argument(arg.name(), nargs_val);
476    }
477
478    /// Resolve an environment variable value for a parameter, if configured.
479    fn resolve_envvar_value(&self, ctx: &Context, param: &dyn Parameter) -> Option<String> {
480        let mut candidates: Vec<String> = Vec::new();
481
482        if let Some(envvars) = param.envvar() {
483            candidates.extend(envvars.iter().cloned());
484        } else if let Some(prefix) = ctx.auto_envvar_prefix() {
485            let normalized = param.name().to_uppercase().replace('-', "_");
486            candidates.push(format!("{}_{}", prefix, normalized));
487        }
488
489        for name in candidates {
490            if let Ok(value) = std::env::var(&name) {
491                if !value.is_empty() {
492                    return Some(value);
493                }
494            }
495        }
496
497        None
498    }
499
500    /// Process a parsed option value and store it in the context.
501    fn process_option_value(
502        &self,
503        ctx: &mut Context,
504        opt: &ClickOption,
505        opts: &HashMap<String, ParsedValue>,
506    ) -> Result<(), ClickError> {
507        let name = opt.name();
508        let parsed_value = opts.get(name);
509
510        // Check for FlagNeedsValue marker from Append action
511        // Uses internal namespace prefix to avoid collision with user options
512        let flag_needs_value_key = format!("__click_internal_flag_needs_value_{}", name);
513        let had_flag_needs_value = opts.get(&flag_needs_value_key).is_some();
514
515        let make_error_ctx = || {
516            ErrorContext::new()
517                .with_command_path(ctx.command_path())
518                .with_usage(self.get_usage(ctx))
519                .with_help_options(ctx.help_option_names().to_vec())
520        };
521
522        let convert_single =
523            |value: &str| -> Result<Arc<dyn std::any::Any + Send + Sync>, ClickError> {
524                opt.convert_any(value).map(Arc::from).map_err(|msg| {
525                    ClickError::bad_parameter_named(msg, opt.human_readable_name())
526                        .with_context(make_error_ctx())
527                })
528            };
529
530        let convert_multi =
531            |values: &[String]| -> Result<Arc<dyn std::any::Any + Send + Sync>, ClickError> {
532                opt.convert_multi(values).map(Arc::from).map_err(|msg| {
533                    ClickError::bad_parameter_named(msg, opt.human_readable_name())
534                        .with_context(make_error_ctx())
535                })
536            };
537
538        // Convert ParsedValue to a boxed value for storage
539        let envvar_value = if matches!(parsed_value, Some(ParsedValue::Unset) | None) {
540            self.resolve_envvar_value(ctx, opt)
541        } else {
542            None
543        };
544
545        let default_map_value = if matches!(parsed_value, Some(ParsedValue::Unset) | None) {
546            ctx.lookup_default_value(name)
547        } else {
548            None
549        };
550
551        let mut source: Option<ParameterSource> = None;
552        let mut value: Option<Arc<dyn std::any::Any + Send + Sync>> = match parsed_value {
553            Some(ParsedValue::Count(n)) => {
554                source = Some(ParameterSource::CommandLine);
555                Some(Arc::new(*n))
556            }
557            Some(ParsedValue::Flag(b)) => {
558                source = Some(ParameterSource::CommandLine);
559                Some(Arc::new(*b))
560            }
561            Some(ParsedValue::Single(s)) => {
562                source = Some(ParameterSource::CommandLine);
563                Some(convert_single(s)?)
564            }
565            Some(ParsedValue::Multiple(v)) => {
566                let mut values = v.clone();
567                if had_flag_needs_value {
568                    if values.is_empty() {
569                        if let Some(ref flag_val) = opt.flag_value {
570                            values.push(flag_val.clone());
571                        } else if let Some(ref default) = opt.default {
572                            values.push(default.clone());
573                        } else {
574                            values.push(String::new());
575                        }
576                    } else if let Some(ref flag_val) = opt.flag_value {
577                        values.push(flag_val.clone());
578                    } else {
579                        values.push(String::new());
580                    }
581                }
582                source = Some(ParameterSource::CommandLine);
583                Some(convert_multi(&values)?)
584            }
585            Some(ParsedValue::FlagNeedsValue) => {
586                let fallback = opt
587                    .flag_value
588                    .as_ref()
589                    .or(opt.default.as_ref())
590                    .cloned()
591                    .unwrap_or_else(String::new);
592                source = Some(ParameterSource::CommandLine);
593                Some(convert_single(&fallback)?)
594            }
595            Some(ParsedValue::Unset) | None => {
596                if let Some(envval) = envvar_value {
597                    source = Some(ParameterSource::Environment);
598                    if opt.count {
599                        let parsed = envval.parse::<usize>().map_err(|_| {
600                            ClickError::bad_parameter_named(
601                                format!("'{}' is not a valid integer.", envval),
602                                opt.human_readable_name(),
603                            )
604                            .with_context(make_error_ctx())
605                        })?;
606                        Some(Arc::new(parsed))
607                    } else if opt.nargs().is_multi() || opt.multiple() {
608                        let values = opt.type_converter().split_envvar_value(&envval);
609                        if values.is_empty() {
610                            None
611                        } else {
612                            Some(convert_multi(&values)?)
613                        }
614                    } else {
615                        Some(convert_single(&envval)?)
616                    }
617                } else if let Some(default_map) = default_map_value {
618                    source = Some(ParameterSource::DefaultMap);
619                    Some(default_map)
620                } else if let Some(ref prompt_text) = opt.prompt {
621                    if ctx.resilient_parsing() || opt.is_flag || opt.count {
622                        None
623                    } else {
624                        let default_value = opt.default.clone();
625                        let prompted = termui::prompt(
626                            prompt_text,
627                            default_value,
628                            opt.hide_input,
629                            opt.confirmation_prompt,
630                            |input| match opt.convert_any(input) {
631                                Ok(any_val) => {
632                                    if let Ok(val) = any_val.downcast::<String>() {
633                                        Ok(*val)
634                                    } else {
635                                        Ok(input.to_string())
636                                    }
637                                }
638                                Err(msg) => Err(msg),
639                            },
640                        )?;
641
642                        source = Some(ParameterSource::Prompt);
643                        if opt.nargs().is_multi() || opt.multiple() {
644                            Some(convert_multi(&vec![prompted])?)
645                        } else {
646                            Some(convert_single(&prompted)?)
647                        }
648                    }
649                } else if opt.count {
650                    source = Some(ParameterSource::Default);
651                    Some(Arc::new(0usize))
652                } else if let Some(ref default) = opt.default {
653                    source = Some(ParameterSource::Default);
654                    if opt.nargs().is_multi() || opt.multiple() {
655                        Some(convert_multi(&vec![default.clone()])?)
656                    } else {
657                        Some(convert_single(default)?)
658                    }
659                } else {
660                    None
661                }
662            }
663        };
664
665        // Check if required option is missing
666        if value.is_none() && opt.required() && !ctx.resilient_parsing() {
667            let error_ctx = ErrorContext::new()
668                .with_command_path(ctx.command_path())
669                .with_usage(self.get_usage(ctx))
670                .with_help_options(ctx.help_option_names().to_vec());
671            return Err(
672                ClickError::missing_option(opt.human_readable_name()).with_context(error_ctx)
673            );
674        }
675
676        if let Some(ref callback) = opt.config.callback {
677            if let Some(current) = value.take() {
678                value = Some(callback(ctx, opt, current)?);
679            }
680        }
681
682        // Store in context if expose_value is true
683        if let Some(v) = value {
684            if let Some(source) = source {
685                ctx.set_parameter_source(name, source);
686            }
687            if opt.expose_value() {
688                ctx.params_mut().insert(name.to_string(), v);
689            }
690        }
691
692        Ok(())
693    }
694
695    /// Process a parsed argument value and store it in the context.
696    fn process_argument_value(
697        &self,
698        ctx: &mut Context,
699        arg: &Argument,
700        opts: &HashMap<String, ParsedValue>,
701    ) -> Result<(), ClickError> {
702        let name = arg.name();
703        let parsed_value = opts.get(name);
704
705        let envvar_value = if matches!(parsed_value, Some(ParsedValue::Unset) | None) {
706            self.resolve_envvar_value(ctx, arg)
707        } else {
708            None
709        };
710
711        let make_error_ctx = || {
712            ErrorContext::new()
713                .with_command_path(ctx.command_path())
714                .with_usage(self.get_usage(ctx))
715                .with_help_options(ctx.help_option_names().to_vec())
716        };
717
718        let convert_single =
719            |value: &str| -> Result<Arc<dyn std::any::Any + Send + Sync>, ClickError> {
720                arg.convert_any(value).map(Arc::from).map_err(|msg| {
721                    ClickError::bad_parameter_named(msg, arg.human_readable_name())
722                        .with_context(make_error_ctx())
723                })
724            };
725
726        let convert_multi =
727            |values: &[String]| -> Result<Arc<dyn std::any::Any + Send + Sync>, ClickError> {
728                arg.type_converter()
729                    .convert_multi(values)
730                    .map(Arc::from)
731                    .map_err(|msg| {
732                        ClickError::bad_parameter_named(msg, arg.human_readable_name())
733                            .with_context(make_error_ctx())
734                    })
735            };
736
737        // Convert ParsedValue to a boxed value for storage
738        let default_map_value = if matches!(parsed_value, Some(ParsedValue::Unset) | None) {
739            ctx.lookup_default_value(name)
740        } else {
741            None
742        };
743
744        let mut source: Option<ParameterSource> = None;
745        let mut value: Option<Arc<dyn std::any::Any + Send + Sync>> = match parsed_value {
746            Some(ParsedValue::Single(s)) => {
747                source = Some(ParameterSource::CommandLine);
748                Some(convert_single(s)?)
749            }
750            Some(ParsedValue::Multiple(v)) => {
751                source = Some(ParameterSource::CommandLine);
752                Some(convert_multi(v)?)
753            }
754            Some(ParsedValue::Count(n)) => {
755                source = Some(ParameterSource::CommandLine);
756                Some(Arc::new(*n))
757            }
758            Some(ParsedValue::Flag(b)) => {
759                source = Some(ParameterSource::CommandLine);
760                Some(Arc::new(*b))
761            }
762            Some(ParsedValue::FlagNeedsValue) | Some(ParsedValue::Unset) | None => {
763                if let Some(envval) = envvar_value {
764                    source = Some(ParameterSource::Environment);
765                    if arg.nargs().is_multi() || arg.multiple() {
766                        let values = arg.type_converter().split_envvar_value(&envval);
767                        if values.is_empty() {
768                            None
769                        } else {
770                            Some(convert_multi(&values)?)
771                        }
772                    } else {
773                        Some(convert_single(&envval)?)
774                    }
775                } else if let Some(default_map) = default_map_value {
776                    source = Some(ParameterSource::DefaultMap);
777                    Some(default_map)
778                } else {
779                    arg.default_value()
780                        .map(|d| {
781                            source = Some(ParameterSource::Default);
782                            if arg.nargs().is_multi() || arg.multiple() {
783                                convert_multi(&vec![d.to_string()]).map_err(|e| e)
784                            } else {
785                                convert_single(d).map_err(|e| e)
786                            }
787                        })
788                        .transpose()?
789                }
790            }
791        };
792
793        // Check if required argument is missing
794        if value.is_none() && arg.required() && !ctx.resilient_parsing() {
795            let error_ctx = ErrorContext::new()
796                .with_command_path(ctx.command_path())
797                .with_usage(self.get_usage(ctx))
798                .with_help_options(ctx.help_option_names().to_vec());
799            return Err(
800                ClickError::missing_argument(arg.human_readable_name()).with_context(error_ctx)
801            );
802        }
803
804        if let Some(ref callback) = arg.config.callback {
805            if let Some(current) = value.take() {
806                value = Some(callback(ctx, arg, current)?);
807            }
808        }
809
810        // Store in context if expose_value is true
811        if let Some(v) = value {
812            if let Some(source) = source {
813                ctx.set_parameter_source(name, source);
814            }
815            if arg.expose_value() {
816                ctx.params_mut().insert(name.to_string(), v);
817            }
818        }
819
820        Ok(())
821    }
822
823    /// Invoke the command callback with the context.
824    ///
825    /// Returns `Ok(())` if no callback is set.
826    pub fn invoke(&self, ctx: &Context) -> Result<(), ClickError> {
827        // Show deprecation warning
828        if let Some(ref deprecated) = self.deprecated {
829            let extra = if deprecated.is_empty() {
830                String::new()
831            } else {
832                format!(" {}", deprecated)
833            };
834            eprintln!(
835                "DeprecationWarning: The command '{}' is deprecated.{}",
836                self.name.as_deref().unwrap_or(""),
837                extra
838            );
839        }
840
841        // Invoke callback
842        if let Some(ref callback) = self.callback {
843            callback(ctx)
844        } else {
845            Ok(())
846        }
847    }
848
849    /// Main entry point - make context, parse args, and invoke.
850    ///
851    /// This is the primary way to run a command as a CLI application.
852    ///
853    /// # Arguments
854    ///
855    /// * `args` - Command-line arguments (typically `std::env::args().skip(1)`)
856    ///
857    /// # Example
858    ///
859    /// ```no_run
860    /// use click::command::Command;
861    ///
862    /// let cmd = Command::new("hello")
863    ///     .callback(|_ctx| {
864    ///         println!("Hello, world!");
865    ///         Ok(())
866    ///     })
867    ///     .build();
868    ///
869    /// let args: Vec<String> = std::env::args().skip(1).collect();
870    /// if let Err(e) = cmd.main(args) {
871    ///     eprintln!("{}", e.format_full());
872    ///     std::process::exit(e.exit_code());
873    /// }
874    /// ```
875    #[allow(clippy::arc_with_non_send_sync)]
876    pub fn main(&self, args: Vec<String>) -> Result<(), ClickError> {
877        let prog_name = self.name.clone().unwrap_or_else(|| {
878            std::env::args()
879                .next()
880                .unwrap_or_else(|| "program".to_string())
881        });
882
883        let args_for_eager = args.clone();
884
885        // Try to make context - this may fail early for --help
886        let ctx_result = self.make_context(&prog_name, args, None);
887
888        match ctx_result {
889            Ok(ctx) => {
890                // Note: Context contains RefCell for close_callbacks which makes it !Send+!Sync.
891                // This is fine for single-threaded CLI usage.
892                let ctx = Arc::new(ctx);
893
894                // Push context onto thread-local stack
895                push_context(Arc::clone(&ctx));
896
897                // Invoke the command
898                let result = self.invoke(&ctx);
899
900                // Pop context
901                pop_context();
902
903                // Run close callbacks
904                ctx.close();
905
906                result
907            }
908            Err(ClickError::Exit { code: 0 }) => {
909                // Help (or other eager exits) was requested.
910                //
911                // Version is implemented as an eager option that signals Exit(0) and stores the
912                // output string in the option metavar with a reserved prefix.
913                if let Some(version_output) = self.get_version_output_from_args(&args_for_eager) {
914                    println!("{}", version_output);
915                    return Ok(());
916                }
917
918                // Default: print help.
919                // Create a minimal context for help formatting.
920                let ctx = ContextBuilder::new().info_name(&prog_name).build();
921                println!("{}", self.get_help(&ctx));
922                Ok(())
923            }
924            Err(e) => Err(e),
925        }
926    }
927
928    fn arg_matches_opt(arg: &str, opt: &str) -> bool {
929        if arg == opt {
930            return true;
931        }
932        if opt.starts_with("--")
933            && arg.starts_with(opt)
934            && arg.get(opt.len()..opt.len() + 1) == Some("=")
935        {
936            return true;
937        }
938        if opt.starts_with('-') && opt.len() == 2 && !opt.starts_with("--") {
939            let needle = opt.chars().nth(1).unwrap_or('\0');
940            if arg.starts_with('-') && !arg.starts_with("--") {
941                return arg.chars().skip(1).any(|c| c == needle);
942            }
943        }
944        false
945    }
946
947    /// Check if a version option was triggered and return the version output.
948    ///
949    /// This is used internally by `main()` and `Group::main()` to handle
950    /// version options that trigger `Exit { code: 0 }`.
951    pub fn get_version_output_from_args(&self, args: &[String]) -> Option<String> {
952        for opt in &self.options {
953            // NOTE: must `continue`, not `?` — `?` aborts the whole scan at the
954            // first option without a version metavar, missing --version whenever
955            // any other option precedes it in declaration order.
956            let Some(meta) = opt.config.metavar.as_deref() else {
957                continue;
958            };
959            let Some(output) = meta.strip_prefix(Self::VERSION_METAVAR_PREFIX) else {
960                continue;
961            };
962
963            let mut names = opt.long.iter().chain(opt.short.iter());
964            if names.any(|n| args.iter().any(|a| Self::arg_matches_opt(a, n))) {
965                return Some(output.to_string());
966            }
967        }
968        None
969    }
970
971    /// Format the usage line.
972    ///
973    /// Returns a string like: `Usage: COMMAND [OPTIONS] ARGUMENT`
974    pub fn get_usage(&self, ctx: &Context) -> String {
975        let mut pieces = Vec::new();
976
977        // Add options metavar
978        if !self.options_metavar.is_empty() {
979            pieces.push(self.options_metavar.clone());
980        }
981
982        // Add argument metavars
983        for arg in &self.arguments {
984            if !arg.hidden() {
985                pieces.push(arg.make_metavar());
986            }
987        }
988
989        format!("Usage: {} {}", ctx.command_path(), pieces.join(" "))
990    }
991
992    /// Format the help text.
993    ///
994    /// Returns the complete help output including usage, description,
995    /// options, arguments, and epilog.
996    pub fn get_help(&self, ctx: &Context) -> String {
997        let mut parts = Vec::new();
998
999        // Usage line
1000        parts.push(self.get_usage(ctx));
1001
1002        // Help text
1003        if let Some(ref help) = self.help {
1004            let text = help.lines().next().unwrap_or("");
1005            if !text.is_empty() {
1006                parts.push(String::new()); // blank line
1007                let help_text = if let Some(ref dep) = self.deprecated {
1008                    if dep.is_empty() {
1009                        format!("{}  (DEPRECATED)", text)
1010                    } else {
1011                        format!("{}  (DEPRECATED: {})", text, dep)
1012                    }
1013                } else {
1014                    text.to_string()
1015                };
1016                parts.push(format!("  {}", help_text));
1017            }
1018        } else if let Some(ref dep) = self.deprecated {
1019            parts.push(String::new());
1020            let dep_msg = if dep.is_empty() {
1021                "(DEPRECATED)".to_string()
1022            } else {
1023                format!("(DEPRECATED: {})", dep)
1024            };
1025            parts.push(format!("  {}", dep_msg));
1026        }
1027
1028        // Options section
1029        let opt_records: Vec<(String, String)> = self
1030            .options
1031            .iter()
1032            .filter_map(|opt| opt.get_help_record())
1033            .collect();
1034
1035        // Add help option record
1036        let help_opt = self.get_help_option(ctx);
1037        let help_record = help_opt.as_ref().and_then(|h| h.get_help_record());
1038
1039        if !opt_records.is_empty() || help_record.is_some() {
1040            parts.push(String::new());
1041            parts.push("Options:".to_string());
1042
1043            for (opt_str, help) in &opt_records {
1044                parts.push(format!("  {}  {}", opt_str, help));
1045            }
1046            if let Some((opt_str, help)) = help_record {
1047                parts.push(format!("  {}  {}", opt_str, help));
1048            }
1049        }
1050
1051        // Arguments section (if any have help text)
1052        let arg_records: Vec<(String, String)> = self
1053            .arguments
1054            .iter()
1055            .filter_map(|arg| arg.get_help_record())
1056            .filter(|(_, help)| !help.is_empty())
1057            .collect();
1058
1059        if !arg_records.is_empty() {
1060            parts.push(String::new());
1061            parts.push("Arguments:".to_string());
1062            for (metavar, help) in &arg_records {
1063                parts.push(format!("  {}  {}", metavar, help));
1064            }
1065        }
1066
1067        // Epilog
1068        if let Some(ref epilog) = self.epilog {
1069            parts.push(String::new());
1070            parts.push(epilog.clone());
1071        }
1072
1073        parts.join("\n")
1074    }
1075
1076    /// Get the short help text for command listings.
1077    ///
1078    /// Returns `short_help` if set, otherwise derives from the first
1079    /// sentence of `help`.
1080    pub fn get_short_help(&self) -> String {
1081        if let Some(ref short_help) = self.short_help {
1082            let text = short_help.clone();
1083            if let Some(ref dep) = self.deprecated {
1084                if dep.is_empty() {
1085                    format!("{} (DEPRECATED)", text)
1086                } else {
1087                    format!("{} (DEPRECATED: {})", text, dep)
1088                }
1089            } else {
1090                text
1091            }
1092        } else if let Some(ref help) = self.help {
1093            // Get first line/sentence
1094            let text = help
1095                .lines()
1096                .next()
1097                .unwrap_or("")
1098                .split('.')
1099                .next()
1100                .unwrap_or("")
1101                .trim();
1102            if let Some(ref dep) = self.deprecated {
1103                if dep.is_empty() {
1104                    format!("{} (DEPRECATED)", text)
1105                } else {
1106                    format!("{} (DEPRECATED: {})", text, dep)
1107                }
1108            } else {
1109                text.to_string()
1110            }
1111        } else if let Some(ref dep) = self.deprecated {
1112            if dep.is_empty() {
1113                "(DEPRECATED)".to_string()
1114            } else {
1115                format!("(DEPRECATED: {})", dep)
1116            }
1117        } else {
1118            String::new()
1119        }
1120    }
1121}
1122
1123// =============================================================================
1124// CommandBuilder
1125// =============================================================================
1126
1127/// Builder for creating [`Command`] instances.
1128///
1129/// Use [`Command::new`] to create a builder, then chain methods to configure
1130/// the command, and finally call [`build`](CommandBuilder::build) to create
1131/// the command.
1132pub struct CommandBuilder {
1133    name: String,
1134    callback: Option<CommandCallback>,
1135    options: Vec<ClickOption>,
1136    arguments: Vec<Argument>,
1137    help: Option<String>,
1138    epilog: Option<String>,
1139    short_help: Option<String>,
1140    options_metavar: String,
1141    add_help_option: bool,
1142    help_option: Option<ClickOption>,
1143    no_args_is_help: bool,
1144    hidden: bool,
1145    deprecated: Option<String>,
1146    allow_extra_args: bool,
1147    allow_interspersed_args: bool,
1148    ignore_unknown_options: bool,
1149}
1150
1151impl CommandBuilder {
1152    /// Create a new command builder with the given name.
1153    pub fn new(name: &str) -> Self {
1154        Self {
1155            name: name.to_string(),
1156            callback: None,
1157            options: Vec::new(),
1158            arguments: Vec::new(),
1159            help: None,
1160            epilog: None,
1161            short_help: None,
1162            options_metavar: "[OPTIONS]".to_string(),
1163            add_help_option: true,
1164            help_option: None,
1165            no_args_is_help: false,
1166            hidden: false,
1167            deprecated: None,
1168            allow_extra_args: false,
1169            allow_interspersed_args: true,
1170            ignore_unknown_options: false,
1171        }
1172    }
1173
1174    /// Set the callback function for this command.
1175    ///
1176    /// The callback receives a reference to the [`Context`] and should return
1177    /// `Ok(())` on success or a [`ClickError`] on failure.
1178    pub fn callback<F>(mut self, f: F) -> Self
1179    where
1180        F: Fn(&Context) -> Result<(), ClickError> + Send + Sync + 'static,
1181    {
1182        self.callback = Some(Box::new(f));
1183        self
1184    }
1185
1186    /// Add an option to this command.
1187    pub fn option(mut self, opt: ClickOption) -> Self {
1188        self.options.push(opt);
1189        self
1190    }
1191
1192    /// Add an argument to this command.
1193    pub fn argument(mut self, arg: Argument) -> Self {
1194        self.arguments.push(arg);
1195        self
1196    }
1197
1198    /// Set the help text for this command.
1199    pub fn help(mut self, help: &str) -> Self {
1200        self.help = Some(help.to_string());
1201        self
1202    }
1203
1204    /// Set the epilog (text shown after help).
1205    pub fn epilog(mut self, epilog: &str) -> Self {
1206        self.epilog = Some(epilog.to_string());
1207        self
1208    }
1209
1210    /// Set the short help text for command listings.
1211    pub fn short_help(mut self, short_help: &str) -> Self {
1212        self.short_help = Some(short_help.to_string());
1213        self
1214    }
1215
1216    /// Set the options metavar (default: "[OPTIONS]").
1217    pub fn options_metavar(mut self, metavar: &str) -> Self {
1218        self.options_metavar = metavar.to_string();
1219        self
1220    }
1221
1222    /// Set whether to add a --help option (default: true).
1223    pub fn add_help_option(mut self, add: bool) -> Self {
1224        self.add_help_option = add;
1225        self
1226    }
1227
1228    /// Override the automatically generated help option.
1229    ///
1230    /// This lets you customize the help option names (e.g. `-h`, `--help`) and
1231    /// its help text while keeping it eager.
1232    ///
1233    /// Setting a custom help option implicitly enables `add_help_option`.
1234    pub fn help_option(mut self, opt: ClickOption) -> Self {
1235        self.add_help_option = true;
1236        self.help_option = Some(opt);
1237        self
1238    }
1239
1240    /// Set whether to show help if no args provided (default: false).
1241    pub fn no_args_is_help(mut self, value: bool) -> Self {
1242        self.no_args_is_help = value;
1243        self
1244    }
1245
1246    /// Hide this command from help output.
1247    pub fn hidden(mut self) -> Self {
1248        self.hidden = true;
1249        self
1250    }
1251
1252    /// Mark this command as deprecated.
1253    ///
1254    /// Pass an empty string for a generic deprecation message,
1255    /// or a custom message for more details.
1256    pub fn deprecated(mut self, message: &str) -> Self {
1257        self.deprecated = Some(message.to_string());
1258        self
1259    }
1260
1261    /// Set whether extra arguments are allowed.
1262    pub fn allow_extra_args(mut self, allow: bool) -> Self {
1263        self.allow_extra_args = allow;
1264        self
1265    }
1266
1267    /// Set whether interspersed arguments are allowed.
1268    pub fn allow_interspersed_args(mut self, allow: bool) -> Self {
1269        self.allow_interspersed_args = allow;
1270        self
1271    }
1272
1273    /// Set whether unknown options should be ignored.
1274    pub fn ignore_unknown_options(mut self, ignore: bool) -> Self {
1275        self.ignore_unknown_options = ignore;
1276        self
1277    }
1278
1279    /// Build the command.
1280    pub fn build(self) -> Command {
1281        Command {
1282            name: Some(self.name),
1283            callback: self.callback,
1284            options: self.options,
1285            arguments: self.arguments,
1286            help: self.help,
1287            epilog: self.epilog,
1288            short_help: self.short_help,
1289            options_metavar: self.options_metavar,
1290            add_help_option: self.add_help_option,
1291            no_args_is_help: self.no_args_is_help,
1292            hidden: self.hidden,
1293            deprecated: self.deprecated,
1294            allow_extra_args: self.allow_extra_args,
1295            allow_interspersed_args: self.allow_interspersed_args,
1296            ignore_unknown_options: self.ignore_unknown_options,
1297            help_option: self.help_option,
1298        }
1299    }
1300}
1301
1302// =============================================================================
1303// Helper Functions
1304// =============================================================================
1305
1306/// Create the standard help option.
1307fn make_help_option(names: &[String]) -> ClickOption {
1308    // Convert to &str for the option builder
1309    let name_refs: Vec<&str> = names.iter().map(|s| s.as_str()).collect();
1310
1311    ClickOption::new(&name_refs)
1312        .flag("true")
1313        .eager()
1314        .help("Show this message and exit.")
1315        .build()
1316}
1317
1318// =============================================================================
1319// Tests
1320// =============================================================================
1321
1322#[cfg(test)]
1323mod tests {
1324    use super::*;
1325    use crate::source::ParameterSource;
1326    use crate::types::INT;
1327    use std::collections::HashMap;
1328    use std::sync::Arc;
1329
1330    #[test]
1331    fn test_command_creation_defaults() {
1332        let cmd = Command::new("test").build();
1333
1334        assert_eq!(cmd.name, Some("test".to_string()));
1335        assert!(cmd.callback.is_none());
1336        assert!(cmd.options.is_empty());
1337        assert!(cmd.arguments.is_empty());
1338        assert!(cmd.help.is_none());
1339        assert!(cmd.epilog.is_none());
1340        assert!(cmd.short_help.is_none());
1341        assert_eq!(cmd.options_metavar, "[OPTIONS]");
1342        assert!(cmd.add_help_option);
1343        assert!(!cmd.no_args_is_help);
1344        assert!(!cmd.hidden);
1345        assert!(cmd.deprecated.is_none());
1346        assert!(!cmd.allow_extra_args);
1347        assert!(cmd.allow_interspersed_args);
1348        assert!(!cmd.ignore_unknown_options);
1349    }
1350
1351    #[test]
1352    fn test_command_builder_chain() {
1353        let cmd = Command::new("hello")
1354            .help("Say hello to someone")
1355            .epilog("Example: hello --name World")
1356            .short_help("Say hello")
1357            .options_metavar("[OPTS]")
1358            .add_help_option(false)
1359            .no_args_is_help(true)
1360            .hidden()
1361            .deprecated("Use 'greet' instead")
1362            .allow_extra_args(true)
1363            .allow_interspersed_args(false)
1364            .ignore_unknown_options(true)
1365            .build();
1366
1367        assert_eq!(cmd.name, Some("hello".to_string()));
1368        assert_eq!(cmd.help, Some("Say hello to someone".to_string()));
1369        assert_eq!(cmd.epilog, Some("Example: hello --name World".to_string()));
1370        assert_eq!(cmd.short_help, Some("Say hello".to_string()));
1371        assert_eq!(cmd.options_metavar, "[OPTS]");
1372        assert!(!cmd.add_help_option);
1373        assert!(cmd.no_args_is_help);
1374        assert!(cmd.hidden);
1375        assert_eq!(cmd.deprecated, Some("Use 'greet' instead".to_string()));
1376        assert!(cmd.allow_extra_args);
1377        assert!(!cmd.allow_interspersed_args);
1378        assert!(cmd.ignore_unknown_options);
1379    }
1380
1381    #[test]
1382    fn test_command_with_callback() {
1383        use std::sync::atomic::{AtomicBool, Ordering};
1384
1385        let called = Arc::new(AtomicBool::new(false));
1386        let called_clone = Arc::clone(&called);
1387
1388        let cmd = Command::new("test")
1389            .callback(move |_ctx| {
1390                called_clone.store(true, Ordering::SeqCst);
1391                Ok(())
1392            })
1393            .build();
1394
1395        assert!(cmd.callback.is_some());
1396
1397        // Create a minimal context and invoke
1398        let ctx = ContextBuilder::new().info_name("test").build();
1399        let result = cmd.invoke(&ctx);
1400        assert!(result.is_ok());
1401        assert!(called.load(Ordering::SeqCst));
1402    }
1403
1404    #[test]
1405    fn test_command_with_option() {
1406        let cmd = Command::new("greet")
1407            .option(
1408                ClickOption::new(&["--name", "-n"])
1409                    .help("Name to greet")
1410                    .default("World")
1411                    .build(),
1412            )
1413            .build();
1414
1415        assert_eq!(cmd.options.len(), 1);
1416        assert_eq!(cmd.options[0].name(), "name");
1417    }
1418
1419    #[test]
1420    fn test_command_with_argument() {
1421        let cmd = Command::new("cat")
1422            .argument(Argument::new("file").help("File to read").build())
1423            .build();
1424
1425        assert_eq!(cmd.arguments.len(), 1);
1426        assert_eq!(cmd.arguments[0].name(), "file");
1427    }
1428
1429    #[test]
1430    fn test_command_with_multiple_params() {
1431        let cmd = Command::new("copy")
1432            .option(
1433                ClickOption::new(&["--recursive", "-r"])
1434                    .flag("true")
1435                    .help("Copy recursively")
1436                    .build(),
1437            )
1438            .argument(Argument::new("src").help("Source path").build())
1439            .argument(Argument::new("dst").help("Destination path").build())
1440            .build();
1441
1442        assert_eq!(cmd.options.len(), 1);
1443        assert_eq!(cmd.arguments.len(), 2);
1444    }
1445
1446    #[test]
1447    fn test_make_context_basic() {
1448        let cmd = Command::new("hello").build();
1449        let ctx = cmd.make_context("hello", vec![], None);
1450
1451        assert!(ctx.is_ok());
1452        let ctx = ctx.unwrap();
1453        assert_eq!(ctx.info_name(), Some("hello"));
1454    }
1455
1456    #[test]
1457    fn test_parse_args_with_option() {
1458        let cmd = Command::new("greet")
1459            .option(ClickOption::new(&["--name", "-n"]).default("World").build())
1460            .build();
1461
1462        let ctx = cmd.make_context(
1463            "greet",
1464            vec!["--name".to_string(), "Alice".to_string()],
1465            None,
1466        );
1467        assert!(ctx.is_ok());
1468
1469        let ctx = ctx.unwrap();
1470        let name = ctx.get_param::<String>("name");
1471        assert_eq!(name, Some(&"Alice".to_string()));
1472    }
1473
1474    #[test]
1475    fn test_parse_args_with_argument() {
1476        let cmd = Command::new("cat")
1477            .argument(Argument::new("file").build())
1478            .build();
1479
1480        let ctx = cmd.make_context("cat", vec!["test.txt".to_string()], None);
1481        assert!(ctx.is_ok());
1482
1483        let ctx = ctx.unwrap();
1484        let file = ctx.get_param::<String>("file");
1485        assert_eq!(file, Some(&"test.txt".to_string()));
1486    }
1487
1488    #[test]
1489    fn test_parse_args_missing_required() {
1490        let cmd = Command::new("cat")
1491            .argument(Argument::new("file").build())
1492            .build();
1493
1494        let ctx = cmd.make_context("cat", vec![], None);
1495        assert!(ctx.is_err());
1496
1497        let err = ctx.unwrap_err();
1498        assert!(matches!(err, ClickError::MissingParameter { .. }));
1499    }
1500
1501    #[test]
1502    fn test_parse_args_extra_args_error() {
1503        let cmd = Command::new("hello").build();
1504
1505        let ctx = cmd.make_context("hello", vec!["extra".to_string()], None);
1506        assert!(ctx.is_err());
1507
1508        let err = ctx.unwrap_err();
1509        assert!(matches!(err, ClickError::UsageError { .. }));
1510    }
1511
1512    #[test]
1513    fn test_parse_args_extra_args_allowed() {
1514        let cmd = Command::new("hello").allow_extra_args(true).build();
1515
1516        let ctx = cmd.make_context("hello", vec!["extra".to_string()], None);
1517        assert!(ctx.is_ok());
1518
1519        let ctx = ctx.unwrap();
1520        assert_eq!(ctx.args(), &["extra".to_string()]);
1521    }
1522
1523    #[test]
1524    fn test_get_usage() {
1525        let cmd = Command::new("copy")
1526            .argument(Argument::new("src").build())
1527            .argument(Argument::new("dst").build())
1528            .build();
1529
1530        let ctx = ContextBuilder::new().info_name("copy").build();
1531        let usage = cmd.get_usage(&ctx);
1532
1533        assert!(usage.contains("Usage:"));
1534        assert!(usage.contains("copy"));
1535        assert!(usage.contains("[OPTIONS]"));
1536        assert!(usage.contains("SRC"));
1537        assert!(usage.contains("DST"));
1538    }
1539
1540    #[test]
1541    fn test_get_help() {
1542        let cmd = Command::new("greet")
1543            .help("Greet someone")
1544            .option(
1545                ClickOption::new(&["--name", "-n"])
1546                    .help("Name to greet")
1547                    .build(),
1548            )
1549            .epilog("Example: greet --name World")
1550            .build();
1551
1552        let ctx = ContextBuilder::new().info_name("greet").build();
1553        let help = cmd.get_help(&ctx);
1554
1555        assert!(help.contains("Usage:"));
1556        assert!(help.contains("Greet someone"));
1557        assert!(help.contains("Options:"));
1558        assert!(help.contains("--name"));
1559        assert!(help.contains("Name to greet"));
1560        assert!(help.contains("Example:"));
1561    }
1562
1563    #[test]
1564    fn test_get_short_help() {
1565        // Explicit short_help
1566        let cmd = Command::new("test").short_help("Test command").build();
1567        assert_eq!(cmd.get_short_help(), "Test command");
1568
1569        // Derived from help
1570        let cmd = Command::new("test")
1571            .help("This is a test. It does things.")
1572            .build();
1573        assert_eq!(cmd.get_short_help(), "This is a test");
1574
1575        // With deprecation
1576        let cmd = Command::new("test")
1577            .short_help("Test command")
1578            .deprecated("Use 'new-test' instead")
1579            .build();
1580        assert!(cmd.get_short_help().contains("DEPRECATED"));
1581        assert!(cmd.get_short_help().contains("Use 'new-test' instead"));
1582    }
1583
1584    #[test]
1585    fn test_make_help_option() {
1586        let help_opt = make_help_option(&["--help".to_string(), "-h".to_string()]);
1587
1588        assert!(help_opt.is_flag);
1589        assert!(help_opt.is_eager());
1590        assert_eq!(help_opt.help(), Some("Show this message and exit."));
1591    }
1592
1593    #[test]
1594    fn test_command_debug() {
1595        let cmd = Command::new("test").build();
1596        let debug_str = format!("{:?}", cmd);
1597
1598        assert!(debug_str.contains("Command"));
1599        assert!(debug_str.contains("test"));
1600    }
1601
1602    #[test]
1603    fn test_invoke_with_deprecation() {
1604        let cmd = Command::new("old")
1605            .deprecated("Use 'new' instead")
1606            .callback(|_| Ok(()))
1607            .build();
1608
1609        let ctx = ContextBuilder::new().info_name("old").build();
1610        // This will print a deprecation warning to stderr
1611        let result = cmd.invoke(&ctx);
1612        assert!(result.is_ok());
1613    }
1614
1615    #[test]
1616    fn test_parse_flag_option() {
1617        let cmd = Command::new("test")
1618            .option(ClickOption::new(&["--verbose", "-v"]).flag("true").build())
1619            .build();
1620
1621        let ctx = cmd.make_context("test", vec!["--verbose".to_string()], None);
1622        assert!(ctx.is_ok());
1623
1624        let ctx = ctx.unwrap();
1625        let verbose = ctx.get_param::<String>("verbose");
1626        assert_eq!(verbose, Some(&"true".to_string()));
1627    }
1628
1629    #[test]
1630    fn test_parse_count_option() {
1631        let cmd = Command::new("test")
1632            .option(ClickOption::new(&["--verbose", "-v"]).count().build())
1633            .build();
1634
1635        let ctx = cmd.make_context(
1636            "test",
1637            vec!["-v".to_string(), "-v".to_string(), "-v".to_string()],
1638            None,
1639        );
1640        assert!(ctx.is_ok());
1641
1642        let ctx = ctx.unwrap();
1643        let verbose = ctx.get_param::<usize>("verbose");
1644        assert_eq!(verbose, Some(&3));
1645    }
1646
1647    #[test]
1648    fn test_option_type_conversion() {
1649        let cmd = Command::new("test")
1650            .option(ClickOption::new(&["--count"]).type_any(INT).build())
1651            .build();
1652
1653        let ctx = cmd.make_context("test", vec!["--count".to_string(), "42".to_string()], None);
1654        assert!(ctx.is_ok());
1655
1656        let ctx = ctx.unwrap();
1657        let count = ctx.get_param::<i64>("count");
1658        assert_eq!(count, Some(&42));
1659    }
1660
1661    #[test]
1662    fn test_option_callback_applied() {
1663        let cmd = Command::new("test")
1664            .option(
1665                ClickOption::new(&["--count"])
1666                    .type_any(INT)
1667                    .callback(|_ctx, _param, value| {
1668                        let count = *value
1669                            .as_ref()
1670                            .downcast_ref::<i64>()
1671                            .ok_or_else(|| ClickError::bad_parameter("bad callback value"))?;
1672                        Ok(Arc::new(count + 1))
1673                    })
1674                    .build(),
1675            )
1676            .build();
1677
1678        let ctx = cmd.make_context("test", vec!["--count".to_string(), "2".to_string()], None);
1679        assert!(ctx.is_ok());
1680
1681        let ctx = ctx.unwrap();
1682        let count = ctx.get_param::<i64>("count");
1683        assert_eq!(count, Some(&3));
1684    }
1685
1686    #[test]
1687    fn test_option_with_default() {
1688        let cmd = Command::new("greet")
1689            .option(ClickOption::new(&["--name"]).default("World").build())
1690            .build();
1691
1692        // Without providing the option
1693        let ctx = cmd.make_context("greet", vec![], None);
1694        assert!(ctx.is_ok());
1695
1696        let ctx = ctx.unwrap();
1697        let name = ctx.get_param::<String>("name");
1698        assert_eq!(name, Some(&"World".to_string()));
1699        assert_eq!(
1700            ctx.get_parameter_source("name"),
1701            Some(ParameterSource::Default)
1702        );
1703    }
1704
1705    #[test]
1706    fn test_option_default_map_value() {
1707        let cmd = Command::new("greet")
1708            .option(ClickOption::new(&["--name"]).build())
1709            .build();
1710
1711        let mut defaults: HashMap<String, Arc<dyn std::any::Any + Send + Sync>> = HashMap::new();
1712        defaults.insert("name".to_string(), Arc::new("Bob".to_string()));
1713
1714        let mut ctx = ContextBuilder::new()
1715            .info_name("greet")
1716            .default_map(defaults)
1717            .build();
1718
1719        let result = cmd.parse_args(&mut ctx, vec![]);
1720        assert!(result.is_ok());
1721
1722        let name = ctx.get_param::<String>("name");
1723        assert_eq!(name, Some(&"Bob".to_string()));
1724        assert_eq!(
1725            ctx.get_parameter_source("name"),
1726            Some(ParameterSource::DefaultMap)
1727        );
1728    }
1729
1730    #[test]
1731    fn test_option_envvar_value() {
1732        std::env::set_var("CLICK_TEST_COUNT", "9");
1733        let cmd = Command::new("test")
1734            .option(
1735                ClickOption::new(&["--count"])
1736                    .envvar("CLICK_TEST_COUNT")
1737                    .type_any(INT)
1738                    .build(),
1739            )
1740            .build();
1741
1742        let ctx = cmd.make_context("test", vec![], None);
1743        std::env::remove_var("CLICK_TEST_COUNT");
1744
1745        assert!(ctx.is_ok());
1746        let ctx = ctx.unwrap();
1747        let count = ctx.get_param::<i64>("count");
1748        assert_eq!(count, Some(&9));
1749        assert_eq!(
1750            ctx.get_parameter_source("count"),
1751            Some(ParameterSource::Environment)
1752        );
1753    }
1754
1755    #[test]
1756    fn test_argument_type_conversion() {
1757        let cmd = Command::new("greet")
1758            .argument(Argument::new("count").type_(INT).build())
1759            .build();
1760
1761        let ctx = cmd.make_context("greet", vec!["7".to_string()], None);
1762        assert!(ctx.is_ok());
1763
1764        let ctx = ctx.unwrap();
1765        let count = ctx.get_param::<i64>("count");
1766        assert_eq!(count, Some(&7));
1767        assert_eq!(
1768            ctx.get_parameter_source("count"),
1769            Some(ParameterSource::CommandLine)
1770        );
1771    }
1772
1773    #[test]
1774    fn test_argument_callback_applied() {
1775        let cmd = Command::new("greet")
1776            .argument(
1777                Argument::new("count")
1778                    .type_(INT)
1779                    .callback(|_ctx, _param, value| {
1780                        let count = *value
1781                            .as_ref()
1782                            .downcast_ref::<i64>()
1783                            .ok_or_else(|| ClickError::bad_parameter("bad callback value"))?;
1784                        Ok(Arc::new(count * 2))
1785                    })
1786                    .build(),
1787            )
1788            .build();
1789
1790        let ctx = cmd.make_context("greet", vec!["3".to_string()], None);
1791        assert!(ctx.is_ok());
1792
1793        let ctx = ctx.unwrap();
1794        let count = ctx.get_param::<i64>("count");
1795        assert_eq!(count, Some(&6));
1796    }
1797
1798    #[test]
1799    fn test_argument_type_conversion_error() {
1800        let cmd = Command::new("greet")
1801            .argument(Argument::new("count").type_(INT).build())
1802            .build();
1803
1804        let ctx = cmd.make_context("greet", vec!["nope".to_string()], None);
1805        assert!(matches!(ctx, Err(ClickError::BadParameter { .. })));
1806    }
1807
1808    #[test]
1809    fn test_argument_with_default() {
1810        let cmd = Command::new("greet")
1811            .argument(Argument::new("name").default("World").build())
1812            .build();
1813
1814        // Without providing the argument
1815        let ctx = cmd.make_context("greet", vec![], None);
1816        assert!(ctx.is_ok());
1817
1818        let ctx = ctx.unwrap();
1819        let name = ctx.get_param::<String>("name");
1820        assert_eq!(name, Some(&"World".to_string()));
1821    }
1822
1823    #[test]
1824    fn test_argument_default_map_value() {
1825        let cmd = Command::new("greet")
1826            .argument(Argument::new("name").build())
1827            .build();
1828
1829        let mut defaults: HashMap<String, Arc<dyn std::any::Any + Send + Sync>> = HashMap::new();
1830        defaults.insert("name".to_string(), Arc::new("Alice".to_string()));
1831
1832        let mut ctx = ContextBuilder::new()
1833            .info_name("greet")
1834            .default_map(defaults)
1835            .build();
1836
1837        let result = cmd.parse_args(&mut ctx, vec![]);
1838        assert!(result.is_ok());
1839
1840        let name = ctx.get_param::<String>("name");
1841        assert_eq!(name, Some(&"Alice".to_string()));
1842    }
1843
1844    #[test]
1845    fn test_argument_auto_envvar_prefix() {
1846        std::env::set_var("MYAPP_NAME", "Alice");
1847        let cmd = Command::new("greet")
1848            .argument(Argument::new("name").build())
1849            .build();
1850
1851        let mut ctx = ContextBuilder::new()
1852            .info_name("greet")
1853            .auto_envvar_prefix("MYAPP")
1854            .build();
1855
1856        let result = cmd.parse_args(&mut ctx, vec![]);
1857        std::env::remove_var("MYAPP_NAME");
1858
1859        assert!(result.is_ok());
1860        let name = ctx.get_param::<String>("name");
1861        assert_eq!(name, Some(&"Alice".to_string()));
1862    }
1863
1864    // -------------------------------------------------------------------------
1865    // Optional positional argument tests (fix for Nargs::Optional)
1866    // -------------------------------------------------------------------------
1867
1868    #[test]
1869    fn test_optional_argument_with_value() {
1870        use crate::parameter::Nargs;
1871
1872        let cmd = Command::new("test")
1873            .argument(
1874                Argument::new("file")
1875                    .nargs(Nargs::Optional)
1876                    .default("default.txt")
1877                    .build(),
1878            )
1879            .build();
1880
1881        // With a value provided
1882        let ctx = cmd.make_context("test", vec!["input.txt".to_string()], None);
1883        assert!(ctx.is_ok());
1884
1885        let ctx = ctx.unwrap();
1886        let file = ctx.get_param::<String>("file");
1887        assert_eq!(file, Some(&"input.txt".to_string()));
1888    }
1889
1890    #[test]
1891    fn test_optional_argument_without_value() {
1892        use crate::parameter::Nargs;
1893
1894        let cmd = Command::new("test")
1895            .argument(
1896                Argument::new("file")
1897                    .nargs(Nargs::Optional)
1898                    .default("default.txt")
1899                    .build(),
1900            )
1901            .build();
1902
1903        // Without providing the optional argument - should use default
1904        let ctx = cmd.make_context("test", vec![], None);
1905        assert!(ctx.is_ok());
1906
1907        let ctx = ctx.unwrap();
1908        let file = ctx.get_param::<String>("file");
1909        assert_eq!(file, Some(&"default.txt".to_string()));
1910    }
1911
1912    #[test]
1913    fn test_required_followed_by_optional_argument() {
1914        use crate::parameter::Nargs;
1915
1916        let cmd = Command::new("copy")
1917            .argument(Argument::new("src").build()) // required
1918            .argument(
1919                Argument::new("dst")
1920                    .nargs(Nargs::Optional)
1921                    .default(".")
1922                    .build(),
1923            )
1924            .build();
1925
1926        // Only providing required arg
1927        let ctx = cmd.make_context("copy", vec!["source.txt".to_string()], None);
1928        assert!(ctx.is_ok());
1929
1930        let ctx = ctx.unwrap();
1931        let src = ctx.get_param::<String>("src");
1932        let dst = ctx.get_param::<String>("dst");
1933        assert_eq!(src, Some(&"source.txt".to_string()));
1934        assert_eq!(dst, Some(&".".to_string()));
1935    }
1936
1937    #[test]
1938    fn test_required_followed_by_optional_with_both_values() {
1939        use crate::parameter::Nargs;
1940
1941        let cmd = Command::new("copy")
1942            .argument(Argument::new("src").build()) // required
1943            .argument(
1944                Argument::new("dst")
1945                    .nargs(Nargs::Optional)
1946                    .default(".")
1947                    .build(),
1948            )
1949            .build();
1950
1951        // Providing both args
1952        let ctx = cmd.make_context(
1953            "copy",
1954            vec!["source.txt".to_string(), "dest.txt".to_string()],
1955            None,
1956        );
1957        assert!(ctx.is_ok());
1958
1959        let ctx = ctx.unwrap();
1960        let src = ctx.get_param::<String>("src");
1961        let dst = ctx.get_param::<String>("dst");
1962        assert_eq!(src, Some(&"source.txt".to_string()));
1963        assert_eq!(dst, Some(&"dest.txt".to_string()));
1964    }
1965
1966    // -------------------------------------------------------------------------
1967    // Multi-value argument error test (fix for issue 2)
1968    // -------------------------------------------------------------------------
1969
1970    #[test]
1971    fn test_multi_value_argument_incomplete_error() {
1972        use crate::parameter::Nargs;
1973
1974        let cmd = Command::new("point")
1975            .argument(Argument::new("coords").nargs(Nargs::Count(3)).build())
1976            .build();
1977
1978        // Only providing 2 of 3 required values
1979        let ctx = cmd.make_context("point", vec!["1".to_string(), "2".to_string()], None);
1980
1981        // Should fail with an error about missing values
1982        assert!(ctx.is_err());
1983        let err = ctx.unwrap_err();
1984        assert!(err.to_string().contains("takes 3 values"));
1985    }
1986
1987    // -------------------------------------------------------------------------
1988    // Optional option value test (fix for issue 4)
1989    // -------------------------------------------------------------------------
1990
1991    #[test]
1992    fn test_optional_option_value_with_flag_value() {
1993        use crate::parameter::Nargs;
1994
1995        let cmd = Command::new("test")
1996            .option(
1997                ClickOption::new(&["--opt"])
1998                    .nargs(Nargs::Optional)
1999                    .flag("flagval") // Used when option is used without value
2000                    .default("default")
2001                    .build(),
2002            )
2003            .build();
2004
2005        // Using --opt followed by another option (would trigger FlagNeedsValue)
2006        let ctx = cmd.make_context("test", vec!["--opt".to_string()], None);
2007        assert!(ctx.is_ok());
2008
2009        let ctx = ctx.unwrap();
2010        let opt = ctx.get_param::<String>("opt");
2011        // Should use the flag_value since --opt was used without an argument
2012        assert_eq!(opt, Some(&"flagval".to_string()));
2013    }
2014
2015    #[test]
2016    fn test_flag_value_group_shared_destination() {
2017        let cmd = Command::new("mine")
2018            .option(
2019                ClickOption::new(&["--moored", "-m"])
2020                    .dest("ty")
2021                    .flag("moored")
2022                    .default("moored")
2023                    .build(),
2024            )
2025            .option(
2026                ClickOption::new(&["--drifting", "-d"])
2027                    .dest("ty")
2028                    .flag("drifting")
2029                    .build(),
2030            )
2031            .build();
2032
2033        let ctx_default = cmd.make_context("mine", vec![], None).unwrap();
2034        assert_eq!(
2035            ctx_default.get_param::<String>("ty"),
2036            Some(&"moored".to_string())
2037        );
2038
2039        let ctx_drifting = cmd
2040            .make_context("mine", vec!["--drifting".to_string()], None)
2041            .unwrap();
2042        assert_eq!(
2043            ctx_drifting.get_param::<String>("ty"),
2044            Some(&"drifting".to_string())
2045        );
2046
2047        let ctx_moored = cmd
2048            .make_context("mine", vec!["--moored".to_string()], None)
2049            .unwrap();
2050        assert_eq!(
2051            ctx_moored.get_param::<String>("ty"),
2052            Some(&"moored".to_string())
2053        );
2054    }
2055
2056    #[test]
2057    fn test_flag_value_group_last_option_wins() {
2058        let cmd = Command::new("mine")
2059            .option(
2060                ClickOption::new(&["--moored"])
2061                    .dest("ty")
2062                    .flag("moored")
2063                    .default("moored")
2064                    .build(),
2065            )
2066            .option(
2067                ClickOption::new(&["--drifting"])
2068                    .dest("ty")
2069                    .flag("drifting")
2070                    .build(),
2071            )
2072            .build();
2073
2074        let ctx = cmd
2075            .make_context(
2076                "mine",
2077                vec!["--moored".to_string(), "--drifting".to_string()],
2078                None,
2079            )
2080            .unwrap();
2081        assert_eq!(ctx.get_param::<String>("ty"), Some(&"drifting".to_string()));
2082    }
2083
2084    // -------------------------------------------------------------------------
2085    // Eager option (--help) tests
2086    // -------------------------------------------------------------------------
2087
2088    #[test]
2089    fn test_help_with_missing_required_arg() {
2090        // --help should work even when a required argument is missing
2091        let cmd = Command::new("test")
2092            .argument(Argument::new("required_file").build())
2093            .build();
2094
2095        // Without --help, missing required arg should fail
2096        let ctx = cmd.make_context("test", vec![], None);
2097        assert!(ctx.is_err());
2098
2099        // With --help, should exit cleanly (Exit code 0)
2100        let ctx = cmd.make_context("test", vec!["--help".to_string()], None);
2101        assert!(matches!(ctx, Err(ClickError::Exit { code: 0 })));
2102    }
2103
2104    #[test]
2105    fn test_help_with_missing_required_option() {
2106        // --help should work even when a required option is missing
2107        let cmd = Command::new("test")
2108            .option(ClickOption::new(&["--name", "-n"]).required().build())
2109            .build();
2110
2111        // Without --help, missing required option should fail
2112        let ctx = cmd.make_context("test", vec![], None);
2113        assert!(ctx.is_err());
2114
2115        // With --help, should exit cleanly (Exit code 0)
2116        let ctx = cmd.make_context("test", vec!["--help".to_string()], None);
2117        assert!(matches!(ctx, Err(ClickError::Exit { code: 0 })));
2118    }
2119}