use std::any::Any;
use std::fmt;
use std::sync::Arc;
use crate::context::Context;
use crate::error::ClickError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Nargs {
Count(usize),
Variadic,
Optional,
}
impl Default for Nargs {
fn default() -> Self {
Nargs::Count(1)
}
}
impl Nargs {
pub fn is_single(&self) -> bool {
matches!(self, Nargs::Count(1))
}
pub fn is_multi(&self) -> bool {
match self {
Nargs::Variadic => true,
Nargs::Count(n) => *n > 1,
Nargs::Optional => false,
}
}
pub fn is_variadic(&self) -> bool {
matches!(self, Nargs::Variadic)
}
pub fn is_optional(&self) -> bool {
matches!(self, Nargs::Optional)
}
pub fn count(&self) -> Option<usize> {
match self {
Nargs::Count(n) => Some(*n),
_ => None,
}
}
}
impl fmt::Display for Nargs {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Nargs::Count(1) => write!(f, "1"),
Nargs::Count(n) => write!(f, "{}", n),
Nargs::Variadic => write!(f, "-1"),
Nargs::Optional => write!(f, "?"),
}
}
}
pub trait Parameter: Send + Sync + fmt::Debug {
fn name(&self) -> &str;
fn human_readable_name(&self) -> String;
fn nargs(&self) -> Nargs;
fn multiple(&self) -> bool;
fn is_eager(&self) -> bool;
fn expose_value(&self) -> bool;
fn required(&self) -> bool;
fn envvar(&self) -> Option<&[String]>;
fn help(&self) -> Option<&str>;
fn hidden(&self) -> bool;
fn get_metavar(&self) -> Option<String>;
fn get_help_record(&self) -> Option<(String, String)>;
fn param_type_name(&self) -> &str {
"parameter"
}
}
pub type ParameterCallback = Arc<
dyn Fn(&Context, &dyn Parameter, Arc<dyn Any + Send + Sync>)
-> Result<Arc<dyn Any + Send + Sync>, ClickError>
+ Send
+ Sync,
>;
#[derive(Clone)]
pub struct ParameterConfig {
pub name: String,
pub nargs: Nargs,
pub multiple: bool,
pub is_eager: bool,
pub expose_value: bool,
pub required: bool,
pub envvar: Option<Vec<String>>,
pub help: Option<String>,
pub hidden: bool,
pub metavar: Option<String>,
pub deprecated: Option<DeprecationInfo>,
pub callback: Option<ParameterCallback>,
}
impl fmt::Debug for ParameterConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ParameterConfig")
.field("name", &self.name)
.field("nargs", &self.nargs)
.field("multiple", &self.multiple)
.field("is_eager", &self.is_eager)
.field("expose_value", &self.expose_value)
.field("required", &self.required)
.field("envvar", &self.envvar)
.field("help", &self.help)
.field("hidden", &self.hidden)
.field("metavar", &self.metavar)
.field("deprecated", &self.deprecated)
.field("has_callback", &self.callback.is_some())
.finish()
}
}
#[derive(Debug, Clone, Default)]
pub struct DeprecationInfo {
pub message: Option<String>,
}
impl DeprecationInfo {
pub fn new() -> Self {
Self::default()
}
pub fn with_message(message: impl Into<String>) -> Self {
Self {
message: Some(message.into()),
}
}
}
impl Default for ParameterConfig {
fn default() -> Self {
Self {
name: String::new(),
nargs: Nargs::default(),
multiple: false,
is_eager: false,
expose_value: true,
required: false,
envvar: None,
help: None,
hidden: false,
metavar: None,
deprecated: None,
callback: None,
}
}
}
impl ParameterConfig {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
..Default::default()
}
}
pub fn nargs(mut self, nargs: Nargs) -> Self {
self.nargs = nargs;
self
}
pub fn multiple(mut self, multiple: bool) -> Self {
self.multiple = multiple;
self
}
pub fn eager(mut self, eager: bool) -> Self {
self.is_eager = eager;
self
}
pub fn expose_value(mut self, expose: bool) -> Self {
self.expose_value = expose;
self
}
pub fn required(mut self, required: bool) -> Self {
self.required = required;
self
}
pub fn envvar(mut self, var: impl Into<String>) -> Self {
self.envvar = Some(vec![var.into()]);
self
}
pub fn envvars(mut self, vars: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.envvar = Some(vars.into_iter().map(|v| v.into()).collect());
self
}
pub fn help(mut self, help: impl Into<String>) -> Self {
self.help = Some(help.into());
self
}
pub fn hidden(mut self, hidden: bool) -> Self {
self.hidden = hidden;
self
}
pub fn metavar(mut self, metavar: impl Into<String>) -> Self {
self.metavar = Some(metavar.into());
self
}
pub fn callback(mut self, callback: ParameterCallback) -> Self {
self.callback = Some(callback);
self
}
pub fn deprecated(mut self, deprecated: bool) -> Self {
self.deprecated = if deprecated {
Some(DeprecationInfo::default())
} else {
None
};
self
}
pub fn deprecated_with_message(mut self, message: impl Into<String>) -> Self {
self.deprecated = Some(DeprecationInfo::with_message(message));
self
}
pub fn validate(&self) -> Result<(), ClickError> {
if self.deprecated.is_some() && self.required {
return Err(ClickError::usage(format!(
"The parameter '{}' is deprecated and required. \
A deprecated parameter cannot be required.",
self.name
)));
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_nargs_default() {
let nargs = Nargs::default();
assert_eq!(nargs, Nargs::Count(1));
assert!(nargs.is_single());
assert!(!nargs.is_multi());
assert!(!nargs.is_variadic());
assert!(!nargs.is_optional());
}
#[test]
fn test_nargs_count() {
let nargs = Nargs::Count(3);
assert!(!nargs.is_single());
assert!(nargs.is_multi());
assert!(!nargs.is_variadic());
assert!(!nargs.is_optional());
assert_eq!(nargs.count(), Some(3));
}
#[test]
fn test_nargs_variadic() {
let nargs = Nargs::Variadic;
assert!(!nargs.is_single());
assert!(nargs.is_multi());
assert!(nargs.is_variadic());
assert!(!nargs.is_optional());
assert_eq!(nargs.count(), None);
}
#[test]
fn test_nargs_optional() {
let nargs = Nargs::Optional;
assert!(!nargs.is_single());
assert!(!nargs.is_multi());
assert!(!nargs.is_variadic());
assert!(nargs.is_optional());
assert_eq!(nargs.count(), None);
}
#[test]
fn test_nargs_display() {
assert_eq!(Nargs::Count(1).to_string(), "1");
assert_eq!(Nargs::Count(3).to_string(), "3");
assert_eq!(Nargs::Variadic.to_string(), "-1");
assert_eq!(Nargs::Optional.to_string(), "?");
}
#[test]
fn test_parameter_config_builder() {
let config = ParameterConfig::new("name")
.nargs(Nargs::Count(2))
.multiple(true)
.eager(true)
.expose_value(false)
.required(true)
.envvar("MY_VAR")
.help("Help text")
.hidden(false)
.metavar("VALUE");
assert_eq!(config.name, "name");
assert_eq!(config.nargs, Nargs::Count(2));
assert!(config.multiple);
assert!(config.is_eager);
assert!(!config.expose_value);
assert!(config.required);
assert_eq!(config.envvar, Some(vec!["MY_VAR".to_string()]));
assert_eq!(config.help, Some("Help text".to_string()));
assert!(!config.hidden);
assert_eq!(config.metavar, Some("VALUE".to_string()));
}
#[test]
fn test_parameter_config_envvars() {
let config = ParameterConfig::new("name").envvars(["VAR1", "VAR2", "VAR3"]);
assert_eq!(
config.envvar,
Some(vec![
"VAR1".to_string(),
"VAR2".to_string(),
"VAR3".to_string()
])
);
}
#[test]
fn test_parameter_config_default() {
let config = ParameterConfig::default();
assert_eq!(config.name, "");
assert_eq!(config.nargs, Nargs::Count(1));
assert!(!config.multiple);
assert!(!config.is_eager);
assert!(config.expose_value);
assert!(!config.required);
assert!(config.envvar.is_none());
assert!(config.help.is_none());
assert!(!config.hidden);
assert!(config.metavar.is_none());
assert!(config.deprecated.is_none());
}
#[test]
fn test_parameter_config_deprecated() {
let config = ParameterConfig::new("old_option").deprecated(true);
assert!(config.deprecated.is_some());
assert!(config.deprecated.as_ref().unwrap().message.is_none());
let config =
ParameterConfig::new("old_option").deprecated_with_message("Use --new-option instead");
assert!(config.deprecated.is_some());
assert_eq!(
config.deprecated.as_ref().unwrap().message,
Some("Use --new-option instead".to_string())
);
}
#[test]
fn test_parameter_config_validate_deprecated_required() {
let config = ParameterConfig::new("option")
.deprecated(true)
.required(true);
let result = config.validate();
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.to_string().contains("deprecated"));
assert!(err.to_string().contains("required"));
}
#[test]
fn test_parameter_config_validate_ok() {
let config = ParameterConfig::new("option").required(true);
assert!(config.validate().is_ok());
let config = ParameterConfig::new("option").deprecated(true);
assert!(config.validate().is_ok());
}
#[test]
fn test_deprecation_info() {
let info = DeprecationInfo::new();
assert!(info.message.is_none());
let info = DeprecationInfo::with_message("Custom message");
assert_eq!(info.message, Some("Custom message".to_string()));
}
}