1use 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
54pub type CommandCallback = Box<dyn Fn(&Context) -> Result<(), ClickError> + Send + Sync>;
63
64pub struct Command {
85 pub name: Option<String>,
90
91 pub callback: Option<CommandCallback>,
95
96 pub options: Vec<ClickOption>,
98
99 pub arguments: Vec<Argument>,
101
102 pub help: Option<String>,
106
107 pub epilog: Option<String>,
109
110 pub short_help: Option<String>,
114
115 pub options_metavar: String,
117
118 pub add_help_option: bool,
120
121 pub no_args_is_help: bool,
123
124 pub hidden: bool,
126
127 pub deprecated: Option<String>,
132
133 pub allow_extra_args: bool,
135
136 pub allow_interspersed_args: bool,
138
139 pub ignore_unknown_options: bool,
141
142 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 #[allow(clippy::new_ret_no_self)]
208 pub fn new(name: &str) -> CommandBuilder {
209 CommandBuilder::new(name)
210 }
211
212 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 let Some(ref help_opt) = self.help_option {
228 return Some(help_opt.clone());
229 }
230
231 Some(make_help_option(&help_names))
233 }
234
235 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 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 pub fn param_count(&self) -> usize {
257 self.options.len() + self.arguments.len()
258 }
259
260 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 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 self.parse_args(&mut ctx, args)?;
314
315 Ok(ctx)
316 }
317
318 pub fn parse_args(&self, ctx: &mut Context, args: Vec<String>) -> Result<(), ClickError> {
323 if args.is_empty() && self.no_args_is_help && !ctx.resilient_parsing() {
325 return Err(ClickError::Exit { code: 0 });
327 }
328
329 let mut parser = OptionParser::new()
331 .allow_interspersed_args(ctx.allow_interspersed_args())
332 .ignore_unknown_options(ctx.ignore_unknown_options());
333
334 let help_opt = self.get_help_option(ctx);
336
337 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 for arg in &self.arguments {
347 self.add_argument_to_parser(&mut parser, arg);
348 }
349
350 let (opts, remaining, _order) = parser.parse_args(args)?;
352
353 if let Some(ref help) = help_opt {
356 if let Some(ParsedValue::Single(val)) = opts.get(help.name()) {
357 if val == "true" {
358 ctx.params_mut().insert(
361 help.name().to_string(),
362 Arc::new(true) as Arc<dyn std::any::Any + Send + Sync>,
363 );
364 return Err(ClickError::Exit { code: 0 });
367 }
368 }
369 }
370
371 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 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 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 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 for arg in &self.arguments {
407 self.process_argument_value(ctx, arg, &opts)?;
408 }
409
410 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 ctx.args_mut().extend(remaining);
422
423 Ok(())
424 }
425
426 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), };
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), };
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 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, };
474
475 parser.add_argument(arg.name(), nargs_val);
476 }
477
478 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 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 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 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 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 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 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 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 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 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 pub fn invoke(&self, ctx: &Context) -> Result<(), ClickError> {
827 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 if let Some(ref callback) = self.callback {
843 callback(ctx)
844 } else {
845 Ok(())
846 }
847 }
848
849 #[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 let ctx_result = self.make_context(&prog_name, args, None);
887
888 match ctx_result {
889 Ok(ctx) => {
890 let ctx = Arc::new(ctx);
893
894 push_context(Arc::clone(&ctx));
896
897 let result = self.invoke(&ctx);
899
900 pop_context();
902
903 ctx.close();
905
906 result
907 }
908 Err(ClickError::Exit { code: 0 }) => {
909 if let Some(version_output) = self.get_version_output_from_args(&args_for_eager) {
914 println!("{}", version_output);
915 return Ok(());
916 }
917
918 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 pub fn get_version_output_from_args(&self, args: &[String]) -> Option<String> {
952 for opt in &self.options {
953 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 pub fn get_usage(&self, ctx: &Context) -> String {
975 let mut pieces = Vec::new();
976
977 if !self.options_metavar.is_empty() {
979 pieces.push(self.options_metavar.clone());
980 }
981
982 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 pub fn get_help(&self, ctx: &Context) -> String {
997 let mut parts = Vec::new();
998
999 parts.push(self.get_usage(ctx));
1001
1002 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()); 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 let opt_records: Vec<(String, String)> = self
1030 .options
1031 .iter()
1032 .filter_map(|opt| opt.get_help_record())
1033 .collect();
1034
1035 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 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 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 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 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
1123pub 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 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 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 pub fn option(mut self, opt: ClickOption) -> Self {
1188 self.options.push(opt);
1189 self
1190 }
1191
1192 pub fn argument(mut self, arg: Argument) -> Self {
1194 self.arguments.push(arg);
1195 self
1196 }
1197
1198 pub fn help(mut self, help: &str) -> Self {
1200 self.help = Some(help.to_string());
1201 self
1202 }
1203
1204 pub fn epilog(mut self, epilog: &str) -> Self {
1206 self.epilog = Some(epilog.to_string());
1207 self
1208 }
1209
1210 pub fn short_help(mut self, short_help: &str) -> Self {
1212 self.short_help = Some(short_help.to_string());
1213 self
1214 }
1215
1216 pub fn options_metavar(mut self, metavar: &str) -> Self {
1218 self.options_metavar = metavar.to_string();
1219 self
1220 }
1221
1222 pub fn add_help_option(mut self, add: bool) -> Self {
1224 self.add_help_option = add;
1225 self
1226 }
1227
1228 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 pub fn no_args_is_help(mut self, value: bool) -> Self {
1242 self.no_args_is_help = value;
1243 self
1244 }
1245
1246 pub fn hidden(mut self) -> Self {
1248 self.hidden = true;
1249 self
1250 }
1251
1252 pub fn deprecated(mut self, message: &str) -> Self {
1257 self.deprecated = Some(message.to_string());
1258 self
1259 }
1260
1261 pub fn allow_extra_args(mut self, allow: bool) -> Self {
1263 self.allow_extra_args = allow;
1264 self
1265 }
1266
1267 pub fn allow_interspersed_args(mut self, allow: bool) -> Self {
1269 self.allow_interspersed_args = allow;
1270 self
1271 }
1272
1273 pub fn ignore_unknown_options(mut self, ignore: bool) -> Self {
1275 self.ignore_unknown_options = ignore;
1276 self
1277 }
1278
1279 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
1302fn make_help_option(names: &[String]) -> ClickOption {
1308 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#[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 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 let cmd = Command::new("test").short_help("Test command").build();
1567 assert_eq!(cmd.get_short_help(), "Test command");
1568
1569 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 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 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 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 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 #[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 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 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()) .argument(
1919 Argument::new("dst")
1920 .nargs(Nargs::Optional)
1921 .default(".")
1922 .build(),
1923 )
1924 .build();
1925
1926 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()) .argument(
1944 Argument::new("dst")
1945 .nargs(Nargs::Optional)
1946 .default(".")
1947 .build(),
1948 )
1949 .build();
1950
1951 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 #[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 let ctx = cmd.make_context("point", vec!["1".to_string(), "2".to_string()], None);
1980
1981 assert!(ctx.is_err());
1983 let err = ctx.unwrap_err();
1984 assert!(err.to_string().contains("takes 3 values"));
1985 }
1986
1987 #[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") .default("default")
2001 .build(),
2002 )
2003 .build();
2004
2005 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 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 #[test]
2089 fn test_help_with_missing_required_arg() {
2090 let cmd = Command::new("test")
2092 .argument(Argument::new("required_file").build())
2093 .build();
2094
2095 let ctx = cmd.make_context("test", vec![], None);
2097 assert!(ctx.is_err());
2098
2099 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 let cmd = Command::new("test")
2108 .option(ClickOption::new(&["--name", "-n"]).required().build())
2109 .build();
2110
2111 let ctx = cmd.make_context("test", vec![], None);
2113 assert!(ctx.is_err());
2114
2115 let ctx = cmd.make_context("test", vec!["--help".to_string()], None);
2117 assert!(matches!(ctx, Err(ClickError::Exit { code: 0 })));
2118 }
2119}