use ::pyro_spec::InterfaceSpec;
use syn::parse_file;
use crate::{
ffi::{capability::CapabilityImpl, config::CapConfig, spec::build_spec},
format::{BridgeableArgs, DocRec, magma},
utils::has_attr,
};
pub mod capability;
pub mod config;
pub mod lifecycle;
pub mod methods;
pub mod paths;
pub mod spec;
pub fn generate_interface(
content: &str,
cap_name: &str,
cap_version: &str,
) -> syn::Result<(syn::File, InterfaceSpec<'static>)> {
let file = parse_file(content)?;
let spec = build_spec(cap_name, &file);
let import_location: syn::Path = syn::parse_quote!(::pyroduct);
let mut generated_code = quote::quote! {
#![allow(unused_imports, dead_code, unused_variables, nonstandard_style)]
use pyroduct;
};
for item in file.items {
match item {
syn::Item::Impl(item_impl) if has_attr(&item_impl.attrs, "capability") => {
let cap = CapabilityImpl::new(item_impl, true, cap_name, cap_version)?;
generated_code.extend(cap.expand_module());
}
syn::Item::Struct(mut item_struct) => {
if let Some(args) = extract_magma_args(&item_struct.attrs)? {
item_struct.attrs.retain(|a| !is_magma_attr(a));
let expanded = magma(args, &mut item_struct, &import_location)?;
generated_code.extend(expanded);
}
}
_ => {}
}
}
let code: syn::File = syn::parse2(generated_code)?;
Ok((code, spec))
}
pub fn generate_capability(
content: &str,
cap_name: &str,
cap_version: &str,
) -> syn::Result<syn::File> {
let file = parse_file(content)?;
let mut generated_code = quote::quote! {
#![allow(unused_imports, dead_code, unused_variables, nonstandard_style)]
use ::pyroduct;
};
let import_location: syn::Path = syn::parse_quote!(::pyroduct);
for item in file.items {
match item {
syn::Item::Impl(item_impl) if has_attr(&item_impl.attrs, "capability") => {
let cap = CapabilityImpl::new(item_impl, true, cap_name, cap_version)?;
generated_code.extend(cap.expand_capability());
}
syn::Item::Struct(mut item_struct) => {
if let Some(args) = extract_magma_args(&item_struct.attrs)? {
let expanded = magma(args, &mut item_struct, &import_location)?;
generated_code.extend(expanded);
} else if has_attr(&item_struct.attrs, "config") {
let found_config = CapConfig::new(item_struct, DocRec::StructDoc)?;
generated_code.extend(found_config.expand());
}
}
_ => {}
}
}
let code: syn::File = syn::parse2(generated_code)?;
Ok(code)
}
fn extract_magma_args(attrs: &[syn::Attribute]) -> syn::Result<Option<BridgeableArgs>> {
for attr in attrs {
if is_magma_attr(attr) {
return match &attr.meta {
syn::Meta::List(list) => {
syn::parse2::<BridgeableArgs>(list.tokens.clone()).map(Some)
}
syn::Meta::Path(_) => {
Ok(Some(BridgeableArgs {
derives_to_pass: Vec::new(),
compares_to_add: Vec::new(),
doc_rec: DocRec::NoReq,
}))
}
syn::Meta::NameValue(nv) => Err(syn::Error::new_spanned(
nv,
"Invalid magma attribute format",
)),
};
}
}
Ok(None)
}
fn is_magma_attr(attr: &syn::Attribute) -> bool {
if attr.path().is_ident("magma") {
return true;
}
if attr.path().segments.len() == 2
&& attr.path().segments[0].ident == "pyroduct"
&& attr.path().segments[1].ident == "magma"
{
return true;
}
false
}