From 86c3a679ae8e79165c3acd8fd29883400f9fec87 Mon Sep 17 00:00:00 2001 From: DEADFED5 Date: Sat, 9 Sep 2023 01:14:09 -0700 Subject: [PATCH] ! --- .gitignore | 4 ++ Cargo.toml | 6 +++ LICENSE | 7 ++++ README.md | 84 ++++++++++++++++++++++++++++++++++++++ set_field/.gitignore | 4 ++ set_field/Cargo.toml | 14 +++++++ set_field/README.md | 1 + set_field/src/lib.rs | 33 +++++++++++++++ set_field_macro/.gitignore | 4 ++ set_field_macro/Cargo.toml | 18 ++++++++ set_field_macro/README.md | 2 + set_field_macro/src/lib.rs | 83 +++++++++++++++++++++++++++++++++++++ 12 files changed, 260 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 set_field/.gitignore create mode 100644 set_field/Cargo.toml create mode 100644 set_field/README.md create mode 100644 set_field/src/lib.rs create mode 100644 set_field_macro/.gitignore create mode 100644 set_field_macro/Cargo.toml create mode 100644 set_field_macro/README.md create mode 100644 set_field_macro/src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..59c21bd --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/target +/.idea +/.vscode +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..bd5eba7 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[workspace] +members = [ + "set_field", + "set_field_macro", +] +resolver = "2" \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..801ff6c --- /dev/null +++ b/LICENSE @@ -0,0 +1,7 @@ +Copyright 2023 0xDEADFED5 + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..0f930a3 --- /dev/null +++ b/README.md @@ -0,0 +1,84 @@ +## set_field: Rust derive macro for structs + +Set fields on structs by string. + +# SetField trait +```rust +pub trait SetField { + fn set_field(&mut self, field: &str, value: T) -> bool; +} +``` + +# Example + +```rust +use set_field::SetField; + +#[derive(SetField)] +struct Foo { + a: i32, + b: Option, + c: i32, +} +fn test() { + let mut t = Foo { a: 777, b: None, c: 0 }; + // return true on success: + assert_eq!(t.set_field("a", 888), true); + // return true on success: + assert_eq!(t.set_field("b", Some(true)), true); + assert_eq!(t.a, 888); + assert_eq!(t.b, Some(true)); + // return false on nonexistent field: + assert_eq!(t.set_field("d", 0), false); + // return false on wrong type: + assert_eq!(t.set_field("b", 0), false); + // won't compile: + // assert_eq!(t.set_field("a", 0.0), false); +} +``` + +* set_field returns true on success. +* set_field returns false if field doesn't exist. +* set_field returns false if you attempt to set a field to the wrong type. + +# Expanded Foo struct from above + +The SetField macro expands Foo into this: + +```rust +struct Foo { + a: i32, + b: Option, + c: i32, +} +impl SetField for Foo { + fn set_field(&mut self, field: &str, value: i32) -> bool { + match field { + "a" => { + self.a = value; + true + } + "c" => { + self.c = value; + true + } + _ => false, + } + } +} +impl SetField> for Foo { + fn set_field(&mut self, field: &str, value: Option) -> bool { + match field { + "b" => { + self.b = value; + true + } + _ => false, + } + } +} +``` + +# Dependencies + +[syn](https://crates.io/crates/syn) and [quote](https://crates.io/crates/quote) \ No newline at end of file diff --git a/set_field/.gitignore b/set_field/.gitignore new file mode 100644 index 0000000..59c21bd --- /dev/null +++ b/set_field/.gitignore @@ -0,0 +1,4 @@ +/target +/.idea +/.vscode +Cargo.lock diff --git a/set_field/Cargo.toml b/set_field/Cargo.toml new file mode 100644 index 0000000..b9f1664 --- /dev/null +++ b/set_field/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "set_field" +version = "0.1.0" +edition = "2021" +authors = ["https://github.com/0xDEADFED5"] +license = "MIT" +description = "Set fields on structs by string" +homepage = "https://github.com/0xDEADFED5/set_field" +repository = "https://github.com/0xDEADFED5/set_field" +readme = "README.md" +keywords = ["struct", "derive", "string"] + +[dependencies] +set_field_macro = { version = "0.1.0", path = "../set_field_macro" } diff --git a/set_field/README.md b/set_field/README.md new file mode 100644 index 0000000..a139a52 --- /dev/null +++ b/set_field/README.md @@ -0,0 +1 @@ +# actual folder for set_field crate \ No newline at end of file diff --git a/set_field/src/lib.rs b/set_field/src/lib.rs new file mode 100644 index 0000000..6af192e --- /dev/null +++ b/set_field/src/lib.rs @@ -0,0 +1,33 @@ +//! Set struct fields by string +pub use set_field_macro::SetField; +pub trait SetField { + fn set_field(&mut self, field: &str, value: T) -> bool; +} + +#[cfg(test)] +mod tests { + use crate::SetField; + + #[derive(SetField)] + struct Foo { + a: i32, + b: Option, + c: i32, + } + #[test] + fn test() { + let mut foo = Foo { a: 777, b: None, c: 0 }; + // return true on success: + assert_eq!(foo.set_field("a", 888), true); + // return true on success: + assert_eq!(foo.set_field("b", Some(true)), true); + assert_eq!(foo.a, 888); + assert_eq!(foo.b, Some(true)); + // return false on nonexistent field: + assert_eq!(foo.set_field("d", 0), false); + // return false on wrong type: + assert_eq!(foo.set_field("b", 0), false); + // won't compile: + // assert_eq!(t.set_field("a", 0.0), false); + } +} \ No newline at end of file diff --git a/set_field_macro/.gitignore b/set_field_macro/.gitignore new file mode 100644 index 0000000..59c21bd --- /dev/null +++ b/set_field_macro/.gitignore @@ -0,0 +1,4 @@ +/target +/.idea +/.vscode +Cargo.lock diff --git a/set_field_macro/Cargo.toml b/set_field_macro/Cargo.toml new file mode 100644 index 0000000..958f3e1 --- /dev/null +++ b/set_field_macro/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "set_field_macro" +version = "0.1.0" +edition = "2021" +authors = ["https://github.com/0xDEADFED5"] +license = "MIT" +description = "Derive macro for set_field" +homepage = "https://github.com/0xDEADFED5/set_field" +repository = "https://github.com/0xDEADFED5/set_field" +readme = "README.md" +keywords = ["struct", "derive", "string"] + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "2.0.29", features = ["extra-traits"]} +quote = "1.0.33" \ No newline at end of file diff --git a/set_field_macro/README.md b/set_field_macro/README.md new file mode 100644 index 0000000..ca1e96d --- /dev/null +++ b/set_field_macro/README.md @@ -0,0 +1,2 @@ +# Derive macro for set_field +see [set_field](https://github.com/0xDEADFED5/set_field) \ No newline at end of file diff --git a/set_field_macro/src/lib.rs b/set_field_macro/src/lib.rs new file mode 100644 index 0000000..b77ca05 --- /dev/null +++ b/set_field_macro/src/lib.rs @@ -0,0 +1,83 @@ +//! derive macro for set_field +use proc_macro::TokenStream; +use quote::quote; +use std::collections::BTreeMap; +use syn::{Data, DeriveInput, Fields, Ident, Type}; + +#[proc_macro_derive(SetField)] +/// # Example +/// ``` +/// use set_field::SetField; +/// #[derive(SetField)] +/// struct Foo { a: i32 } +/// fn test() { +/// let mut foo = Foo { a: 777 }; +/// foo.set_field("a", 888); +/// } +// ``` +pub fn set_field_derive(input: TokenStream) -> TokenStream { + let ast = syn::parse(input).unwrap(); + impl_set_field(&ast) +} + +fn impl_set_field(ast: &DeriveInput) -> TokenStream { + let name = &ast.ident; + // indirect BTreeMap> + let mut type_map: BTreeMap> = BTreeMap::new(); + let mut types: Vec = vec![]; + match &ast.data { + Data::Struct(ref data) => match &data.fields { + Fields::Named(fields) => { + for f in &fields.named { + match &f.ident { + Some(i) => { + if !types.contains(&f.ty) { + types.push(f.ty.to_owned()); + } + let index = types.iter().position(|x| x == &f.ty).unwrap(); + match type_map.get_mut(&index) { + Some(v) => { + v.push(i.clone()); + } + None => { + type_map.insert(index, vec![i.clone()]); + } + } + } + None => {} + } + } + } + _ => {} + }, + _ => { + panic!("struct only!"); + } + }; + let mut impls = quote!(); + let mut matches; + for x in type_map { + matches = quote!(); + let ty = &types[x.0]; + for id in x.1 { + let s = id.to_string(); + matches.extend(quote! { + #s => { + self.#id = value; + true + } + }); + } + impls.extend(quote! { + impl SetField<#ty> for #name { + fn set_field(&mut self, field: &str, value: #ty) -> bool { + match field { + #matches + _ => { false } + } + } + } + }); + } + impls.into() +} \ No newline at end of file