1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
//! [![crates.io](https://img.shields.io/crates/v/ibuilder_derive.svg)](https://crates.io/crates/ibuilder_derive)
//! [![Docs](https://docs.rs/ibuilder_derive/badge.svg)](https://docs.rs/ibuilder_derive)
//!
//! See the documentation of the [`ibuilder`](https://crates.io/crates/ibuilder) create for the details,
//! you probably are looking for that.
//!
//! ## ibuilder derive macro
//!
//! Usage:
//! ```
//! # use ibuilder_derive::IBuilder;
//! #[derive(IBuilder)]
//! struct Example {
//!     field1: i64,
//!     #[ibuilder(default = "something")]
//!     field2: String,
//! }
//! ```
//!
//! Will implement the trait `ibuilder::Buildable` for `Example`, prodiding the `builder()` method
//! for getting a `ibuilder::Builder`.
//!
//! It will also implement a private struct for keeping the state of the builder and implement the
//! `NewBuildableValue` trait for `Example`, allowing it to be inside a fields of other derived
//! types.

use proc_macro::TokenStream;

use quote::{quote, ToTokens};

use proc_macro_error::{abort, proc_macro_error, set_dummy};

use crate::enum_gen::EnumGenerator;
use crate::struct_gen::StructGenerator;

mod enum_gen;
mod struct_gen;

/// Derive macro for `IBuilder`.
///
/// # Supported features
/// This is a somewhat complete list of all the attributes it's possible to use deriving from
/// `IBuilder`.
///
/// ## `#[ibuilder(rename = "new name")]`
/// When applied to a struct, a named field (of a struct or of a variant) or an enum's variant it
/// changes the named displayed in the list of possible options returned by `get_options()` and in
/// the tree structure returned by `to_node`.
///
/// Renaming an enum is not is supported since that name is not shown anywhere.
///
/// ```
/// # use ibuilder_derive::IBuilder;
/// #[derive(IBuilder)]
/// #[ibuilder(rename = "new struct name")]
/// struct Struct {
///     #[ibuilder(rename = "new field name")]
///     field1: i64,
/// }
/// #[derive(IBuilder)]
/// enum Enum {
///     #[ibuilder(rename = "new variant name")]
///     Var1,
///     Var2 {
///         #[ibuilder(rename = "new field name")]
///         field: i32,
///     },
/// }
/// ```
///
/// ## `#[ibuilder(prompt = "new prompt message")]`
/// Change the message attached to the result of `get_options()` for a struct, an enum, a field or a
/// variant. The prompt set on fields and variants overwrites the one on the structs and enum. If
/// not specified a default value is used.
///
/// ```
/// # use ibuilder_derive::IBuilder;
/// #[derive(IBuilder)]
/// #[ibuilder(prompt = "new struct prompt")]
/// struct Struct {
///     #[ibuilder(rename = "new field prompt")]
///     field1: i64,
/// }
/// #[derive(IBuilder)]
/// #[ibuilder(prompt = "new enum prompt")]
/// enum Enum {
///     #[ibuilder(rename = "new variant promp")]
///     Var1,
///     Var2 {
///         #[ibuilder(rename = "new field prompt")]
///         field: i32,
///     },
/// }
/// ```
///
/// ## `#[ibuilder(default = something)]`
/// Set a default value for the field. After the equal sign a literal is expected, if it is a string
/// literal the conversion is done using `FromStr` **at runtime**, otherwise the literal is
/// converted using the `as` syntax.
///
/// For now only the builtin types can be defaulted (numeric types, bool, char and String).
///
/// ```
/// # use ibuilder_derive::IBuilder;
/// #[derive(IBuilder)]
/// struct Struct {
///     #[ibuilder(default = 42)]
///     field1: u8,
///     #[ibuilder(default = "something")]
///     field2: String,
///     #[ibuilder(default = true)]
///     field3: bool,
///     #[ibuilder(default = 'x')]
///     field4: char,
/// }
/// #[derive(IBuilder)]
/// enum Enum {
///     Var {
///         #[ibuilder(default = 42)]
///         field: i32,
///     },
/// }
/// ```
///
/// ## `#[ibuilder(default)]`
/// Set a variant of an enum as the default one for that enum. At most one variant can be set as
/// default.
///
/// ```
/// # use ibuilder_derive::IBuilder;
/// #[derive(IBuilder)]
/// enum Enum {
///     #[ibuilder(default)]
///     Var1,
///     Var2,
/// }
/// ```
///
/// ## `#[ibuilder(hidden)]`
/// Hide a field or a variant from the return value of `get_options()` and `to_node()`. The field
/// cannot be accessed neither using `apply`. If a field is hidden it must have a default value or
/// it must implement `Default`.
///
/// When hiding the fields of an enum, at least one of them must be visible.
///
/// ```
/// # use ibuilder_derive::IBuilder;
/// #[derive(IBuilder)]
/// struct Struct {
///     #[ibuilder(hidden, default = 42)]
///     field1: u8,
///     #[ibuilder(hidden)] // uses Default::default()
///     field2: u8,
/// }
/// #[derive(IBuilder)]
/// enum Enum {
///     Var1,
///     #[ibuilder(hidden)]
///     Var2,
/// }
/// ```
#[proc_macro_error]
#[proc_macro_derive(IBuilder, attributes(ibuilder))]
pub fn ibuilder_derive(input: TokenStream) -> TokenStream {
    ibuilder_macro(&syn::parse(input).unwrap())
}

/// Main macro body. It generates the following constructs:
/// - `impl Buildable for Name`
/// - `impl NewBuildableValue for Name`
/// - Some private structure and enums for keeping the state of the builder for `Name`, all those
///   types have the name starting with `__`.
fn ibuilder_macro(ast: &syn::DeriveInput) -> TokenStream {
    fix_double_error(&ast.ident);
    match &ast.data {
        syn::Data::Struct(_) => StructGenerator::from_struct(ast).to_token_stream().into(),
        syn::Data::Enum(_) => EnumGenerator::from_enum(ast).to_token_stream().into(),
        _ => abort!(ast, "only structs can derive ibuilder"),
    }
}

/// Parse the string attribute into the `Option<String>`. In case of duplicate or not string a
/// compile error is raised.
fn parse_string_meta(out: &mut Option<String>, lit: syn::Lit) {
    if out.is_none() {
        match lit {
            syn::Lit::Str(content) => *out = Some(content.value()),
            _ => abort!(lit, "expecting a string"),
        }
    } else {
        abort!(lit, "duplicated attribute");
    }
}

fn fix_double_error(ident: &syn::Ident) {
    set_dummy(quote! {
        impl ibuilder::Buildable<#ident> for #ident {
            fn builder() -> ibuilder::Builder<#ident> { unimplemented!() }
        }
    });
}