use std::rc::Rc;
use quote::ToTokens;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct RustCfgCondition {
predicates: Rc<[Rc<str>]>,
}
impl Default for RustCfgCondition {
fn default() -> Self {
Self {
predicates: Rc::from(Vec::<Rc<str>>::new()),
}
}
}
impl RustCfgCondition {
pub(crate) fn stated(predicate: &str) -> Self {
Self::normalized(vec![Rc::from(predicate)])
}
pub(crate) fn with(&self, attrs: &[syn::Attribute]) -> Self {
let stated = predicates_of(attrs);
match stated.is_empty() {
true => self.clone(),
false => Self::normalized(self.predicates.iter().cloned().chain(stated).collect()),
}
}
pub(crate) fn and(&self, other: &Self) -> Self {
match (self.is_empty(), other.is_empty()) {
(true, _) => other.clone(),
(_, true) => self.clone(),
_ => Self::normalized(
self.predicates
.iter()
.chain(other.predicates.iter())
.cloned()
.collect(),
),
}
}
pub(crate) fn is_empty(&self) -> bool {
self.predicates.is_empty()
}
fn normalized(mut predicates: Vec<Rc<str>>) -> Self {
predicates.sort();
predicates.dedup();
Self {
predicates: Rc::from(predicates),
}
}
}
pub(crate) fn cfg_predicate(attr: &syn::Attribute) -> Option<Rc<str>> {
match attr.path().is_ident("cfg") {
true => attr
.meta
.require_list()
.ok()
.map(|list| Rc::from(list.tokens.to_string().as_str())),
false => None,
}
}
fn predicates_of(attrs: &[syn::Attribute]) -> Vec<Rc<str>> {
attrs
.iter()
.flat_map(|attr| match attr.path().is_ident("cfg_attr") {
true => conditional_predicates(attr),
false => cfg_predicate(attr).into_iter().collect(),
})
.collect()
}
fn conditional_predicates(attr: &syn::Attribute) -> Vec<Rc<str>> {
let parsed = attr.parse_args_with(
syn::punctuated::Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated,
);
let Ok(metas) = parsed else {
return Vec::new();
};
let mut stated = metas.into_iter();
let Some(gate) = stated.next().map(|meta| meta.to_token_stream().to_string()) else {
return Vec::new();
};
stated
.filter_map(|meta| added_cfg(&meta))
.map(|added| Rc::from(format!("all({gate}, {added})").as_str()))
.collect()
}
fn added_cfg(meta: &syn::Meta) -> Option<String> {
let syn::Meta::List(list) = meta else {
return None;
};
list.path.is_ident("cfg").then(|| list.tokens.to_string())
}