use darling::ast::NestedMeta;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use syn::punctuated::Punctuated;
use syn::{Ident, Meta, Token};
pub fn gen_use_block(oopsie_path: &syn::Path, scopes: &[(&str, &[Ident])]) -> TokenStream2 {
if scopes.iter().all(|(_, idents)| idents.is_empty()) {
return TokenStream2::new();
}
let uses = scopes.iter().flat_map(|(scope, idents)| {
let scope = format_ident!("{scope}");
idents.iter().map(move |id| {
quote! { use #oopsie_path::__private::documented::#scope::#id as _; }
})
});
quote! {
#[doc(hidden)]
#[allow(unused_imports)]
const _: () = {
#(#uses)*
};
}
}
fn collect_into(attrs: &[syn::Attribute], out: &mut Vec<Ident>, helpers: &mut Vec<Ident>) {
for attr in attrs {
let Some(name) = attr.path().get_ident() else {
continue;
};
if name != "oopsie" {
continue;
}
helpers.push(name.clone());
let Ok(metas) = attr.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)
else {
continue;
};
out.extend(
metas
.iter()
.filter_map(|meta| meta.path().get_ident().cloned()),
);
}
}
fn is_container_key(ident: &Ident) -> bool {
["module", "suffix", "size", "path", "vis", "exit_code"]
.iter()
.any(|k| ident == k)
}
pub fn gen_keyword_docs(input: &syn::DeriveInput, oopsie_path: &syn::Path) -> TokenStream2 {
let mut collected = Vec::new();
let mut helper = Vec::new();
collect_into(&input.attrs, &mut collected, &mut helper);
let (container, mut variant): (Vec<_>, Vec<_>) =
collected.into_iter().partition(is_container_key);
let mut field = Vec::new();
match &input.data {
syn::Data::Enum(data) => {
for v in &data.variants {
collect_into(&v.attrs, &mut variant, &mut helper);
for f in &v.fields {
collect_into(&f.attrs, &mut field, &mut helper);
}
}
}
syn::Data::Struct(data) => {
for f in &data.fields {
collect_into(&f.attrs, &mut field, &mut helper);
}
}
syn::Data::Union(_) => {}
}
gen_use_block(
oopsie_path,
&[
("helper", &helper),
("container", &container),
("variant", &variant),
("field", &field),
],
)
}
pub fn collect_attr_keywords(meta: &[NestedMeta]) -> (Vec<Ident>, Vec<Ident>) {
fn recurse_lists(meta: &Meta, out: &mut Vec<Ident>) {
let Meta::List(list) = meta else { return };
let Ok(metas) = list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)
else {
return;
};
for inner in &metas {
out.extend(inner.path().get_ident().cloned());
recurse_lists(inner, out);
}
}
let mut attr = Vec::new();
let mut traced = Vec::new();
for m in meta {
let NestedMeta::Meta(m) = m else { continue };
attr.extend(m.path().get_ident().cloned());
recurse_lists(m, &mut traced);
}
(attr, traced)
}