use std::collections::HashMap;
use super::{super::attributes::*, *};
use crate::*;
#[derive(Default, Clone)]
pub(crate) enum ClapOption<T> {
#[default]
None,
Empty,
Some(T),
}
type MaybeString = ClapOption<TokenStream>;
impl<T> ClapOption<T> {
fn on_empty<F: FnOnce() -> T>(self, alternative: F) -> Option<T> {
match self {
ClapOption::None => None,
ClapOption::Some(v) => Some(v),
ClapOption::Empty => Some(alternative()),
}
}
fn on_empty_res<F: FnOnce() -> Result<T>>(self, alternative: F) -> Result<Option<T>> {
match self {
ClapOption::None => Ok(None),
ClapOption::Some(v) => Ok(Some(v)),
ClapOption::Empty => Some(alternative()).transpose(),
}
}
}
fn clap_attr_parser(attr: &Meta) -> Result<MaybeString> {
let name = path_to_string(attr.path());
let name = &name.as_str();
let accepted_value = if CLAP_MULTIVALUES_FIELD_ATTRIBUTES.contains(name)
|| CLAP_ATTRIBUTE_TAKES_CODE.contains(name)
{
AcceptedLiterals::Code
} else if CLAP_ATTRIBUTE_TAKES_CHAR.contains(name) {
AcceptedLiterals::Char
} else if CLAP_ATTRIBUTE_TAKES_INT.contains(name) {
AcceptedLiterals::Int
} else {
AcceptedLiterals::String
};
Ok(match_literal_or_init_from(attr, accepted_value)?
.map(ClapOption::Some)
.unwrap_or(ClapOption::Empty))
}
pub(crate) struct ClapAppParseResult {
pub(crate) span: Span,
pub(crate) docs: Option<String>,
pub(crate) attributes: HashMap<String, MaybeString>,
}
impl ClapAppParseResult {
pub(crate) fn new(span: Span) -> Self {
Self {
span,
docs: Default::default(),
attributes: ALLOWED_CLAP_APP_ATTRS
.iter()
.map(|name| (name.to_string(), MaybeString::None))
.collect(),
}
}
}
fn generic_attributes(
parsed: HashMap<String, MaybeString>,
span: Span,
) -> Result<HashMap<String, TokenStream>> {
let mut res = HashMap::new();
for (attr, val) in parsed {
let flag = CLAP_FLAG_ATTRIBUTES.contains(&attr.as_str());
match (val, flag) {
(ClapOption::None, _) => (),
(ClapOption::Empty, true) => {
res.insert(attr, quote_spanned!(span=> true));
}
(ClapOption::Empty, false) => panic_span!(
span,
"clap attribute \"{attr}\" must take value(s), can't be empty"
),
(ClapOption::Some(v), true) => panic_span!(
v.span(),
"clap attribute \"{attr}\" can't take any value(s), it's a flag"
),
(ClapOption::Some(v), false) => {
res.insert(attr, v);
}
}
}
Ok(res)
}
pub(crate) fn parse_clap_app_attribute(
attributes: &MetaList,
docs: Option<String>,
) -> Result<ClapAppParseResult> {
let mut res = ClapAppParseResult::new(attributes.span());
res.docs = docs;
let attrs = &attributes.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
for attr in attrs {
let attr_name = path_to_string(attr.path());
if ALLOWED_CLAP_APP_ATTRS.contains(&attr_name.as_str()) {
let not_set = res.attributes.get_mut(&attr_name).unwrap();
if !matches!(not_set, ClapOption::None) {
panic_span!(attr.span(), "trying to set clap({attr_name}) twice");
}
*not_set = clap_attr_parser(attr)?;
} else {
panic_span!(
attr.span(),
"clap attibute \"{attr_name}\" is not supported",
)
}
}
Ok(res)
}
impl ClapAppParseResult {
pub(crate) fn normalize(mut self) -> Result<NormalClapAppInfo> {
let span = self.span;
let name = match self.attributes.remove("name").unwrap() {
ClapOption::Empty | ClapOption::None => {
quote_spanned!(span=> ::config_manager::__private::clap::crate_name!())
}
ClapOption::Some(n) => n,
};
let mut attributes = HashMap::new();
let version = self.attributes.remove("version").unwrap();
if let Some(v) = version.on_empty(
|| quote_spanned!(span=> ::config_manager::__private::clap::crate_version!()),
) {
attributes.insert("version".to_string(), v);
}
let long_about = self.attributes.remove("long_about").unwrap();
if let Some(v) = long_about.on_empty_res(|| self.get_docs("long_about"))? {
attributes.insert("long_about".to_string(), v);
}
let author = self.attributes.remove("author").unwrap();
if let Some(v) = author.on_empty(|| {
quote_spanned! {self.span=>
::config_manager::__private::clap::crate_authors!("\n")
}
}) {
attributes.insert("author".to_string(), v);
}
let about = self.attributes.remove("about").unwrap();
if let Some(v) = about.on_empty(
|| quote_spanned!(self.span=> ::config_manager::__private::clap::crate_description!()),
) {
attributes.insert("about".to_string(), v);
}
attributes.extend(generic_attributes(self.attributes, span)?);
Ok(NormalClapAppInfo {
span,
name,
attributes,
})
}
fn get_docs<S: AsRef<str>>(&self, attr_name: S) -> Result<TokenStream> {
match self.docs.clone() {
Some(val) => Ok(str_to_tokens(val, self.span)),
None => Err(Error::new(
self.span,
format!("if clap({}) is used without value, struct docs must be provided. But there are no docs", attr_name.as_ref())
)),
}
}
}
#[derive(Clone)]
pub(crate) struct ClapFieldParseResult {
pub(crate) span: Span,
pub(crate) docs: Option<String>,
pub(crate) attributes: HashMap<String, MaybeString>,
}
impl ClapFieldParseResult {
pub(crate) fn new(span: Span) -> Self {
Self {
span,
docs: Default::default(),
attributes: ALLOWED_CLAP_FIELD_ATTRS
.iter()
.map(|name| (name.to_string(), MaybeString::None))
.collect(),
}
}
}
pub(crate) fn parse_clap_field_attribute(
attributes: &MetaList,
is_bool: bool,
) -> Result<ClapFieldParseResult> {
let mut res = ClapFieldParseResult::new(attributes.span());
let attrs = &attributes.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
for attr in attrs {
let attr_name = path_to_string(attr.path());
if ALLOWED_CLAP_FIELD_ATTRS.contains(&attr_name.as_str()) {
let not_set = res.attributes.get_mut(&attr_name).unwrap();
if !matches!(not_set, ClapOption::None) {
panic_span!(attr.span(), "trying to set clap({attr_name}) twice");
}
if attr_name == "flag" && !is_bool {
panic_span!(attr.span(), "Only boolean arguments can be flags")
}
*not_set = clap_attr_parser(attr)?;
} else {
panic_span!(
attr.span(),
"clap attibute \"{attr_name}\" is not supported",
)
}
}
Ok(res)
}
impl ClapFieldParseResult {
fn get_docs<S: AsRef<str>>(&self, attr_name: S) -> Result<TokenStream> {
match self.docs.clone() {
Some(val) => Ok(str_to_tokens(val, self.span)),
None => Err(Error::new(
self.span,
format!("if clap({}) is used without value, field docs must be provided. But there are no docs", attr_name.as_ref())
)),
}
}
pub(crate) fn normal_long(&self, field_name: &str) -> TokenStream {
match self.attributes.get("long").unwrap() {
ClapOption::Empty | ClapOption::None => str_to_tokens(field_name, self.span),
ClapOption::Some(n) => n.clone(),
}
}
pub(crate) fn has_explicit_long(&self) -> bool {
matches!(self.attributes.get("long").unwrap(), ClapOption::Some(_))
}
pub(crate) fn normalize(mut self, field_name: &str) -> Result<NormalClapFieldInfo> {
let span = self.span;
let long = match self.attributes.remove("long").unwrap() {
ClapOption::Empty | ClapOption::None => str_to_tokens(field_name, span),
ClapOption::Some(n) => n,
};
let mut attributes = HashMap::new();
let help = self.attributes.remove("help").unwrap();
if let Some(v) = help.on_empty_res(|| self.get_docs("help"))? {
attributes.insert("help".to_string(), v);
}
let long_help = self.attributes.remove("long_help").unwrap();
if let Some(v) = long_help.on_empty_res(|| self.get_docs("long_help"))? {
attributes.insert("long_help".to_string(), v);
}
let short = self.attributes.remove("short").unwrap();
if let Some(v) = short.on_empty_res(|| {
field_name
.chars()
.next()
.ok_or_else(|| {
Error::new(self.span, "empty clap(short) is forbidden for config files")
})
.map(|c| LitChar::new(c, self.span).to_token_stream())
})? {
attributes.insert("short".to_string(), v);
}
attributes.extend(generic_attributes(self.attributes, span)?);
Ok(NormalClapFieldInfo {
span,
long,
attributes,
})
}
}