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

Add #[patch_attribute(...)]] #29

Merged
merged 1 commit into from
Jul 29, 2024
Merged
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
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ jobs:
nix develop -c cargo run --no-default-features --example diff
nix develop -c cargo run --no-default-features --example json
nix develop -c cargo run --no-default-features --example rename-patch-struct
nix develop -c cargo run --no-default-features --example patch-attr
nix develop -c cargo test --no-default-features

- name: Test with default features
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ members = [

[workspace.package]
authors = ["Antonio Yang <[email protected]>"]
version = "0.5.0"
version = "0.6.0"
edition = "2021"
categories = ["development-tools"]
keywords = ["struct", "patch", "macro", "derive", "overlay"]
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,15 @@ fn patch_json() {
```

## Documentation and Examples
Also, you can modify the patch structure by `#[patch_derive(...)]`, `#[patch_name = "..."]` and `#[patch_skip]`.
Also, you can modify the patch structure by `#[patch_attribute(...)]`, `#[patch_derive(...)]`, `#[patch_name = "..."]` and `#[patch_skip]`.
Please check the [traits][doc-traits] of document to learn more.

The [examples][examples] demo following scenarios.
- diff two instance for a patch
- create a patch from json string
- rename the patch structure
- check a patch is empty or not
- add attribute to patch struct


[crates-badge]: https://img.shields.io/crates/v/struct-patch.svg
Expand Down
2 changes: 1 addition & 1 deletion struct-patch-derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ proc-macro = true
[dependencies]
proc-macro2 = "1.0"
quote = "1.0"
syn = { version = "2.0", features = ["parsing"] }
syn = { version = "2.0", features = ["parsing", "extra-traits"] }

[features]
status = []
51 changes: 36 additions & 15 deletions struct-patch-derive/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
extern crate proc_macro;

use std::str::FromStr;

use proc_macro::TokenStream;
use proc_macro2::{Ident, Span};
use quote::quote;
use syn::{DeriveInput, Expr, Lit, LitStr, Meta, MetaList, MetaNameValue, PathSegment, Type};

#[proc_macro_derive(Patch, attributes(patch_derive, patch_name, patch_skip))]
#[proc_macro_derive(Patch, attributes(patch_attribute, patch_derive, patch_name, patch_skip))]
pub fn derive_patch(item: TokenStream) -> TokenStream {
let DeriveInput {
ident,
Expand All @@ -20,6 +19,7 @@ pub fn derive_patch(item: TokenStream) -> TokenStream {
let where_clause = &generics.where_clause;
let mut patch_struct_name: Option<Ident> = None;
let mut patch_derive = None;
let mut patch_attrs = Vec::new();

for attr in attrs {
let mut attr_clone = attr.clone();
Expand All @@ -32,25 +32,40 @@ pub fn derive_patch(item: TokenStream) -> TokenStream {
}) => {
let mut path_clone = path.clone();
let mut segments = path.segments.clone();
if let Some("patch_derive") = path
match path
.segments
.first()
.map(|s| s.ident.to_string())
.as_deref()
{
if let Some(seg) = segments.first_mut() {
*seg = PathSegment {
ident: Ident::new("derive", Span::call_site()),
arguments: seg.arguments.clone(),
};
Some("patch_derive") => {
if let Some(seg) = segments.first_mut() {
*seg = PathSegment {
ident: Ident::new("derive", Span::call_site()),
arguments: seg.arguments.clone(),
};
}
path_clone.segments = segments;
attr_clone.meta = Meta::List(MetaList {
path: path_clone,
tokens: tokens.clone(),
delimiter: delimiter.clone(),
});
patch_derive = Some(attr_clone);
},
Some("patch_attribute") => {
for token in tokens.to_string().split(',') {
if ! token.is_empty() {
let attr = proc_macro2::TokenStream::from_str(&format!("#[{}]", token)).expect("");
patch_attrs.push(
quote::quote!(
#attr
)
);
}
}
}
path_clone.segments = segments;
attr_clone.meta = Meta::List(MetaList {
path: path_clone,
tokens: tokens.clone(),
delimiter: delimiter.clone(),
});
patch_derive = Some(attr_clone);
_ => (),
}
}
Meta::NameValue(MetaNameValue {
Expand Down Expand Up @@ -185,12 +200,18 @@ pub fn derive_patch(item: TokenStream) -> TokenStream {
let patch_struct = if let Some(patch_derive) = patch_derive {
quote!(
#patch_derive
#(
#patch_attrs
)*
pub struct #patch_struct_name #generics #where_clause {
#(pub #field_names: #wrapped_types,)*
}
)
} else {
quote::quote!(
#(
#patch_attrs
)*
pub struct #patch_struct_name #generics #where_clause {
#(pub #field_names: #wrapped_types,)*
}
Expand Down
3 changes: 2 additions & 1 deletion struct-patch/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ license.workspace = true
readme.workspace = true

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

[dev-dependencies]
serde_json = "1.0"
serde = { version = "1", features = ["derive"] }
serde_with = "*"

[features]
default = ["status"]
Expand Down
30 changes: 30 additions & 0 deletions struct-patch/examples/patch-attr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use struct_patch::Patch;
use serde_with::skip_serializing_none;

#[derive(Default, Patch)]
#[patch_derive(serde::Serialize, Debug, Default)]
#[patch_attribute(skip_serializing_none)]
struct Item {
field_bool: bool,
field_int: usize,
field_string: String,
}

// Generated by Patch derive macro
//
// #[derive(Debug, Default)] // pass by patch_derive
// #[skip_serializing_none] // pass by patch_attribute
// struct ItemPatch { // pass by patch_name
// field_bool: Option<bool>,
// field_int: Option<usize>,
// field_string: Option<String>,
// }

fn main() {
let patch = Item::new_empty_patch();

assert_eq!(
format!("{patch:?}"),
"ItemPatch { field_bool: None, field_int: None, field_string: None }"
);
}
16 changes: 16 additions & 0 deletions struct-patch/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,22 @@
/// // struct ItemPatch {}
/// ```
///
/// ### `#[patch_attribute(...)]`
/// Use this attribute to pass the attributes on the generated patch struct
/// ```compile_fail
/// // This example need `serde` and `serde_with` crates
/// # use struct_patch::Patch;
/// #[derive(Patch, Debug)]
/// #[patch_derive(Serialize, Deserialize, Default)]
/// #[patch_attribute(skip_serializing_none)]
/// struct Item;
///
/// // Generated struct
/// // #[derive(Default, Deserialize, Serialize)]
/// // #[skip_serializing_none]
/// // struct ItemPatch {}
/// ```
///
/// ### `#[patch_name = "..."]`
/// Use this attribute to change the name of the generated patch struct
/// ```rust
Expand Down