From 8fd07321ceea09ce550643985af3695b6775f15b Mon Sep 17 00:00:00 2001 From: ImaMapleTree <59880284+ImaMapleTree@users.noreply.github.com> Date: Wed, 29 May 2024 08:01:49 +0900 Subject: [PATCH 1/3] split tests across multiple files --- tests/struct_base.rs | 42 +++++ tests/struct_field_all.rs | 19 +++ tests/struct_field_default.rs | 29 ++++ tests/struct_field_impl.rs | 29 ++++ tests/struct_field_specific_ctor.rs | 60 +++++++ tests/struct_field_value.rs | 55 +++++++ tests/struct_method_config.rs | 58 +++++++ tests/struct_mixed_method_field.rs | 15 ++ tests/test.rs | 232 ---------------------------- 9 files changed, 307 insertions(+), 232 deletions(-) create mode 100644 tests/struct_base.rs create mode 100644 tests/struct_field_all.rs create mode 100644 tests/struct_field_default.rs create mode 100644 tests/struct_field_impl.rs create mode 100644 tests/struct_field_specific_ctor.rs create mode 100644 tests/struct_field_value.rs create mode 100644 tests/struct_method_config.rs create mode 100644 tests/struct_mixed_method_field.rs delete mode 100644 tests/test.rs diff --git a/tests/struct_base.rs b/tests/struct_base.rs new file mode 100644 index 0000000..1e04ad9 --- /dev/null +++ b/tests/struct_base.rs @@ -0,0 +1,42 @@ +use derive_ctor::ctor; + +#[derive(ctor, Debug, PartialEq)] +pub struct Unit; + +#[test] +fn test_unit_struct() { + let unit = Unit::new(); + assert_eq!(Unit, unit); +} + +#[derive(ctor, Debug, PartialEq)] +pub struct Empty {} + +#[test] +fn test_empty_struct_no_config() { + let empty = Empty::new(); + assert_eq!(Empty { }, empty) +} + +#[derive(ctor, Debug, PartialEq)] +pub struct OneFieldStruct { + value: u32 +} + +#[test] +fn test_struct_with_field() { + let ofs = OneFieldStruct::new(300); + assert_eq!(OneFieldStruct { value: 300 }, ofs) +} + +#[derive(ctor, Debug, PartialEq)] +pub struct ManyFieldStruct { + value1: u32, + value2: bool +} + +#[test] +fn test_struct_with_many_fields() { + let mfs = ManyFieldStruct::new(400, true); + assert_eq!(ManyFieldStruct { value1: 400, value2: true }, mfs); +} \ No newline at end of file diff --git a/tests/struct_field_all.rs b/tests/struct_field_all.rs new file mode 100644 index 0000000..67999ff --- /dev/null +++ b/tests/struct_field_all.rs @@ -0,0 +1,19 @@ +use derive_ctor::ctor; + +#[derive(ctor, Debug, PartialEq)] +struct MixedStruct { + provided1: i16, + provided2: bool, + #[ctor(impl)] + provided3: String, + #[ctor(value("Foo"))] + generated1: &'static str, + #[ctor(default)] + generated2: u32 +} + +#[test] +fn test_struct_with_multiple_generated_fields() { + let multi = MixedStruct::new(41, false, "Test"); + assert_eq!(MixedStruct { provided1: 41, provided2: false, provided3: String::from("Test"), generated1: "Foo", generated2: Default::default() }, multi) +} \ No newline at end of file diff --git a/tests/struct_field_default.rs b/tests/struct_field_default.rs new file mode 100644 index 0000000..abf5bcf --- /dev/null +++ b/tests/struct_field_default.rs @@ -0,0 +1,29 @@ +use derive_ctor::ctor; + +#[derive(ctor, Debug, PartialEq)] +struct StructDefault { + provided: String, + #[ctor(default)] + generated: u32 +} + +#[derive(ctor, Debug, PartialEq)] +struct StructManyDefault { + provided: String, + #[ctor(default)] + generated1: u32, + #[ctor(default)] + generated2: String +} + +#[test] +fn test_struct_with_default_field() { + let test = StructDefault::new(String::from("ABC")); + assert_eq!(StructDefault { provided: String::from("ABC"), generated: Default::default() }, test); +} + +#[test] +fn test_struct_with_multiple_default_fields() { + let test = StructManyDefault::new(String::from("ABC")); + assert_eq!(StructManyDefault { provided: String::from("ABC"), generated1: Default::default(), generated2: Default::default() }, test); +} \ No newline at end of file diff --git a/tests/struct_field_impl.rs b/tests/struct_field_impl.rs new file mode 100644 index 0000000..b298dc0 --- /dev/null +++ b/tests/struct_field_impl.rs @@ -0,0 +1,29 @@ +use derive_ctor::ctor; + +#[derive(ctor, Debug, PartialEq)] +struct StructImpl { + #[ctor(impl)] + provided: String, + other: bool +} + +#[derive(ctor, Debug, PartialEq)] +struct StructManyImpl { + provided: bool, + #[ctor(impl)] + one: String, + #[ctor(impl)] + two: String +} + +#[test] +fn test_struct_with_impl_value() { + let test = StructImpl::new("Foo", false); + assert_eq!(StructImpl { provided: String::from("Foo"), other: false }, test); +} + +#[test] +fn test_struct_with_many_impl() { + let test = StructManyImpl::new(false, "One", "Two"); + assert_eq!(StructManyImpl { provided: false, one: String::from("One"), two: String::from("Two") }, test) +} \ No newline at end of file diff --git a/tests/struct_field_specific_ctor.rs b/tests/struct_field_specific_ctor.rs new file mode 100644 index 0000000..f1093f2 --- /dev/null +++ b/tests/struct_field_specific_ctor.rs @@ -0,0 +1,60 @@ +use derive_ctor::ctor; + +#[derive(ctor, Debug, PartialEq)] +#[ctor(new, new2)] +struct TargetedGenerationStruct { + arg1: u32, + #[ctor(default = [0])] + arg2: u32 +} + +#[test] +fn test_struct_with_targeted_generation() { + let targeted = TargetedGenerationStruct::new(100); + assert_eq!(TargetedGenerationStruct { arg1: 100, arg2: Default::default() }, targeted); + let targeted2 = TargetedGenerationStruct::new2(50, 41); + assert_eq!(TargetedGenerationStruct { arg1: 50, arg2: 41 }, targeted2); +} + +fn test_method_2() -> String { + String::from("FooBar") +} + +#[derive(ctor, Debug, PartialEq)] +#[ctor(new, new2)] +struct TargetedGenerationStruct2 { + #[ctor(value(test_method_2()) = [1])] + arg1: String, + #[ctor(value(33) = 0)] + arg2: u32 +} + +#[test] +fn test_struct_with_multiple_targeted_generations() { + let tgs1 = TargetedGenerationStruct2::new(String::from("one")); + assert_eq!(TargetedGenerationStruct2 { arg1: String::from("one"), arg2: 33 }, tgs1); + + let tgs2 = TargetedGenerationStruct2::new2(95); + assert_eq!(TargetedGenerationStruct2 { arg1: String::from("FooBar"), arg2: 95}, tgs2); +} + +#[derive(ctor, Debug, PartialEq)] +#[ctor(m1, m2, m3)] +struct TestStructWithFieldWithMultipleTargets { + #[ctor(impl)] + arg1: String, + #[ctor(value(5) = [0, 1])] + arg2: u32 +} + +#[test] +fn test_struct_multiple_targeted_generations_single_field() { + let tswfwmt1 = TestStructWithFieldWithMultipleTargets::m1("One"); + assert_eq!(TestStructWithFieldWithMultipleTargets { arg1: String::from("One"), arg2: 5 }, tswfwmt1); + + let tswfwmt1 = TestStructWithFieldWithMultipleTargets::m2("Two"); + assert_eq!(TestStructWithFieldWithMultipleTargets { arg1: String::from("Two"), arg2: 5 }, tswfwmt1); + + let tswfwmt1 = TestStructWithFieldWithMultipleTargets::m3("Three", 77); + assert_eq!(TestStructWithFieldWithMultipleTargets { arg1: String::from("Three"), arg2: 77 }, tswfwmt1); +} \ No newline at end of file diff --git a/tests/struct_field_value.rs b/tests/struct_field_value.rs new file mode 100644 index 0000000..632fdb3 --- /dev/null +++ b/tests/struct_field_value.rs @@ -0,0 +1,55 @@ +use derive_ctor::ctor; + +#[derive(ctor, Debug, PartialEq)] +struct StructValue { + provided: u32, + #[ctor(value(10))] + generated: u32 +} + +#[derive(ctor, Debug, PartialEq)] +struct StructManyValue { + provided: u32, + #[ctor(value(11))] + generated1: u32, + #[ctor(value(false))] + generated2: bool +} + +#[derive(ctor, Debug, PartialEq)] +struct StructComplexValue { + provided: u32, + #[ctor(value(String::from("Foo")))] + generated: String +} + +#[derive(ctor, Debug, PartialEq)] +struct StructReliantValue { + provided: u32, + #[ctor(value(provided.to_string()))] + generated: String +} + +#[test] +fn test_struct_value_field() { + let test = StructValue::new(100); + assert_eq!(StructValue { provided: 100, generated: 10 }, test); +} + +#[test] +fn test_struct_many_value_fields() { + let test = StructManyValue::new(101); + assert_eq!(StructManyValue { provided: 101, generated1: 11, generated2: false }, test); +} + +#[test] +fn test_struct_complex_value_field() { + let test = StructComplexValue::new(102); + assert_eq!(StructComplexValue { provided: 102, generated: String::from("Foo") }, test); +} + +#[test] +fn test_struct_reliant_value_field() { + let test = StructReliantValue::new(103); + assert_eq!(StructReliantValue { provided: 103, generated: 103.to_string() }, test); +} \ No newline at end of file diff --git a/tests/struct_method_config.rs b/tests/struct_method_config.rs new file mode 100644 index 0000000..c431145 --- /dev/null +++ b/tests/struct_method_config.rs @@ -0,0 +1,58 @@ +use derive_ctor::ctor; +#[derive(ctor, Debug, PartialEq)] +#[ctor(init)] +struct Empty2 {} + +#[test] +fn test_empty_struct_config_name() { + let empty = Empty2::init(); + assert_eq!(Empty2 { }, empty) +} + +#[derive(ctor, Debug, PartialEq)] +#[ctor(pub(crate) new)] +struct VisibilityStruct {} + +#[test] +fn test_method_visibility() { + let visibility = VisibilityStruct::new(); + assert_eq!(VisibilityStruct {}, visibility) +} + +#[derive(ctor, Debug, PartialEq)] +#[ctor(m1, m2)] +struct ManyMethodStruct {} + +#[test] +fn test_empty_struct_many_methods() { + let many1 = ManyMethodStruct::m1(); + assert_eq!(ManyMethodStruct {}, many1); + let many2 = ManyMethodStruct::m2(); + assert_eq!(ManyMethodStruct {}, many2); +} + +#[derive(ctor, Debug, PartialEq)] +#[ctor(pub m1, pub(crate) m2, m3)] +struct ManyVisibleMethodStruct {} + +#[test] +fn test_empty_struct_many_methods_with_visibility() { + let m1 = ManyVisibleMethodStruct::m1(); + assert_eq!(ManyVisibleMethodStruct {}, m1); + let m2 = ManyVisibleMethodStruct::m2(); + assert_eq!(ManyVisibleMethodStruct {}, m2); + let m3 = ManyVisibleMethodStruct::m3(); + assert_eq!(ManyVisibleMethodStruct {}, m3); +} + +#[derive(ctor, Debug, PartialEq)] +#[ctor(init)] +struct FieldStructCustomCtor { + value: u32 +} + +#[test] +fn test_field_struct_with_custom_ctor_name() { + let field_struct = FieldStructCustomCtor::init(15); + assert_eq!(FieldStructCustomCtor { value: 15 }, field_struct); +} \ No newline at end of file diff --git a/tests/struct_mixed_method_field.rs b/tests/struct_mixed_method_field.rs new file mode 100644 index 0000000..669bcf3 --- /dev/null +++ b/tests/struct_mixed_method_field.rs @@ -0,0 +1,15 @@ +use derive_ctor::ctor; + +#[derive(ctor, Debug, PartialEq)] +#[ctor(a)] +struct MixedCtorFieldStruct { + provided: u32, + #[ctor(default)] + generated: bool +} + +#[test] +fn test_struct_with_custom_ctor_and_generated_field() { + let test = MixedCtorFieldStruct::a(100); + assert_eq!(MixedCtorFieldStruct { provided: 100, generated: false }, test) +} \ No newline at end of file diff --git a/tests/test.rs b/tests/test.rs deleted file mode 100644 index f14c8aa..0000000 --- a/tests/test.rs +++ /dev/null @@ -1,232 +0,0 @@ -#[macro_use] -extern crate derive_ctor; - -#[derive(ctor, Debug, PartialEq)] -pub struct Empty {} - -#[test] -fn test_empty_struct_no_config() { - let empty = Empty::new(); - assert_eq!(Empty { }, empty) -} - -#[derive(ctor, Debug, PartialEq)] -#[ctor(init)] -pub struct Empty2 {} - -#[test] -fn test_empty_struct_config_name() { - let empty = Empty2::init(); - assert_eq!(Empty2 { }, empty) -} - -#[derive(ctor, Debug, PartialEq)] -pub struct Unit; - -#[test] -fn test_unit_struct() { - let unit = Unit::new(); - assert_eq!(Unit, unit); -} - -#[derive(ctor, Debug, PartialEq)] -#[ctor(pub(crate) new)] -pub struct VisibilityStruct {} - -#[test] -fn test_method_visibility() { - let visibility = VisibilityStruct::new(); - assert_eq!(VisibilityStruct {}, visibility) -} - -#[derive(ctor, Debug, PartialEq)] -#[ctor(m1, m2)] -pub struct ManyMethodStruct {} - -#[test] -fn test_empty_struct_many_methods() { - let many1 = ManyMethodStruct::m1(); - assert_eq!(ManyMethodStruct {}, many1); - let many2 = ManyMethodStruct::m2(); - assert_eq!(ManyMethodStruct {}, many2); -} - -#[derive(ctor, Debug, PartialEq)] -#[ctor(pub m1, pub(crate) m2, m3)] -pub struct ManyVisibleMethodStruct {} - -#[test] -fn test_empty_struct_many_methods_with_visibility() { - let m1 = ManyVisibleMethodStruct::m1(); - assert_eq!(ManyVisibleMethodStruct {}, m1); - let m2 = ManyVisibleMethodStruct::m2(); - assert_eq!(ManyVisibleMethodStruct {}, m2); - let m3 = ManyVisibleMethodStruct::m3(); - assert_eq!(ManyVisibleMethodStruct {}, m3); -} - -#[derive(ctor, Debug, PartialEq)] -pub struct OneFieldStruct { - value: u32 -} - -#[test] -fn test_struct_with_field() { - let ofs = OneFieldStruct::new(300); - assert_eq!(OneFieldStruct { value: 300 }, ofs) -} - -#[derive(ctor, Debug, PartialEq)] -pub struct ManyFieldStruct { - value1: u32, - value2: bool -} - -#[test] -fn test_struct_with_many_fields() { - let mfs = ManyFieldStruct::new(400, true); - assert_eq!(ManyFieldStruct { value1: 400, value2: true }, mfs); -} - -#[derive(ctor, Debug, PartialEq)] -#[ctor(init)] -pub struct FieldStructCustomCtor { - value: u32 -} - -#[test] -fn test_field_struct_with_custom_ctor_name() { - let field_struct = FieldStructCustomCtor::init(15); - assert_eq!(FieldStructCustomCtor { value: 15 }, field_struct); -} - -#[derive(ctor, Debug, PartialEq)] -pub struct DefaultValueStruct { - provided: String, - #[ctor(default)] - generated: u32 -} - -#[test] -fn test_struct_with_default_field() { - let test = DefaultValueStruct::new(String::from("ABC")); - assert_eq!(DefaultValueStruct { provided: String::from("ABC"), generated: Default::default() }, test); -} - -#[derive(ctor, Debug, PartialEq)] -pub struct ValueValueStruct { - provided: u32, - #[ctor(value(10))] - generated: u32 -} - -#[test] -fn test_struct_with_value_field() { - let test = ValueValueStruct::new(100); - assert_eq!(ValueValueStruct { provided: 100, generated: 10 }, test); -} - -fn generation_method() -> Option> { - Some(Some(4123)) -} - -#[derive(ctor, Debug, PartialEq)] -pub struct MethodValueStruct { - provided: bool, - #[ctor(method(generation_method))] - generated: Option> -} - -#[test] -fn test_struct_with_method_field() { - let test = MethodValueStruct::new(false); - assert_eq!(MethodValueStruct { provided: false, generated: Some(Some(4123))}, test) -} - -#[derive(ctor, Debug, PartialEq)] -pub struct ImplValueStruct { - #[ctor(impl)] - provided: String, - other: bool -} - -#[test] -fn test_struct_with_impl_value() { - let test = ImplValueStruct::new("Foo", false); - assert_eq!(ImplValueStruct { provided: String::from("Foo"), other: false }, test); -} - -#[derive(ctor, Debug, PartialEq)] -pub struct MultipleGenerated { - provided1: i16, - provided2: bool, - #[ctor(value("Foo"))] - generated1: &'static str, - #[ctor(default)] - generated2: u32 -} - -#[test] -fn test_struct_with_multiple_generated_fields() { - let multi = MultipleGenerated::new(41, false); - assert_eq!(MultipleGenerated { provided1: 41, provided2: false, generated1: "Foo", generated2: Default::default() }, multi) -} - -#[derive(ctor, Debug, PartialEq)] -#[ctor(new, new2)] -pub struct TargetedGenerationStruct { - arg1: u32, - #[ctor(default = [0])] - arg2: u32 -} - -#[test] -fn test_struct_with_targeted_generation() { - let targeted = TargetedGenerationStruct::new(100); - assert_eq!(TargetedGenerationStruct { arg1: 100, arg2: Default::default() }, targeted); - let targeted2 = TargetedGenerationStruct::new2(50, 41); - assert_eq!(TargetedGenerationStruct { arg1: 50, arg2: 41 }, targeted2); -} - -fn test_method_2() -> String { - String::from("FooBar") -} - -#[derive(ctor, Debug, PartialEq)] -#[ctor(new, new2)] -pub struct TargetedGenerationStruct2 { - #[ctor(method(test_method_2) = [1])] - arg1: String, - #[ctor(value(33) = 0)] - arg2: u32 -} - -#[test] -fn test_struct_with_multiple_targeted_generations() { - let tgs1 = TargetedGenerationStruct2::new(String::from("one")); - assert_eq!(TargetedGenerationStruct2 { arg1: String::from("one"), arg2: 33 }, tgs1); - - let tgs2 = TargetedGenerationStruct2::new2(95); - assert_eq!(TargetedGenerationStruct2 { arg1: String::from("FooBar"), arg2: 95}, tgs2); -} - -#[derive(ctor, Debug, PartialEq)] -#[ctor(m1, m2, m3)] -pub struct TestStructWithFieldWithMultipleTargets { - #[ctor(impl)] - arg1: String, - #[ctor(value(5) = [0, 1])] - arg2: u32 -} - -#[test] -fn test_struct_multiple_targeted_generations_single_field() { - let tswfwmt1 = TestStructWithFieldWithMultipleTargets::m1("One"); - assert_eq!(TestStructWithFieldWithMultipleTargets { arg1: String::from("One"), arg2: 5 }, tswfwmt1); - - let tswfwmt1 = TestStructWithFieldWithMultipleTargets::m2("Two"); - assert_eq!(TestStructWithFieldWithMultipleTargets { arg1: String::from("Two"), arg2: 5 }, tswfwmt1); - - let tswfwmt1 = TestStructWithFieldWithMultipleTargets::m3("Three", 77); - assert_eq!(TestStructWithFieldWithMultipleTargets { arg1: String::from("Three"), arg2: 77 }, tswfwmt1); -} \ No newline at end of file From 7af2253d50f609fd889ffbcf403ed6baf6f893e8 Mon Sep 17 00:00:00 2001 From: ImaMapleTree <59880284+ImaMapleTree@users.noreply.github.com> Date: Wed, 29 May 2024 08:02:16 +0900 Subject: [PATCH 2/3] added no-std support and feature --- Cargo.lock | 2 +- Cargo.toml | 4 +- doc/overview.md | 158 ++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 26 +++++++- 4 files changed, 187 insertions(+), 3 deletions(-) create mode 100644 doc/overview.md diff --git a/Cargo.lock b/Cargo.lock index c3cc074..ac0bae4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 3 [[package]] name = "derive-ctor" -version = "0.1.0" +version = "0.1.1" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index df8fffe..788bc68 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "derive-ctor" -version = "0.1.0" +version = "0.1.1" description = "Adds `#[derive(ctor)]` which allows for the auto-generation of a constructor." keywords = ["derive", "macro", "trait", "procedural"] authors = ["Evan Cowin"] @@ -12,6 +12,8 @@ exclude = [".github/*", ".gitignore"] [lib] proc-macro = true +[features] +no-std = [] [dependencies] syn = { version = "2.0.*" } diff --git a/doc/overview.md b/doc/overview.md new file mode 100644 index 0000000..56d92d1 --- /dev/null +++ b/doc/overview.md @@ -0,0 +1,158 @@ +# derive-ctor + +`derive-ctor` is a Rust procedural macro crate that allows you to easily generate constructor methods for your structs. With the `#[derive(ctor)]` attribute, you can automatically create a constructor for all fields in the struct. The crate also provides various options to customize the generated constructor methods. + +## Features + +- Automatically generate a constructor method for all fields in a struct with `#[derive(ctor)]`. +- Customize the name and visibility of the auto-generated constructor using `#[ctor(visibility method_name)]`. +- Provide a list of names to generate multiple constructors. +- Customize field behavior in the constructor with the following attributes: + - `#[ctor(default)]` - Exclude the field from the generated method and use its default value. + - `#[ctor(value(EXPRESSION))]` - Exclude the field from the generated method and use the defined expression as its default value. + - `#[ctor(method(METHOD_NAME))]` - Exclude the field from the generated method and call the defined method for its default value. + - `#[ctor(impl)]` - Change the parameter type for the generated method to `impl Into`. + +## Basic Usage + +Add `derive-ctor` to your `Cargo.toml`: + +```toml +[dependencies] +derive-ctor = "0.1" +``` + +Import the crate in your Rust code: +```rust +use derive_ctor::ctor; +``` + +Annotate your struct with `#[derive(ctor)]` to automatically generate a `new` constructor: + +```rust +use derive_ctor::ctor; + +#[derive(ctor)] +struct MyStruct { + field1: i32, + field2: String +} + +let my_struct = MyStruct::new(1, String::from("Foo")); +``` + +## Configurations + +You can modify the name and visibility of the generated method, and define additional +constructors by using the `#[ctor]` attribute on the target struct after `ctor` is derived. + +In the following example, three constructor methods are created: `new`, `with_defaults`, and `internal`. +These methods all inherit their respective visibilities defined within the `#[ctor]` attribute. + +```rust +use derive_ctor::ctor; + +#[derive(ctor)] +#[ctor(pub new, pub(crate) with_defaults, internal)] +struct MyStruct { + field1: i32, + field2: String +} +``` + +### Field Configurations + +Fields can also be annotated with `#[ctor(PROPERTY)]` to change their behaviour in the generated methods. +The following are the available properties that can be used with the field-attributes + +`#[ctor(default)]` - This property excludes the annotated field from the constructor and uses its default value. +```rust +use derive_ctor::ctor; + +#[derive(ctor)] +struct MyStruct { + field1: i32, + #[ctor(default)] + field2: String +} + +let my_struct = MyStruct::new(100); +``` + +`#[ctor(impl)]` - This property modifies the parameter type of the annotated field for the generated method +converting it from `Type` -> `impl Into`. +```rust +use derive_ctor::ctor; + +#[derive(ctor)] +struct MyStruct { + field1: i32, + #[ctor(impl)] // the parameter type will now be impl Into instead of String + field2: String +} + +let my_struct = MyStruct::new(100, "Foo"); +``` + +`#[ctor(value(VALUE))]` - This property excludes the annotated field from the constructor and utilizes the defined expression +to generate its value. +```rust +use derive_ctor::ctor; + +#[derive(ctor)] +struct MyStruct { + field1: i32, + #[ctor(value(String::from("Foo")))] + field2: String +} + +let my_struct = MyStruct::new(100); // generates MyStruct { field1: 100, field2: "foo" } +``` + +`#[ctor(method(METHOD_NAME)]` - This property exludes the annotated field from the constructor and invokes the provided +method to generate its value. +```rust +use derive_ctor::ctor; + +fn generate_struct_value() -> String { + String::from("Foo") +} + +#[derive(ctor)] +struct MyStruct { + field1: i32, + #[ctor(method(generate_struct_value))] + field2: String +} + +let my_struct = MyStruct::new(100); +``` + +### Advanced Configuration + +Field attributes can additionally be configured with a list of indices corresponding to the methods to use the generated +value for. This allows for the creation of multiple functions with different parameter requirements. + +```rust +use derive_ctor::ctor; + +#[derive(ctor)] +#[ctor(new, with_defaults)] +struct MyStruct { + field1: i32, + #[ctor(default = [1])] + field2: String, + #[ctor(default = 1)] // brackets can be removed if specifying only 1 index + field3: bool, + #[ctor(default = [0, 1])] + field4: u64, + #[ctor(default)] // this is the same as specifying all indices + field5: u64 +} + +let my_struct1 = MyStruct::new(100, "Foo".to_string(), true); +let my_struct2 = MyStruct::with_defaults(100); +``` + + + diff --git a/src/lib.rs b/src/lib.rs index 6826d7c..3250715 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,27 @@ -use proc_macro::TokenStream; +#![cfg_attr(feature = "no-std", no_std, doc = "Test")] +#![doc = include_str!("../doc/overview.md")] + +#[cfg(feature = "no-std")] +extern crate alloc; +#[cfg(feature = "no-std")] +use alloc::vec; +#[cfg(feature = "no-std")] +use alloc::vec::Vec; +#[cfg(feature = "no-std")] +use alloc::collections::BTreeSet as HashSet; +#[cfg(feature = "no-std")] +use alloc::string::ToString; + +#[cfg(not(feature = "no-std"))] +use std::vec; +#[cfg(not(feature = "no-std"))] +use std::vec::Vec; +#[cfg(not(feature = "no-std"))] use std::collections::HashSet; + +use proc_macro::TokenStream; + use proc_macro2::{Delimiter, Punct, Span}; use proc_macro2::Spacing::Alone; use quote::{quote, TokenStreamExt, ToTokens}; @@ -10,6 +31,8 @@ use syn::parse::ParseStream; use syn::spanned::Spanned; use syn::token::{Comma, Impl, Pub}; + + static FIELD_CONFIG_ERR_MSG: &str = "Unexpected property: \"{prop}\" (must be one of the following:\ \"default\", \"method\", \"value\", \"impl\")"; @@ -37,6 +60,7 @@ impl Default for CtorTypeConfiguration { /// field: i16 /// } /// ``` + #[derive(Clone)] struct FieldConfig { property: FieldConfigProperty, From ed4ef15b0c7336a204138d8eb825e05d5edf905e Mon Sep 17 00:00:00 2001 From: ImaMapleTree <59880284+ImaMapleTree@users.noreply.github.com> Date: Wed, 29 May 2024 08:08:53 +0900 Subject: [PATCH 3/3] removed `method` property from ctor field configuration and renamed `value` property to `expr` --- README.md | 63 +++++------ doc/overview.md | 158 ---------------------------- src/lib.rs | 27 ++--- tests/struct_field_all.rs | 2 +- tests/struct_field_expr.rs | 55 ++++++++++ tests/struct_field_specific_ctor.rs | 6 +- tests/struct_field_value.rs | 55 ---------- 7 files changed, 96 insertions(+), 270 deletions(-) delete mode 100644 doc/overview.md create mode 100644 tests/struct_field_expr.rs delete mode 100644 tests/struct_field_value.rs diff --git a/README.md b/README.md index a06d093..ea84a5d 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,9 @@ - Customize the name and visibility of the auto-generated constructor using `#[ctor(visibility method_name)]`. - Provide a list of names to generate multiple constructors. - Customize field behavior in the constructor with the following attributes: - - `#[ctor(default)]` - Exclude the field from the generated method and use its default value. - - `#[ctor(value(EXPRESSION))]` - Exclude the field from the generated method and use the defined expression as its default value. - - `#[ctor(method(METHOD_NAME))]` - Exclude the field from the generated method and call the defined method for its default value. - - `#[ctor(impl)]` - Change the parameter type for the generated method to `impl Into`. + - `#[ctor(default)]` - Exclude the field from the generated method and use its default value. + - `#[ctor(expr(EXPRESSION))]` - Exclude the field from the generated method and use the defined expression as its default value. + - `#[ctor(impl)]` - Change the parameter type for the generated method to `impl Into`. ## Basic Usage @@ -30,6 +29,8 @@ use derive_ctor::ctor; Annotate your struct with `#[derive(ctor)]` to automatically generate a `new` constructor: ```rust +use derive_ctor::ctor; + #[derive(ctor)] struct MyStruct { field1: i32, @@ -48,6 +49,8 @@ In the following example, three constructor methods are created: `new`, `with_de These methods all inherit their respective visibilities defined within the `#[ctor]` attribute. ```rust +use derive_ctor::ctor; + #[derive(ctor)] #[ctor(pub new, pub(crate) with_defaults, internal)] struct MyStruct { @@ -63,6 +66,8 @@ The following are the available properties that can be used with the field-attri `#[ctor(default)]` - This property excludes the annotated field from the constructor and uses its default value. ```rust +use derive_ctor::ctor; + #[derive(ctor)] struct MyStruct { field1: i32, @@ -76,6 +81,8 @@ let my_struct = MyStruct::new(100); `#[ctor(impl)]` - This property modifies the parameter type of the annotated field for the generated method converting it from `Type` -> `impl Into`. ```rust +use derive_ctor::ctor; + #[derive(ctor)] struct MyStruct { field1: i32, @@ -86,34 +93,19 @@ struct MyStruct { let my_struct = MyStruct::new(100, "Foo"); ``` -`#[ctor(value(VALUE))]` - This property excludes the annotated field from the constructor and utilizes the defined expression +`#[ctor(expr(VALUE))]` - This property excludes the annotated field from the constructor and utilizes the defined expression to generate its value. ```rust -#[derive(ctor)] -struct MyStruct { - field1: i32, - #[ctor(value(String::from("Foo")))] - field2: String -} - -let my_struct = MyStruct::new(100); // generates MyStruct { field1: 100, field2: "foo" } -``` - -`#[ctor(method(METHOD_NAME)]` - This property exludes the annotated field from the constructor and invokes the provided -method to generate its value. -```rust -fn generate_struct_value() -> String { - String::from("Foo") -} +use derive_ctor::ctor; #[derive(ctor)] struct MyStruct { field1: i32, - #[ctor(method(generate_struct_value))] + #[ctor(expr(String::from("Foo")))] field2: String } -let my_struct = MyStruct::new(100); +let my_struct = MyStruct::new(100); // generates MyStruct { field1: 100, field2: "foo" } ``` ### Advanced Configuration @@ -122,23 +114,22 @@ Field attributes can additionally be configured with a list of indices correspon value for. This allows for the creation of multiple functions with different parameter requirements. ```rust +use derive_ctor::ctor; + #[derive(ctor)] #[ctor(new, with_defaults)] struct MyStruct { - field1: i32, - #[ctor(default) = [1]] - field2: String, - #[ctor(default) = 1] // brackets can be removed if specifying only 1 index - field3: bool, - #[ctor(default) = [0, 1]] - field4: u64, - #[ctor(default)] // this is the same as specifying all indices - field5: u64 + field1: i32, + #[ctor(default = [1])] + field2: String, + #[ctor(default = 1)] // brackets can be removed if specifying only 1 index + field3: bool, + #[ctor(default = [0, 1])] + field4: u64, + #[ctor(default)] // this is the same as specifying all indices + field5: u64 } let my_struct1 = MyStruct::new(100, "Foo".to_string(), true); let my_struct2 = MyStruct::with_defaults(100); -``` - - - +``` \ No newline at end of file diff --git a/doc/overview.md b/doc/overview.md deleted file mode 100644 index 56d92d1..0000000 --- a/doc/overview.md +++ /dev/null @@ -1,158 +0,0 @@ -# derive-ctor - -`derive-ctor` is a Rust procedural macro crate that allows you to easily generate constructor methods for your structs. With the `#[derive(ctor)]` attribute, you can automatically create a constructor for all fields in the struct. The crate also provides various options to customize the generated constructor methods. - -## Features - -- Automatically generate a constructor method for all fields in a struct with `#[derive(ctor)]`. -- Customize the name and visibility of the auto-generated constructor using `#[ctor(visibility method_name)]`. -- Provide a list of names to generate multiple constructors. -- Customize field behavior in the constructor with the following attributes: - - `#[ctor(default)]` - Exclude the field from the generated method and use its default value. - - `#[ctor(value(EXPRESSION))]` - Exclude the field from the generated method and use the defined expression as its default value. - - `#[ctor(method(METHOD_NAME))]` - Exclude the field from the generated method and call the defined method for its default value. - - `#[ctor(impl)]` - Change the parameter type for the generated method to `impl Into`. - -## Basic Usage - -Add `derive-ctor` to your `Cargo.toml`: - -```toml -[dependencies] -derive-ctor = "0.1" -``` - -Import the crate in your Rust code: -```rust -use derive_ctor::ctor; -``` - -Annotate your struct with `#[derive(ctor)]` to automatically generate a `new` constructor: - -```rust -use derive_ctor::ctor; - -#[derive(ctor)] -struct MyStruct { - field1: i32, - field2: String -} - -let my_struct = MyStruct::new(1, String::from("Foo")); -``` - -## Configurations - -You can modify the name and visibility of the generated method, and define additional -constructors by using the `#[ctor]` attribute on the target struct after `ctor` is derived. - -In the following example, three constructor methods are created: `new`, `with_defaults`, and `internal`. -These methods all inherit their respective visibilities defined within the `#[ctor]` attribute. - -```rust -use derive_ctor::ctor; - -#[derive(ctor)] -#[ctor(pub new, pub(crate) with_defaults, internal)] -struct MyStruct { - field1: i32, - field2: String -} -``` - -### Field Configurations - -Fields can also be annotated with `#[ctor(PROPERTY)]` to change their behaviour in the generated methods. -The following are the available properties that can be used with the field-attributes - -`#[ctor(default)]` - This property excludes the annotated field from the constructor and uses its default value. -```rust -use derive_ctor::ctor; - -#[derive(ctor)] -struct MyStruct { - field1: i32, - #[ctor(default)] - field2: String -} - -let my_struct = MyStruct::new(100); -``` - -`#[ctor(impl)]` - This property modifies the parameter type of the annotated field for the generated method -converting it from `Type` -> `impl Into`. -```rust -use derive_ctor::ctor; - -#[derive(ctor)] -struct MyStruct { - field1: i32, - #[ctor(impl)] // the parameter type will now be impl Into instead of String - field2: String -} - -let my_struct = MyStruct::new(100, "Foo"); -``` - -`#[ctor(value(VALUE))]` - This property excludes the annotated field from the constructor and utilizes the defined expression -to generate its value. -```rust -use derive_ctor::ctor; - -#[derive(ctor)] -struct MyStruct { - field1: i32, - #[ctor(value(String::from("Foo")))] - field2: String -} - -let my_struct = MyStruct::new(100); // generates MyStruct { field1: 100, field2: "foo" } -``` - -`#[ctor(method(METHOD_NAME)]` - This property exludes the annotated field from the constructor and invokes the provided -method to generate its value. -```rust -use derive_ctor::ctor; - -fn generate_struct_value() -> String { - String::from("Foo") -} - -#[derive(ctor)] -struct MyStruct { - field1: i32, - #[ctor(method(generate_struct_value))] - field2: String -} - -let my_struct = MyStruct::new(100); -``` - -### Advanced Configuration - -Field attributes can additionally be configured with a list of indices corresponding to the methods to use the generated -value for. This allows for the creation of multiple functions with different parameter requirements. - -```rust -use derive_ctor::ctor; - -#[derive(ctor)] -#[ctor(new, with_defaults)] -struct MyStruct { - field1: i32, - #[ctor(default = [1])] - field2: String, - #[ctor(default = 1)] // brackets can be removed if specifying only 1 index - field3: bool, - #[ctor(default = [0, 1])] - field4: u64, - #[ctor(default)] // this is the same as specifying all indices - field5: u64 -} - -let my_struct1 = MyStruct::new(100, "Foo".to_string(), true); -let my_struct2 = MyStruct::with_defaults(100); -``` - - - diff --git a/src/lib.rs b/src/lib.rs index 3250715..8e68f0f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,5 @@ #![cfg_attr(feature = "no-std", no_std, doc = "Test")] -#![doc = include_str!("../doc/overview.md")] +#![doc = include_str!("../README.md")] #[cfg(feature = "no-std")] extern crate alloc; @@ -35,7 +35,7 @@ use syn::token::{Comma, Impl, Pub}; static FIELD_CONFIG_ERR_MSG: &str = "Unexpected property: \"{prop}\" (must be one of the following:\ - \"default\", \"method\", \"value\", \"impl\")"; + \"default\", \"expr\", \"impl\")"; struct CtorTypeConfiguration { definitions: Vec<(Visibility, Ident)> @@ -71,8 +71,7 @@ struct FieldConfig { enum FieldConfigProperty { Default, Impl, - Method { ident: Ident }, - Value { expression: proc_macro2::TokenStream } + Expression { expression: proc_macro2::TokenStream } } struct RequiredStructField { @@ -150,27 +149,22 @@ impl Parse for FieldConfigProperty { let property_name = property.to_string(); match property_name.as_str() { "default" => Ok(FieldConfigProperty::Default), - "method" => { + "expr" => { let (delimiter, span, buffer) = input.parse_any_delimiter()?; if delimiter != Delimiter::Parenthesis { return Err(Error::new(span.span(), "Expected enclosing parenthesis")) } - Ok(FieldConfigProperty::Method { ident: buffer.parse()? }) - }, - "value" => { - let (delimiter, span, buffer) = input.parse_any_delimiter()?; - - if delimiter != Delimiter::Parenthesis { - return Err(Error::new(span.span(), "Expected enclosing parenthesis")) - } - - Ok(FieldConfigProperty::Value { + Ok(FieldConfigProperty::Expression { expression: proc_macro2::TokenStream::parse(&buffer) .expect("Unable to convert buffer back into TokenStream") }) }, + "method" => Err(Error::new( + property.span(), + "\"method\" property has been removed. Please refer to documentation for a list of valid properties." + )), _ => Err(Error::new( property.span(), FIELD_CONFIG_ERR_MSG.replace("{prop}", &property_name) @@ -199,8 +193,7 @@ impl ToTokens for GeneratedStructField { tokens.extend(match &self.configuration { FieldConfigProperty::Default => quote! { Default::default() }, - FieldConfigProperty::Method { ident } => quote! { #ident() }, - FieldConfigProperty::Value { expression } => expression.clone(), + FieldConfigProperty::Expression { expression } => expression.clone(), FieldConfigProperty::Impl => quote! { #ident.into() } }); diff --git a/tests/struct_field_all.rs b/tests/struct_field_all.rs index 67999ff..0fa82e5 100644 --- a/tests/struct_field_all.rs +++ b/tests/struct_field_all.rs @@ -6,7 +6,7 @@ struct MixedStruct { provided2: bool, #[ctor(impl)] provided3: String, - #[ctor(value("Foo"))] + #[ctor(expr("Foo"))] generated1: &'static str, #[ctor(default)] generated2: u32 diff --git a/tests/struct_field_expr.rs b/tests/struct_field_expr.rs new file mode 100644 index 0000000..51682ac --- /dev/null +++ b/tests/struct_field_expr.rs @@ -0,0 +1,55 @@ +use derive_ctor::ctor; + +#[derive(ctor, Debug, PartialEq)] +struct StructExpr { + provided: u32, + #[ctor(expr(10))] + generated: u32 +} + +#[derive(ctor, Debug, PartialEq)] +struct StructManyExpr { + provided: u32, + #[ctor(expr(11))] + generated1: u32, + #[ctor(expr(false))] + generated2: bool +} + +#[derive(ctor, Debug, PartialEq)] +struct StructComplexExpr { + provided: u32, + #[ctor(expr(String::from("Foo")))] + generated: String +} + +#[derive(ctor, Debug, PartialEq)] +struct StructReliantExpr { + provided: u32, + #[ctor(expr(provided.to_string()))] + generated: String +} + +#[test] +fn test_struct_expr_field() { + let test = StructExpr::new(100); + assert_eq!(StructExpr { provided: 100, generated: 10 }, test); +} + +#[test] +fn test_struct_many_expr_fields() { + let test = StructManyExpr::new(101); + assert_eq!(StructManyExpr { provided: 101, generated1: 11, generated2: false }, test); +} + +#[test] +fn test_struct_complex_expr_field() { + let test = StructComplexExpr::new(102); + assert_eq!(StructComplexExpr { provided: 102, generated: String::from("Foo") }, test); +} + +#[test] +fn test_struct_reliant_expr_field() { + let test = StructReliantExpr::new(103); + assert_eq!(StructReliantExpr { provided: 103, generated: 103.to_string() }, test); +} \ No newline at end of file diff --git a/tests/struct_field_specific_ctor.rs b/tests/struct_field_specific_ctor.rs index f1093f2..e7b50bd 100644 --- a/tests/struct_field_specific_ctor.rs +++ b/tests/struct_field_specific_ctor.rs @@ -23,9 +23,9 @@ fn test_method_2() -> String { #[derive(ctor, Debug, PartialEq)] #[ctor(new, new2)] struct TargetedGenerationStruct2 { - #[ctor(value(test_method_2()) = [1])] + #[ctor(expr(test_method_2()) = [1])] arg1: String, - #[ctor(value(33) = 0)] + #[ctor(expr(33) = 0)] arg2: u32 } @@ -43,7 +43,7 @@ fn test_struct_with_multiple_targeted_generations() { struct TestStructWithFieldWithMultipleTargets { #[ctor(impl)] arg1: String, - #[ctor(value(5) = [0, 1])] + #[ctor(expr(5) = [0, 1])] arg2: u32 } diff --git a/tests/struct_field_value.rs b/tests/struct_field_value.rs deleted file mode 100644 index 632fdb3..0000000 --- a/tests/struct_field_value.rs +++ /dev/null @@ -1,55 +0,0 @@ -use derive_ctor::ctor; - -#[derive(ctor, Debug, PartialEq)] -struct StructValue { - provided: u32, - #[ctor(value(10))] - generated: u32 -} - -#[derive(ctor, Debug, PartialEq)] -struct StructManyValue { - provided: u32, - #[ctor(value(11))] - generated1: u32, - #[ctor(value(false))] - generated2: bool -} - -#[derive(ctor, Debug, PartialEq)] -struct StructComplexValue { - provided: u32, - #[ctor(value(String::from("Foo")))] - generated: String -} - -#[derive(ctor, Debug, PartialEq)] -struct StructReliantValue { - provided: u32, - #[ctor(value(provided.to_string()))] - generated: String -} - -#[test] -fn test_struct_value_field() { - let test = StructValue::new(100); - assert_eq!(StructValue { provided: 100, generated: 10 }, test); -} - -#[test] -fn test_struct_many_value_fields() { - let test = StructManyValue::new(101); - assert_eq!(StructManyValue { provided: 101, generated1: 11, generated2: false }, test); -} - -#[test] -fn test_struct_complex_value_field() { - let test = StructComplexValue::new(102); - assert_eq!(StructComplexValue { provided: 102, generated: String::from("Foo") }, test); -} - -#[test] -fn test_struct_reliant_value_field() { - let test = StructReliantValue::new(103); - assert_eq!(StructReliantValue { provided: 103, generated: 103.to_string() }, test); -} \ No newline at end of file