diff --git a/Cargo.lock b/Cargo.lock index ffc6abb..c324ef2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,9 +6,65 @@ version = 3 name = "bok" version = "0.1.0" dependencies = [ - "bokmacro", + "bok-macro", + "paste", ] [[package]] -name = "bokmacro" +name = "bok-macro" version = "0.1.0" +dependencies = [ + "bok", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bok-utils" +version = "0.1.0" +dependencies = [ + "bok", + "bok-macro", +] + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" diff --git a/Cargo.toml b/Cargo.toml index 8262a6b..6bf28b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,5 +3,6 @@ resolver = "2" members = [ "bok", - "bokmacro", + "bok-macro", + "bok-utils", ] diff --git a/bok-macro/Cargo.toml b/bok-macro/Cargo.toml new file mode 100644 index 0000000..f06026f --- /dev/null +++ b/bok-macro/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "bok-macro" +version = "0.1.0" +edition = "2021" +authors = ["Luca Fulchir "] +homepage = "https://git.runesauth.com/RunesAuth/bok" +repository = "https://git.runesauth.com/RunesAuth/bok" +license = "Apache-2.0 WITH LLVM-exception" +keywords = [ "bok" ] +categories = [ "config" ] + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "2.0" } +quote = { version = "1.0" } +bok = { path = "../bok" } +proc-macro2 = "1.0" diff --git a/bok-macro/src/lib.rs b/bok-macro/src/lib.rs new file mode 100644 index 0000000..5ce9640 --- /dev/null +++ b/bok-macro/src/lib.rs @@ -0,0 +1,212 @@ +/* + * Copyright 2024 Luca Fulchir + * + * 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}; + +#[proc_macro_attribute] +pub fn repository(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!("`repository` has to be used with a struct"), + } +} +//#[proc_macro_attribute] +//pub fn packages(attrs: TokenStream, input: TokenStream) -> TokenStream {} + +#[proc_macro_derive(Repository)] +pub fn derive_repository(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let name = input.ident.clone(); + let elements = match input.data { + ::syn::Data::Struct(s) => match s.fields { + syn::Fields::Named(n) => n.named, + _ => panic!("only named supported"), + }, + _ => panic!("`Repository` has to be used on a struct"), + }; + let base_type = elements + .iter() + .find_map(|field| { + if let Some(name) = &field.ident { + if name != "base" { + None + } else { + let t = &field.ty; + Some(quote! {#t}) + } + } else { + None + } + }) + .expect("can't find base value. Add #[repository] to your struct"); + + let expanded = quote! { + impl ::bok::Repository for #name {} + impl ::std::ops::Deref for #name { + type Target = #base_type; + fn deref(&self) -> &#base_type { + &self.base + } + } + }; + + TokenStream::from(expanded) +} + +#[proc_macro_derive(Package)] +pub fn derive_package(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let name = input.ident.clone(); + let name_builder = quote::format_ident!("{name}Builder"); + 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().map(|field| &field.ident); + let non_opt_fields = 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" { + None + } else { + let id = &field.ident; + Some(quote! {#id}) + } + } + t => Some(quote! {#t}), + }); + let opt_fields = 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" { + let id = &field.ident; + Some(quote! {#id}) + } else { + None + } + } + _ => None, + }); + let non_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" { + None + } else { + 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, + }); + 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 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(); + + let expanded = quote! { + impl ::bok::Pkg for #name {} + impl #name { + pub fn builder() -> #name_builder { + #name_builder { + #(#all_fields : ::std::option::Option::None,)* + } + } + } + pub struct #name_builder { + #(#non_opt_fields: ::std::option::Option<#non_opt_types>,)* + #(#opt_fields: ::std::option::Option<#opt_types>,)* + } + impl #name_builder { + pub fn build(&mut self) -> Result<#name, ::std::boxed::Box> { + #(if self.#non_opt_fields2.is_none() { + return ::std::result::Result::Err("unset field".into()); + })* + Ok( + #name { + #(#non_opt_fields3 : self.#non_opt_fields3.clone().unwrap(),)* + #(#opt_fields2 : self.#opt_fields2.clone(),)* + } + ) + } + #(pub fn #non_opt_fields4 (&mut self, val : #non_opt_types2) -> &mut Self { + self.#non_opt_fields4 = Some(val); + self + })* + #(pub fn #opt_fields3 (&mut self, val : #opt_types2) -> &mut Self { + self.#opt_fields3 = Some(val); + self + })* + } + }; + + TokenStream::from(expanded) +} diff --git a/bok-utils/Cargo.toml b/bok-utils/Cargo.toml new file mode 100644 index 0000000..d3d40a3 --- /dev/null +++ b/bok-utils/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "bok-utils" +version = "0.1.0" +edition = "2021" +authors = ["Luca Fulchir "] +homepage = "https://git.runesauth.com/RunesAuth/bok" +repository = "https://git.runesauth.com/RunesAuth/bok" +license = "Apache-2.0 WITH LLVM-exception" +keywords = [ "bok" ] +categories = [ "config" ] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bok = { path = "../bok" } +bok-macro = { path = "../bok-macro" } diff --git a/bok/src/conf/mod.rs b/bok-utils/src/conf/mod.rs similarity index 84% rename from bok/src/conf/mod.rs rename to bok-utils/src/conf/mod.rs index 0f34141..8d8c288 100644 --- a/bok/src/conf/mod.rs +++ b/bok-utils/src/conf/mod.rs @@ -14,12 +14,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* -use crate::libt; -static CONF: libt::Conf = libt::Conf {}; + +static CONF: ::bok::Conf = ::bok::Conf {}; mod conf { - use crate::libt; - static CONF: libt::Conf = libt::Conf {}; + static CONF: ::bok::Conf = ::bok::Conf {}; } -*/ diff --git a/bok/src/main.rs b/bok-utils/src/main.rs similarity index 99% rename from bok/src/main.rs rename to bok-utils/src/main.rs index 5106b10..9b79e7f 100644 --- a/bok/src/main.rs +++ b/bok-utils/src/main.rs @@ -16,7 +16,6 @@ */ mod conf; -mod libt; mod pkgs; fn main() { diff --git a/bok-utils/src/pkgs/mod.rs b/bok-utils/src/pkgs/mod.rs new file mode 100644 index 0000000..bacfb8f --- /dev/null +++ b/bok-utils/src/pkgs/mod.rs @@ -0,0 +1,68 @@ +/* + * Copyright 2024 Luca Fulchir + * + * 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. + */ + +//! +//! Example of two package repositories, where one +//! extends ancd changes another +//! + +// Export multiple packages in this module +bok::moduse! { + one, + two, + three, +} + +/// +/// Base repository with some packages +/// +#[::bok_macro::repository(::bok::RepositoryEmpty)] +#[derive(::bok_macro::Repository, ::std::default::Default)] +pub struct Pkgs1 {} + +// Add some packages to the repository +// all packages will have `::default()` values +bok::repo_packages! { + Pkgs1 { + One, + Two, + } +} + +/// +/// This repository extends and changes Pkgs1 +/// +#[::bok_macro::repository(Pkgs1)] +#[derive(::bok_macro::Repository, ::std::default::Default)] +pub struct Pkgs2 {} + +// add a third package with `::default()` values +bok::repo_packages! { + Pkgs1 { + Three, + } +} + +impl Pkgs2 { + /// override the package `two` options from the base repostiory (Pkgs1) + pub fn two(&self) -> Two { + Two::builder() + .my_attr(42) + .build() + .expect("Can't build package Two") + } +} diff --git a/bok/src/pkgs/one.rs b/bok-utils/src/pkgs/one.rs similarity index 80% rename from bok/src/pkgs/one.rs rename to bok-utils/src/pkgs/one.rs index bc5a928..673bd6b 100644 --- a/bok/src/pkgs/one.rs +++ b/bok-utils/src/pkgs/one.rs @@ -15,17 +15,15 @@ * limitations under the License. */ -use crate::libt; -//use ::std::boxed::Box; - +/// Example package +/// Automatically implements `.builder().my_attr(42).build()` pattern +#[derive(::bok_macro::Package)] pub struct One { pub my_attr: u32, } -impl libt::Pkg for One {} - -impl One { - pub fn new() -> Self { +impl ::std::default::Default for One { + fn default() -> Self { One { my_attr: 1 } } } diff --git a/bok/src/pkgs/three.rs b/bok-utils/src/pkgs/three.rs similarity index 80% rename from bok/src/pkgs/three.rs rename to bok-utils/src/pkgs/three.rs index 075bde8..55a0aa0 100644 --- a/bok/src/pkgs/three.rs +++ b/bok-utils/src/pkgs/three.rs @@ -15,16 +15,15 @@ * limitations under the License. */ -use crate::libt; - +/// Example package +/// Automatically implements `.builder().my_attr(42).build()` pattern +#[derive(::bok_macro::Package)] pub struct Three { pub my_attr: u32, } -impl libt::Pkg for Three {} - -impl Three { - pub fn new() -> Self { +impl ::std::default::Default for Three { + fn default() -> Self { Three { my_attr: 3 } } } diff --git a/bok/src/pkgs/two.rs b/bok-utils/src/pkgs/two.rs similarity index 80% rename from bok/src/pkgs/two.rs rename to bok-utils/src/pkgs/two.rs index 4f46cc2..c256bdb 100644 --- a/bok/src/pkgs/two.rs +++ b/bok-utils/src/pkgs/two.rs @@ -15,16 +15,15 @@ * limitations under the License. */ -use crate::libt; - +/// Example package +/// Automatically implements `.builder().my_attr(42).build()` pattern +#[derive(::bok_macro::Package)] pub struct Two { pub my_attr: u32, } -impl libt::Pkg for Two {} - -impl Two { - pub fn new() -> Self { +impl ::std::default::Default for Two { + fn default() -> Self { Two { my_attr: 2 } } } diff --git a/bok/Cargo.toml b/bok/Cargo.toml index c424ad4..b22f619 100644 --- a/bok/Cargo.toml +++ b/bok/Cargo.toml @@ -14,4 +14,7 @@ publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bokmacro = { path="../bokmacro" } +paste = "1.0" + +[dev-dependencies] +bok-macro = { path="../bok-macro" } diff --git a/bok/src/libt/mod.rs b/bok/src/lib.rs similarity index 58% rename from bok/src/libt/mod.rs rename to bok/src/lib.rs index 341da78..dde0ca5 100644 --- a/bok/src/libt/mod.rs +++ b/bok/src/lib.rs @@ -19,9 +19,10 @@ use ::std::any::Any; // Package stuff +/// Get a package from its module and export it in the current module #[macro_export] macro_rules! moduse { - ($($name:ident)*) => { + ($($name:ident,)*) => { $( mod $name; #[allow(unused_imports)] @@ -29,18 +30,57 @@ macro_rules! moduse { )* }; } -pub use moduse; +// re-export `paste` crate for next macros +pub use paste; -pub trait PkgsList {} +/// Add multipla packages to a repo +/// e.g.: +/// bok::repo_packages! { +/// MyRepo { +/// Mypkg1, +/// Mypkg2, +/// } +/// } +#[macro_export] +macro_rules! repo_packages { + ( $repo:ident { $($name:ident,)*} ) => { + impl $repo { + $crate::packages!{$($name ,)*} + } + }; +} + +/// Add multipla packages to a repo Impl +/// e.g.: +/// impl MyRepo { +/// bok::packages! { +/// Mypkg1, +/// Mypkg2, +/// } +/// } +#[macro_export] +macro_rules! packages { + ($($name:ident,)*) => { + $crate::paste::paste! { + $( + pub fn [<$name:snake>] (&self) -> $name { + $name::default() + } + )* + } + }; +} + +pub trait Repository: Default {} /// /// This is an empty Package List. Will be available in the main library /// #[derive(::std::default::Default)] -pub struct PkgsEmpty {} -impl PkgsList for PkgsEmpty {} +pub struct RepositoryEmpty {} +impl Repository for RepositoryEmpty {} -pub trait Pkg: Any { +pub trait Pkg: ::std::default::Default + Any { fn as_any(&self) -> &dyn Any where Self: Sized, @@ -50,9 +90,9 @@ pub trait Pkg: Any { } // Conf stuff -/* pub struct Conf {} +/* pub enum Value { /// Set, only once per Set(T), diff --git a/bok/src/pkgs/mod.rs b/bok/src/pkgs/mod.rs deleted file mode 100644 index affdd2b..0000000 --- a/bok/src/pkgs/mod.rs +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2024 Luca Fulchir - * - * 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. - */ - -//! -//! Example of two package repositories, where one -//! extends ancd changes another -//! - -use crate::libt; -//use ::std::{boxed::Box, vec::Vec}; - -crate::libt::moduse! { - one - two - three -} - -/// -/// This is a base Package List -/// -#[derive(::std::default::Default)] -pub struct Pkgs1 { - base: libt::PkgsEmpty, -} -/// eventually PkgsList and deref will be replaced by macros -impl libt::PkgsList for Pkgs1 {} -impl ::std::ops::Deref for Pkgs1 { - type Target = libt::PkgsEmpty; - fn deref(&self) -> &libt::PkgsEmpty { - &self.base - } -} - -impl Pkgs1 { - /// Packages like this can become a macro - pub fn one(&self) -> One { - One::new() - } - /// Packages like this can become a macro - pub fn two(&self) -> Two { - Two::new() - } -} - -/// -/// This is a Package List that extends and changes Pkgs1 -/// -#[derive(::std::default::Default)] -pub struct Pkgs2 { - base: Pkgs1, -} -/// eventually PkgsList and deref will be replaced by macros -impl libt::PkgsList for Pkgs2 {} - -impl ::std::ops::Deref for Pkgs2 { - type Target = Pkgs1; - fn deref(&self) -> &Pkgs1 { - &self.base - } -} - -impl Pkgs2 { - /// example of option override in a package - pub fn two(&self) -> Two { - let mut t = Two::new(); - t.my_attr = 42; - t - } - /// Packages like this can become a macro - pub fn three(&self) -> Three { - Three::new() - } -} diff --git a/bokmacro/Cargo.toml b/bokmacro/Cargo.toml deleted file mode 100644 index cae761d..0000000 --- a/bokmacro/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "bokmacro" -version = "0.1.0" -edition = "2021" - -[lib] -proc-macro = true - -[dependencies] diff --git a/bokmacro/src/lib.rs b/bokmacro/src/lib.rs deleted file mode 100644 index 2c7baea..0000000 --- a/bokmacro/src/lib.rs +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2024 Luca Fulchir - * - * 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. - */ - -/* -macro_rules! moduse { - ($(mod $name:ident;)*) => { - $( - mod $name; - #[allow(unused_imports)] - use $name::*; - )* - }; -} -*/ diff --git a/flake.lock b/flake.lock index 10ca34e..013ad06 100644 --- a/flake.lock +++ b/flake.lock @@ -38,11 +38,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1707978831, - "narHash": "sha256-UblFdWQ2MMZNzD9C/w8+7RjAJ2QIbebbzHUniQ/a44o=", + "lastModified": 1708702655, + "narHash": "sha256-qxT5jSLhelfLhQ07+AUxSTm1VnVH+hQxDkQSZ/m/Smo=", "owner": "nixos", "repo": "nixpkgs", - "rev": "c68a9fc85c2cb3a313be6ff40511635544dde8da", + "rev": "c5101e457206dd437330d283d6626944e28794b3", "type": "github" }, "original": { @@ -54,11 +54,11 @@ }, "nixpkgs-unstable": { "locked": { - "lastModified": 1707956935, - "narHash": "sha256-ZL2TrjVsiFNKOYwYQozpbvQSwvtV/3Me7Zwhmdsfyu4=", + "lastModified": 1708655239, + "narHash": "sha256-ZrP/yACUvDB+zbqYJsln4iwotbH6CTZiTkANJ0AgDv4=", "owner": "nixos", "repo": "nixpkgs", - "rev": "a4d4fe8c5002202493e87ec8dbc91335ff55552c", + "rev": "cbc4211f0afffe6dfd2478a62615dd5175a13f9a", "type": "github" }, "original": { @@ -98,11 +98,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1708049456, - "narHash": "sha256-8qGWZTQPPBhcF5dsl1KSWF+H7RX8C3BZGvqYWKBtLjQ=", + "lastModified": 1708827164, + "narHash": "sha256-oBNS6pO04Y6gZBLThP3JDDgviex0+WTXz3bVBenyzms=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "4ee92bf124fbc4e157cbce1bc2a35499866989fc", + "rev": "e0626adabd5ea461f80b1b11390da2a6575adb30", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 548133d..d341833 100644 --- a/flake.nix +++ b/flake.nix @@ -42,10 +42,11 @@ cargo-watch cargo-flamegraph cargo-license + cargo-expand lld rust-bin.stable.${RUST_VERSION}.default rustfmt - rust-analyzer + pkgs-unstable.rust-analyzer #clang_16 #mold # fenrir deps