1use ::pyro_spec::InterfaceSpec;
3use syn::parse_file;
4
5use crate::{
6 ffi::{capability::CapabilityImpl, config::CapConfig, spec::build_spec},
7 format::{BridgeableArgs, DocRec, magma},
8 utils::has_attr,
9};
10
11pub mod capability;
12pub mod config;
13pub mod lifecycle;
14pub mod methods;
15pub mod paths;
16pub mod spec;
17
18pub fn generate_interface(
20 content: &str,
21 cap_name: &str,
22 cap_version: &str,
23) -> syn::Result<(syn::File, InterfaceSpec<'static>)> {
24 let file = parse_file(content)?;
25 let spec = build_spec(cap_name, &file);
26 let import_location: syn::Path = syn::parse_quote!(::pyroduct);
27
28 let mut generated_code = quote::quote! {
29 #![allow(unused_imports, dead_code, unused_variables, nonstandard_style)]
31 use pyroduct;
32 };
33
34 for item in file.items {
35 match item {
36 syn::Item::Impl(item_impl) if has_attr(&item_impl.attrs, "capability") => {
37 let cap = CapabilityImpl::new(item_impl, true, cap_name, cap_version)?;
38 generated_code.extend(cap.expand_module());
39 }
40 syn::Item::Struct(mut item_struct) => {
41 if let Some(args) = extract_magma_args(&item_struct.attrs)? {
42 item_struct.attrs.retain(|a| !is_magma_attr(a));
43 let expanded = magma(args, &mut item_struct, &import_location)?;
44 generated_code.extend(expanded);
45 }
46 }
47 _ => {}
48 }
49 }
50 let code: syn::File = syn::parse2(generated_code)?;
51 Ok((code, spec))
52}
53
54pub fn generate_capability(
56 content: &str,
57 cap_name: &str,
58 cap_version: &str,
59) -> syn::Result<syn::File> {
60 let file = parse_file(content)?;
61
62 let mut generated_code = quote::quote! {
63 #![allow(unused_imports, dead_code, unused_variables, nonstandard_style)]
65 use ::pyroduct;
66 };
67 let import_location: syn::Path = syn::parse_quote!(::pyroduct);
68
69 for item in file.items {
70 match item {
71 syn::Item::Impl(item_impl) if has_attr(&item_impl.attrs, "capability") => {
72 let cap = CapabilityImpl::new(item_impl, true, cap_name, cap_version)?;
73 generated_code.extend(cap.expand_capability());
74 }
75 syn::Item::Struct(mut item_struct) => {
76 if let Some(args) = extract_magma_args(&item_struct.attrs)? {
77 let expanded = magma(args, &mut item_struct, &import_location)?;
78 generated_code.extend(expanded);
79 } else if has_attr(&item_struct.attrs, "config") {
80 let found_config = CapConfig::new(item_struct, DocRec::StructDoc)?;
81 generated_code.extend(found_config.expand());
82 }
83 }
84 _ => {}
85 }
86 }
87
88 let code: syn::File = syn::parse2(generated_code)?;
89 Ok(code)
90}
91
92fn extract_magma_args(attrs: &[syn::Attribute]) -> syn::Result<Option<BridgeableArgs>> {
95 for attr in attrs {
96 if is_magma_attr(attr) {
97 return match &attr.meta {
98 syn::Meta::List(list) => {
99 syn::parse2::<BridgeableArgs>(list.tokens.clone()).map(Some)
100 }
101 syn::Meta::Path(_) => {
102 Ok(Some(BridgeableArgs {
104 derives_to_pass: Vec::new(),
105 compares_to_add: Vec::new(),
106 doc_rec: DocRec::NoReq,
107 }))
108 }
109 syn::Meta::NameValue(nv) => Err(syn::Error::new_spanned(
110 nv,
111 "Invalid magma attribute format",
112 )),
113 };
114 }
115 }
116 Ok(None)
117}
118
119fn is_magma_attr(attr: &syn::Attribute) -> bool {
120 if attr.path().is_ident("magma") {
121 return true;
122 }
123 if attr.path().segments.len() == 2
124 && attr.path().segments[0].ident == "pyroduct"
125 && attr.path().segments[1].ident == "magma"
126 {
127 return true;
128 }
129 false
130}