Skip to content

Commit

Permalink
Add #[patch_attribute(...)]] (#29)
Browse files Browse the repository at this point in the history
  • Loading branch information
yanganto committed Jul 29, 2024
1 parent 4dd5c82 commit 7c5abd0
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 19 deletions.
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

0 comments on commit 7c5abd0

Please sign in to comment.