more builder

Signed-off-by: Luca Fulchir <luca.fulchir@runesauth.com>
This commit is contained in:
Luca Fulchir 2024-03-17 20:35:28 +01:00
parent 48b4fc6113
commit c83cf30a24
Signed by: luca.fulchir
GPG Key ID: 8F6440603D13A78E
8 changed files with 265 additions and 74 deletions

7
Cargo.lock generated
View File

@ -2,10 +2,17 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "bitflags"
version = "2.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
[[package]] [[package]]
name = "bok" name = "bok"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bitflags",
"bok-macro", "bok-macro",
"paste", "paste",
] ]

View File

@ -19,6 +19,10 @@ use ::proc_macro::TokenStream;
use ::quote::quote; use ::quote::quote;
use ::syn::{parse::Parser, parse_macro_input, DeriveInput}; use ::syn::{parse::Parser, parse_macro_input, DeriveInput};
/// Use as #[repository(MyBaseRepo)]
/// Will setup a `base` field that is the given base repo
///
/// e.g.: `#[repository(::bok::RepositoryEmpty)]`
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn repository(attrs: TokenStream, input: TokenStream) -> TokenStream { pub fn repository(attrs: TokenStream, input: TokenStream) -> TokenStream {
let mut ast = parse_macro_input!(input as DeriveInput); let mut ast = parse_macro_input!(input as DeriveInput);
@ -44,9 +48,9 @@ pub fn repository(attrs: TokenStream, input: TokenStream) -> TokenStream {
_ => panic!("`repository` has to be used with a struct"), _ => panic!("`repository` has to be used with a struct"),
} }
} }
//#[proc_macro_attribute]
//pub fn packages(attrs: TokenStream, input: TokenStream) -> TokenStream {}
/// Use as #[derive(Repository)]
/// 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); let input = parse_macro_input!(input as DeriveInput);
@ -88,12 +92,49 @@ pub fn derive_repository(input: TokenStream) -> TokenStream {
TokenStream::from(expanded) TokenStream::from(expanded)
} }
/// Use as #[package(MyBasePackage)]
/// Will setup a `base` field that is the given base package
///
/// e.g.: `#[package(::bok::PkgEmpty)]`
#[proc_macro_attribute]
pub 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(),
);
}
_ => (),
}
quote! {
#ast
}
.into()
}
_ => panic!("`package` has to be used with a struct"),
}
}
/// #[derive(Package)]
/// adds:
/// * Builder pattern to a package
/// * deref for builder towards `.base`
/// * getters/setters for all package fields
/// * 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); let input = parse_macro_input!(input as DeriveInput);
let name = input.ident.clone(); let name = input.ident.clone();
let name_builder = quote::format_ident!("{name}Builder"); let name_builder = quote::format_ident!("{name}Builder");
let name_builder2 = name_builder.clone();
let elements = match input.data { let elements = match input.data {
::syn::Data::Struct(s) => match s.fields { ::syn::Data::Struct(s) => match s.fields {
syn::Fields::Named(n) => n.named, syn::Fields::Named(n) => n.named,
@ -101,68 +142,125 @@ pub fn derive_package(input: TokenStream) -> TokenStream {
}, },
_ => panic!("only struct allowed"), _ => panic!("only struct allowed"),
}; };
let all_fields = elements.iter().map(|field| &field.ident); let all_fields = elements.iter().filter_map(|field| {
let non_opt_fields = elements.iter().filter_map(|field| match &field.ty { if let Some(id) = field.ident.clone() {
syn::Type::Path(pth) => { if id.to_string() != "base" {
let first_path = pth.path.segments.first().unwrap(); return Some(&field.ident);
if first_path.ident == "Option" {
None
} else {
let id = &field.ident;
Some(quote! {#id})
} }
} }
t => Some(quote! {#t}), None
}); });
let opt_fields = elements.iter().filter_map(|field| match &field.ty { let all_fields2 = all_fields.clone();
syn::Type::Path(pth) => { let all_fields3 = all_fields.clone();
let first_path = pth.path.segments.first().unwrap(); let all_fields_mut = elements.iter().filter_map(|field| {
if first_path.ident == "Option" { if let Some(id) = field.ident.clone() {
let id = &field.ident; if id.to_string() != "base" {
Some(quote! {#id}) return Some(quote::format_ident!("{}_mut", id.to_string()));
} else {
None
} }
} }
_ => None, None
}); });
let non_opt_types = elements.iter().filter_map(|field| match &field.ty { let base_type = elements
syn::Type::Path(pth) => { .iter()
let first_path = pth.path.segments.first().unwrap(); .find_map(|field| {
if first_path.ident == "Option" { if let Some(id) = field.ident.clone() {
None if id.to_string() == "base" {
} else { return Some(&field.ty);
let t = &field.ty;
Some(quote! {#t})
}
}
_ => None,
});
let opt_types = elements.iter().filter_map(|field| match &field.ty {
syn::Type::Path(pth) => {
let first_path = pth.path.segments.first().unwrap();
if first_path.ident == "Option" {
match &first_path.arguments {
syn::PathArguments::AngleBracketed(
syn::AngleBracketedGenericArguments { args, .. },
) => {
if let Some(syn::GenericArgument::Type(
syn::Type::Path(p),
)) = args.first()
{
let id = &p.path.segments.first().unwrap().ident;
Some(quote! {#id})
} else {
None
}
}
_ => None,
} }
} else { }
None 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" {
return Some(&field.ty);
} }
} }
_ => None, 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" {
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" {
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" {
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" {
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_fields2 = non_opt_fields.clone();
let non_opt_fields3 = non_opt_fields.clone(); let non_opt_fields3 = non_opt_fields.clone();
@ -180,18 +278,34 @@ pub fn derive_package(input: TokenStream) -> TokenStream {
#(#all_fields : ::std::option::Option::None,)* #(#all_fields : ::std::option::Option::None,)*
} }
} }
#(pub fn #all_fields2(&self) -> &#all_types {
&self.#all_fields2
})*
#(pub fn #all_fields_mut(&mut self) -> &#all_types2 {
&mut self.#all_fields3
})*
} }
pub struct #name_builder { pub struct #name_builder {
#(#non_opt_fields: ::std::option::Option<#non_opt_types>,)* #(#non_opt_fields: ::std::option::Option<#non_opt_types>,)*
#(#opt_fields: ::std::option::Option<#opt_types>,)* #(#opt_fields: ::std::option::Option<#opt_types>,)*
} }
impl #name_builder { /*
impl ::std::ops::Deref for #name_builder {
type Target = #base_type;
fn deref(&self) -> &#base_type {
&self.base
}
}
*/
impl #name_builder2 {
pub fn build(&mut self) -> Result<#name, ::std::boxed::Box<dyn ::std::error::Error>> { pub fn build(&mut self) -> Result<#name, ::std::boxed::Box<dyn ::std::error::Error>> {
#(if self.#non_opt_fields2.is_none() { #(if self.#non_opt_fields2.is_none() {
return ::std::result::Result::Err("unset field".into()); return ::std::result::Result::Err("unset field".into());
})* })*
Ok( Ok(
#name { #name {
// FIXME: user must be able to override. Trait?
base: #base_type::default(),
#(#non_opt_fields3 : self.#non_opt_fields3.clone().unwrap(),)* #(#non_opt_fields3 : self.#non_opt_fields3.clone().unwrap(),)*
#(#opt_fields2 : self.#opt_fields2.clone(),)* #(#opt_fields2 : self.#opt_fields2.clone(),)*
} }

View File

@ -17,6 +17,7 @@
/// Example package /// Example package
/// Automatically implements `.builder().my_attr(42).build()` pattern /// Automatically implements `.builder().my_attr(42).build()` pattern
#[::bok_macro::package(::bok::PkgEmpty)]
#[derive(::bok_macro::Package)] #[derive(::bok_macro::Package)]
pub struct One { pub struct One {
pub my_attr: u32, pub my_attr: u32,
@ -24,6 +25,9 @@ pub struct One {
impl ::std::default::Default for One { impl ::std::default::Default for One {
fn default() -> Self { fn default() -> Self {
One { my_attr: 1 } One {
base: ::bok::PkgEmpty::default(),
my_attr: 1,
}
} }
} }

View File

@ -17,6 +17,7 @@
/// Example package /// Example package
/// Automatically implements `.builder().my_attr(42).build()` pattern /// Automatically implements `.builder().my_attr(42).build()` pattern
#[::bok_macro::package(::bok::PkgEmpty)]
#[derive(::bok_macro::Package)] #[derive(::bok_macro::Package)]
pub struct Three { pub struct Three {
pub my_attr: u32, pub my_attr: u32,
@ -24,6 +25,9 @@ pub struct Three {
impl ::std::default::Default for Three { impl ::std::default::Default for Three {
fn default() -> Self { fn default() -> Self {
Three { my_attr: 3 } Three {
base: bok::PkgEmpty::default(),
my_attr: 3,
}
} }
} }

View File

@ -17,6 +17,7 @@
/// Example package /// Example package
/// Automatically implements `.builder().my_attr(42).build()` pattern /// Automatically implements `.builder().my_attr(42).build()` pattern
#[::bok_macro::package(::bok::PkgEmpty)]
#[derive(::bok_macro::Package)] #[derive(::bok_macro::Package)]
pub struct Two { pub struct Two {
pub my_attr: u32, pub my_attr: u32,
@ -24,6 +25,9 @@ pub struct Two {
impl ::std::default::Default for Two { impl ::std::default::Default for Two {
fn default() -> Self { fn default() -> Self {
Two { my_attr: 2 } Two {
base: ::bok::PkgEmpty::default(),
my_attr: 2,
}
} }
} }

View File

@ -15,6 +15,7 @@ publish = false
[dependencies] [dependencies]
paste = "1.0" paste = "1.0"
bitflags = "2.4"
[dev-dependencies] [dev-dependencies]
bok-macro = { path="../bok-macro" } bok-macro = { path="../bok-macro" }

View File

@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
use ::std::any::Any; //use ::std::any::Any;
// Package stuff // Package stuff
@ -31,7 +31,7 @@ macro_rules! moduse {
}; };
} }
// re-export `paste` crate for next macros // re-export `paste` crate for next macros
pub use paste; pub use ::paste;
/// Add multipla packages to a repo /// Add multipla packages to a repo
/// e.g.: /// e.g.:
@ -71,6 +71,7 @@ macro_rules! packages {
}; };
} }
/// Marks your struct as a repository
pub trait Repository: Default {} pub trait Repository: Default {}
/// ///
@ -80,13 +81,69 @@ pub trait Repository: Default {}
pub struct RepositoryEmpty {} pub struct RepositoryEmpty {}
impl Repository for RepositoryEmpty {} impl Repository for RepositoryEmpty {}
pub trait Pkg: ::std::default::Default + Any { /// Implement common package operations
pub trait Pkg: ::std::default::Default {
/*
fn prepare_pre();
fn prepare();
fn prepare_post();
fn configure_pre();
fn configure();
fn configure_post();
fn build_pre();
fn build();
fn build_post();
fn check_pre();
fn check();
fn check_post();
fn install_pre();
fn install();
fn install_post();
*/
/*
fn build_base(&mut self) {
Self::base::default()
}
*/
/*
fn as_any(&self) -> &dyn Any fn as_any(&self) -> &dyn Any
where where
Self: Sized, Self: Sized,
{ {
self self
} }
*/
}
#[derive(Default)]
pub struct PkgEmpty {}
::bitflags::bitflags! {
/// In each package, what we can build and how
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Build: u64 {
/// only the binary
const Binary = 0b1;
/// only the library
const Library = 0b10;
/// only the development headers
const Development = 0b100;
/// only the documentation
const Documentation = 0b1000;
/// Run checks during build
const Checks = 0b10000;
/// Strip binaries after build
const Strip = 0b100000;
/// Build in release mode
const Release = 0b1000000;
/// Build in debug mode
const Debug = 0b10000000;
/// Build with profiling
const Profile = 0b10000000;
/// Build optimised for size
const MinSize = 0b100000000;
}
} }
// Conf stuff // Conf stuff

View File

@ -5,11 +5,11 @@
"systems": "systems" "systems": "systems"
}, },
"locked": { "locked": {
"lastModified": 1705309234, "lastModified": 1710146030,
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide", "owner": "numtide",
"repo": "flake-utils", "repo": "flake-utils",
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -38,11 +38,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1708702655, "lastModified": 1710420202,
"narHash": "sha256-qxT5jSLhelfLhQ07+AUxSTm1VnVH+hQxDkQSZ/m/Smo=", "narHash": "sha256-MvFKESbq4rUWuaf2RKPNYENaSZEw/jaCLo2gU6oREcM=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "c5101e457206dd437330d283d6626944e28794b3", "rev": "878ef7d9721bee9f81f8a80819f9211ad1f993da",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -54,11 +54,11 @@
}, },
"nixpkgs-unstable": { "nixpkgs-unstable": {
"locked": { "locked": {
"lastModified": 1708655239, "lastModified": 1710451336,
"narHash": "sha256-ZrP/yACUvDB+zbqYJsln4iwotbH6CTZiTkANJ0AgDv4=", "narHash": "sha256-pP86Pcfu3BrAvRO7R64x7hs+GaQrjFes+mEPowCfkxY=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "cbc4211f0afffe6dfd2478a62615dd5175a13f9a", "rev": "d691274a972b3165335d261cc4671335f5c67de9",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -98,11 +98,11 @@
"nixpkgs": "nixpkgs_2" "nixpkgs": "nixpkgs_2"
}, },
"locked": { "locked": {
"lastModified": 1708827164, "lastModified": 1710555016,
"narHash": "sha256-oBNS6pO04Y6gZBLThP3JDDgviex0+WTXz3bVBenyzms=", "narHash": "sha256-Lbdq3/TH4VrrR7A6FxIYwu5tFOcprYh8Q49Nc9s/i6c=",
"owner": "oxalica", "owner": "oxalica",
"repo": "rust-overlay", "repo": "rust-overlay",
"rev": "e0626adabd5ea461f80b1b11390da2a6575adb30", "rev": "42baa9e2e4713572d7481f917243b07dffdf54b8",
"type": "github" "type": "github"
}, },
"original": { "original": {