use darling::{Error, FromMeta, util::Override};
use heck::ToKebabCase;
use syn::{
Attribute, Expr, ExprPath, Ident, LitStr, Meta, Token, parse_quote, punctuated::Punctuated,
};
use crate::syn_utils::ExprExt;
#[derive(FromMeta)]
pub enum ClapCommand {
Flatten,
Subcommand,
}
impl ClapCommand {
pub fn from_attr(attr: &Attribute) -> Option<Self> {
Self::from_meta(&attr.meta).ok()
}
}
#[derive(Debug, FromMeta)]
pub struct ClapArg {
short: Option<Override<char>>,
long: Option<Override<String>>,
default_value: Option<String>,
alias: Option<LitStr>,
aliases: Option<Vec<LitStr>>,
value_parser: Option<Result<ExprPath, Error>>,
#[allow(dead_code)]
#[darling(flatten)]
ignored_args: Result<(), Error>,
}
impl ClapArg {
pub fn from_attr(attr: &Attribute) -> Option<Self> {
Self::from_meta(&attr.meta).ok()
}
pub fn default_value(&self) -> Option<&str> {
self.default_value.as_deref()
}
pub fn aliases(&self) -> Vec<LitStr> {
self.aliases
.iter()
.flatten()
.chain(&self.alias)
.cloned()
.collect()
}
pub fn value_parser(&self) -> Option<ExprPath> {
self.value_parser.clone().transpose().unwrap_or_default()
}
pub fn flag_name(&self, field_ident: &Ident) -> Option<String> {
if let Some(short) = &self.short {
return match short {
Override::Explicit(c) => Some(format!("-{c}")),
Override::Inherit => field_ident
.to_string()
.chars()
.next()
.map(|c| format!("-{}", c.to_lowercase())),
};
}
if let Some(long) = &self.long {
return match long {
Override::Explicit(s) => Some(format!("--{s}")),
Override::Inherit => Some(format!("--{}", field_ident.to_string().to_kebab_case())),
};
}
None
}
pub fn sanitize(attr: Attribute) -> Attribute {
const DENIED_ARGS: [&str; 17] = [
"default_value",
"default_values",
"default_value_if",
"default_value_ifs",
"default_value_t",
"required",
"required_if_eq",
"required_if_eq_any",
"required_unless_present",
"required_unless_present_any",
"required_unless_present_all",
"requires",
"requires_if",
"requires_ifs",
"conflicts_with",
"conflicts_with_all",
"exclusive",
];
if attr.path().is_ident("arg")
&& let Meta::List(list) = &attr.meta
&& let Some(args) = list
.parse_args_with(Punctuated::<Expr, Token![,]>::parse_terminated)
.ok()
{
let sanitized_attrs: Punctuated<Expr, Token![,]> = args
.into_iter()
.filter(|expr| {
!expr
.ident()
.is_none_or(|ident| DENIED_ARGS.contains(&ident.to_string().as_str()))
})
.collect();
parse_quote!(#[arg(#sanitized_attrs)])
} else {
attr
}
}
}