scuffle_bootstrap_derive/
main_impl.rs

1#![allow(unused)]
2
3use darling::FromMeta;
4use darling::ast::NestedMeta;
5use proc_macro2::{Span, TokenStream};
6use quote::{quote, quote_spanned};
7use syn::punctuated::Punctuated;
8use syn::spanned::Spanned;
9use syn::{Ident, Token, parse_macro_input, parse_quote};
10
11struct Main {
12    options: ParseArgs,
13    entry: syn::Type,
14    braced: syn::token::Brace,
15    items: Vec<Item>,
16}
17
18impl syn::parse::Parse for Main {
19    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
20        let content;
21
22        Ok(Main {
23            options: ParseArgs::from_attrs(syn::Attribute::parse_outer(input)?)?,
24            entry: input.parse()?,
25            braced: syn::braced!(content in input),
26            items: content.parse_terminated(Item::parse, Token![,])?.into_iter().collect(),
27        })
28    }
29}
30
31#[derive(Debug)]
32struct Item {
33    cfg_attrs: Vec<syn::Attribute>,
34    expr: syn::Expr,
35    item_kind: ItemKind,
36}
37
38#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
39enum ItemKind {
40    Service,
41}
42
43impl syn::parse::Parse for Item {
44    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
45        // Parse any attributes before the item
46        let attrs = syn::Attribute::parse_outer(input)?;
47        let mut cfg_attrs = Vec::new();
48        let mut item_kind = None;
49
50        for attr in attrs {
51            if attr.path().is_ident("cfg") {
52                cfg_attrs.push(attr);
53            } else {
54                return Err(syn::Error::new_spanned(attr, "unknown attribute"));
55            }
56        }
57
58        Ok(Item {
59            cfg_attrs,
60            expr: input.parse()?,
61            item_kind: item_kind.unwrap_or(ItemKind::Service),
62        })
63    }
64}
65
66#[derive(Debug, darling::FromMeta)]
67#[darling(default)]
68struct ParseArgs {
69    crate_path: syn::Path,
70}
71
72impl ParseArgs {
73    fn from_attrs(attrs: Vec<syn::Attribute>) -> syn::Result<Self> {
74        let mut meta = Vec::new();
75
76        for attr in attrs {
77            if attr.path().is_ident("bootstrap") {
78                match attr.meta {
79                    syn::Meta::List(list) => {
80                        let meta_list =
81                            syn::parse::Parser::parse2(Punctuated::<NestedMeta, Token![,]>::parse_terminated, list.tokens)?;
82                        meta.extend(meta_list);
83                    }
84                    _ => {
85                        return Err(syn::Error::new_spanned(attr, "expected list, #[bootstrap(...)]"));
86                    }
87                }
88            } else {
89                return Err(syn::Error::new_spanned(attr, "unknown attribute"));
90            }
91        }
92
93        Ok(Self::from_list(&meta)?)
94    }
95}
96
97impl Default for ParseArgs {
98    fn default() -> Self {
99        Self {
100            crate_path: syn::parse_str("::scuffle_bootstrap").unwrap(),
101        }
102    }
103}
104
105impl syn::parse::Parse for ParseArgs {
106    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
107        if input.is_empty() {
108            Ok(ParseArgs::default())
109        } else {
110            let meta_list = input
111                .parse_terminated(NestedMeta::parse, Token![,])?
112                .into_iter()
113                .collect::<Vec<_>>();
114            Ok(ParseArgs::from_list(&meta_list)?)
115        }
116    }
117}
118
119pub(crate) fn impl_main(input: TokenStream) -> Result<TokenStream, syn::Error> {
120    let span = input.span();
121    let Main {
122        options,
123        entry,
124        braced,
125        items,
126    } = syn::parse2(input)?;
127
128    let crate_path = &options.crate_path;
129
130    let service_type = quote!(#crate_path::service::Service::<#entry>);
131
132    let global_ident = Ident::new("global", Span::mixed_site());
133    let ctx_handle_ident = Ident::new("ctx_handle", Span::mixed_site());
134    let services_vec_ident = Ident::new("services_vec", Span::mixed_site());
135    let runtime_ident = Ident::new("runtime", Span::mixed_site());
136    let config_ident = Ident::new("config", Span::mixed_site());
137    let shared_global_ident = Ident::new("shared_global", Span::mixed_site());
138
139    let handle_type =
140        quote!(#crate_path::service::NamedFuture<#crate_path::prelude::tokio::task::JoinHandle<anyhow::Result<()>>>);
141
142    let services = items.iter().filter(|item| item.item_kind == ItemKind::Service).map(|item| {
143		let expr = &item.expr;
144		let cfg_attrs = &item.cfg_attrs;
145
146		let expr = quote_spanned!(Span::mixed_site().located_at(expr.span()) => #expr);
147
148		let stringify_expr = quote! { #expr }.to_string();
149
150		quote_spanned! { expr.span() =>
151			#(#cfg_attrs)*
152			{
153				#[doc(hidden)]
154				pub async fn spawn_service(
155					svc: impl #service_type,
156					global: &::std::sync::Arc<#entry>,
157					ctx_handle: &#crate_path::prelude::scuffle_context::Handler,
158					name: &'static str,
159				) -> anyhow::Result<Option<#crate_path::service::NamedFuture<#crate_path::prelude::tokio::task::JoinHandle<anyhow::Result<()>>>>> {
160					let name = #service_type::name(&svc).unwrap_or_else(|| name);
161					if #crate_path::prelude::anyhow::Context::context(#service_type::enabled(&svc, &global).await, name)? {
162						Ok(Some(#crate_path::service::NamedFuture::new(
163							name,
164							#crate_path::prelude::tokio::spawn(#service_type::run(svc, global.clone(), ctx_handle.context())),
165						)))
166					} else {
167						Ok(None)
168					}
169				}
170
171				let res = spawn_service(#expr, &#global_ident, &#ctx_handle_ident, #stringify_expr).await;
172
173				if let Some(spawned) = res? {
174					#services_vec_ident.push(spawned);
175				}
176			}
177		}
178	});
179
180    let entry_as_global = quote_spanned! { entry.span() =>
181        <#entry as #crate_path::global::Global>
182    };
183
184    let boilerplate = quote_spanned! { Span::mixed_site() =>
185        #crate_path::prelude::anyhow::Context::context(#entry_as_global::pre_init(), "pre_init")?;
186
187        let #runtime_ident = #entry_as_global::tokio_runtime();
188
189        let #config_ident = #crate_path::prelude::anyhow::Context::context(
190            #runtime_ident.block_on(
191                <#entry_as_global::Config as #crate_path::config::ConfigParser>::parse()
192            ),
193            "config parse",
194        )?;
195
196        let #ctx_handle_ident = #crate_path::prelude::scuffle_context::Handler::global();
197
198        let mut #shared_global_ident = ::core::option::Option::None;
199        let mut #services_vec_ident = ::std::vec::Vec::<#handle_type>::new();
200    };
201
202    Ok(quote! {
203        #[automatically_derived]
204        fn main() -> #crate_path::prelude::anyhow::Result<()> {
205            #[doc(hidden)]
206            pub const fn impl_global<G: #crate_path::global::Global>() {}
207            const _: () = impl_global::<#entry>();
208
209            #boilerplate
210
211            let result = #runtime_ident.block_on(async {
212                let #global_ident = #entry_as_global::init(#config_ident).await?;
213
214                #shared_global_ident = ::core::option::Option::Some(#global_ident.clone());
215
216                #(#services)*
217
218                #entry_as_global::on_services_start(&#global_ident).await?;
219
220                let mut remaining = #services_vec_ident;
221
222                while !remaining.is_empty() {
223                    let ((name, result), _, new_remaining) = #crate_path::prelude::futures::future::select_all(remaining).await;
224
225                    let result = #crate_path::prelude::anyhow::Context::context(#crate_path::prelude::anyhow::Context::context(result, name)?, name);
226
227                    #entry_as_global::on_service_exit(&#global_ident, name, result).await?;
228
229                    remaining = new_remaining;
230                }
231
232                #crate_path::prelude::anyhow::Ok(())
233            });
234
235            let ::core::option::Option::Some(global) = #shared_global_ident else {
236                return result;
237            };
238
239            #runtime_ident.block_on(#entry_as_global::on_exit(&global, result))
240        }
241    })
242}