Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions builder-pattern-macro/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ keywords = ["builder", "pattern", "macro", "derive", "struct"]

[dependencies]
proc-macro2 = "1.0"
syn = { version = "1.0", features = ["full"] }
syn = { version = "2", features = ["full"] }
quote = "1.0"
bitflags = "1.3"
bitflags = "2"

[lib]
proc-macro = true
76 changes: 47 additions & 29 deletions builder-pattern-macro/src/attributes.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
use bitflags::bitflags;
use syn::{Attribute, Expr, Meta, NestedMeta};
use syn::{
Attribute, Expr,
ext::IdentExt,
parse::{ParseStream, Parser},
punctuated::Punctuated,
token::Comma,
};

bitflags! {
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Setters: u32 {
const VALUE = 0b00000001;
const LAZY = 0b00000010;
Expand Down Expand Up @@ -42,38 +49,38 @@ impl From<Vec<Attribute>> for FieldAttributes {
fn from(attrs: Vec<Attribute>) -> FieldAttributes {
let mut attributes = FieldAttributes::default();
attrs.iter().for_each(|attr| {
if attr.path.is_ident("default") {
if attr.path().is_ident("default") {
if attributes.default.is_some() {
unimplemented!("Duplicated `default` attributes.")
}
parse_default(attr, &mut attributes)
} else if attr.path.is_ident("default_lazy") {
} else if attr.path().is_ident("default_lazy") {
if attributes.default.is_some() {
unimplemented!("Duplicated `default` attributes.")
}
parse_lazy_default(attr, &mut attributes)
} else if attr.path.is_ident("default_async") {
} else if attr.path().is_ident("default_async") {
if attributes.default.is_some() {
unimplemented!("Duplicated `default` attributes.")
}
unimplemented!("Asynchronous default is not implemented yet.")
} else if attr.path.is_ident("hidden") {
} else if attr.path().is_ident("hidden") {
if attributes.vis != FieldVisibility::Default {
unimplemented!("Duplicated `hidden` attributes.")
}
attributes.vis = FieldVisibility::Hidden;
} else if attr.path.is_ident("public") {
} else if attr.path().is_ident("public") {
if attributes.vis != FieldVisibility::Default {
unimplemented!("Duplicated `public` attributes.")
}
attributes.vis = FieldVisibility::Public;
} else if attr.path.is_ident("into") {
} else if attr.path().is_ident("into") {
attributes.use_into = true
} else if attr.path.is_ident("validator") {
} else if attr.path().is_ident("validator") {
parse_validator(attr, &mut attributes)
} else if attr.path.is_ident("doc") {
} else if attr.path().is_ident("doc") {
attributes.documents = get_documents(&attrs);
} else if attr.path.is_ident("setter") {
} else if attr.path().is_ident("setter") {
parse_setters(attr, &mut attributes)
}
});
Expand Down Expand Up @@ -106,34 +113,45 @@ fn parse_validator(attr: &Attribute, attributes: &mut FieldAttributes) {
}

fn parse_setters(attr: &Attribute, attributes: &mut FieldAttributes) {
let meta = attr.parse_meta().unwrap();
let mut setters = Setters::empty();
if let Meta::List(l) = meta {
let it = l.nested.iter();
it.for_each(|m| {
if let NestedMeta::Meta(Meta::Path(p)) = m {
if p.is_ident("value") {
setters.insert(Setters::VALUE);
} else if p.is_ident("lazy") {
setters.insert(Setters::LAZY);
} else if p.is_ident("async") {
setters.insert(Setters::ASYNC);
}
} else {
unimplemented!("Invalid setter.")
let parser = |input: ParseStream| {
let mut values = Punctuated::new();
while !input.is_empty() {
values.push_value(input.call(syn::Ident::parse_any)?);
if input.peek(Comma) {
values.push_punct(input.parse()?);
}
});
} else {
unimplemented!("Invalid setter.")
}
}
Ok::<Punctuated<syn::Ident, Comma>, syn::Error>(values)
};
let metas = parser
.parse2(
attr.meta
.require_list()
.expect("`setter` attribute must be a list, e.g. #[setter(value, lazy)]")
.tokens
.clone(),
)
.expect("`setter` attribute must contain valid identifiers separated by commas");
metas.iter().for_each(|setter| {
if setter == "value" {
setters.insert(Setters::VALUE);
} else if setter == "lazy" {
setters.insert(Setters::LAZY);
} else if setter == "async" {
setters.insert(Setters::ASYNC);
} else {
unimplemented!("Invalid setter.")
}
});
attributes.setters = setters;
}

pub fn get_documents(attrs: &[Attribute]) -> Vec<Attribute> {
let mut documents: Vec<Attribute> = vec![];

for attr in attrs {
if attr.path.is_ident("doc") {
if attr.path().is_ident("doc") {
documents.push(attr.to_owned());
}
}
Expand Down
2 changes: 1 addition & 1 deletion builder-pattern-macro/src/builder/builder_functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ impl<'a> BuilderFunctions<'a> {
let mut docs: Vec<Attribute> = Vec::new();

let default = match f.attrs.default.as_ref() {
Some((expr, _)) => format!("\n - Default: `{}`", expr.into_token_stream().to_string()),
Some((expr, _)) => format!("\n - Default: `{}`", expr.into_token_stream()),
None => String::from(""),
};
let doc = format!(
Expand Down
1 change: 0 additions & 1 deletion builder-pattern-macro/src/builder/builder_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ impl<'a> BuilderImpl<'a> {
fn optional_generics(&self) -> impl Iterator<Item = TokenStream> {
let offset = self.input.required_fields.len() + 1;
(0..self.input.optional_fields.len())
.into_iter()
.map(move |i| {
TokenStream::from_str(&format!("TyBuilderPattern{}", i + offset)).unwrap()
})
Expand Down
4 changes: 2 additions & 2 deletions builder-pattern-macro/src/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ impl Field {
self.attrs
.documents
.iter()
.filter(|a| a.path.is_ident("doc"))
.filter(|a| a.path().is_ident("doc"))
.map(|a| a.to_owned())
.collect()
}
Expand All @@ -40,7 +40,7 @@ impl Ord for Field {

impl PartialOrd for Field {
fn partial_cmp(&self, other: &Field) -> Option<Ordering> {
Some(self.ident.cmp(&other.ident))
Some(self.cmp(other))
}
}

Expand Down
1 change: 0 additions & 1 deletion builder-pattern-macro/src/struct_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ impl<'a> StructImpl<'a> {
/// An iterator to describe initial state of builder.
fn empty_generics(&self) -> impl Iterator<Item = TokenStream> {
(0..(self.input.required_fields.len() + self.input.optional_fields.len()))
.into_iter()
.map(|_| TokenStream::from_str("()").unwrap())
}

Expand Down
10 changes: 5 additions & 5 deletions builder-pattern-macro/src/struct_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@ use proc_macro2::{Ident, Span, TokenStream};
use quote::{ToTokens, TokenStreamExt};
use syn::{
parse::{Parse, ParseStream, Result},
AttrStyle, Attribute, Data, DeriveInput, Fields, GenericParam, Generics, Lifetime, Token,
VisPublic, Visibility,
parse_quote, AttrStyle, Attribute, Data, DeriveInput, Fields, GenericParam, Generics,
Lifetime, Token,
Visibility,
};

pub struct StructInput {
pub vis: Visibility,
pub ident: Ident,
pub generics: Generics,
#[allow(dead_code)]
pub attrs: Vec<Attribute>,
pub required_fields: Vec<Field>,
pub optional_fields: Vec<Field>,
Expand Down Expand Up @@ -57,8 +59,7 @@ impl Parse for StructInput {
};
fields.push(Field {
vis: if attrs.vis == FieldVisibility::Public {
let v = <Token![pub]>::default();
Visibility::Public(VisPublic { pub_token: v })
parse_quote!(pub)
} else {
f.vis
},
Expand Down Expand Up @@ -129,7 +130,6 @@ impl StructInput {
/// An iterator for generics like [U1, U2, ...].
pub fn all_generics(&self) -> impl Iterator<Item = TokenStream> {
(0..(self.num_fields()))
.into_iter()
.map(|i| TokenStream::from_str(&format!("TyBuilderPattern{}", i + 1)).unwrap())
}

Expand Down
4 changes: 2 additions & 2 deletions builder-pattern/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ builder-pattern-macro = { version = "0.4.2", path = "../builder-pattern-macro" }
futures = { version = "0.3", optional = true }

[dev-dependencies]
uuid = { version = "0.8", features = ["v4"] }
tokio = { version = "1.12", features = ["macros", "rt-multi-thread"] }
uuid = { version = "1", features = ["v4"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
tokio-test = "0.4"

[features]
Expand Down
4 changes: 2 additions & 2 deletions builder-pattern/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,9 @@
//! - **Chaining**: Can make structure with chained setters.
//! - **Complex types are supported**: Lifetime, trait bounds, and where clauses are well supported.
//! - **Type safety**: Autocompletion tools can suggest correct setters to build the struct. Also, `build`
//! function is allowed only the all of required fields are provided. **No Result**, **No Unwrap**. Just use it.
//! function is allowed only the all of required fields are provided. **No Result**, **No Unwrap**. Just use it.
//! - **Lazy evaluation and asynchronous**: Lazy evaluation and asynchronous are supported.
//! The values will be evaluated when the structure is built.
//! The values will be evaluated when the structure is built.
//! - **No additional tasks**: There's no additional constraints to use the macro. Any structures and fields are allowed.
//! - **Auto-generated documentation**: Documentation for the builder functions are automatically generated.
//!
Expand Down
4 changes: 2 additions & 2 deletions test-no-future/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ license = "MIT"
builder-pattern = { version = "0.4.2", path = "../builder-pattern", default-features = false }

[dev-dependencies]
uuid = { version = "0.8", features = ["v4"] }
tokio = { version = "1.12", features = ["macros", "rt-multi-thread"] }
uuid = { version = "1", features = ["v4"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
Loading