bok-macro: use sub modules
Signed-off-by: Luca Fulchir <luca.fulchir@runesauth.com>
This commit is contained in:
parent
ba5e30e502
commit
5d3fde9481
@ -16,10 +16,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
use ::proc_macro::TokenStream;
|
use ::proc_macro::TokenStream;
|
||||||
use ::quote::quote;
|
|
||||||
use ::syn::{
|
mod pkgs;
|
||||||
parse::Parser, parse_macro_input, DeriveInput, Fields, ItemStruct,
|
mod repos;
|
||||||
};
|
|
||||||
|
|
||||||
/// Use as #[::bok::repository(MyBaseRepo)]
|
/// Use as #[::bok::repository(MyBaseRepo)]
|
||||||
/// Will setup a `base` field that is the given base repo
|
/// Will setup a `base` field that is the given base repo
|
||||||
@ -29,47 +28,7 @@ use ::syn::{
|
|||||||
#[::macro_magic::import_tokens_attr]
|
#[::macro_magic::import_tokens_attr]
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn repository(attrs: TokenStream, input: TokenStream) -> TokenStream {
|
pub fn repository(attrs: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
let base = parse_macro_input!(attrs as ItemStruct);
|
crate::repos::repository(attrs, input)
|
||||||
let local = parse_macro_input!(input as ItemStruct);
|
|
||||||
|
|
||||||
let Fields::Named(local_fields) = local.fields else {
|
|
||||||
use ::syn::spanned::Spanned;
|
|
||||||
return ::syn::Error::new(
|
|
||||||
local.fields.span(),
|
|
||||||
"unnamed fields are not supported",
|
|
||||||
)
|
|
||||||
.to_compile_error()
|
|
||||||
.into();
|
|
||||||
};
|
|
||||||
|
|
||||||
let Fields::Named(base_fields) = base.fields else {
|
|
||||||
use ::syn::spanned::Spanned;
|
|
||||||
return ::syn::Error::new(
|
|
||||||
base.fields.span(),
|
|
||||||
"unnamed fields are not supported",
|
|
||||||
)
|
|
||||||
.to_compile_error()
|
|
||||||
.into();
|
|
||||||
};
|
|
||||||
let local_fields_it = local_fields.named.iter();
|
|
||||||
let base_fields_extra = base_fields.named.iter();
|
|
||||||
let attrs = local.attrs;
|
|
||||||
let generics = local.generics;
|
|
||||||
let ident = local.ident;
|
|
||||||
let vis = local.vis;
|
|
||||||
quote! {
|
|
||||||
#(#attrs)
|
|
||||||
*
|
|
||||||
#[::macro_magic::export_tokens]
|
|
||||||
#[derive(::bok::Repository, Debug)]
|
|
||||||
#vis struct #ident<#generics> {
|
|
||||||
#(#base_fields_extra),
|
|
||||||
*
|
|
||||||
#(#local_fields_it),
|
|
||||||
*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.into()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unless you know what you are doing, use `#[::bok::repository(MyBaseRepo)]`
|
/// Unless you know what you are doing, use `#[::bok::repository(MyBaseRepo)]`
|
||||||
@ -85,22 +44,7 @@ pub fn repository(attrs: TokenStream, input: TokenStream) -> TokenStream {
|
|||||||
/// adds extension capabilities to a repo
|
/// adds extension capabilities to a repo
|
||||||
#[proc_macro_derive(Repository)]
|
#[proc_macro_derive(Repository)]
|
||||||
pub fn derive_repository(input: TokenStream) -> TokenStream {
|
pub fn derive_repository(input: TokenStream) -> TokenStream {
|
||||||
let input = parse_macro_input!(input as DeriveInput);
|
crate::repos::derive_repository(input)
|
||||||
|
|
||||||
let name = input.ident.clone();
|
|
||||||
|
|
||||||
let expanded = quote! {
|
|
||||||
impl ::bok::Repository for #name {
|
|
||||||
fn name(&self) -> ::bok::RepoName {
|
|
||||||
(module_path!().to_owned() + "::" + ::std::stringify!(#name)).as_str().into()
|
|
||||||
}
|
|
||||||
fn path(&self) -> ::bok::Path<::bok::RepoName> {
|
|
||||||
(module_path!().to_owned() + "::" + ::std::stringify!(#name)).as_str().into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
TokenStream::from(expanded)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Use on a function as `#[::bok::pkg_fn_to_code]`
|
/// Use on a function as `#[::bok::pkg_fn_to_code]`
|
||||||
@ -122,38 +66,8 @@ pub fn derive_repository(input: TokenStream) -> TokenStream {
|
|||||||
/// let tokens : ::proc_macro2::TokenStream = p.build_code()
|
/// let tokens : ::proc_macro2::TokenStream = p.build_code()
|
||||||
/// ```
|
/// ```
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn pkg_fn_to_code(_attrs: TokenStream, input: TokenStream) -> TokenStream {
|
pub fn pkg_fn_to_code(attrs: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
let mut ast_orig: ::syn::ItemFn =
|
crate::pkgs::pkg_fn_to_code(attrs, input)
|
||||||
parse_macro_input!(input as ::syn::ItemFn);
|
|
||||||
|
|
||||||
let mut ast_new = ast_orig.clone();
|
|
||||||
ast_new.block = ast_orig.block.clone();
|
|
||||||
|
|
||||||
// Add a prefix and suffix statement
|
|
||||||
// where we declare and return the result
|
|
||||||
let prefix_ret: ::syn::Stmt = ::syn::parse_quote! {
|
|
||||||
let mut ret : ::core::result::Result<(),()> = ::core::result::Result::Err(());
|
|
||||||
};
|
|
||||||
let suffix_ret: ::syn::Stmt = ::syn::parse_quote! {
|
|
||||||
return ret;
|
|
||||||
};
|
|
||||||
ast_orig.block.stmts.insert(0, prefix_ret);
|
|
||||||
ast_orig.block.stmts.push(suffix_ret);
|
|
||||||
|
|
||||||
let new_fn_name =
|
|
||||||
quote::format_ident!("{}", ast_orig.sig.ident.to_string() + "_code");
|
|
||||||
ast_new.sig.ident = quote::format_ident!("{}", new_fn_name);
|
|
||||||
use ::quote::ToTokens;
|
|
||||||
let fn_block = ast_new.block.to_token_stream();
|
|
||||||
|
|
||||||
let asdf = quote! {
|
|
||||||
#ast_orig
|
|
||||||
fn #new_fn_name (&self) -> ::proc_macro2::TokenStream {
|
|
||||||
::quote::quote! #fn_block
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
asdf.into()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Use as #[::bok::package(MyBasePackage)]
|
/// Use as #[::bok::package(MyBasePackage)]
|
||||||
@ -167,34 +81,7 @@ pub fn pkg_fn_to_code(_attrs: TokenStream, input: TokenStream) -> TokenStream {
|
|||||||
/// ```
|
/// ```
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn package(attrs: TokenStream, input: TokenStream) -> TokenStream {
|
pub fn package(attrs: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
let mut ast = parse_macro_input!(input as DeriveInput);
|
crate::pkgs::package(attrs, input)
|
||||||
match &mut ast.data {
|
|
||||||
syn::Data::Struct(ref mut struct_data) => {
|
|
||||||
match &mut struct_data.fields {
|
|
||||||
syn::Fields::Named(fields) => {
|
|
||||||
let base = proc_macro2::TokenStream::from(attrs);
|
|
||||||
fields.named.push(
|
|
||||||
syn::Field::parse_named
|
|
||||||
.parse2(quote! { base: #base })
|
|
||||||
.unwrap(),
|
|
||||||
);
|
|
||||||
fields.named.push(
|
|
||||||
syn::Field::parse_named
|
|
||||||
.parse2(quote! { version: ::bok::Version })
|
|
||||||
.unwrap(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
#[derive(::bok::Package,::std::fmt::Debug, Clone)]
|
|
||||||
#ast
|
|
||||||
}
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
_ => panic!("`package` has to be used with a struct"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Use as #[::bok::impl_package]
|
/// Use as #[::bok::impl_package]
|
||||||
@ -210,106 +97,8 @@ pub fn package(attrs: TokenStream, input: TokenStream) -> TokenStream {
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn impl_package(_attrs: TokenStream, input: TokenStream) -> TokenStream {
|
pub fn impl_package(attrs: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
let mut ast = parse_macro_input!(input as ::syn::ItemImpl);
|
crate::pkgs::impl_package(attrs, input)
|
||||||
|
|
||||||
let name_pkg = match &*ast.self_ty {
|
|
||||||
::syn::Type::Path(tp) => match tp.path.get_ident() {
|
|
||||||
Some(id) => id.clone(),
|
|
||||||
_ => panic!("impl_package expected path ident"),
|
|
||||||
},
|
|
||||||
_ => panic!("impl_package expected path"),
|
|
||||||
};
|
|
||||||
let name: ::syn::ImplItem = ::syn::parse_quote! {
|
|
||||||
fn name(&self) -> ::bok::PkgName {
|
|
||||||
(module_path!().to_owned() + "::" + ::std::stringify!(#name_pkg)).as_str().into()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let path: ::syn::ImplItem = ::syn::parse_quote! {
|
|
||||||
fn path(&self) -> ::bok::Path<::bok::PkgName> {
|
|
||||||
(module_path!().to_owned() + "::" + ::std::stringify!(#name_pkg)).as_str().into()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let version: ::syn::ImplItem = ::syn::parse_quote! {
|
|
||||||
fn version(&self) -> ::bok::Version {
|
|
||||||
self.version.clone()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let base: ::syn::ImplItem = ::syn::parse_quote! {
|
|
||||||
fn base(&self) -> ::core::option::Option<&dyn ::bok::Pkg> {
|
|
||||||
Some(&self.base)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let base_mut: ::syn::ImplItem = ::syn::parse_quote! {
|
|
||||||
fn base_mut(&mut self) -> ::core::option::Option<&mut dyn ::bok::Pkg> {
|
|
||||||
Some(&mut self.base)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let any: ::syn::ImplItem = ::syn::parse_quote! {
|
|
||||||
fn as_any(&self) -> &dyn ::std::any::Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let any_mut: ::syn::ImplItem = ::syn::parse_quote! {
|
|
||||||
fn as_any_mut(&mut self) -> &mut dyn ::std::any::Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let hash: ::syn::ImplItem = ::syn::parse_quote! {
|
|
||||||
fn hash(&self) -> ::bok::Hash {
|
|
||||||
use ::sha3::{Digest};
|
|
||||||
let mut hasher = ::sha3::Sha3_256::new();
|
|
||||||
let mut pkg_vars = String::new();
|
|
||||||
use ::std::fmt::Write;
|
|
||||||
write!(&mut pkg_vars,
|
|
||||||
"{:?}",
|
|
||||||
self,
|
|
||||||
).ok();
|
|
||||||
hasher.update(&pkg_vars);
|
|
||||||
hasher.update(self.to_string());
|
|
||||||
let hash = hasher.finalize();
|
|
||||||
::bok::Hash::Sha3(hash)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let hash_code: ::syn::ImplItem = ::syn::parse_quote! {
|
|
||||||
fn hash_code(&self) -> ::bok::Hash {
|
|
||||||
use ::sha3::{Digest};
|
|
||||||
let mut hasher = ::sha3::Sha3_256::new();
|
|
||||||
hasher.update(self.to_string());
|
|
||||||
let hash = hasher.finalize();
|
|
||||||
::bok::Hash::Sha3(hash)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for mut impl_item in ast.items.iter_mut() {
|
|
||||||
if let ::syn::ImplItem::Fn(ref mut fn_impl) = &mut impl_item {
|
|
||||||
if fn_impl.sig.ident.to_string() == "dependencies_set" {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let fn_with_code: ::syn::ImplItemFn = ::syn::parse_quote! {
|
|
||||||
#[::bok::pkg_fn_to_code]
|
|
||||||
#fn_impl
|
|
||||||
};
|
|
||||||
*fn_impl = fn_with_code;
|
|
||||||
} else {
|
|
||||||
panic!("wtf");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ast.items.push(name);
|
|
||||||
ast.items.push(path);
|
|
||||||
ast.items.push(version);
|
|
||||||
ast.items.push(base);
|
|
||||||
ast.items.push(base_mut);
|
|
||||||
ast.items.push(any);
|
|
||||||
ast.items.push(any_mut);
|
|
||||||
ast.items.push(hash);
|
|
||||||
ast.items.push(hash_code);
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
#ast
|
|
||||||
}
|
|
||||||
.into()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unless you know what you are doing, use `#[::bok::package(MyBasePackage)]`
|
/// Unless you know what you are doing, use `#[::bok::package(MyBasePackage)]`
|
||||||
@ -327,349 +116,5 @@ pub fn impl_package(_attrs: TokenStream, input: TokenStream) -> TokenStream {
|
|||||||
/// * deref for package
|
/// * deref for package
|
||||||
#[proc_macro_derive(Package)]
|
#[proc_macro_derive(Package)]
|
||||||
pub fn derive_package(input: TokenStream) -> TokenStream {
|
pub fn derive_package(input: TokenStream) -> TokenStream {
|
||||||
let input = parse_macro_input!(input as DeriveInput);
|
crate::pkgs::derive_package(input)
|
||||||
let mut input_nobase = input.clone();
|
|
||||||
|
|
||||||
let name = input.ident.clone();
|
|
||||||
let name_builder = quote::format_ident!("{name}Builder");
|
|
||||||
let name_builder2 = name_builder.clone();
|
|
||||||
let name_builder3 = name_builder.clone();
|
|
||||||
let elements = match input.data {
|
|
||||||
::syn::Data::Struct(s) => match s.fields {
|
|
||||||
syn::Fields::Named(n) => n.named,
|
|
||||||
_ => panic!("only named supported"),
|
|
||||||
},
|
|
||||||
_ => panic!("only struct allowed"),
|
|
||||||
};
|
|
||||||
let all_fields = elements.iter().filter_map(|field| {
|
|
||||||
if let Some(id) = field.ident.clone() {
|
|
||||||
if id.to_string() != "base" && id.to_string() != "version" {
|
|
||||||
return Some(&field.ident);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
});
|
|
||||||
let all_fields2 = all_fields.clone();
|
|
||||||
let all_fields3 = all_fields.clone();
|
|
||||||
let all_fields_mut = elements.iter().filter_map(|field| {
|
|
||||||
if let Some(id) = field.ident.clone() {
|
|
||||||
if id.to_string() != "base" && id.to_string() != "version" {
|
|
||||||
return Some(quote::format_ident!("{}_mut", id.to_string()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
});
|
|
||||||
let base_type = elements
|
|
||||||
.iter()
|
|
||||||
.find_map(|field| {
|
|
||||||
if let Some(id) = field.ident.clone() {
|
|
||||||
if id.to_string() == "base" {
|
|
||||||
return Some(&field.ty);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
})
|
|
||||||
.expect("expected a base type");
|
|
||||||
let all_types = elements.iter().filter_map(|field| {
|
|
||||||
if let Some(id) = field.ident.clone() {
|
|
||||||
if id.to_string() != "base" && id.to_string() != "version" {
|
|
||||||
return Some(&field.ty);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
});
|
|
||||||
let all_types2 = all_types.clone();
|
|
||||||
let non_opt_fields = elements.iter().filter_map(|field| {
|
|
||||||
if let Some(id) = field.ident.clone() {
|
|
||||||
if id.to_string() == "base" || id.to_string() == "version" {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
match &field.ty {
|
|
||||||
syn::Type::Path(pth) => {
|
|
||||||
let first_path = pth.path.segments.first().unwrap();
|
|
||||||
if first_path.ident == "Option" {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
let id = &field.ident;
|
|
||||||
Some(quote! {#id})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t => Some(quote! {#t}),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let opt_fields = elements.iter().filter_map(|field| {
|
|
||||||
if let Some(id) = field.ident.clone() {
|
|
||||||
if id.to_string() == "base" || id.to_string() == "version" {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
match &field.ty {
|
|
||||||
syn::Type::Path(pth) => {
|
|
||||||
let first_path = pth.path.segments.first().unwrap();
|
|
||||||
if first_path.ident == "Option" {
|
|
||||||
let id = &field.ident;
|
|
||||||
Some(quote! {#id})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let non_opt_types = elements.iter().filter_map(|field| {
|
|
||||||
if let Some(id) = field.ident.clone() {
|
|
||||||
if id.to_string() == "base" || id.to_string() == "version" {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
match &field.ty {
|
|
||||||
syn::Type::Path(pth) => {
|
|
||||||
let first_path = pth.path.segments.first().unwrap();
|
|
||||||
if first_path.ident == "Option" {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
let t = &field.ty;
|
|
||||||
Some(quote! {#t})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let opt_types = elements.iter().filter_map(|field| {
|
|
||||||
if let Some(id) = field.ident.clone() {
|
|
||||||
if id.to_string() == "base" || id.to_string() == "version" {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let ::syn::Type::Path(pth) = &field.ty {
|
|
||||||
let first_path = pth.path.segments.first().unwrap();
|
|
||||||
if first_path.ident == "Option" {
|
|
||||||
if let syn::PathArguments::AngleBracketed(
|
|
||||||
syn::AngleBracketedGenericArguments { args, .. },
|
|
||||||
) = &first_path.arguments
|
|
||||||
{
|
|
||||||
if let Some(syn::GenericArgument::Type(syn::Type::Path(
|
|
||||||
p,
|
|
||||||
))) = args.first()
|
|
||||||
{
|
|
||||||
let id = &p.path.segments.first().unwrap().ident;
|
|
||||||
return Some(quote! {#id});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
});
|
|
||||||
let non_opt_fields2 = non_opt_fields.clone();
|
|
||||||
let non_opt_fields3 = non_opt_fields.clone();
|
|
||||||
let non_opt_fields4 = non_opt_fields.clone();
|
|
||||||
let non_opt_fields5 = non_opt_fields.clone();
|
|
||||||
let opt_fields2 = opt_fields.clone();
|
|
||||||
let opt_fields3 = opt_fields.clone();
|
|
||||||
let opt_types2 = opt_types.clone();
|
|
||||||
let non_opt_types2 = non_opt_types.clone();
|
|
||||||
|
|
||||||
{
|
|
||||||
// remove the `base` field from the struct
|
|
||||||
let ::syn::Data::Struct(ref mut ds) = input_nobase.data else {
|
|
||||||
panic!("::bok::package expected a struct");
|
|
||||||
};
|
|
||||||
let ::syn::Fields::Named(ref mut old_nf) = ds.fields else {
|
|
||||||
panic!("::bok::package expected a named field");
|
|
||||||
};
|
|
||||||
let mut new_nf = ::syn::punctuated::Punctuated::<
|
|
||||||
::syn::Field,
|
|
||||||
::syn::token::Comma,
|
|
||||||
>::new();
|
|
||||||
for f in old_nf.named.iter() {
|
|
||||||
if let Some(ref id) = f.ident {
|
|
||||||
if id.to_string() == "base" {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
new_nf.push(f.clone());
|
|
||||||
}
|
|
||||||
old_nf.named = new_nf;
|
|
||||||
}
|
|
||||||
let pkg_struct = quote! {
|
|
||||||
#input_nobase
|
|
||||||
};
|
|
||||||
let mut pkg_struct_str = String::new();
|
|
||||||
{
|
|
||||||
use ::core::fmt::Write;
|
|
||||||
write!(&mut pkg_struct_str, "{}", pkg_struct)
|
|
||||||
.expect("can't write package struct into string");
|
|
||||||
}
|
|
||||||
|
|
||||||
let expanded = quote! {
|
|
||||||
impl ::core::fmt::Display for #name {
|
|
||||||
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>)
|
|
||||||
->::core::fmt::Result
|
|
||||||
{
|
|
||||||
<#name as ::bok::PkgCode>::fmt(self, f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl ::bok::PkgCode for #name {
|
|
||||||
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>)
|
|
||||||
->::core::fmt::Result
|
|
||||||
{
|
|
||||||
// FIXME: this sounds convoluted.
|
|
||||||
// first we turn everything into a string, then we
|
|
||||||
// parse the string into tokenstream again to format it
|
|
||||||
// reason: the `prettyplease` works on `::syn::File`
|
|
||||||
// and I can't find an easy translation
|
|
||||||
// `TokenStream2 -> TokenStream`
|
|
||||||
use ::bok::Pkg;
|
|
||||||
|
|
||||||
// don't add a method if it is just the base Trait implementation
|
|
||||||
fn maybe_add_fn(out: &mut String, name: &str, fn_str: &str) -> ::core::fmt::Result {
|
|
||||||
|
|
||||||
let str_base = "self . base () . unwrap () . ".to_owned() + name + " ()";
|
|
||||||
|
|
||||||
if fn_str.trim() != str_base {
|
|
||||||
use ::core::fmt::Write;
|
|
||||||
|
|
||||||
write!(out,
|
|
||||||
"\
|
|
||||||
fn {}(&self) -> Result<(), ()> {{\n\
|
|
||||||
{}\n\
|
|
||||||
}}\n",
|
|
||||||
name,
|
|
||||||
fn_str,
|
|
||||||
)
|
|
||||||
|
|
||||||
} else {
|
|
||||||
::core::fmt::Result::Ok(())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
use ::core::fmt::Write;
|
|
||||||
|
|
||||||
let pkg_empty = ::bok::PkgEmpty::default();
|
|
||||||
let mut pkg_string = String::new();
|
|
||||||
write!(&mut pkg_string,
|
|
||||||
"#[::bok::package({})]\n\
|
|
||||||
{}",
|
|
||||||
::std::stringify!(#base_type),
|
|
||||||
#pkg_struct_str)?;
|
|
||||||
write!(&mut pkg_string,
|
|
||||||
"#[::bok::impl_package]\n\
|
|
||||||
impl ::bok::Pkg for {} {{\n",
|
|
||||||
::std::stringify!(#name)
|
|
||||||
)?;
|
|
||||||
maybe_add_fn(&mut pkg_string, "prepare", &self.prepare_code().to_string())?;
|
|
||||||
maybe_add_fn(&mut pkg_string, "configure", &self.configure_code().to_string())?;
|
|
||||||
maybe_add_fn(&mut pkg_string, "build", &self.build_code().to_string())?;
|
|
||||||
maybe_add_fn(&mut pkg_string, "check", &self.check_code().to_string())?;
|
|
||||||
maybe_add_fn(&mut pkg_string, "install", &self.install_code().to_string())?;
|
|
||||||
write!(&mut pkg_string, "}}\n")?;
|
|
||||||
let re_parsed = ::syn::parse_file(&pkg_string).unwrap();
|
|
||||||
let formatted = prettyplease::unparse(&re_parsed);
|
|
||||||
f.write_str(&formatted)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl #name {
|
|
||||||
pub fn as_pkg(&self) -> &dyn ::bok::Pkg {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
pub fn as_pkg_mut(&mut self) -> &mut dyn ::bok::Pkg {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
pub fn builder() -> #name_builder {
|
|
||||||
#name_builder::default()
|
|
||||||
}
|
|
||||||
#(pub fn #all_fields2(&self) -> &#all_types {
|
|
||||||
&self.#all_fields2
|
|
||||||
})*
|
|
||||||
#(pub fn #all_fields_mut(&mut self) -> &#all_types2 {
|
|
||||||
&mut self.#all_fields3
|
|
||||||
})*
|
|
||||||
}
|
|
||||||
#[derive(::std::default::Default, ::std::fmt::Debug)]
|
|
||||||
pub struct #name_builder {
|
|
||||||
#(#non_opt_fields: ::std::option::Option<#non_opt_types>,)*
|
|
||||||
#(#opt_fields: ::std::option::Option<#opt_types>,)*
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
impl ::std::ops::Deref for #name_builder {
|
|
||||||
type Target = #base_type;
|
|
||||||
fn deref(&self) -> &#base_type {
|
|
||||||
&self.base
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
impl ::bok::PkgBuilder for #name_builder2 {
|
|
||||||
fn name(&self) -> ::bok::PkgName {
|
|
||||||
use ::bok::Pkg;
|
|
||||||
#name::default().name()
|
|
||||||
}
|
|
||||||
fn path(&self) -> ::bok::Path<::bok::PkgName> {
|
|
||||||
use ::bok::Pkg;
|
|
||||||
#name::default().path()
|
|
||||||
}
|
|
||||||
fn version(&self) -> ::bok::Version {
|
|
||||||
use ::bok::Pkg;
|
|
||||||
#name::default().version()
|
|
||||||
}
|
|
||||||
fn default_unused(&mut self) -> &mut dyn ::bok::PkgBuilder {
|
|
||||||
let def = #name::default();
|
|
||||||
#(if self.#non_opt_fields5.is_none() {
|
|
||||||
self.#non_opt_fields5 = Some(def.#non_opt_fields5);
|
|
||||||
})*
|
|
||||||
|
|
||||||
self
|
|
||||||
}
|
|
||||||
fn build(&mut self) -> Result<Box<dyn ::bok::Pkg>, ::std::boxed::Box<dyn ::std::error::Error>> {
|
|
||||||
#(if self.#non_opt_fields2.is_none() {
|
|
||||||
return ::std::result::Result::Err("unset field".into());
|
|
||||||
})*
|
|
||||||
Ok(
|
|
||||||
Box::new(#name {
|
|
||||||
// FIXME: user must be able to override. Trait?
|
|
||||||
base: #base_type::default(),
|
|
||||||
version: #name::default().version,
|
|
||||||
#(#non_opt_fields3 : self.#non_opt_fields3.clone().unwrap(),)*
|
|
||||||
#(#opt_fields2 : self.#opt_fields2.clone(),)*
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl #name_builder3 {
|
|
||||||
pub fn as_any(&self) -> &dyn ::std::any::Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
pub fn as_any_mut(&mut self) -> &mut dyn ::std::any::Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
pub fn as_builder(&self) -> &dyn ::bok::PkgBuilder {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
pub fn as_builder_mut(&mut self) -> &mut dyn ::bok::PkgBuilder {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
#(pub fn #non_opt_fields4 (&mut self, val : #non_opt_types2) -> &mut Self {
|
|
||||||
if self.#non_opt_fields4 != None &&
|
|
||||||
self.#non_opt_fields4 != Some(val) {
|
|
||||||
panic!("Package \"{}\": mandatory attribute set multiple times: \"{}\"",
|
|
||||||
::std::stringify!(#name),
|
|
||||||
::std::stringify!(#non_opt_fields4));
|
|
||||||
}
|
|
||||||
self.#non_opt_fields4 = Some(val);
|
|
||||||
self
|
|
||||||
})*
|
|
||||||
#(pub fn #opt_fields3 (&mut self, val : #opt_types2) -> &mut Self {
|
|
||||||
if self.#opt_fields3 != None &&
|
|
||||||
self.#opt_fields3 != Some(val) {
|
|
||||||
panic!("Package \"{}\": optional attribute set multiple times: \"{}\"",
|
|
||||||
::std::stringify!(#name),
|
|
||||||
::std::stringify!(#opt_fields3));
|
|
||||||
}
|
|
||||||
self.#opt_fields3 = Some(val);
|
|
||||||
self
|
|
||||||
})*
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
TokenStream::from(expanded)
|
|
||||||
}
|
}
|
||||||
|
541
bok-macro/src/pkgs.rs
Normal file
541
bok-macro/src/pkgs.rs
Normal file
@ -0,0 +1,541 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Luca Fulchir <luca.fulchir@runesauth.com>
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 with LLVM exception (the
|
||||||
|
* "License"); you may not use this file except in compliance with the
|
||||||
|
* License. You may obtain a copy of the License and of the exception at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* https://spdx.org/licenses/LLVM-exception.html
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use ::proc_macro::TokenStream;
|
||||||
|
use ::quote::quote;
|
||||||
|
use ::syn::{parse::Parser, parse_macro_input, DeriveInput};
|
||||||
|
|
||||||
|
pub(crate) fn pkg_fn_to_code(
|
||||||
|
_attrs: TokenStream,
|
||||||
|
input: TokenStream,
|
||||||
|
) -> TokenStream {
|
||||||
|
let mut ast_orig: ::syn::ItemFn =
|
||||||
|
parse_macro_input!(input as ::syn::ItemFn);
|
||||||
|
|
||||||
|
let mut ast_new = ast_orig.clone();
|
||||||
|
ast_new.block = ast_orig.block.clone();
|
||||||
|
|
||||||
|
// Add a prefix and suffix statement
|
||||||
|
// where we declare and return the result
|
||||||
|
let prefix_ret: ::syn::Stmt = ::syn::parse_quote! {
|
||||||
|
let mut ret : ::core::result::Result<(),()> = ::core::result::Result::Err(());
|
||||||
|
};
|
||||||
|
let suffix_ret: ::syn::Stmt = ::syn::parse_quote! {
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
ast_orig.block.stmts.insert(0, prefix_ret);
|
||||||
|
ast_orig.block.stmts.push(suffix_ret);
|
||||||
|
|
||||||
|
let new_fn_name =
|
||||||
|
quote::format_ident!("{}", ast_orig.sig.ident.to_string() + "_code");
|
||||||
|
ast_new.sig.ident = quote::format_ident!("{}", new_fn_name);
|
||||||
|
use ::quote::ToTokens;
|
||||||
|
let fn_block = ast_new.block.to_token_stream();
|
||||||
|
|
||||||
|
let asdf = quote! {
|
||||||
|
#ast_orig
|
||||||
|
fn #new_fn_name (&self) -> ::proc_macro2::TokenStream {
|
||||||
|
::quote::quote! #fn_block
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
asdf.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn package(attrs: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
|
let mut ast = parse_macro_input!(input as DeriveInput);
|
||||||
|
match &mut ast.data {
|
||||||
|
syn::Data::Struct(ref mut struct_data) => {
|
||||||
|
match &mut struct_data.fields {
|
||||||
|
syn::Fields::Named(fields) => {
|
||||||
|
let base = proc_macro2::TokenStream::from(attrs);
|
||||||
|
fields.named.push(
|
||||||
|
syn::Field::parse_named
|
||||||
|
.parse2(quote! { base: #base })
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
fields.named.push(
|
||||||
|
syn::Field::parse_named
|
||||||
|
.parse2(quote! { version: ::bok::Version })
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#[derive(::bok::Package,::std::fmt::Debug, Clone)]
|
||||||
|
#ast
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
_ => panic!("`package` has to be used with a struct"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn impl_package(
|
||||||
|
_attrs: TokenStream,
|
||||||
|
input: TokenStream,
|
||||||
|
) -> TokenStream {
|
||||||
|
let mut ast = parse_macro_input!(input as ::syn::ItemImpl);
|
||||||
|
|
||||||
|
let name_pkg = match &*ast.self_ty {
|
||||||
|
::syn::Type::Path(tp) => match tp.path.get_ident() {
|
||||||
|
Some(id) => id.clone(),
|
||||||
|
_ => panic!("impl_package expected path ident"),
|
||||||
|
},
|
||||||
|
_ => panic!("impl_package expected path"),
|
||||||
|
};
|
||||||
|
let name: ::syn::ImplItem = ::syn::parse_quote! {
|
||||||
|
fn name(&self) -> ::bok::PkgName {
|
||||||
|
(module_path!().to_owned() + "::" + ::std::stringify!(#name_pkg)).as_str().into()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let path: ::syn::ImplItem = ::syn::parse_quote! {
|
||||||
|
fn path(&self) -> ::bok::Path<::bok::PkgName> {
|
||||||
|
(module_path!().to_owned() + "::" + ::std::stringify!(#name_pkg)).as_str().into()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let version: ::syn::ImplItem = ::syn::parse_quote! {
|
||||||
|
fn version(&self) -> ::bok::Version {
|
||||||
|
self.version.clone()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let base: ::syn::ImplItem = ::syn::parse_quote! {
|
||||||
|
fn base(&self) -> ::core::option::Option<&dyn ::bok::Pkg> {
|
||||||
|
Some(&self.base)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let base_mut: ::syn::ImplItem = ::syn::parse_quote! {
|
||||||
|
fn base_mut(&mut self) -> ::core::option::Option<&mut dyn ::bok::Pkg> {
|
||||||
|
Some(&mut self.base)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let any: ::syn::ImplItem = ::syn::parse_quote! {
|
||||||
|
fn as_any(&self) -> &dyn ::std::any::Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let any_mut: ::syn::ImplItem = ::syn::parse_quote! {
|
||||||
|
fn as_any_mut(&mut self) -> &mut dyn ::std::any::Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let hash: ::syn::ImplItem = ::syn::parse_quote! {
|
||||||
|
fn hash(&self) -> ::bok::Hash {
|
||||||
|
use ::sha3::{Digest};
|
||||||
|
let mut hasher = ::sha3::Sha3_256::new();
|
||||||
|
let mut pkg_vars = String::new();
|
||||||
|
use ::std::fmt::Write;
|
||||||
|
write!(&mut pkg_vars,
|
||||||
|
"{:?}",
|
||||||
|
self,
|
||||||
|
).ok();
|
||||||
|
hasher.update(&pkg_vars);
|
||||||
|
hasher.update(self.to_string());
|
||||||
|
let hash = hasher.finalize();
|
||||||
|
::bok::Hash::Sha3(hash)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let hash_code: ::syn::ImplItem = ::syn::parse_quote! {
|
||||||
|
fn hash_code(&self) -> ::bok::Hash {
|
||||||
|
use ::sha3::{Digest};
|
||||||
|
let mut hasher = ::sha3::Sha3_256::new();
|
||||||
|
hasher.update(self.to_string());
|
||||||
|
let hash = hasher.finalize();
|
||||||
|
::bok::Hash::Sha3(hash)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for mut impl_item in ast.items.iter_mut() {
|
||||||
|
if let ::syn::ImplItem::Fn(ref mut fn_impl) = &mut impl_item {
|
||||||
|
if fn_impl.sig.ident.to_string() == "dependencies_set" {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let fn_with_code: ::syn::ImplItemFn = ::syn::parse_quote! {
|
||||||
|
#[::bok::pkg_fn_to_code]
|
||||||
|
#fn_impl
|
||||||
|
};
|
||||||
|
*fn_impl = fn_with_code;
|
||||||
|
} else {
|
||||||
|
panic!("wtf");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ast.items.push(name);
|
||||||
|
ast.items.push(path);
|
||||||
|
ast.items.push(version);
|
||||||
|
ast.items.push(base);
|
||||||
|
ast.items.push(base_mut);
|
||||||
|
ast.items.push(any);
|
||||||
|
ast.items.push(any_mut);
|
||||||
|
ast.items.push(hash);
|
||||||
|
ast.items.push(hash_code);
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#ast
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn derive_package(input: TokenStream) -> TokenStream {
|
||||||
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
|
let mut input_nobase = input.clone();
|
||||||
|
|
||||||
|
let name = input.ident.clone();
|
||||||
|
let name_builder = quote::format_ident!("{name}Builder");
|
||||||
|
let name_builder2 = name_builder.clone();
|
||||||
|
let name_builder3 = name_builder.clone();
|
||||||
|
let elements = match input.data {
|
||||||
|
::syn::Data::Struct(s) => match s.fields {
|
||||||
|
syn::Fields::Named(n) => n.named,
|
||||||
|
_ => panic!("only named supported"),
|
||||||
|
},
|
||||||
|
_ => panic!("only struct allowed"),
|
||||||
|
};
|
||||||
|
let all_fields = elements.iter().filter_map(|field| {
|
||||||
|
if let Some(id) = field.ident.clone() {
|
||||||
|
if id.to_string() != "base" && id.to_string() != "version" {
|
||||||
|
return Some(&field.ident);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
});
|
||||||
|
let all_fields2 = all_fields.clone();
|
||||||
|
let all_fields3 = all_fields.clone();
|
||||||
|
let all_fields_mut = elements.iter().filter_map(|field| {
|
||||||
|
if let Some(id) = field.ident.clone() {
|
||||||
|
if id.to_string() != "base" && id.to_string() != "version" {
|
||||||
|
return Some(quote::format_ident!("{}_mut", id.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
});
|
||||||
|
let base_type = elements
|
||||||
|
.iter()
|
||||||
|
.find_map(|field| {
|
||||||
|
if let Some(id) = field.ident.clone() {
|
||||||
|
if id.to_string() == "base" {
|
||||||
|
return Some(&field.ty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
})
|
||||||
|
.expect("expected a base type");
|
||||||
|
let all_types = elements.iter().filter_map(|field| {
|
||||||
|
if let Some(id) = field.ident.clone() {
|
||||||
|
if id.to_string() != "base" && id.to_string() != "version" {
|
||||||
|
return Some(&field.ty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
});
|
||||||
|
let all_types2 = all_types.clone();
|
||||||
|
let non_opt_fields = elements.iter().filter_map(|field| {
|
||||||
|
if let Some(id) = field.ident.clone() {
|
||||||
|
if id.to_string() == "base" || id.to_string() == "version" {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match &field.ty {
|
||||||
|
syn::Type::Path(pth) => {
|
||||||
|
let first_path = pth.path.segments.first().unwrap();
|
||||||
|
if first_path.ident == "Option" {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let id = &field.ident;
|
||||||
|
Some(quote! {#id})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t => Some(quote! {#t}),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let opt_fields = elements.iter().filter_map(|field| {
|
||||||
|
if let Some(id) = field.ident.clone() {
|
||||||
|
if id.to_string() == "base" || id.to_string() == "version" {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match &field.ty {
|
||||||
|
syn::Type::Path(pth) => {
|
||||||
|
let first_path = pth.path.segments.first().unwrap();
|
||||||
|
if first_path.ident == "Option" {
|
||||||
|
let id = &field.ident;
|
||||||
|
Some(quote! {#id})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let non_opt_types = elements.iter().filter_map(|field| {
|
||||||
|
if let Some(id) = field.ident.clone() {
|
||||||
|
if id.to_string() == "base" || id.to_string() == "version" {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match &field.ty {
|
||||||
|
syn::Type::Path(pth) => {
|
||||||
|
let first_path = pth.path.segments.first().unwrap();
|
||||||
|
if first_path.ident == "Option" {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let t = &field.ty;
|
||||||
|
Some(quote! {#t})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let opt_types = elements.iter().filter_map(|field| {
|
||||||
|
if let Some(id) = field.ident.clone() {
|
||||||
|
if id.to_string() == "base" || id.to_string() == "version" {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let ::syn::Type::Path(pth) = &field.ty {
|
||||||
|
let first_path = pth.path.segments.first().unwrap();
|
||||||
|
if first_path.ident == "Option" {
|
||||||
|
if let syn::PathArguments::AngleBracketed(
|
||||||
|
syn::AngleBracketedGenericArguments { args, .. },
|
||||||
|
) = &first_path.arguments
|
||||||
|
{
|
||||||
|
if let Some(syn::GenericArgument::Type(syn::Type::Path(
|
||||||
|
p,
|
||||||
|
))) = args.first()
|
||||||
|
{
|
||||||
|
let id = &p.path.segments.first().unwrap().ident;
|
||||||
|
return Some(quote! {#id});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
});
|
||||||
|
let non_opt_fields2 = non_opt_fields.clone();
|
||||||
|
let non_opt_fields3 = non_opt_fields.clone();
|
||||||
|
let non_opt_fields4 = non_opt_fields.clone();
|
||||||
|
let non_opt_fields5 = non_opt_fields.clone();
|
||||||
|
let opt_fields2 = opt_fields.clone();
|
||||||
|
let opt_fields3 = opt_fields.clone();
|
||||||
|
let opt_types2 = opt_types.clone();
|
||||||
|
let non_opt_types2 = non_opt_types.clone();
|
||||||
|
|
||||||
|
{
|
||||||
|
// remove the `base` field from the struct
|
||||||
|
let ::syn::Data::Struct(ref mut ds) = input_nobase.data else {
|
||||||
|
panic!("::bok::package expected a struct");
|
||||||
|
};
|
||||||
|
let ::syn::Fields::Named(ref mut old_nf) = ds.fields else {
|
||||||
|
panic!("::bok::package expected a named field");
|
||||||
|
};
|
||||||
|
let mut new_nf = ::syn::punctuated::Punctuated::<
|
||||||
|
::syn::Field,
|
||||||
|
::syn::token::Comma,
|
||||||
|
>::new();
|
||||||
|
for f in old_nf.named.iter() {
|
||||||
|
if let Some(ref id) = f.ident {
|
||||||
|
if id.to_string() == "base" {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
new_nf.push(f.clone());
|
||||||
|
}
|
||||||
|
old_nf.named = new_nf;
|
||||||
|
}
|
||||||
|
let pkg_struct = quote! {
|
||||||
|
#input_nobase
|
||||||
|
};
|
||||||
|
let mut pkg_struct_str = String::new();
|
||||||
|
{
|
||||||
|
use ::core::fmt::Write;
|
||||||
|
write!(&mut pkg_struct_str, "{}", pkg_struct)
|
||||||
|
.expect("can't write package struct into string");
|
||||||
|
}
|
||||||
|
|
||||||
|
let expanded = quote! {
|
||||||
|
impl ::core::fmt::Display for #name {
|
||||||
|
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>)
|
||||||
|
->::core::fmt::Result
|
||||||
|
{
|
||||||
|
<#name as ::bok::PkgCode>::fmt(self, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl ::bok::PkgCode for #name {
|
||||||
|
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>)
|
||||||
|
->::core::fmt::Result
|
||||||
|
{
|
||||||
|
// FIXME: this sounds convoluted.
|
||||||
|
// first we turn everything into a string, then we
|
||||||
|
// parse the string into tokenstream again to format it
|
||||||
|
// reason: the `prettyplease` works on `::syn::File`
|
||||||
|
// and I can't find an easy translation
|
||||||
|
// `TokenStream2 -> TokenStream`
|
||||||
|
use ::bok::Pkg;
|
||||||
|
|
||||||
|
// don't add a method if it is just the base Trait implementation
|
||||||
|
fn maybe_add_fn(out: &mut String, name: &str, fn_str: &str) -> ::core::fmt::Result {
|
||||||
|
|
||||||
|
let str_base = "self . base () . unwrap () . ".to_owned() + name + " ()";
|
||||||
|
|
||||||
|
if fn_str.trim() != str_base {
|
||||||
|
use ::core::fmt::Write;
|
||||||
|
|
||||||
|
write!(out,
|
||||||
|
"\
|
||||||
|
fn {}(&self) -> Result<(), ()> {{\n\
|
||||||
|
{}\n\
|
||||||
|
}}\n",
|
||||||
|
name,
|
||||||
|
fn_str,
|
||||||
|
)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
::core::fmt::Result::Ok(())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
use ::core::fmt::Write;
|
||||||
|
|
||||||
|
let pkg_empty = ::bok::PkgEmpty::default();
|
||||||
|
let mut pkg_string = String::new();
|
||||||
|
write!(&mut pkg_string,
|
||||||
|
"#[::bok::package({})]\n\
|
||||||
|
{}",
|
||||||
|
::std::stringify!(#base_type),
|
||||||
|
#pkg_struct_str)?;
|
||||||
|
write!(&mut pkg_string,
|
||||||
|
"#[::bok::impl_package]\n\
|
||||||
|
impl ::bok::Pkg for {} {{\n",
|
||||||
|
::std::stringify!(#name)
|
||||||
|
)?;
|
||||||
|
maybe_add_fn(&mut pkg_string, "prepare", &self.prepare_code().to_string())?;
|
||||||
|
maybe_add_fn(&mut pkg_string, "configure", &self.configure_code().to_string())?;
|
||||||
|
maybe_add_fn(&mut pkg_string, "build", &self.build_code().to_string())?;
|
||||||
|
maybe_add_fn(&mut pkg_string, "check", &self.check_code().to_string())?;
|
||||||
|
maybe_add_fn(&mut pkg_string, "install", &self.install_code().to_string())?;
|
||||||
|
write!(&mut pkg_string, "}}\n")?;
|
||||||
|
let re_parsed = ::syn::parse_file(&pkg_string).unwrap();
|
||||||
|
let formatted = prettyplease::unparse(&re_parsed);
|
||||||
|
f.write_str(&formatted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl #name {
|
||||||
|
pub fn as_pkg(&self) -> &dyn ::bok::Pkg {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn as_pkg_mut(&mut self) -> &mut dyn ::bok::Pkg {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn builder() -> #name_builder {
|
||||||
|
#name_builder::default()
|
||||||
|
}
|
||||||
|
#(pub fn #all_fields2(&self) -> &#all_types {
|
||||||
|
&self.#all_fields2
|
||||||
|
})*
|
||||||
|
#(pub fn #all_fields_mut(&mut self) -> &#all_types2 {
|
||||||
|
&mut self.#all_fields3
|
||||||
|
})*
|
||||||
|
}
|
||||||
|
#[derive(::std::default::Default, ::std::fmt::Debug)]
|
||||||
|
pub struct #name_builder {
|
||||||
|
#(#non_opt_fields: ::std::option::Option<#non_opt_types>,)*
|
||||||
|
#(#opt_fields: ::std::option::Option<#opt_types>,)*
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
impl ::std::ops::Deref for #name_builder {
|
||||||
|
type Target = #base_type;
|
||||||
|
fn deref(&self) -> &#base_type {
|
||||||
|
&self.base
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
impl ::bok::PkgBuilder for #name_builder2 {
|
||||||
|
fn name(&self) -> ::bok::PkgName {
|
||||||
|
use ::bok::Pkg;
|
||||||
|
#name::default().name()
|
||||||
|
}
|
||||||
|
fn path(&self) -> ::bok::Path<::bok::PkgName> {
|
||||||
|
use ::bok::Pkg;
|
||||||
|
#name::default().path()
|
||||||
|
}
|
||||||
|
fn version(&self) -> ::bok::Version {
|
||||||
|
use ::bok::Pkg;
|
||||||
|
#name::default().version()
|
||||||
|
}
|
||||||
|
fn default_unused(&mut self) -> &mut dyn ::bok::PkgBuilder {
|
||||||
|
let def = #name::default();
|
||||||
|
#(if self.#non_opt_fields5.is_none() {
|
||||||
|
self.#non_opt_fields5 = Some(def.#non_opt_fields5);
|
||||||
|
})*
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
fn build(&mut self) -> Result<Box<dyn ::bok::Pkg>, ::std::boxed::Box<dyn ::std::error::Error>> {
|
||||||
|
#(if self.#non_opt_fields2.is_none() {
|
||||||
|
return ::std::result::Result::Err("unset field".into());
|
||||||
|
})*
|
||||||
|
Ok(
|
||||||
|
Box::new(#name {
|
||||||
|
// FIXME: user must be able to override. Trait?
|
||||||
|
base: #base_type::default(),
|
||||||
|
version: #name::default().version,
|
||||||
|
#(#non_opt_fields3 : self.#non_opt_fields3.clone().unwrap(),)*
|
||||||
|
#(#opt_fields2 : self.#opt_fields2.clone(),)*
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl #name_builder3 {
|
||||||
|
pub fn as_any(&self) -> &dyn ::std::any::Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn as_any_mut(&mut self) -> &mut dyn ::std::any::Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn as_builder(&self) -> &dyn ::bok::PkgBuilder {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn as_builder_mut(&mut self) -> &mut dyn ::bok::PkgBuilder {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
#(pub fn #non_opt_fields4 (&mut self, val : #non_opt_types2) -> &mut Self {
|
||||||
|
if self.#non_opt_fields4 != None &&
|
||||||
|
self.#non_opt_fields4 != Some(val) {
|
||||||
|
panic!("Package \"{}\": mandatory attribute set multiple times: \"{}\"",
|
||||||
|
::std::stringify!(#name),
|
||||||
|
::std::stringify!(#non_opt_fields4));
|
||||||
|
}
|
||||||
|
self.#non_opt_fields4 = Some(val);
|
||||||
|
self
|
||||||
|
})*
|
||||||
|
#(pub fn #opt_fields3 (&mut self, val : #opt_types2) -> &mut Self {
|
||||||
|
if self.#opt_fields3 != None &&
|
||||||
|
self.#opt_fields3 != Some(val) {
|
||||||
|
panic!("Package \"{}\": optional attribute set multiple times: \"{}\"",
|
||||||
|
::std::stringify!(#name),
|
||||||
|
::std::stringify!(#opt_fields3));
|
||||||
|
}
|
||||||
|
self.#opt_fields3 = Some(val);
|
||||||
|
self
|
||||||
|
})*
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TokenStream::from(expanded)
|
||||||
|
}
|
87
bok-macro/src/repos.rs
Normal file
87
bok-macro/src/repos.rs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Luca Fulchir <luca.fulchir@runesauth.com>
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 with LLVM exception (the
|
||||||
|
* "License"); you may not use this file except in compliance with the
|
||||||
|
* License. You may obtain a copy of the License and of the exception at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* https://spdx.org/licenses/LLVM-exception.html
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use ::proc_macro::TokenStream;
|
||||||
|
use ::quote::quote;
|
||||||
|
use ::syn::{parse_macro_input, DeriveInput, Fields, ItemStruct};
|
||||||
|
|
||||||
|
pub(crate) fn repository(
|
||||||
|
attrs: TokenStream,
|
||||||
|
input: TokenStream,
|
||||||
|
) -> TokenStream {
|
||||||
|
let base = parse_macro_input!(attrs as ItemStruct);
|
||||||
|
let local = parse_macro_input!(input as ItemStruct);
|
||||||
|
|
||||||
|
let Fields::Named(local_fields) = local.fields else {
|
||||||
|
use ::syn::spanned::Spanned;
|
||||||
|
return ::syn::Error::new(
|
||||||
|
local.fields.span(),
|
||||||
|
"unnamed fields are not supported",
|
||||||
|
)
|
||||||
|
.to_compile_error()
|
||||||
|
.into();
|
||||||
|
};
|
||||||
|
|
||||||
|
let Fields::Named(base_fields) = base.fields else {
|
||||||
|
use ::syn::spanned::Spanned;
|
||||||
|
return ::syn::Error::new(
|
||||||
|
base.fields.span(),
|
||||||
|
"unnamed fields are not supported",
|
||||||
|
)
|
||||||
|
.to_compile_error()
|
||||||
|
.into();
|
||||||
|
};
|
||||||
|
let local_fields_it = local_fields.named.iter();
|
||||||
|
let base_fields_extra = base_fields.named.iter();
|
||||||
|
let attrs = local.attrs;
|
||||||
|
let generics = local.generics;
|
||||||
|
let ident = local.ident;
|
||||||
|
let vis = local.vis;
|
||||||
|
quote! {
|
||||||
|
#(#attrs)
|
||||||
|
*
|
||||||
|
#[::macro_magic::export_tokens]
|
||||||
|
#[derive(::bok::Repository, Debug)]
|
||||||
|
#vis struct #ident<#generics> {
|
||||||
|
#(#base_fields_extra),
|
||||||
|
*
|
||||||
|
,
|
||||||
|
#(#local_fields_it),
|
||||||
|
*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn derive_repository(input: TokenStream) -> TokenStream {
|
||||||
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
|
|
||||||
|
let name = input.ident.clone();
|
||||||
|
|
||||||
|
let expanded = quote! {
|
||||||
|
impl ::bok::Repository for #name {
|
||||||
|
fn name(&self) -> ::bok::RepoName {
|
||||||
|
(module_path!().to_owned() + "::" + ::std::stringify!(#name)).as_str().into()
|
||||||
|
}
|
||||||
|
fn path(&self) -> ::bok::Path<::bok::RepoName> {
|
||||||
|
(module_path!().to_owned() + "::" + ::std::stringify!(#name)).as_str().into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TokenStream::from(expanded)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user