From 2c3fd8159e7adf5c3f723ce435093f9f96bc9ad5 Mon Sep 17 00:00:00 2001 From: Aiden McClelland Date: Tue, 23 Mar 2021 18:10:22 -0600 Subject: [PATCH] wip: macros --- Cargo.toml | 4 +- patch-db-derive-internals/src/lib.rs | 44 -------- patch-db-derive/src/lib.rs | 13 --- .../Cargo.toml | 7 +- patch-db-macro-internals/src/lib.rs | 105 ++++++++++++++++++ .../Cargo.toml | 9 +- patch-db-macro/src/lib.rs | 13 +++ patch-db/src/lib.rs | 2 +- patch-db/src/test.rs | 19 +++- 9 files changed, 148 insertions(+), 68 deletions(-) delete mode 100644 patch-db-derive-internals/src/lib.rs delete mode 100644 patch-db-derive/src/lib.rs rename {patch-db-derive-internals => patch-db-macro-internals}/Cargo.toml (65%) create mode 100644 patch-db-macro-internals/src/lib.rs rename {patch-db-derive => patch-db-macro}/Cargo.toml (54%) create mode 100644 patch-db-macro/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 7c73749..77f6638 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [workspace] members = [ "patch-db", - "patch-db-derive", - "patch-db-derive-internals", + "patch-db-macro", + "patch-db-macro-internals", "cbor", "json-patch", "json-ptr", diff --git a/patch-db-derive-internals/src/lib.rs b/patch-db-derive-internals/src/lib.rs deleted file mode 100644 index 061d401..0000000 --- a/patch-db-derive-internals/src/lib.rs +++ /dev/null @@ -1,44 +0,0 @@ -use proc_macro2::TokenStream; -use quote::quote; - -pub fn build_model(input: &syn::DeriveInput) -> TokenStream { - match &input.data { - syn::Data::Struct(struct_ast) => build_model_struct(input, struct_ast), - syn::Data::Enum(enum_ast) => build_model_enum(enum_ast), - syn::Data::Union(_) => panic!("Unions are not supported"), - } -} - -fn build_model_struct(input: &syn::DeriveInput, ast: &syn::DataStruct) -> TokenStream { - let model_name = syn::Ident::new( - &format!("{}Model", input.ident), - proc_macro2::Span::call_site(), - ); - let base_name = &input.ident; - let model_vis = &input.vis; - quote! { - #model_vis struct #model_name { - data: Option>, - lock_type: patch_db::LockType, - ptr: json_ptr::JsonPointer, - tx: Tx, - } - impl #model_name { - pub fn get(&mut self, lock: patch_db::LockType) -> Result<&#base_name, patch_db::Error> { - if let Some(data) = self.data.as_ref() { - match (self.lock_type, lock) { - (patch_db::LockType::None, patch_db::LockType::Read) => Ok(data), - - } - } else { - self.data = Some(Box::new(self.tx.get(&self.ptr, lock).await?)); - Ok(self.data.as_ref().unwrap()) - } - } - } - } -} - -fn build_model_enum(ast: &syn::DataEnum) -> TokenStream { - todo!() -} diff --git a/patch-db-derive/src/lib.rs b/patch-db-derive/src/lib.rs deleted file mode 100644 index be328bb..0000000 --- a/patch-db-derive/src/lib.rs +++ /dev/null @@ -1,13 +0,0 @@ -extern crate proc_macro; - -use proc_macro::TokenStream; - -#[proc_macro_derive(Model, attributes(serde))] -pub fn model_derive(input: TokenStream) -> TokenStream { - // Construct a representation of Rust code as a syntax tree - // that we can manipulate - let ast = syn::parse(input).unwrap(); - - // Build the trait implementation - patch_db_derive_internals::build_model(&ast).into() -} diff --git a/patch-db-derive-internals/Cargo.toml b/patch-db-macro-internals/Cargo.toml similarity index 65% rename from patch-db-derive-internals/Cargo.toml rename to patch-db-macro-internals/Cargo.toml index 0c58efc..a81e60c 100644 --- a/patch-db-derive-internals/Cargo.toml +++ b/patch-db-macro-internals/Cargo.toml @@ -1,11 +1,11 @@ [package] -name = "patch-db-derive-internals" +name = "patch-db-macro-internals" version = "0.1.0" authors = ["Aiden McClelland "] edition = "2018" -description = "internals for derive macros for defining typed patch dbs" +description = "internals for macros for defining typed patch dbs" license = "MIT" -repository = "https://github.com/dr-bonez/patch-db" +repository = "https://github.com/Start9Labs/patch-db" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -13,3 +13,4 @@ repository = "https://github.com/dr-bonez/patch-db" syn = { version = "1.0.5", features = ["full", "extra-traits"] } quote = "1.0.1" proc-macro2 = "1.0.1" +heck = "0.3.2" diff --git a/patch-db-macro-internals/src/lib.rs b/patch-db-macro-internals/src/lib.rs new file mode 100644 index 0000000..fa44b5b --- /dev/null +++ b/patch-db-macro-internals/src/lib.rs @@ -0,0 +1,105 @@ +use proc_macro2::TokenStream; +use quote::quote; +use syn::{ + AttributeArgs, Fields, Ident, Item, ItemEnum, ItemStruct, Lit, LitStr, Meta, MetaNameValue, + NestedMeta, Type, +}; + +pub fn build_model(attr: &AttributeArgs, item: &Item) -> TokenStream { + let mut model_name = None; + for arg in attr { + match arg { + NestedMeta::Meta(Meta::NameValue(MetaNameValue { + path, + lit: Lit::Str(s), + .. + })) if path.is_ident("name") => model_name = Some(s.parse().unwrap()), + _ => (), + } + } + match item { + Item::Struct(struct_ast) => build_model_struct(struct_ast, model_name), + Item::Enum(enum_ast) => build_model_enum(enum_ast, model_name), + _ => panic!("Models can only be created for Structs and Enums"), + } +} + +fn build_model_struct(ast: &ItemStruct, model_name: Option) -> TokenStream { + let model_name = model_name.unwrap_or_else(|| { + Ident::new( + &format!("{}Model", ast.ident), + proc_macro2::Span::call_site(), + ) + }); + let base_name = &ast.ident; + let model_vis = &ast.vis; + let mut child_fn_name: Vec = Vec::new(); + let mut child_model: Vec = Vec::new(); + let mut child_path: Vec = Vec::new(); + let serde_rename_all = todo!(); + match &ast.fields { + Fields::Named(f) => { + for field in &f.named { + let ident = field.ident.clone().unwrap(); + child_fn_name.push(ident.clone()); + child_model.push(field.ty.clone()); + let serde_rename = todo!(); + match (serde_rename, serde_rename_all) { + (Some(a), _) => child_path.push(a), + (None, Some("lowercase")) => child_path.push(LitStr::new( + &heck::CamelCase::to_camel_case(ident.to_string().as_str()).to_lowercase(), + ident.span(), + )), + (None, Some("UPPERCASE")) => child_path.push(LitStr::new( + &heck::CamelCase::to_camel_case(ident.to_string().as_str()).to_uppercase(), + ident.span(), + )), + (None, Some("PascalCase")) => child_path.push(LitStr::new( + &heck::CamelCase::to_camel_case(ident.to_string().as_str()), + ident.span(), + )), + (None, Some("camelCase")) => child_path.push(LitStr::new( + &heck::MixedCase::to_mixed_case(ident.to_string().as_str()), + ident.span(), + )), + (None, Some("SCREAMING_SNAKE_CASE")) => child_path.push(LitStr::new( + &heck::ShoutySnakeCase::to_shouty_snake_case(ident.to_string().as_str()), + ident.span(), + )), + (None, Some("kebab-case")) => child_path.push(LitStr::new( + &heck::KebabCase::to_kebab_case(ident.to_string().as_str()), + ident.span(), + )), + (None, Some("SCREAMING-KEBAB-CASE")) => child_path.push(LitStr::new( + &heck::ShoutyKebabCase::to_shouty_kebab_case(ident.to_string().as_str()), + ident.span(), + )), + _ => child_path.push(LitStr::new(&ident.to_string(), ident.span())), + } + } + } + Fields::Unnamed(f) => {} + Fields::Unit => (), + } + quote! { + #model_vis struct #model_name(patch_db::Model<#base_name>); + impl core::ops::Deref for #model_name { + type Target = patch_db::Model<#base_name>; + fn deref(&self) -> &Self::Target { + &self.0 + } + } + impl #model_name { + // foreach element, create accessor fn + #( + pub fn #child_fn_name(&self) -> #child_model { + self.0.child(#child_path).into() + } + )* + } + } +} + +fn build_model_enum(ast: &ItemEnum, model_name: Option) -> TokenStream { + todo!() +} diff --git a/patch-db-derive/Cargo.toml b/patch-db-macro/Cargo.toml similarity index 54% rename from patch-db-derive/Cargo.toml rename to patch-db-macro/Cargo.toml index b5577a6..d70b279 100644 --- a/patch-db-derive/Cargo.toml +++ b/patch-db-macro/Cargo.toml @@ -1,16 +1,17 @@ [package] -name = "patch-db-derive" +name = "patch-db-macro" version = "0.1.0" authors = ["Aiden McClelland "] edition = "2018" -description = "derive macros for defining typed patch dbs" +description = "macros for defining typed patch dbs" license = "MIT" -repository = "https://github.com/dr-bonez/patch-db" +repository = "https://github.com/Start9Labs/patch-db" [lib] proc-macro = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -patch-db-derive-internals = { path = "../patch-db-derive-internals" } +patch-db-macro-internals = { path = "../patch-db-macro-internals" } syn = "1.0.62" +proc-macro2 = "1.0.1" diff --git a/patch-db-macro/src/lib.rs b/patch-db-macro/src/lib.rs new file mode 100644 index 0000000..d6d4dbb --- /dev/null +++ b/patch-db-macro/src/lib.rs @@ -0,0 +1,13 @@ +extern crate proc_macro; + +use proc_macro::TokenStream; +use syn::{parse_macro_input, AttributeArgs, Item}; + +#[proc_macro_attribute] +pub fn model(attr_in: TokenStream, item_in: TokenStream) -> TokenStream { + let mut res = proc_macro2::TokenStream::from(item_in.clone()); + let attr = parse_macro_input!(attr_in as AttributeArgs); + let item = parse_macro_input!(item_in as Item); + res.extend(patch_db_macro_internals::build_model(&attr, &item)); + res.into() +} diff --git a/patch-db/src/lib.rs b/patch-db/src/lib.rs index 6e3ad53..5d23d21 100644 --- a/patch-db/src/lib.rs +++ b/patch-db/src/lib.rs @@ -1025,7 +1025,7 @@ where Ok(ModelData(tx.get(&self.ptr, LockType::Read).await?)) } - pub async fn get_mut(&mut self, tx: &mut Tx) -> Result, Error> { + pub async fn get_mut(&self, tx: &mut Tx) -> Result, Error> { self.lock(tx, LockType::Write).await; let original = tx.get_value(&self.ptr, None).await?; let current = serde_json::from_value(original.clone())?; diff --git a/patch-db/src/test.rs b/patch-db/src/test.rs index 79ce572..6261bbc 100644 --- a/patch-db/src/test.rs +++ b/patch-db/src/test.rs @@ -3,7 +3,18 @@ use super::*; #[tokio::test] async fn basic() { let db = PatchDb::open("test.db").await.unwrap(); - db.put(&JsonPointer::<&'static str>::default(), &Sample{a: "test1".to_string(), b: Child{a: "test2".to_string(), b: 4} }).await.unwrap(); + db.put( + &JsonPointer::<&'static str>::default(), + &Sample { + a: "test1".to_string(), + b: Child { + a: "test2".to_string(), + b: 4, + }, + }, + ) + .await + .unwrap(); let ptr: JsonPointer = "/b/b".parse().unwrap(); db.put(&ptr, &"hello").await.unwrap(); let get_res: Value = db.get(&ptr).await.unwrap(); @@ -17,6 +28,12 @@ pub struct Sample { } pub struct SampleModel(Model); +impl core::ops::Deref for SampleModel { + type Target = Model; + fn deref(&self) -> &Self::Target { + &self.0 + } +} #[derive(Debug, serde::Deserialize, serde::Serialize)] pub struct Child {