diff --git a/bok-macro/src/repos.rs b/bok-macro/src/repos.rs index df1afef..d3e5ec0 100644 --- a/bok-macro/src/repos.rs +++ b/bok-macro/src/repos.rs @@ -278,7 +278,72 @@ pub(crate) fn derive_repository(input: TokenStream) -> TokenStream { let name = input.ident.clone(); - let expanded = quote! { + let ::syn::Data::Struct(items) = &input.data else { + use ::syn::spanned::Spanned; + return ::syn::Error::new( + input.span(), + "#[derive(::bok::Repository)]: not called on a struct", + ) + .to_compile_error() + .into(); + }; + + if items + .fields + .iter() + .find(|&f| { + f.ident + .as_ref() + .is_some_and(|id| id.to_string() == "_bok_repo") + }) + .is_none() + { + use ::syn::spanned::Spanned; + return ::syn::Error::new( + input.span(), + "#[derive(::bok::Repository)]: struct is not a bok repo. use \ + `#[::bok::repository(..)]` first", + ) + .to_compile_error() + .into(); + } + // holds the list of all package names, snake case + let mut all_pkgs = + Vec::<(String, ::syn::Path)>::with_capacity(items.fields.len()); + + for it in items.fields.iter() { + let Some(id) = &it.ident else { continue }; + let id_str = id.to_string(); + if id_str.starts_with("_p_") { + let path = { + let ::syn::Type::Path(phantom_path) = &it.ty else { + continue; + }; + let segment = phantom_path.path.segments.last().unwrap(); + let ::syn::PathArguments::AngleBracketed(bracketed) = + &segment.arguments + else { + panic!("derive: phantom without anglebracket?"); + }; + let ::syn::GenericArgument::Type(::syn::Type::Path(t)) = + bracketed.args.first().expect("phantom bracketed, no args") + else { + panic!( + "derive: phantom bracketed, generic not a type path" + ); + }; + t.path.clone() + }; + let name = id_str.strip_prefix("_p_").unwrap().to_owned(); + all_pkgs.push((name, path)); + } + } + all_pkgs.sort_by(|a, b| a.0.cmp(&b.0)); + let pkgs_num: usize = all_pkgs.len(); + let (pkg_names, pkg_types): (Vec, Vec<::syn::Path>) = + all_pkgs.into_iter().map(|(a, b)| (a, b)).unzip(); + + quote! { impl ::bok::Repository for #name { fn name(&self) -> ::bok::RepoName { (module_path!().to_owned() + "::" + ::std::stringify!(#name)).as_str().into() @@ -286,10 +351,22 @@ pub(crate) fn derive_repository(input: TokenStream) -> TokenStream { fn path(&self) -> ::bok::Path<::bok::RepoName> { (module_path!().to_owned() + "::" + ::std::stringify!(#name)).as_str().into() } + fn pkg_list(&self) -> &[&'static str] { + const PKGS : [&'static str; #pkgs_num] = [ + #(#pkg_names), + * + ]; + &PKGS + } + fn get(&self, pkg_name: &str) -> Option<::std::boxed::Box> { + match pkg_name { + #(#pkg_names => Some(::std::boxed::Box::new(#pkg_types::default())),) + * + _ => None, + } + } } - }; - - TokenStream::from(expanded) + }.into() } struct PathList(Vec<::syn::Path>); diff --git a/bok/src/lib.rs b/bok/src/lib.rs index dfc2a96..e6c3366 100644 --- a/bok/src/lib.rs +++ b/bok/src/lib.rs @@ -50,6 +50,8 @@ macro_rules! moduse { pub trait Repository: ::core::fmt::Debug { fn name(&self) -> RepoName; fn path(&self) -> Path; + fn pkg_list(&self) -> &[&'static str]; + fn get(&self, pkg_name: &str) -> Option<::std::boxed::Box>; } /// This is an empty Package List. @@ -66,6 +68,13 @@ impl Repository for RepositoryEmpty { fn path(&self) -> Path { "::bok::RepositoryEmpty".into() } + fn pkg_list(&self) -> &[&'static str] { + const PKGS: [&'static str; 0] = []; + &PKGS + } + fn get(&self, _pkg_name: &str) -> Option<::std::boxed::Box> { + None + } } /// Print the code of the package