diff --git a/README.md b/README.md new file mode 100644 index 0000000..b35daa2 --- /dev/null +++ b/README.md @@ -0,0 +1,144 @@ +# 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 of the auto-generated constructor using `#[ctor(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 +#[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 +#[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 +#[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 +#[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 +#[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") +} + +#[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 +#[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 e6616b3..6826d7c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -97,17 +97,19 @@ impl Parse for FieldConfig { return Ok(config) } - let (delim, span, buffer) = input.parse_any_delimiter()?; - if delim != Delimiter::Bracket { - return Err(Error::new(span.span(), "Expected enclosing brackets")) - } - // consume constructor specifier ex: 1, 2, 3 - loop { - config.applications.insert(buffer.parse::()?.base10_parse()?); - if buffer.parse::().is_err() { - break; + if let Ok((delim, span, buffer)) = input.parse_any_delimiter() { + if delim != Delimiter::Bracket { + return Err(Error::new(span.span(), "Expected enclosing brackets")) + } + loop { + config.applications.insert(buffer.parse::()?.base10_parse()?); + if buffer.parse::().is_err() { + break; + } } + } else { + config.applications.insert(input.parse::()?.base10_parse()?); } Ok(config) diff --git a/tests/test.rs b/tests/test.rs index e9b6229..f14c8aa 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -197,7 +197,7 @@ fn test_method_2() -> String { pub struct TargetedGenerationStruct2 { #[ctor(method(test_method_2) = [1])] arg1: String, - #[ctor(value(33) = [0])] + #[ctor(value(33) = 0)] arg2: u32 }