Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/macros ono dot one #32

Merged
merged 14 commits into from
Jan 19, 2017
5 changes: 1 addition & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
sudo: false
language: rust
rust:
- stable
- beta
- nightly
env:
global:
Expand All @@ -27,8 +25,7 @@ before_script: |
export PATH=$LOCAL/bin:$PATH
script: |
travis-cargo build &&
travis-cargo test &&
travis-cargo bench &&
(cd derive-builder-test && travis-cargo test) &&
travis-cargo doc
after_success: |
# upload the documentation from the build with stable (automatically only actually
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]
### Added
- different setter pattern, e.g. `#[setters(immutable)]`
- private setters, e.g. `#[setters(private)]`
- additional debug info via env_logger, e.g. `RUST_LOG=derive_builder=trace cargo test`

### Changed
- migration to macros 1.1, please refer to the new docs

### Fixed
- missing lifetime support #21

## [0.2.1] - 2016-09-24

### Fixed
Expand Down
15 changes: 11 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,22 @@
name = "derive_builder"
version = "0.2.1"
authors = ["Colin Kiegel <kiegel@gmx.de>",
"Pascal Hertleif <killercup@gmail.com>"]
"Pascal Hertleif <killercup@gmail.com>",
"Jan-Erik Rediger <janerik@fnordig.de>"]

description = "Rust macro based on custom_derive to automatically implement the builder pattern for arbitrary structs."
description = "Rust macro to automatically implement the builder pattern for arbitrary structs."
repository = "https://github.com/colin-kiegel/rust-derive-builder"
documentation = "https://docs.rs/derive_builder"

license = "MIT/Apache-2.0"
keywords = ["derive", "macro", "builder", "setter", "struct"]
readme = "README.md"

[dev-dependencies]
custom_derive = "0.1.5"
[lib]
proc-macro = true

[dependencies]
syn = "0.10"
quote = "0.3"
log = "0.3"
env_logger = "0.3"
30 changes: 15 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,21 @@

# Builder pattern derive

[Rust][rust] macro (based on [custom_derive][custom_derive]) to automatically implement the **builder pattern** for arbitrary structs. A simple `#[derive(Builder)]` will generate code of public setter-methods for all struct fields.
[Rust][rust] macro to automatically implement the **builder pattern** for arbitrary structs. A simple `#[derive(Builder)]` will generate code of public setter-methods for all struct fields.

**This is a work in progress.** Use it at your own risk.
**This is a work in progress.** Use it at your own risk.
**This requires Rust 1.15, due to the usage of Macros 1.1.**

And this is how it works:

```rust
#[macro_use] extern crate custom_derive;
#[macro_use] extern crate derive_builder;

custom_derive! {
#[derive(Default, Builder)]
struct Channel {
token: i32,
special_info: i32,
// .. a whole bunch of other fields ..
}
#[derive(Default, Builder)]
struct Channel {
token: i32,
special_info: i32,
// .. a whole bunch of other fields ..
}

impl Channel {
Expand All @@ -38,7 +36,7 @@ fn main() {
}
```

Note that we did not write any implementation of a method called `special_info`. Instead the [`custom_derive!`][custom_derive] macro scans the `#[derive(..)]` attribute of the struct for non-std identifiers – in our case `#[derive(Builder)]` and delegates the code generation to the `Builder!` macro defined in this crate.
Note that we did not write any implementation of a method called `special_info`. Instead the `derive_builder` crate acts on a `#[derive(Builder)]` and generates the necessary code at compile time.

The automatically generated setter method for the `special_info` field will look like this:

Expand All @@ -51,26 +49,28 @@ pub fn special_info<VALUE: Into<i32>>(&mut self, value: VALUE) -> &mut Self {

## Usage and Features

* **Chaining**: The setter calls can be chained, because they consume and return `&mut self`.
* **Chaining**: The setter calls can be chained, because they consume and return `&mut self` by default.
* **Extensible**: You can still define your own implementation of the struct and define additional methods. Just make sure to name them differently than the fields.
* **Setter type conversions**: Setter methods are generic over the input types – you can supply every argument that implements the [`Into`][into] trait for the field type.
* **Generic structs**: Are also supported, but you **must not** use a type parameter named `VALUE`, because this is already reserved for the setter-methods.
* **Documentation and attributes**: Setter methods can be documented by simply documenting the corresponding field. Similarly `#[cfg(...)]` and `#[allow(...)]` attributes are also applied to the setter methods.
* **Builder patterns**: You can opt into other builder patterns by preceding your struct with `#[setters(owned)]` or `#[setters(immutable)]`.
* **Visibility**: You can opt into private setter by preceding your struct with `#[setters(private)]`.
* **Logging**: If anything works unexpectedly you can enable detailed logs by setting this environment variable before calling cargo `RUST_LOG=derive_builder=trace`.

For more information and examples please take a look at our [documentation][doc].

## Gotchas

* Tuple structs and unit structs are not supported as they have no field names. We do not intend to support them.
* When defining a generic struct, you cannot use `VALUE` as a generic parameter as this is what all setters are using.
* This crate exports a macro named `Builder!`, make sure you don't use this name for another macro.
* If you hit the macro recursion limit, you can increase it by adding this `#![recursion_limit="128"]` to your crate (default is `64`).

## [Documentation][doc]

The builder pattern is explained [here][builder-pattern], including its variants.

[doc]: https://colin-kiegel.github.io/rust-derive-builder
[rust]: https://www.rust-lang.org/
[custom_derive]: https://crates.io/crates/custom_derive
[builder-pattern]: https://aturon.github.io/ownership/builders.html
[into]: https://doc.rust-lang.org/nightly/std/convert/trait.Into.html

Expand Down
9 changes: 9 additions & 0 deletions derive-builder-test/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "derive_builder_test"
version = "0.1.0"

description = "Test derive_builder"

[dependencies]
log = "0.3"
derive_builder = { path = "../" }
3 changes: 3 additions & 0 deletions derive-builder-test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
run tests with LOGGING enabled

`RUST_LOG=derive_builder=trace cargo test`
25 changes: 25 additions & 0 deletions derive-builder-test/tests/attributes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#[macro_use] extern crate derive_builder;

/// This is a doc comment for the struct
#[warn(missing_docs)]
#[allow(non_snake_case, dead_code)]
#[derive(Builder)]
struct Lorem {
/// This is a doc comment for a field
field_with_doc_comment: String,
#[allow(missing_docs)]
undocumented: String,
#[allow(non_snake_case)]
CamelCase: i32,
#[cfg(target_os = "macos")]
mac_only: bool,
#[allow(non_snake_case)]
#[cfg(target_os = "linux")]
LinuxOnly: (),
}

#[test]
fn annotations() {
// this is currently just a compile-test (may switch to token comparisons here)
// https://github.com/colin-kiegel/rust-derive-builder/issues/19
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
#[macro_use] extern crate custom_derive;
#[macro_use] extern crate derive_builder;

custom_derive!{
#[derive(Debug, PartialEq, Default, Builder, Clone)]
struct Lorem {
ipsum: String,
pub dolor: Option<String>,
pub sit: i32,
amet: bool,
}
#[derive(Debug, PartialEq, Default, Builder, Clone)]
struct Lorem {
ipsum: String,
pub dolor: Option<String>,
pub sit: i32,
amet: bool,
}

impl Lorem {
Expand Down
12 changes: 12 additions & 0 deletions derive-builder-test/tests/empty_struct.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#[macro_use] extern crate derive_builder;

/// This is a doc comment for the struct
#[warn(missing_docs)]
#[allow(non_snake_case)]
#[derive(Debug, PartialEq, Default, Builder)]
struct IgnoreEmptyStruct { }

#[test]
fn empty_struct() {
// this is just a compile-test - no run time checks required.
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
#[macro_use] extern crate custom_derive;
#[macro_use] extern crate derive_builder;

custom_derive!{
#[derive(Debug, PartialEq, Default, Builder, Clone)]
struct GenLorem<T> {
ipsum: String,
pub dolor: T, // generics are a pain, so this field name is fitting
}
#[derive(Debug, PartialEq, Default, Builder, Clone)]
struct GenLorem<T> {
ipsum: String,
pub dolor: T, // generics are a pain, so this field name is fitting
}

impl<T: Default> GenLorem<T> {
Expand Down
33 changes: 33 additions & 0 deletions derive-builder-test/tests/immutable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#[macro_use] extern crate derive_builder;

#[derive(Debug, PartialEq, Default, Builder, Clone)]
#[setters(immutable)]
struct Lorem {
ipsum: String,
}

impl Lorem {
pub fn new<T: Into<String>>(value: T) -> Self {
Lorem {
ipsum: value.into()
}
}
}

#[test]
fn contructor_sanity_check() {
let x = Lorem::new("lorem");

assert_eq!(x, Lorem { ipsum: "lorem".into() });
}

#[test]
fn immutable_setter() {
// the setter must have the correct signature
let immutable_setter: fn(&Lorem, String) -> Lorem = Lorem::ipsum;

let old = Lorem::new("lorem");
let new = immutable_setter(&old, "new".to_string());

assert_eq!(new, Lorem { ipsum: "new".into() });
}
30 changes: 30 additions & 0 deletions derive-builder-test/tests/lifetime.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#[macro_use] extern crate derive_builder;

#[derive(Debug, PartialEq, Default, Builder, Clone)]
struct Lorem<'a> {
ipsum: &'a str,
}

impl<'a> Lorem<'a> {
pub fn new<T: Into<&'a str>>(value: T) -> Self {
Lorem {
ipsum: value.into()
}
}
}

#[test]
fn contructor_sanity_check() {
let x = Lorem::new("ipsum");

assert_eq!(x, Lorem { ipsum: "ipsum" });
}

#[test]
fn immutable_setter() {
let x = Lorem::new("")
.ipsum("ipsum")
.clone();

assert_eq!(x, Lorem { ipsum: "ipsum" });
}
33 changes: 33 additions & 0 deletions derive-builder-test/tests/mutable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#[macro_use] extern crate derive_builder;

#[derive(Debug, PartialEq, Default, Builder, Clone)]
#[setters(mutable)]
struct Lorem {
ipsum: String,
}

impl Lorem {
pub fn new<T: Into<String>>(value: T) -> Self {
Lorem {
ipsum: value.into()
}
}
}

#[test]
fn contructor_sanity_check() {
let x = Lorem::new("lorem");

assert_eq!(x, Lorem { ipsum: "lorem".into() });
}

#[test]
fn mutable_setter() {
// the setter must have the correct signature
let mutable_setter: fn(&mut Lorem, String) -> &mut Lorem = Lorem::ipsum;

let mut old = Lorem::new("lorem");
let new = mutable_setter(&mut old, "new".to_string());

assert_eq!(*new, Lorem { ipsum: "new".into() });
}
33 changes: 33 additions & 0 deletions derive-builder-test/tests/owned.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#[macro_use] extern crate derive_builder;

#[derive(Debug, PartialEq, Default, Builder, Clone)]
#[setters(owned)]
struct Lorem {
ipsum: String,
}

impl Lorem {
pub fn new<T: Into<String>>(value: T) -> Self {
Lorem {
ipsum: value.into()
}
}
}

#[test]
fn contructor_sanity_check() {
let x = Lorem::new("lorem");

assert_eq!(x, Lorem { ipsum: "lorem".into() });
}

#[test]
fn consuming_setter() {
// the setter must have the correct signature
let consuming_setter: fn(Lorem, String) -> Lorem = Lorem::ipsum;

let old = Lorem::new("lorem");
let new = consuming_setter(old, "new".to_string());

assert_eq!(new, Lorem { ipsum: "new".into() });
}
27 changes: 27 additions & 0 deletions derive-builder-test/tests/private_setters.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#[macro_use] extern crate derive_builder;

pub mod foo {
#[derive(Debug, PartialEq, Default, Builder, Clone)]
#[setters(private)]
pub struct Lorem {
pub ipsum: String,
}

#[test]
fn setters_same_module() {
let x = Lorem::default()
.ipsum("Hello world!")
.clone();

assert_eq!(x, Lorem { ipsum: "Hello world!".into() });
}
}

// compile-test should fail with "error: method `ipsum` is private"
// fn setters_foreign_module() {
// let x = foo::Lorem::default()
// .ipsum("Hello world!")
// .clone();
//
// assert_eq!(x, foo::Lorem { ipsum: "Hello world!".into() });
// }
Loading