1use std::collections::HashMap;
43use std::sync::Arc;
44
45use crate::argument::Argument;
46use crate::context::{push_context, pop_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("arguments", &format!("<{} arguments>", self.arguments.len()))
152 .field("help", &self.help)
153 .field("epilog", &self.epilog)
154 .field("short_help", &self.short_help)
155 .field("options_metavar", &self.options_metavar)
156 .field("add_help_option", &self.add_help_option)
157 .field("no_args_is_help", &self.no_args_is_help)
158 .field("hidden", &self.hidden)
159 .field("deprecated", &self.deprecated)
160 .field("allow_extra_args", &self.allow_extra_args)
161 .field("allow_interspersed_args", &self.allow_interspersed_args)
162 .field("ignore_unknown_options", &self.ignore_unknown_options)
163 .finish()
164 }
165}
166
167impl Default for Command {
168 fn default() -> Self {
169 Self {
170 name: None,
171 callback: None,
172 options: Vec::new(),
173 arguments: Vec::new(),
174 help: None,
175 epilog: None,
176 short_help: None,
177 options_metavar: "[OPTIONS]".to_string(),
178 add_help_option: true,
179 no_args_is_help: false,
180 hidden: false,
181 deprecated: None,
182 allow_extra_args: false,
183 allow_interspersed_args: true,
184 ignore_unknown_options: false,
185 help_option: None,
186 }
187 }
188}
189
190impl Command {
191 const VERSION_METAVAR_PREFIX: &'static str = "__click_version__:";
192
193 #[allow(clippy::new_ret_no_self)]
205 pub fn new(name: &str) -> CommandBuilder {
206 CommandBuilder::new(name)
207 }
208
209 pub fn get_help_option(&self, ctx: &Context) -> Option<ClickOption> {
214 if !self.add_help_option {
215 return None;
216 }
217
218 let help_names = self.get_help_option_names(ctx);
219 if help_names.is_empty() {
220 return None;
221 }
222
223 if let Some(ref help_opt) = self.help_option {
225 return Some(help_opt.clone());
226 }
227
228 Some(make_help_option(&help_names))
230 }
231
232 fn get_help_option_names(&self, ctx: &Context) -> Vec<String> {
236 let mut all_names: std::collections::HashSet<String> =
237 ctx.help_option_names().iter().cloned().collect();
238
239 for opt in &self.options {
241 for name in &opt.long {
242 all_names.remove(name);
243 }
244 for name in &opt.short {
245 all_names.remove(name);
246 }
247 }
248
249 all_names.into_iter().collect()
250 }
251
252 pub fn param_count(&self) -> usize {
254 self.options.len() + self.arguments.len()
255 }
256
257 pub fn get_params(&self, _ctx: &Context) -> Vec<&dyn Parameter> {
262 let mut params: Vec<&dyn Parameter> = Vec::with_capacity(self.param_count());
263 for opt in &self.options {
264 params.push(opt);
265 }
266 for arg in &self.arguments {
267 params.push(arg);
268 }
269 params
270 }
271
272 pub fn make_context(
292 &self,
293 info_name: &str,
294 args: Vec<String>,
295 parent: Option<Arc<Context>>,
296 ) -> Result<Context, ClickError> {
297 let mut builder = ContextBuilder::new()
298 .info_name(info_name)
299 .allow_extra_args(self.allow_extra_args)
300 .allow_interspersed_args(self.allow_interspersed_args)
301 .ignore_unknown_options(self.ignore_unknown_options);
302
303 if let Some(parent) = parent {
304 builder = builder.parent(parent);
305 }
306
307 let mut ctx = builder.build();
308
309 self.parse_args(&mut ctx, args)?;
311
312 Ok(ctx)
313 }
314
315 pub fn parse_args(&self, ctx: &mut Context, args: Vec<String>) -> Result<(), ClickError> {
320 if args.is_empty() && self.no_args_is_help && !ctx.resilient_parsing() {
322 return Err(ClickError::Exit { code: 0 });
324 }
325
326 let mut parser = OptionParser::new()
328 .allow_interspersed_args(ctx.allow_interspersed_args())
329 .ignore_unknown_options(ctx.ignore_unknown_options());
330
331 let help_opt = self.get_help_option(ctx);
333
334 for opt in &self.options {
336 self.add_option_to_parser(&mut parser, opt);
337 }
338 if let Some(ref help) = help_opt {
339 self.add_option_to_parser(&mut parser, help);
340 }
341
342 for arg in &self.arguments {
344 self.add_argument_to_parser(&mut parser, arg);
345 }
346
347 let (opts, remaining, _order) = parser.parse_args(args)?;
349
350 if let Some(ref help) = help_opt {
353 if let Some(ParsedValue::Single(val)) = opts.get(help.name()) {
354 if val == "true" {
355 ctx.params_mut().insert(
358 help.name().to_string(),
359 Arc::new(true) as Arc<dyn std::any::Any + Send + Sync>,
360 );
361 return Err(ClickError::Exit { code: 0 });
364 }
365 }
366 }
367
368 for opt in &self.options {
370 if opt.is_eager() {
371 if let Some(ParsedValue::Single(val)) = opts.get(opt.name()) {
372 if val == "true" || !val.is_empty() {
373 ctx.params_mut().insert(
376 opt.name().to_string(),
377 Arc::new(val.clone()) as Arc<dyn std::any::Any + Send + Sync>,
378 );
379
380 if opt
382 .config
383 .metavar
384 .as_deref()
385 .is_some_and(|m| m.starts_with(Self::VERSION_METAVAR_PREFIX))
386 {
387 return Err(ClickError::Exit { code: 0 });
388 }
389 }
390 }
391 }
392 }
393
394 for opt in &self.options {
396 self.process_option_value(ctx, opt, &opts)?;
397 }
398 if let Some(ref help) = help_opt {
399 self.process_option_value(ctx, help, &opts)?;
400 }
401
402 for arg in &self.arguments {
404 self.process_argument_value(ctx, arg, &opts)?;
405 }
406
407 if !remaining.is_empty() && !ctx.allow_extra_args() && !ctx.resilient_parsing() {
409 let extra_args = remaining.join(" ");
410 return Err(ClickError::usage(if remaining.len() == 1 {
411 format!("Got unexpected extra argument ({})", extra_args)
412 } else {
413 format!("Got unexpected extra arguments ({})", extra_args)
414 }));
415 }
416
417 ctx.args_mut().extend(remaining);
419
420 Ok(())
421 }
422
423 fn add_option_to_parser(&self, parser: &mut OptionParser, opt: &ClickOption) {
425 let mut opts: Vec<&str> = Vec::new();
426 for s in &opt.short {
427 opts.push(s.as_str());
428 }
429 for l in &opt.long {
430 opts.push(l.as_str());
431 }
432
433 let (action, nargs, const_value, flag_needs_value) = if opt.count {
434 (OptionAction::Count, 0, None, false)
435 } else if opt.is_flag {
436 let const_val = opt.flag_value.as_deref().unwrap_or("true");
437 (OptionAction::StoreConst, 0, Some(const_val), false)
438 } else if opt.multiple() {
439 let (nargs_val, fnv) = match opt.nargs() {
440 Nargs::Count(n) => (n as i32, false),
441 Nargs::Variadic => (-1, false),
442 Nargs::Optional => (1, true), };
444 (OptionAction::Append, nargs_val, None, fnv)
445 } else {
446 let (nargs_val, fnv) = match opt.nargs() {
447 Nargs::Count(n) => (n as i32, false),
448 Nargs::Variadic => (-1, false),
449 Nargs::Optional => (1, true), };
451 (OptionAction::Store, nargs_val, None, fnv)
452 };
453
454 parser.add_option_ex(&opts, opt.name(), action, nargs, const_value, flag_needs_value);
455 }
456
457 fn add_argument_to_parser(&self, parser: &mut OptionParser, arg: &Argument) {
459 let nargs_val = match arg.nargs() {
460 Nargs::Count(n) => n as i32,
461 Nargs::Variadic => -1,
462 Nargs::Optional => NARGS_OPTIONAL, };
464
465 parser.add_argument(arg.name(), nargs_val);
466 }
467
468 fn resolve_envvar_value(&self, ctx: &Context, param: &dyn Parameter) -> Option<String> {
470 let mut candidates: Vec<String> = Vec::new();
471
472 if let Some(envvars) = param.envvar() {
473 candidates.extend(envvars.iter().cloned());
474 } else if let Some(prefix) = ctx.auto_envvar_prefix() {
475 let normalized = param.name().to_uppercase().replace('-', "_");
476 candidates.push(format!("{}_{}", prefix, normalized));
477 }
478
479 for name in candidates {
480 if let Ok(value) = std::env::var(&name) {
481 if !value.is_empty() {
482 return Some(value);
483 }
484 }
485 }
486
487 None
488 }
489
490 fn process_option_value(
492 &self,
493 ctx: &mut Context,
494 opt: &ClickOption,
495 opts: &HashMap<String, ParsedValue>,
496 ) -> Result<(), ClickError> {
497 let name = opt.name();
498 let parsed_value = opts.get(name);
499
500 let flag_needs_value_key = format!("__click_internal_flag_needs_value_{}", name);
503 let had_flag_needs_value = opts.get(&flag_needs_value_key).is_some();
504
505 let make_error_ctx = || {
506 ErrorContext::new()
507 .with_command_path(ctx.command_path())
508 .with_usage(self.get_usage(ctx))
509 .with_help_options(ctx.help_option_names().to_vec())
510 };
511
512 let convert_single = |value: &str| -> Result<Arc<dyn std::any::Any + Send + Sync>, ClickError> {
513 opt.convert_any(value)
514 .map(Arc::from)
515 .map_err(|msg| {
516 ClickError::bad_parameter_named(msg, opt.human_readable_name())
517 .with_context(make_error_ctx())
518 })
519 };
520
521 let convert_multi = |values: &[String]| -> Result<Arc<dyn std::any::Any + Send + Sync>, ClickError> {
522 opt.convert_multi(values)
523 .map(Arc::from)
524 .map_err(|msg| {
525 ClickError::bad_parameter_named(msg, opt.human_readable_name())
526 .with_context(make_error_ctx())
527 })
528 };
529
530 let envvar_value = if matches!(parsed_value, Some(ParsedValue::Unset) | None) {
532 self.resolve_envvar_value(ctx, opt)
533 } else {
534 None
535 };
536
537 let default_map_value = if matches!(parsed_value, Some(ParsedValue::Unset) | None) {
538 ctx.lookup_default_value(name)
539 } else {
540 None
541 };
542
543 let mut source: Option<ParameterSource> = None;
544 let mut value: Option<Arc<dyn std::any::Any + Send + Sync>> = match parsed_value {
545 Some(ParsedValue::Count(n)) => {
546 source = Some(ParameterSource::CommandLine);
547 Some(Arc::new(*n))
548 }
549 Some(ParsedValue::Flag(b)) => {
550 source = Some(ParameterSource::CommandLine);
551 Some(Arc::new(*b))
552 }
553 Some(ParsedValue::Single(s)) => {
554 source = Some(ParameterSource::CommandLine);
555 Some(convert_single(s)?)
556 }
557 Some(ParsedValue::Multiple(v)) => {
558 let mut values = v.clone();
559 if had_flag_needs_value {
560 if values.is_empty() {
561 if let Some(ref flag_val) = opt.flag_value {
562 values.push(flag_val.clone());
563 } else if let Some(ref default) = opt.default {
564 values.push(default.clone());
565 } else {
566 values.push(String::new());
567 }
568 } else if let Some(ref flag_val) = opt.flag_value {
569 values.push(flag_val.clone());
570 } else {
571 values.push(String::new());
572 }
573 }
574 source = Some(ParameterSource::CommandLine);
575 Some(convert_multi(&values)?)
576 }
577 Some(ParsedValue::FlagNeedsValue) => {
578 let fallback = opt
579 .flag_value
580 .as_ref()
581 .or(opt.default.as_ref())
582 .cloned()
583 .unwrap_or_else(String::new);
584 source = Some(ParameterSource::CommandLine);
585 Some(convert_single(&fallback)?)
586 }
587 Some(ParsedValue::Unset) | None => {
588 if let Some(envval) = envvar_value {
589 source = Some(ParameterSource::Environment);
590 if opt.count {
591 let parsed = envval.parse::<usize>().map_err(|_| {
592 ClickError::bad_parameter_named(
593 format!("'{}' is not a valid integer.", envval),
594 opt.human_readable_name(),
595 )
596 .with_context(make_error_ctx())
597 })?;
598 Some(Arc::new(parsed))
599 } else if opt.nargs().is_multi() || opt.multiple() {
600 let values = opt.type_converter().split_envvar_value(&envval);
601 if values.is_empty() {
602 None
603 } else {
604 Some(convert_multi(&values)?)
605 }
606 } else {
607 Some(convert_single(&envval)?)
608 }
609 } else if let Some(default_map) = default_map_value {
610 source = Some(ParameterSource::DefaultMap);
611 Some(default_map)
612 } else if let Some(ref prompt_text) = opt.prompt {
613 if ctx.resilient_parsing() || opt.is_flag || opt.count {
614 None
615 } else {
616 let default_value = opt.default.clone();
617 let prompted = termui::prompt(
618 prompt_text,
619 default_value,
620 opt.hide_input,
621 opt.confirmation_prompt,
622 |input| {
623 match opt.convert_any(input) {
624 Ok(any_val) => {
625 if let Ok(val) = any_val.downcast::<String>() {
626 Ok(*val)
627 } else {
628 Ok(input.to_string())
629 }
630 }
631 Err(msg) => Err(msg),
632 }
633 },
634 )?;
635
636 source = Some(ParameterSource::Prompt);
637 if opt.nargs().is_multi() || opt.multiple() {
638 Some(convert_multi(&vec![prompted])?)
639 } else {
640 Some(convert_single(&prompted)?)
641 }
642 }
643 } else if opt.count {
644 source = Some(ParameterSource::Default);
645 Some(Arc::new(0usize))
646 } else if let Some(ref default) = opt.default {
647 source = Some(ParameterSource::Default);
648 if opt.nargs().is_multi() || opt.multiple() {
649 Some(convert_multi(&vec![default.clone()])?)
650 } else {
651 Some(convert_single(default)?)
652 }
653 } else {
654 None
655 }
656 }
657 };
658
659 if value.is_none() && opt.required() && !ctx.resilient_parsing() {
661 let error_ctx = ErrorContext::new()
662 .with_command_path(ctx.command_path())
663 .with_usage(self.get_usage(ctx))
664 .with_help_options(ctx.help_option_names().to_vec());
665 return Err(ClickError::missing_option(opt.human_readable_name()).with_context(error_ctx));
666 }
667
668 if let Some(ref callback) = opt.config.callback {
669 if let Some(current) = value.take() {
670 value = Some(callback(ctx, opt, current)?);
671 }
672 }
673
674 if let Some(v) = value {
676 if let Some(source) = source {
677 ctx.set_parameter_source(name, source);
678 }
679 if opt.expose_value() {
680 ctx.params_mut().insert(name.to_string(), v);
681 }
682 }
683
684 Ok(())
685 }
686
687 fn process_argument_value(
689 &self,
690 ctx: &mut Context,
691 arg: &Argument,
692 opts: &HashMap<String, ParsedValue>,
693 ) -> Result<(), ClickError> {
694 let name = arg.name();
695 let parsed_value = opts.get(name);
696
697 let envvar_value = if matches!(parsed_value, Some(ParsedValue::Unset) | None) {
698 self.resolve_envvar_value(ctx, arg)
699 } else {
700 None
701 };
702
703 let make_error_ctx = || {
704 ErrorContext::new()
705 .with_command_path(ctx.command_path())
706 .with_usage(self.get_usage(ctx))
707 .with_help_options(ctx.help_option_names().to_vec())
708 };
709
710 let convert_single = |value: &str| -> Result<Arc<dyn std::any::Any + Send + Sync>, ClickError> {
711 arg.convert_any(value)
712 .map(Arc::from)
713 .map_err(|msg| {
714 ClickError::bad_parameter_named(msg, arg.human_readable_name())
715 .with_context(make_error_ctx())
716 })
717 };
718
719 let convert_multi = |values: &[String]| -> Result<Arc<dyn std::any::Any + Send + Sync>, ClickError> {
720 arg.type_converter()
721 .convert_multi(values)
722 .map(Arc::from)
723 .map_err(|msg| {
724 ClickError::bad_parameter_named(msg, arg.human_readable_name())
725 .with_context(make_error_ctx())
726 })
727 };
728
729 let default_map_value = if matches!(parsed_value, Some(ParsedValue::Unset) | None) {
731 ctx.lookup_default_value(name)
732 } else {
733 None
734 };
735
736 let mut source: Option<ParameterSource> = None;
737 let mut value: Option<Arc<dyn std::any::Any + Send + Sync>> = match parsed_value {
738 Some(ParsedValue::Single(s)) => {
739 source = Some(ParameterSource::CommandLine);
740 Some(convert_single(s)?)
741 }
742 Some(ParsedValue::Multiple(v)) => {
743 source = Some(ParameterSource::CommandLine);
744 Some(convert_multi(v)?)
745 }
746 Some(ParsedValue::Count(n)) => {
747 source = Some(ParameterSource::CommandLine);
748 Some(Arc::new(*n))
749 }
750 Some(ParsedValue::Flag(b)) => {
751 source = Some(ParameterSource::CommandLine);
752 Some(Arc::new(*b))
753 }
754 Some(ParsedValue::FlagNeedsValue) | Some(ParsedValue::Unset) | None => {
755 if let Some(envval) = envvar_value {
756 source = Some(ParameterSource::Environment);
757 if arg.nargs().is_multi() || arg.multiple() {
758 let values = arg.type_converter().split_envvar_value(&envval);
759 if values.is_empty() {
760 None
761 } else {
762 Some(convert_multi(&values)?)
763 }
764 } else {
765 Some(convert_single(&envval)?)
766 }
767 } else if let Some(default_map) = default_map_value {
768 source = Some(ParameterSource::DefaultMap);
769 Some(default_map)
770 } else {
771 arg.default_value()
772 .map(|d| {
773 source = Some(ParameterSource::Default);
774 if arg.nargs().is_multi() || arg.multiple() {
775 convert_multi(&vec![d.to_string()]).map_err(|e| e)
776 } else {
777 convert_single(d).map_err(|e| e)
778 }
779 })
780 .transpose()?
781 }
782 }
783 };
784
785 if value.is_none() && arg.required() && !ctx.resilient_parsing() {
787 let error_ctx = ErrorContext::new()
788 .with_command_path(ctx.command_path())
789 .with_usage(self.get_usage(ctx))
790 .with_help_options(ctx.help_option_names().to_vec());
791 return Err(ClickError::missing_argument(arg.human_readable_name()).with_context(error_ctx));
792 }
793
794 if let Some(ref callback) = arg.config.callback {
795 if let Some(current) = value.take() {
796 value = Some(callback(ctx, arg, current)?);
797 }
798 }
799
800 if let Some(v) = value {
802 if let Some(source) = source {
803 ctx.set_parameter_source(name, source);
804 }
805 if arg.expose_value() {
806 ctx.params_mut().insert(name.to_string(), v);
807 }
808 }
809
810 Ok(())
811 }
812
813 pub fn invoke(&self, ctx: &Context) -> Result<(), ClickError> {
817 if let Some(ref deprecated) = self.deprecated {
819 let extra = if deprecated.is_empty() {
820 String::new()
821 } else {
822 format!(" {}", deprecated)
823 };
824 eprintln!(
825 "DeprecationWarning: The command '{}' is deprecated.{}",
826 self.name.as_deref().unwrap_or(""),
827 extra
828 );
829 }
830
831 if let Some(ref callback) = self.callback {
833 callback(ctx)
834 } else {
835 Ok(())
836 }
837 }
838
839 #[allow(clippy::arc_with_non_send_sync)]
866 pub fn main(&self, args: Vec<String>) -> Result<(), ClickError> {
867 let prog_name = self.name.clone().unwrap_or_else(|| {
868 std::env::args()
869 .next()
870 .unwrap_or_else(|| "program".to_string())
871 });
872
873 let args_for_eager = args.clone();
874
875 let ctx_result = self.make_context(&prog_name, args, None);
877
878 match ctx_result {
879 Ok(ctx) => {
880 let ctx = Arc::new(ctx);
883
884 push_context(Arc::clone(&ctx));
886
887 let result = self.invoke(&ctx);
889
890 pop_context();
892
893 ctx.close();
895
896 result
897 }
898 Err(ClickError::Exit { code: 0 }) => {
899 if let Some(version_output) = self.get_version_output_from_args(&args_for_eager) {
904 println!("{}", version_output);
905 return Ok(());
906 }
907
908 let ctx = ContextBuilder::new().info_name(&prog_name).build();
911 println!("{}", self.get_help(&ctx));
912 Ok(())
913 }
914 Err(e) => Err(e),
915 }
916 }
917
918 fn arg_matches_opt(arg: &str, opt: &str) -> bool {
919 if arg == opt {
920 return true;
921 }
922 if opt.starts_with("--") && arg.starts_with(opt) && arg.get(opt.len()..opt.len() + 1) == Some("=") {
923 return true;
924 }
925 if opt.starts_with('-') && opt.len() == 2 && !opt.starts_with("--") {
926 let needle = opt.chars().nth(1).unwrap_or('\0');
927 if arg.starts_with('-') && !arg.starts_with("--") {
928 return arg.chars().skip(1).any(|c| c == needle);
929 }
930 }
931 false
932 }
933
934 pub fn get_version_output_from_args(&self, args: &[String]) -> Option<String> {
939 for opt in &self.options {
940 let Some(meta) = opt.config.metavar.as_deref() else {
944 continue;
945 };
946 let Some(output) = meta.strip_prefix(Self::VERSION_METAVAR_PREFIX) else {
947 continue;
948 };
949
950 let mut names = opt.long.iter().chain(opt.short.iter());
951 if names.any(|n| args.iter().any(|a| Self::arg_matches_opt(a, n))) {
952 return Some(output.to_string());
953 }
954 }
955 None
956 }
957
958 pub fn get_usage(&self, ctx: &Context) -> String {
962 let mut pieces = Vec::new();
963
964 if !self.options_metavar.is_empty() {
966 pieces.push(self.options_metavar.clone());
967 }
968
969 for arg in &self.arguments {
971 if !arg.hidden() {
972 pieces.push(arg.make_metavar());
973 }
974 }
975
976 format!("Usage: {} {}", ctx.command_path(), pieces.join(" "))
977 }
978
979 pub fn get_help(&self, ctx: &Context) -> String {
984 let mut parts = Vec::new();
985
986 parts.push(self.get_usage(ctx));
988
989 if let Some(ref help) = self.help {
991 let text = help.lines().next().unwrap_or("");
992 if !text.is_empty() {
993 parts.push(String::new()); let help_text = if let Some(ref dep) = self.deprecated {
995 if dep.is_empty() {
996 format!("{} (DEPRECATED)", text)
997 } else {
998 format!("{} (DEPRECATED: {})", text, dep)
999 }
1000 } else {
1001 text.to_string()
1002 };
1003 parts.push(format!(" {}", help_text));
1004 }
1005 } else if let Some(ref dep) = self.deprecated {
1006 parts.push(String::new());
1007 let dep_msg = if dep.is_empty() {
1008 "(DEPRECATED)".to_string()
1009 } else {
1010 format!("(DEPRECATED: {})", dep)
1011 };
1012 parts.push(format!(" {}", dep_msg));
1013 }
1014
1015 let opt_records: Vec<(String, String)> = self
1017 .options
1018 .iter()
1019 .filter_map(|opt| opt.get_help_record())
1020 .collect();
1021
1022 let help_opt = self.get_help_option(ctx);
1024 let help_record = help_opt.as_ref().and_then(|h| h.get_help_record());
1025
1026 if !opt_records.is_empty() || help_record.is_some() {
1027 parts.push(String::new());
1028 parts.push("Options:".to_string());
1029
1030 for (opt_str, help) in &opt_records {
1031 parts.push(format!(" {} {}", opt_str, help));
1032 }
1033 if let Some((opt_str, help)) = help_record {
1034 parts.push(format!(" {} {}", opt_str, help));
1035 }
1036 }
1037
1038 let arg_records: Vec<(String, String)> = self
1040 .arguments
1041 .iter()
1042 .filter_map(|arg| arg.get_help_record())
1043 .filter(|(_, help)| !help.is_empty())
1044 .collect();
1045
1046 if !arg_records.is_empty() {
1047 parts.push(String::new());
1048 parts.push("Arguments:".to_string());
1049 for (metavar, help) in &arg_records {
1050 parts.push(format!(" {} {}", metavar, help));
1051 }
1052 }
1053
1054 if let Some(ref epilog) = self.epilog {
1056 parts.push(String::new());
1057 parts.push(epilog.clone());
1058 }
1059
1060 parts.join("\n")
1061 }
1062
1063 pub fn get_short_help(&self) -> String {
1068 if let Some(ref short_help) = self.short_help {
1069 let text = short_help.clone();
1070 if let Some(ref dep) = self.deprecated {
1071 if dep.is_empty() {
1072 format!("{} (DEPRECATED)", text)
1073 } else {
1074 format!("{} (DEPRECATED: {})", text, dep)
1075 }
1076 } else {
1077 text
1078 }
1079 } else if let Some(ref help) = self.help {
1080 let text = help
1082 .lines()
1083 .next()
1084 .unwrap_or("")
1085 .split('.')
1086 .next()
1087 .unwrap_or("")
1088 .trim();
1089 if let Some(ref dep) = self.deprecated {
1090 if dep.is_empty() {
1091 format!("{} (DEPRECATED)", text)
1092 } else {
1093 format!("{} (DEPRECATED: {})", text, dep)
1094 }
1095 } else {
1096 text.to_string()
1097 }
1098 } else if let Some(ref dep) = self.deprecated {
1099 if dep.is_empty() {
1100 "(DEPRECATED)".to_string()
1101 } else {
1102 format!("(DEPRECATED: {})", dep)
1103 }
1104 } else {
1105 String::new()
1106 }
1107 }
1108}
1109
1110pub struct CommandBuilder {
1120 name: String,
1121 callback: Option<CommandCallback>,
1122 options: Vec<ClickOption>,
1123 arguments: Vec<Argument>,
1124 help: Option<String>,
1125 epilog: Option<String>,
1126 short_help: Option<String>,
1127 options_metavar: String,
1128 add_help_option: bool,
1129 help_option: Option<ClickOption>,
1130 no_args_is_help: bool,
1131 hidden: bool,
1132 deprecated: Option<String>,
1133 allow_extra_args: bool,
1134 allow_interspersed_args: bool,
1135 ignore_unknown_options: bool,
1136}
1137
1138impl CommandBuilder {
1139 pub fn new(name: &str) -> Self {
1141 Self {
1142 name: name.to_string(),
1143 callback: None,
1144 options: Vec::new(),
1145 arguments: Vec::new(),
1146 help: None,
1147 epilog: None,
1148 short_help: None,
1149 options_metavar: "[OPTIONS]".to_string(),
1150 add_help_option: true,
1151 help_option: None,
1152 no_args_is_help: false,
1153 hidden: false,
1154 deprecated: None,
1155 allow_extra_args: false,
1156 allow_interspersed_args: true,
1157 ignore_unknown_options: false,
1158 }
1159 }
1160
1161 pub fn callback<F>(mut self, f: F) -> Self
1166 where
1167 F: Fn(&Context) -> Result<(), ClickError> + Send + Sync + 'static,
1168 {
1169 self.callback = Some(Box::new(f));
1170 self
1171 }
1172
1173 pub fn option(mut self, opt: ClickOption) -> Self {
1175 self.options.push(opt);
1176 self
1177 }
1178
1179 pub fn argument(mut self, arg: Argument) -> Self {
1181 self.arguments.push(arg);
1182 self
1183 }
1184
1185 pub fn help(mut self, help: &str) -> Self {
1187 self.help = Some(help.to_string());
1188 self
1189 }
1190
1191 pub fn epilog(mut self, epilog: &str) -> Self {
1193 self.epilog = Some(epilog.to_string());
1194 self
1195 }
1196
1197 pub fn short_help(mut self, short_help: &str) -> Self {
1199 self.short_help = Some(short_help.to_string());
1200 self
1201 }
1202
1203 pub fn options_metavar(mut self, metavar: &str) -> Self {
1205 self.options_metavar = metavar.to_string();
1206 self
1207 }
1208
1209 pub fn add_help_option(mut self, add: bool) -> Self {
1211 self.add_help_option = add;
1212 self
1213 }
1214
1215 pub fn help_option(mut self, opt: ClickOption) -> Self {
1222 self.add_help_option = true;
1223 self.help_option = Some(opt);
1224 self
1225 }
1226
1227 pub fn no_args_is_help(mut self, value: bool) -> Self {
1229 self.no_args_is_help = value;
1230 self
1231 }
1232
1233 pub fn hidden(mut self) -> Self {
1235 self.hidden = true;
1236 self
1237 }
1238
1239 pub fn deprecated(mut self, message: &str) -> Self {
1244 self.deprecated = Some(message.to_string());
1245 self
1246 }
1247
1248 pub fn allow_extra_args(mut self, allow: bool) -> Self {
1250 self.allow_extra_args = allow;
1251 self
1252 }
1253
1254 pub fn allow_interspersed_args(mut self, allow: bool) -> Self {
1256 self.allow_interspersed_args = allow;
1257 self
1258 }
1259
1260 pub fn ignore_unknown_options(mut self, ignore: bool) -> Self {
1262 self.ignore_unknown_options = ignore;
1263 self
1264 }
1265
1266 pub fn build(self) -> Command {
1268 Command {
1269 name: Some(self.name),
1270 callback: self.callback,
1271 options: self.options,
1272 arguments: self.arguments,
1273 help: self.help,
1274 epilog: self.epilog,
1275 short_help: self.short_help,
1276 options_metavar: self.options_metavar,
1277 add_help_option: self.add_help_option,
1278 no_args_is_help: self.no_args_is_help,
1279 hidden: self.hidden,
1280 deprecated: self.deprecated,
1281 allow_extra_args: self.allow_extra_args,
1282 allow_interspersed_args: self.allow_interspersed_args,
1283 ignore_unknown_options: self.ignore_unknown_options,
1284 help_option: self.help_option,
1285 }
1286 }
1287}
1288
1289fn make_help_option(names: &[String]) -> ClickOption {
1295 let name_refs: Vec<&str> = names.iter().map(|s| s.as_str()).collect();
1297
1298 ClickOption::new(&name_refs)
1299 .flag("true")
1300 .eager()
1301 .help("Show this message and exit.")
1302 .build()
1303}
1304
1305#[cfg(test)]
1310mod tests {
1311 use super::*;
1312 use crate::source::ParameterSource;
1313 use crate::types::INT;
1314 use std::collections::HashMap;
1315 use std::sync::Arc;
1316
1317 #[test]
1318 fn test_command_creation_defaults() {
1319 let cmd = Command::new("test").build();
1320
1321 assert_eq!(cmd.name, Some("test".to_string()));
1322 assert!(cmd.callback.is_none());
1323 assert!(cmd.options.is_empty());
1324 assert!(cmd.arguments.is_empty());
1325 assert!(cmd.help.is_none());
1326 assert!(cmd.epilog.is_none());
1327 assert!(cmd.short_help.is_none());
1328 assert_eq!(cmd.options_metavar, "[OPTIONS]");
1329 assert!(cmd.add_help_option);
1330 assert!(!cmd.no_args_is_help);
1331 assert!(!cmd.hidden);
1332 assert!(cmd.deprecated.is_none());
1333 assert!(!cmd.allow_extra_args);
1334 assert!(cmd.allow_interspersed_args);
1335 assert!(!cmd.ignore_unknown_options);
1336 }
1337
1338 #[test]
1339 fn test_command_builder_chain() {
1340 let cmd = Command::new("hello")
1341 .help("Say hello to someone")
1342 .epilog("Example: hello --name World")
1343 .short_help("Say hello")
1344 .options_metavar("[OPTS]")
1345 .add_help_option(false)
1346 .no_args_is_help(true)
1347 .hidden()
1348 .deprecated("Use 'greet' instead")
1349 .allow_extra_args(true)
1350 .allow_interspersed_args(false)
1351 .ignore_unknown_options(true)
1352 .build();
1353
1354 assert_eq!(cmd.name, Some("hello".to_string()));
1355 assert_eq!(cmd.help, Some("Say hello to someone".to_string()));
1356 assert_eq!(cmd.epilog, Some("Example: hello --name World".to_string()));
1357 assert_eq!(cmd.short_help, Some("Say hello".to_string()));
1358 assert_eq!(cmd.options_metavar, "[OPTS]");
1359 assert!(!cmd.add_help_option);
1360 assert!(cmd.no_args_is_help);
1361 assert!(cmd.hidden);
1362 assert_eq!(cmd.deprecated, Some("Use 'greet' instead".to_string()));
1363 assert!(cmd.allow_extra_args);
1364 assert!(!cmd.allow_interspersed_args);
1365 assert!(cmd.ignore_unknown_options);
1366 }
1367
1368 #[test]
1369 fn test_command_with_callback() {
1370 use std::sync::atomic::{AtomicBool, Ordering};
1371
1372 let called = Arc::new(AtomicBool::new(false));
1373 let called_clone = Arc::clone(&called);
1374
1375 let cmd = Command::new("test")
1376 .callback(move |_ctx| {
1377 called_clone.store(true, Ordering::SeqCst);
1378 Ok(())
1379 })
1380 .build();
1381
1382 assert!(cmd.callback.is_some());
1383
1384 let ctx = ContextBuilder::new().info_name("test").build();
1386 let result = cmd.invoke(&ctx);
1387 assert!(result.is_ok());
1388 assert!(called.load(Ordering::SeqCst));
1389 }
1390
1391 #[test]
1392 fn test_command_with_option() {
1393 let cmd = Command::new("greet")
1394 .option(
1395 ClickOption::new(&["--name", "-n"])
1396 .help("Name to greet")
1397 .default("World")
1398 .build(),
1399 )
1400 .build();
1401
1402 assert_eq!(cmd.options.len(), 1);
1403 assert_eq!(cmd.options[0].name(), "name");
1404 }
1405
1406 #[test]
1407 fn test_command_with_argument() {
1408 let cmd = Command::new("cat")
1409 .argument(Argument::new("file").help("File to read").build())
1410 .build();
1411
1412 assert_eq!(cmd.arguments.len(), 1);
1413 assert_eq!(cmd.arguments[0].name(), "file");
1414 }
1415
1416 #[test]
1417 fn test_command_with_multiple_params() {
1418 let cmd = Command::new("copy")
1419 .option(
1420 ClickOption::new(&["--recursive", "-r"])
1421 .flag("true")
1422 .help("Copy recursively")
1423 .build(),
1424 )
1425 .argument(Argument::new("src").help("Source path").build())
1426 .argument(Argument::new("dst").help("Destination path").build())
1427 .build();
1428
1429 assert_eq!(cmd.options.len(), 1);
1430 assert_eq!(cmd.arguments.len(), 2);
1431 }
1432
1433 #[test]
1434 fn test_make_context_basic() {
1435 let cmd = Command::new("hello").build();
1436 let ctx = cmd.make_context("hello", vec![], None);
1437
1438 assert!(ctx.is_ok());
1439 let ctx = ctx.unwrap();
1440 assert_eq!(ctx.info_name(), Some("hello"));
1441 }
1442
1443 #[test]
1444 fn test_parse_args_with_option() {
1445 let cmd = Command::new("greet")
1446 .option(
1447 ClickOption::new(&["--name", "-n"])
1448 .default("World")
1449 .build(),
1450 )
1451 .build();
1452
1453 let ctx = cmd.make_context("greet", vec!["--name".to_string(), "Alice".to_string()], None);
1454 assert!(ctx.is_ok());
1455
1456 let ctx = ctx.unwrap();
1457 let name = ctx.get_param::<String>("name");
1458 assert_eq!(name, Some(&"Alice".to_string()));
1459 }
1460
1461 #[test]
1462 fn test_parse_args_with_argument() {
1463 let cmd = Command::new("cat")
1464 .argument(Argument::new("file").build())
1465 .build();
1466
1467 let ctx = cmd.make_context("cat", vec!["test.txt".to_string()], None);
1468 assert!(ctx.is_ok());
1469
1470 let ctx = ctx.unwrap();
1471 let file = ctx.get_param::<String>("file");
1472 assert_eq!(file, Some(&"test.txt".to_string()));
1473 }
1474
1475 #[test]
1476 fn test_parse_args_missing_required() {
1477 let cmd = Command::new("cat")
1478 .argument(Argument::new("file").build())
1479 .build();
1480
1481 let ctx = cmd.make_context("cat", vec![], None);
1482 assert!(ctx.is_err());
1483
1484 let err = ctx.unwrap_err();
1485 assert!(matches!(err, ClickError::MissingParameter { .. }));
1486 }
1487
1488 #[test]
1489 fn test_parse_args_extra_args_error() {
1490 let cmd = Command::new("hello").build();
1491
1492 let ctx = cmd.make_context("hello", vec!["extra".to_string()], None);
1493 assert!(ctx.is_err());
1494
1495 let err = ctx.unwrap_err();
1496 assert!(matches!(err, ClickError::UsageError { .. }));
1497 }
1498
1499 #[test]
1500 fn test_parse_args_extra_args_allowed() {
1501 let cmd = Command::new("hello").allow_extra_args(true).build();
1502
1503 let ctx = cmd.make_context("hello", vec!["extra".to_string()], None);
1504 assert!(ctx.is_ok());
1505
1506 let ctx = ctx.unwrap();
1507 assert_eq!(ctx.args(), &["extra".to_string()]);
1508 }
1509
1510 #[test]
1511 fn test_get_usage() {
1512 let cmd = Command::new("copy")
1513 .argument(Argument::new("src").build())
1514 .argument(Argument::new("dst").build())
1515 .build();
1516
1517 let ctx = ContextBuilder::new().info_name("copy").build();
1518 let usage = cmd.get_usage(&ctx);
1519
1520 assert!(usage.contains("Usage:"));
1521 assert!(usage.contains("copy"));
1522 assert!(usage.contains("[OPTIONS]"));
1523 assert!(usage.contains("SRC"));
1524 assert!(usage.contains("DST"));
1525 }
1526
1527 #[test]
1528 fn test_get_help() {
1529 let cmd = Command::new("greet")
1530 .help("Greet someone")
1531 .option(
1532 ClickOption::new(&["--name", "-n"])
1533 .help("Name to greet")
1534 .build(),
1535 )
1536 .epilog("Example: greet --name World")
1537 .build();
1538
1539 let ctx = ContextBuilder::new().info_name("greet").build();
1540 let help = cmd.get_help(&ctx);
1541
1542 assert!(help.contains("Usage:"));
1543 assert!(help.contains("Greet someone"));
1544 assert!(help.contains("Options:"));
1545 assert!(help.contains("--name"));
1546 assert!(help.contains("Name to greet"));
1547 assert!(help.contains("Example:"));
1548 }
1549
1550 #[test]
1551 fn test_get_short_help() {
1552 let cmd = Command::new("test").short_help("Test command").build();
1554 assert_eq!(cmd.get_short_help(), "Test command");
1555
1556 let cmd = Command::new("test")
1558 .help("This is a test. It does things.")
1559 .build();
1560 assert_eq!(cmd.get_short_help(), "This is a test");
1561
1562 let cmd = Command::new("test")
1564 .short_help("Test command")
1565 .deprecated("Use 'new-test' instead")
1566 .build();
1567 assert!(cmd.get_short_help().contains("DEPRECATED"));
1568 assert!(cmd.get_short_help().contains("Use 'new-test' instead"));
1569 }
1570
1571 #[test]
1572 fn test_make_help_option() {
1573 let help_opt = make_help_option(&["--help".to_string(), "-h".to_string()]);
1574
1575 assert!(help_opt.is_flag);
1576 assert!(help_opt.is_eager());
1577 assert_eq!(help_opt.help(), Some("Show this message and exit."));
1578 }
1579
1580 #[test]
1581 fn test_command_debug() {
1582 let cmd = Command::new("test").build();
1583 let debug_str = format!("{:?}", cmd);
1584
1585 assert!(debug_str.contains("Command"));
1586 assert!(debug_str.contains("test"));
1587 }
1588
1589 #[test]
1590 fn test_invoke_with_deprecation() {
1591 let cmd = Command::new("old")
1592 .deprecated("Use 'new' instead")
1593 .callback(|_| Ok(()))
1594 .build();
1595
1596 let ctx = ContextBuilder::new().info_name("old").build();
1597 let result = cmd.invoke(&ctx);
1599 assert!(result.is_ok());
1600 }
1601
1602 #[test]
1603 fn test_parse_flag_option() {
1604 let cmd = Command::new("test")
1605 .option(
1606 ClickOption::new(&["--verbose", "-v"])
1607 .flag("true")
1608 .build(),
1609 )
1610 .build();
1611
1612 let ctx = cmd.make_context("test", vec!["--verbose".to_string()], None);
1613 assert!(ctx.is_ok());
1614
1615 let ctx = ctx.unwrap();
1616 let verbose = ctx.get_param::<String>("verbose");
1617 assert_eq!(verbose, Some(&"true".to_string()));
1618 }
1619
1620 #[test]
1621 fn test_parse_count_option() {
1622 let cmd = Command::new("test")
1623 .option(ClickOption::new(&["--verbose", "-v"]).count().build())
1624 .build();
1625
1626 let ctx = cmd.make_context(
1627 "test",
1628 vec!["-v".to_string(), "-v".to_string(), "-v".to_string()],
1629 None,
1630 );
1631 assert!(ctx.is_ok());
1632
1633 let ctx = ctx.unwrap();
1634 let verbose = ctx.get_param::<usize>("verbose");
1635 assert_eq!(verbose, Some(&3));
1636 }
1637
1638 #[test]
1639 fn test_option_type_conversion() {
1640 let cmd = Command::new("test")
1641 .option(ClickOption::new(&["--count"]).type_any(INT).build())
1642 .build();
1643
1644 let ctx = cmd.make_context("test", vec!["--count".to_string(), "42".to_string()], None);
1645 assert!(ctx.is_ok());
1646
1647 let ctx = ctx.unwrap();
1648 let count = ctx.get_param::<i64>("count");
1649 assert_eq!(count, Some(&42));
1650 }
1651
1652 #[test]
1653 fn test_option_callback_applied() {
1654 let cmd = Command::new("test")
1655 .option(
1656 ClickOption::new(&["--count"])
1657 .type_any(INT)
1658 .callback(|_ctx, _param, value| {
1659 let count = *value
1660 .as_ref()
1661 .downcast_ref::<i64>()
1662 .ok_or_else(|| ClickError::bad_parameter("bad callback value"))?;
1663 Ok(Arc::new(count + 1))
1664 })
1665 .build(),
1666 )
1667 .build();
1668
1669 let ctx = cmd.make_context("test", vec!["--count".to_string(), "2".to_string()], None);
1670 assert!(ctx.is_ok());
1671
1672 let ctx = ctx.unwrap();
1673 let count = ctx.get_param::<i64>("count");
1674 assert_eq!(count, Some(&3));
1675 }
1676
1677 #[test]
1678 fn test_option_with_default() {
1679 let cmd = Command::new("greet")
1680 .option(
1681 ClickOption::new(&["--name"])
1682 .default("World")
1683 .build(),
1684 )
1685 .build();
1686
1687 let ctx = cmd.make_context("greet", vec![], None);
1689 assert!(ctx.is_ok());
1690
1691 let ctx = ctx.unwrap();
1692 let name = ctx.get_param::<String>("name");
1693 assert_eq!(name, Some(&"World".to_string()));
1694 assert_eq!(ctx.get_parameter_source("name"), Some(ParameterSource::Default));
1695 }
1696
1697 #[test]
1698 fn test_option_default_map_value() {
1699 let cmd = Command::new("greet")
1700 .option(ClickOption::new(&["--name"]).build())
1701 .build();
1702
1703 let mut defaults: HashMap<String, Arc<dyn std::any::Any + Send + Sync>> = HashMap::new();
1704 defaults.insert("name".to_string(), Arc::new("Bob".to_string()));
1705
1706 let mut ctx = ContextBuilder::new()
1707 .info_name("greet")
1708 .default_map(defaults)
1709 .build();
1710
1711 let result = cmd.parse_args(&mut ctx, vec![]);
1712 assert!(result.is_ok());
1713
1714 let name = ctx.get_param::<String>("name");
1715 assert_eq!(name, Some(&"Bob".to_string()));
1716 assert_eq!(
1717 ctx.get_parameter_source("name"),
1718 Some(ParameterSource::DefaultMap)
1719 );
1720 }
1721
1722 #[test]
1723 fn test_option_envvar_value() {
1724 std::env::set_var("CLICK_TEST_COUNT", "9");
1725 let cmd = Command::new("test")
1726 .option(
1727 ClickOption::new(&["--count"])
1728 .envvar("CLICK_TEST_COUNT")
1729 .type_any(INT)
1730 .build(),
1731 )
1732 .build();
1733
1734 let ctx = cmd.make_context("test", vec![], None);
1735 std::env::remove_var("CLICK_TEST_COUNT");
1736
1737 assert!(ctx.is_ok());
1738 let ctx = ctx.unwrap();
1739 let count = ctx.get_param::<i64>("count");
1740 assert_eq!(count, Some(&9));
1741 assert_eq!(
1742 ctx.get_parameter_source("count"),
1743 Some(ParameterSource::Environment)
1744 );
1745 }
1746
1747 #[test]
1748 fn test_argument_type_conversion() {
1749 let cmd = Command::new("greet")
1750 .argument(Argument::new("count").type_(INT).build())
1751 .build();
1752
1753 let ctx = cmd.make_context("greet", vec!["7".to_string()], None);
1754 assert!(ctx.is_ok());
1755
1756 let ctx = ctx.unwrap();
1757 let count = ctx.get_param::<i64>("count");
1758 assert_eq!(count, Some(&7));
1759 assert_eq!(
1760 ctx.get_parameter_source("count"),
1761 Some(ParameterSource::CommandLine)
1762 );
1763 }
1764
1765 #[test]
1766 fn test_argument_callback_applied() {
1767 let cmd = Command::new("greet")
1768 .argument(
1769 Argument::new("count")
1770 .type_(INT)
1771 .callback(|_ctx, _param, value| {
1772 let count = *value
1773 .as_ref()
1774 .downcast_ref::<i64>()
1775 .ok_or_else(|| ClickError::bad_parameter("bad callback value"))?;
1776 Ok(Arc::new(count * 2))
1777 })
1778 .build(),
1779 )
1780 .build();
1781
1782 let ctx = cmd.make_context("greet", vec!["3".to_string()], None);
1783 assert!(ctx.is_ok());
1784
1785 let ctx = ctx.unwrap();
1786 let count = ctx.get_param::<i64>("count");
1787 assert_eq!(count, Some(&6));
1788 }
1789
1790 #[test]
1791 fn test_argument_type_conversion_error() {
1792 let cmd = Command::new("greet")
1793 .argument(Argument::new("count").type_(INT).build())
1794 .build();
1795
1796 let ctx = cmd.make_context("greet", vec!["nope".to_string()], None);
1797 assert!(matches!(ctx, Err(ClickError::BadParameter { .. })));
1798 }
1799
1800 #[test]
1801 fn test_argument_with_default() {
1802 let cmd = Command::new("greet")
1803 .argument(Argument::new("name").default("World").build())
1804 .build();
1805
1806 let ctx = cmd.make_context("greet", vec![], None);
1808 assert!(ctx.is_ok());
1809
1810 let ctx = ctx.unwrap();
1811 let name = ctx.get_param::<String>("name");
1812 assert_eq!(name, Some(&"World".to_string()));
1813 }
1814
1815 #[test]
1816 fn test_argument_default_map_value() {
1817 let cmd = Command::new("greet")
1818 .argument(Argument::new("name").build())
1819 .build();
1820
1821 let mut defaults: HashMap<String, Arc<dyn std::any::Any + Send + Sync>> = HashMap::new();
1822 defaults.insert("name".to_string(), Arc::new("Alice".to_string()));
1823
1824 let mut ctx = ContextBuilder::new()
1825 .info_name("greet")
1826 .default_map(defaults)
1827 .build();
1828
1829 let result = cmd.parse_args(&mut ctx, vec![]);
1830 assert!(result.is_ok());
1831
1832 let name = ctx.get_param::<String>("name");
1833 assert_eq!(name, Some(&"Alice".to_string()));
1834 }
1835
1836 #[test]
1837 fn test_argument_auto_envvar_prefix() {
1838 std::env::set_var("MYAPP_NAME", "Alice");
1839 let cmd = Command::new("greet")
1840 .argument(Argument::new("name").build())
1841 .build();
1842
1843 let mut ctx = ContextBuilder::new()
1844 .info_name("greet")
1845 .auto_envvar_prefix("MYAPP")
1846 .build();
1847
1848 let result = cmd.parse_args(&mut ctx, vec![]);
1849 std::env::remove_var("MYAPP_NAME");
1850
1851 assert!(result.is_ok());
1852 let name = ctx.get_param::<String>("name");
1853 assert_eq!(name, Some(&"Alice".to_string()));
1854 }
1855
1856 #[test]
1861 fn test_optional_argument_with_value() {
1862 use crate::parameter::Nargs;
1863
1864 let cmd = Command::new("test")
1865 .argument(
1866 Argument::new("file")
1867 .nargs(Nargs::Optional)
1868 .default("default.txt")
1869 .build(),
1870 )
1871 .build();
1872
1873 let ctx = cmd.make_context("test", vec!["input.txt".to_string()], None);
1875 assert!(ctx.is_ok());
1876
1877 let ctx = ctx.unwrap();
1878 let file = ctx.get_param::<String>("file");
1879 assert_eq!(file, Some(&"input.txt".to_string()));
1880 }
1881
1882 #[test]
1883 fn test_optional_argument_without_value() {
1884 use crate::parameter::Nargs;
1885
1886 let cmd = Command::new("test")
1887 .argument(
1888 Argument::new("file")
1889 .nargs(Nargs::Optional)
1890 .default("default.txt")
1891 .build(),
1892 )
1893 .build();
1894
1895 let ctx = cmd.make_context("test", vec![], None);
1897 assert!(ctx.is_ok());
1898
1899 let ctx = ctx.unwrap();
1900 let file = ctx.get_param::<String>("file");
1901 assert_eq!(file, Some(&"default.txt".to_string()));
1902 }
1903
1904 #[test]
1905 fn test_required_followed_by_optional_argument() {
1906 use crate::parameter::Nargs;
1907
1908 let cmd = Command::new("copy")
1909 .argument(Argument::new("src").build()) .argument(
1911 Argument::new("dst")
1912 .nargs(Nargs::Optional)
1913 .default(".")
1914 .build(),
1915 )
1916 .build();
1917
1918 let ctx = cmd.make_context("copy", vec!["source.txt".to_string()], None);
1920 assert!(ctx.is_ok());
1921
1922 let ctx = ctx.unwrap();
1923 let src = ctx.get_param::<String>("src");
1924 let dst = ctx.get_param::<String>("dst");
1925 assert_eq!(src, Some(&"source.txt".to_string()));
1926 assert_eq!(dst, Some(&".".to_string()));
1927 }
1928
1929 #[test]
1930 fn test_required_followed_by_optional_with_both_values() {
1931 use crate::parameter::Nargs;
1932
1933 let cmd = Command::new("copy")
1934 .argument(Argument::new("src").build()) .argument(
1936 Argument::new("dst")
1937 .nargs(Nargs::Optional)
1938 .default(".")
1939 .build(),
1940 )
1941 .build();
1942
1943 let ctx = cmd.make_context(
1945 "copy",
1946 vec!["source.txt".to_string(), "dest.txt".to_string()],
1947 None,
1948 );
1949 assert!(ctx.is_ok());
1950
1951 let ctx = ctx.unwrap();
1952 let src = ctx.get_param::<String>("src");
1953 let dst = ctx.get_param::<String>("dst");
1954 assert_eq!(src, Some(&"source.txt".to_string()));
1955 assert_eq!(dst, Some(&"dest.txt".to_string()));
1956 }
1957
1958 #[test]
1963 fn test_multi_value_argument_incomplete_error() {
1964 use crate::parameter::Nargs;
1965
1966 let cmd = Command::new("point")
1967 .argument(Argument::new("coords").nargs(Nargs::Count(3)).build())
1968 .build();
1969
1970 let ctx = cmd.make_context(
1972 "point",
1973 vec!["1".to_string(), "2".to_string()],
1974 None,
1975 );
1976
1977 assert!(ctx.is_err());
1979 let err = ctx.unwrap_err();
1980 assert!(err.to_string().contains("takes 3 values"));
1981 }
1982
1983 #[test]
1988 fn test_optional_option_value_with_flag_value() {
1989 use crate::parameter::Nargs;
1990
1991 let cmd = Command::new("test")
1992 .option(
1993 ClickOption::new(&["--opt"])
1994 .nargs(Nargs::Optional)
1995 .flag("flagval") .default("default")
1997 .build(),
1998 )
1999 .build();
2000
2001 let ctx = cmd.make_context("test", vec!["--opt".to_string()], None);
2003 assert!(ctx.is_ok());
2004
2005 let ctx = ctx.unwrap();
2006 let opt = ctx.get_param::<String>("opt");
2007 assert_eq!(opt, Some(&"flagval".to_string()));
2009 }
2010
2011 #[test]
2012 fn test_flag_value_group_shared_destination() {
2013 let cmd = Command::new("mine")
2014 .option(
2015 ClickOption::new(&["--moored", "-m"])
2016 .dest("ty")
2017 .flag("moored")
2018 .default("moored")
2019 .build(),
2020 )
2021 .option(
2022 ClickOption::new(&["--drifting", "-d"])
2023 .dest("ty")
2024 .flag("drifting")
2025 .build(),
2026 )
2027 .build();
2028
2029 let ctx_default = cmd.make_context("mine", vec![], None).unwrap();
2030 assert_eq!(ctx_default.get_param::<String>("ty"), Some(&"moored".to_string()));
2031
2032 let ctx_drifting = cmd
2033 .make_context("mine", vec!["--drifting".to_string()], None)
2034 .unwrap();
2035 assert_eq!(
2036 ctx_drifting.get_param::<String>("ty"),
2037 Some(&"drifting".to_string())
2038 );
2039
2040 let ctx_moored = cmd
2041 .make_context("mine", vec!["--moored".to_string()], None)
2042 .unwrap();
2043 assert_eq!(ctx_moored.get_param::<String>("ty"), Some(&"moored".to_string()));
2044 }
2045
2046 #[test]
2047 fn test_flag_value_group_last_option_wins() {
2048 let cmd = Command::new("mine")
2049 .option(
2050 ClickOption::new(&["--moored"])
2051 .dest("ty")
2052 .flag("moored")
2053 .default("moored")
2054 .build(),
2055 )
2056 .option(
2057 ClickOption::new(&["--drifting"])
2058 .dest("ty")
2059 .flag("drifting")
2060 .build(),
2061 )
2062 .build();
2063
2064 let ctx = cmd
2065 .make_context(
2066 "mine",
2067 vec!["--moored".to_string(), "--drifting".to_string()],
2068 None,
2069 )
2070 .unwrap();
2071 assert_eq!(ctx.get_param::<String>("ty"), Some(&"drifting".to_string()));
2072 }
2073
2074 #[test]
2079 fn test_help_with_missing_required_arg() {
2080 let cmd = Command::new("test")
2082 .argument(Argument::new("required_file").build())
2083 .build();
2084
2085 let ctx = cmd.make_context("test", vec![], None);
2087 assert!(ctx.is_err());
2088
2089 let ctx = cmd.make_context("test", vec!["--help".to_string()], None);
2091 assert!(matches!(ctx, Err(ClickError::Exit { code: 0 })));
2092 }
2093
2094 #[test]
2095 fn test_help_with_missing_required_option() {
2096 let cmd = Command::new("test")
2098 .option(
2099 ClickOption::new(&["--name", "-n"])
2100 .required()
2101 .build(),
2102 )
2103 .build();
2104
2105 let ctx = cmd.make_context("test", vec![], None);
2107 assert!(ctx.is_err());
2108
2109 let ctx = cmd.make_context("test", vec!["--help".to_string()], None);
2111 assert!(matches!(ctx, Err(ClickError::Exit { code: 0 })));
2112 }
2113}