use proc_macro::TokenStream as TokenStream1;
use proc_macro2::{Span, TokenStream};
use proc_macro_crate::{FoundCrate, crate_name};
use quote::quote;
use syn::parse::{Parse, ParseStream};
use syn::{Attribute, Ident, Meta, Token, Type, Visibility, braced};
pub(crate) fn app_path() -> TokenStream {
match crate_name("guinea-app") {
Ok(FoundCrate::Itself) => return quote!(crate::app),
Ok(FoundCrate::Name(name)) => {
let ident = Ident::new(&name, Span::call_site());
return quote!(::#ident::app);
}
Err(_) => {}
}
match crate_name("guinea") {
Ok(FoundCrate::Itself) => quote!(crate::app),
Ok(FoundCrate::Name(name)) => {
let ident = Ident::new(&name, Span::call_site());
quote!(::#ident::app)
}
Err(_) => quote!(::guinea::app),
}
}
struct Line {
cfg: Vec<TokenStream>,
ty: Type,
}
struct Manifest {
attrs: Vec<Attribute>,
vis: Visibility,
name: Ident,
installs: Vec<Line>,
}
impl Parse for Line {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut cfg = Vec::new();
for attribute in input.call(Attribute::parse_outer)? {
match &attribute.meta {
Meta::List(list) if list.path.is_ident("cfg") => cfg.push(list.tokens.clone()),
_ => {
return Err(syn::Error::new_spanned(
attribute,
"only `#[cfg(..)]` goes over a line of `installs`",
));
}
}
}
Ok(Line {
cfg,
ty: input.parse()?,
})
}
}
impl Parse for Manifest {
fn parse(input: ParseStream) -> syn::Result<Self> {
let attrs = input.call(Attribute::parse_outer)?;
let vis: Visibility = input.parse()?;
let name: Ident = input.parse()?;
if input.peek(Token![<]) {
return Err(input.error("an application is one type, not a generic one"));
}
let body;
braced!(body in input);
let mut installs = Vec::new();
while !body.is_empty() {
let section: Ident = body.parse()?;
if section != "installs" {
return Err(syn::Error::new(
section.span(),
format!("unknown section `{section}`; an application lists `installs`"),
));
}
let listed;
braced!(listed in body);
let lines = listed.parse_terminated(Line::parse, Token![,])?;
installs.extend(lines);
}
Ok(Manifest {
attrs,
vis,
name,
installs,
})
}
}
pub fn app_impl(input: TokenStream1) -> TokenStream1 {
let manifest = syn::parse_macro_input!(input as Manifest);
expand(manifest).into()
}
fn expand(manifest: Manifest) -> TokenStream {
let Manifest {
attrs,
vis,
name,
installs,
} = manifest;
let app = app_path();
let fields = installs.iter().map(|line| {
let ty = &line.ty;
let cfg = &line.cfg;
quote! {
#(#[cfg(#cfg)])*
#vis #app::Installed<#ty>
}
});
if installs.is_empty() {
quote! {
#(#attrs)*
#vis struct #name;
}
} else {
quote! {
#(#attrs)*
#vis struct #name(#(#fields),*);
}
}
}