Skip to content
Open
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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ exclude = [

[workspace.package]
authors = ["Antonio Yang <yanganto@gmail.com>"]
version = "0.12.0"
version = "0.12.1"
edition = "2021"
categories = ["development-tools"]
keywords = ["struct", "patch", "macro", "derive", "overlay"]
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ Struct attributes:
- `#[patch(attribute(...))]`: add attributes to the generated patch struct.
- `#[patch(attribute(derive(...)))]`: add derives to the generated patch struct.
- `#[catalyst(bind = "...")]`: decide the base structure. (catalyst feature)
- `#[catalyst(keep_field_attribute)]`: the attributes of fields in substrate will also pass to complex. (catalyst feature)
- `#[complex(name = "...")]`: change the name of the generated complex struct. (catalyst feature)
- `#[complex(attribute(...))]`: add attributes to the generated complex struct. (catalyst feature)

Expand All @@ -171,6 +172,7 @@ Field attributes:
- `#[patch(empty_value = ...)]`: define a value as empty, so the corresponding field of patch will not wrapped by Option, and apply patch when the field is empty.
- `#[filler(extendable)]`: use the field of the struct for filler, the struct needs implement `Default`, `Extend`, `IntoIterator` and `is_empty`.
- `#[filler(empty_value = ...)]`: define a value as empty, so the corresponding field of Filler will be applied, even the field is not `Option` or `Extendable`.
- `#[complex(attribute(...))]`: add attributes to the field in the generated complex struct. (catalyst feature)

Please check the [traits][doc-traits] of document to learn more.

Expand Down
2 changes: 1 addition & 1 deletion complex-example/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ struct-patch = { path = "../lib", features = ["catalyst"] }

[workspace.package]
authors = ["Antonio Yang <yanganto@gmail.com>"]
version = "0.12.0"
version = "0.12.1"
edition = "2021"
categories = ["development-tools"]
keywords = ["struct", "patch", "macro", "derive", "overlay"]
Expand Down
2 changes: 2 additions & 0 deletions complex-example/catalyst/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ rust-version.workspace = true
[dependencies]
struct-patch.workspace = true
substrate = { path = "../substrate" }
serde = { version = "1", features = ["derive"] }
toml = "1.1.2+spec-1.1.0"

[build-dependencies]
struct-patch.workspace = true
Expand Down
26 changes: 26 additions & 0 deletions complex-example/catalyst/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
use serde::{Deserialize, Serialize};
use struct_patch::Catalyst;
use substrate::Base;

#[derive(Default, Catalyst)]
#[catalyst(bind = Base)]
// The Substrate has `#[serde(...)]` on fields , and catalyst keep_field_attribute
// so the complex should have the corresponding Serialize derive
#[catalyst(keep_field_attribute)]
#[complex(attribute(derive(Debug, Deserialize, Serialize)))]
#[allow(dead_code)]
struct Amyloid {
pub extra_bool: bool,
#[complex(attribute(serde(default = "default_str")))]
pub extra_string: String,
pub extra_option: Option<usize>,
}

fn default_str() -> String {
"default".to_string()
}

#[derive(Catalyst)]
#[catalyst(bind = Base)]
#[complex(name = "SmallCpx")]
Expand Down Expand Up @@ -48,5 +58,21 @@ mod tests {
let amyloid = Amyloid::default();
let complex = amyloid.bind(substrate);
assert_eq!(complex.field_bool, true);

let toml_str = toml::to_string_pretty(&complex).unwrap();
assert_eq!(
toml_str,
r#"field_bool = true
field_string = ""
extra_bool = false
extra_string = ""
"#
);
let toml_str = r#" field_bool = true
field_string = ""
extra_bool = true
"#;
let complex: AmyloidComplex = toml::from_str(toml_str).unwrap();
assert_eq!(complex.extra_string, "default");
}
}
1 change: 1 addition & 0 deletions complex-example/substrate/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ rust-version.workspace = true

[dependencies]
struct-patch.workspace = true
serde = { version = "1", features = ["derive"] }

[dev-dependencies]
syn = { version = "2", features = ["full"] }
Expand Down
6 changes: 4 additions & 2 deletions complex-example/substrate/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#![allow(unused)]
use serde::Deserialize;
use struct_patch::Substrate;

#[derive(Default, Substrate)]
#[derive(Deserialize, Default, Substrate)]
pub struct Base {
#[serde(default)]
pub field_bool: bool,
pub field_string: String,
pub field_option: Option<usize>,
Expand All @@ -22,7 +24,7 @@ mod tests {
fn expose_works() {
assert_eq!(
Base::expose_content(),
r#"{"named":[{"vis":"pub","ident":"field_bool","colon_token":true,"ty":{"path":{"segments":[{"ident":"bool"}]}}},{"vis":"pub","ident":"field_string","colon_token":true,"ty":{"path":{"segments":[{"ident":"String"}]}}},{"vis":"pub","ident":"field_option","colon_token":true,"ty":{"path":{"segments":[{"ident":"Option","arguments":{"angle_bracketed":{"args":[{"type":{"path":{"segments":[{"ident":"usize"}]}}}]}}}]}}}]}"#
r#"{"named":[{"attrs":[{"style":"outer","meta":{"list":{"path":{"segments":[{"ident":"serde"}]},"delimiter":"paren","tokens":[{"ident":"default"}]}}}],"vis":"pub","ident":"field_bool","colon_token":true,"ty":{"path":{"segments":[{"ident":"bool"}]}}},{"vis":"pub","ident":"field_string","colon_token":true,"ty":{"path":{"segments":[{"ident":"String"}]}}},{"vis":"pub","ident":"field_option","colon_token":true,"ty":{"path":{"segments":[{"ident":"Option","arguments":{"angle_bracketed":{"args":[{"type":{"path":{"segments":[{"ident":"usize"}]}}}]}}}]}}}]}"#
);

let _fields: syn::Fields = syn_serde::json::from_str(&Base::expose_content()).unwrap();
Expand Down
79 changes: 67 additions & 12 deletions derive/src/catalyst.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ extern crate proc_macro;
use proc_macro2::{Ident, Span, TokenStream};
use quote::{quote, ToTokens};
use std::str::FromStr;
use syn::{meta::ParseNestedMeta, parenthesized, DeriveInput, LitStr, Result, Type};
use syn::{Attribute, meta::ParseNestedMeta, parenthesized, DeriveInput, LitStr, Result, Type};

pub(crate) struct Catalyst {
visibility: syn::Visibility,
Expand All @@ -12,9 +12,12 @@ pub(crate) struct Catalyst {
attributes: Vec<TokenStream>,
fields: syn::Fields,
bind: String,
keep_field_attribute: bool,
}

struct Field {
attrs: Vec<Attribute>,
attributes: Vec<TokenStream>,
ident: Option<Ident>,
ty: Type,
}
Expand All @@ -24,6 +27,8 @@ const COMPLEX: &str = "complex";
const BIND: &str = "bind";
const NAME: &str = "name";
const ATTRIBUTE: &str = "attribute";
// TODO #[catalyst(keep_field_attrs = [ "serde" ])] for more detail selections
const KEEP_FIELD_ATTRIBUTE: &str = "keep_field_attribute";

impl Catalyst {
/// let catalyst bind the substrate and generate the token stream for complex
Expand All @@ -36,6 +41,7 @@ impl Catalyst {
attributes,
fields,
bind,
keep_field_attribute,
} = self;

let substrate_name: Ident = {
Expand All @@ -57,13 +63,13 @@ impl Catalyst {
}

for field in fields.iter() {
raw_complex_fields.push(Field::from_ast(field.clone()));
catalyst_fields.push(Field::from_ast(field.clone()));
raw_complex_fields.push(Field::from_cat_ast(field.clone()));
catalyst_fields.push(Field::from_cat_ast(field.clone()));
}

let complex_fields = raw_complex_fields
.iter()
.map(|f| f.to_token_stream())
.map(|f| f.to_token_stream(*keep_field_attribute))
.collect::<Result<Vec<_>>>()?;

let unpack_complex_fields = raw_complex_fields
Expand Down Expand Up @@ -153,6 +159,7 @@ impl Catalyst {
let mut name = None;
let mut attributes = vec![];
let mut bind = String::new();
let mut keep_field_attribute = false;

for attr in attrs {
let attr_str = attr.path().to_string();
Expand Down Expand Up @@ -194,6 +201,10 @@ impl Catalyst {
}
}
}
KEEP_FIELD_ATTRIBUTE if attr_str == CATALYST => {
// #[catalyst(KEEP_FIELD_ATTRIBUTE )]
keep_field_attribute = true;
}
_ => {
return Err(meta.error(format_args!(
"unknown catalyst/complex attribute `{}`",
Expand Down Expand Up @@ -222,30 +233,74 @@ impl Catalyst {
attributes,
fields,
bind,
keep_field_attribute,
})
}
}

impl Field {
/// Generate the token stream for the Complex struct fields
pub fn to_token_stream(&self) -> Result<TokenStream> {
let Field { ident, ty } = self;
Ok(quote! {
pub #ident: #ty,
})
pub fn to_token_stream(&self, keep_field_attribute: bool) -> Result<TokenStream> {
let Field {
attrs,
attributes,
ident,
ty,
} = self;
if keep_field_attribute {
if attrs.len() > 0 {
Ok(quote! {
#( #attrs )*
pub #ident: #ty,
})
} else {
Ok(quote! {
#( #[ #attributes ] )*
pub #ident: #ty,
})
}
} else {
Ok(quote! {
pub #ident: #ty,
})
}
}

/// Generate the token stream for unpack Complex struct fields
pub fn to_unpack_stream(&self) -> Result<TokenStream> {
let Field { ident, ty: _ } = self;
let Field { ident, ty: _, attributes: _, attrs: _ } = self;
Ok(quote! {
#ident,
})
}

/// Parse the Catalyst struct field
pub fn from_ast(syn::Field { ident, ty, .. }: syn::Field) -> Field {
Field { ident, ty }
pub fn from_cat_ast(syn::Field { ident, ty, attrs, .. }: syn::Field) -> Field {
let mut attributes = Vec::new();
for attr in attrs.iter() {
let attr_str = attr.path().to_string();
if attr_str != COMPLEX {
continue;
}
let _ = attr.parse_nested_meta(|meta| {
let path = meta.path.to_string();
match path.as_str() {
ATTRIBUTE => {
// #[complex(attribute(serde(default))]
let content;
parenthesized!(content in meta.input);
let attribute: TokenStream = content.parse()?;
attributes.push(attribute);
}
_ => {}
}
Ok(())
});
}
Field { ident, ty, attributes, attrs: Vec::new() }
}
pub fn from_ast(syn::Field { ident, ty, attrs, .. }: syn::Field) -> Field {
Field { ident, ty, attrs, attributes: Vec::new() }
}
}

Expand Down
2 changes: 1 addition & 1 deletion lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ readme.workspace = true
rust-version.workspace = true

[dependencies]
struct-patch-derive = { version = "=0.12.0", path = "../derive" }
struct-patch-derive = { version = "=0.12.1", path = "../derive" }

[dev-dependencies]
serde_json = "1.0"
Expand Down
2 changes: 1 addition & 1 deletion no-std-examples/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "no-std-examples"
authors = ["Antonio Yang <yanganto@gmail.com>"]
version = "0.12.0"
version = "0.12.1"
edition = "2021"
license = "MIT"

Expand Down