diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 32cb589..777fc4b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -73,3 +73,6 @@ jobs: cd no-std-examples nix develop .#no-std -c cargo run --features=box --bin no-std-box nix develop .#no-std -c cargo run --features=option --bin no-std-option + + - name: Test with catalyst + nix develop .#ci -c check-catalyst diff --git a/.gitignore b/.gitignore index cb76358..1fa5e3d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ Cargo.lock .direnv/ struct-patch/examples no-std-examples/target +complex-example/target diff --git a/Cargo.toml b/Cargo.toml index 9f23a03..ad791a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,10 @@ members = [ "lib", "derive", ] -exclude = [ "no-std-examples" ] +exclude = [ + "no-std-examples", + "complex-example", +] [workspace.package] authors = ["Antonio Yang "] diff --git a/README.md b/README.md index 6e23746..23a9440 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,10 @@ [![MIT licensed][mit-badge]][mit-url] [![Docs][doc-badge]][doc-url] -A lib help you patch Rust instance, and easy to partial update configures. +A lib help you modify the config struct, you can +- patch an instance, and easy to partial update with `Patch` derive macro +- fill up an instance with `Filler` derive macro +- extend with extra fields with `Substrate` and `Catalyst` derive macros ## Introduction This crate provides the `Patch`, `Filler` traits and accompanying derive macro. @@ -12,8 +15,11 @@ If the any field in the instance is none then it will try to fill the field with Currently, `Filler` only support `Option` and `Vec` fields, and also you can check this [template](https://github.com/yanganto/ConfigTemplate) if you already work on a big project with a lot of configs. This crate support `no_std`, please check [no-std-examples](./no-std-examples). +When extending a config, the base struct should be expose in the build script with `Substrate` trait, then a catalyst struct can bind and produce complex struct, +please check [complex-example](./complex-example) and the [Quick Example: case 3](#case-3---extend-a-struct-from-a-crate). ## Quick Example +#### Case 1 - Patch on a Config Deriving `Patch` on a struct will generate a struct similar to the original one, but with all fields wrapped in an `Option`. An instance of such a patch struct can be applied onto the original struct, replacing values only if they are set to `Some`, leaving them unchanged otherwise. ```rust @@ -65,6 +71,7 @@ fn patch_json() { } ``` +#### Case 2 - Fill up on a Config Deriving `Filler` on a struct will generate a struct similar to the original one with the field with `Option`. Unlike `Patch`, the `Filler` only work on the empty fields of instance. ```rust @@ -99,13 +106,57 @@ assert_eq!(item.maybe_field_int, Some(7)); assert_eq!(item.list, vec![7]); ``` +#### Case 3 - Extend a struct from a crate +Deriving `Substrate` on a struct will help you expose the field information, and you can easy to expose in build.rs of other crate. +Deriving `Catalyst` on can read the field information of Substrate and generate a new Complex struct. +All the fields in substrate and catalyst need be public, and the fields in complex are also public. +The overall behavior likes chemical catalysts, a catalyst **bind** on a substrate to form a complex struct, which has all fields from substrate and catalyst. +Also, a complex can **decouple** and return a catalyst and substrate, please check [complex-example](./complex-example/catalyst/src/lib.rs). + +```rust +/// In $dependency_crate/src/lib.rs +use struct_patch::Substrate; +#[derive(Substrate)] +pub struct Base { + pub field_bool: bool, + pub field_string: String, +} + +/// In $main_crate/src/build.rs +use struct_patch::Substrate; + +fn main() { + $dependency_crate::Base::expose(); +} + +/// In $main_crate/src/lib.rs +use struct_patch::Catalyst; + +#[derive(Catalyst)] +#[catalyst(bind = substrate::Base)] +struct Amyloid { + pub extra_bool: bool, + pub extra_option: Option, +} +// Now AmyloidComplex is generated +// struct AmyloidComplex { +// pub field_bool: bool, +// pub field_string: String, +// pub extra_bool: bool, +// pub extra_option: Option, +//} +``` + ## Documentation and Examples -Also, you can modify the patch structure by defining `#[patch(...)]` or `#[filler(...)]` attributes on the original struct or fields. +Also, you can modify the patch structure by defining `#[patch(...)]`, `#[filler(...)]` or `#[complex(...)]`, `#[catalyst(...)]` attributes on the original struct or fields. Struct attributes: - `#[patch(name = "...")]`: change the name of the generated patch struct. - `#[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) +- `#[complex(name = "...")]`: change the name of the generated complex struct. (catalyst feature) +- `#[complex(attribute(...))]`: add attributes to the generated complex struct. (catalyst feature) Field attributes: - `#[patch(skip)]`: skip the field in the generated patch struct. diff --git a/complex-example/Cargo.toml b/complex-example/Cargo.toml new file mode 100644 index 0000000..c221c03 --- /dev/null +++ b/complex-example/Cargo.toml @@ -0,0 +1,20 @@ +[workspace] +resolver = "2" +members = [ + "substrate", + "catalyst", +] +[workspace.dependencies] +struct-patch = { path = "../lib", features = ["catalyst"] } + +[workspace.package] +authors = ["Antonio Yang "] +version = "0.11.0" +edition = "2021" +categories = ["development-tools"] +keywords = ["struct", "patch", "macro", "derive", "overlay"] +license = "MIT" +readme = "README.md" +repository = "https://github.com/yanganto/struct-patch/" +description = "A library that helps you implement partial updates for your structs." +rust-version = "1.61.0" diff --git a/complex-example/catalyst/Cargo.toml b/complex-example/catalyst/Cargo.toml new file mode 100644 index 0000000..409b5f8 --- /dev/null +++ b/complex-example/catalyst/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "catalyst" +authors.workspace = true +version.workspace = true +edition.workspace = true +categories.workspace = true +keywords.workspace = true +license.workspace = true +readme.workspace = true +repository.workspace = true +description.workspace = true +rust-version.workspace = true + +[dependencies] +struct-patch.workspace = true +substrate = { path = "../substrate" } + +[build-dependencies] +struct-patch.workspace = true +substrate = { path = "../substrate" } diff --git a/complex-example/catalyst/build.rs b/complex-example/catalyst/build.rs new file mode 100644 index 0000000..faab882 --- /dev/null +++ b/complex-example/catalyst/build.rs @@ -0,0 +1,5 @@ +use struct_patch::Substrate; + +fn main() { + substrate::Base::expose(); +} diff --git a/complex-example/catalyst/src/lib.rs b/complex-example/catalyst/src/lib.rs new file mode 100644 index 0000000..aba2d89 --- /dev/null +++ b/complex-example/catalyst/src/lib.rs @@ -0,0 +1,52 @@ +use struct_patch::Catalyst; +use substrate::Base; + +#[derive(Default, Catalyst)] +#[catalyst(bind = Base)] +#[allow(dead_code)] +struct Amyloid { + pub extra_bool: bool, + pub extra_string: String, + pub extra_option: Option, +} + +#[derive(Catalyst)] +#[catalyst(bind = Base)] +#[complex(name = "SmallCpx")] +#[allow(dead_code)] +#[complex(attribute(derive(Default)))] +struct SmallAmyloid { + pub extra_bool: bool, +} + +#[allow(dead_code)] +impl SmallCpx { + /// A reaction to change the substrate + pub fn reaction(&mut self) { + self.field_bool = true; + } +} + +#[cfg(test)] +mod tests { + use super::*; + use struct_patch::Complex; + + #[test] + fn complex_works() { + let mut small_complex = SmallCpx::default(); + assert_eq!(small_complex.field_bool, false); + assert_eq!(small_complex.field_string, String::new()); + assert_eq!(small_complex.field_option, None); + assert_eq!(small_complex.extra_bool, false); + + small_complex.reaction(); + + let (_cat, substrate) = small_complex.decouple(); + assert!(substrate.has_bool()); + + let amyloid = Amyloid::default(); + let complex = amyloid.bind(substrate); + assert_eq!(complex.field_bool, true); + } +} diff --git a/complex-example/substrate/Cargo.toml b/complex-example/substrate/Cargo.toml new file mode 100644 index 0000000..e7b8148 --- /dev/null +++ b/complex-example/substrate/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "substrate" +authors.workspace = true +version.workspace = true +edition.workspace = true +categories.workspace = true +keywords.workspace = true +license.workspace = true +readme.workspace = true +repository.workspace = true +description.workspace = true +rust-version.workspace = true + +[dependencies] +struct-patch.workspace = true + +[dev-dependencies] +syn = { version = "2", features = ["full"] } +syn-serde = { version = "0.3.2", features = ["json"] } diff --git a/complex-example/substrate/src/lib.rs b/complex-example/substrate/src/lib.rs new file mode 100644 index 0000000..88166f5 --- /dev/null +++ b/complex-example/substrate/src/lib.rs @@ -0,0 +1,30 @@ +#![allow(unused)] +use struct_patch::Substrate; + +#[derive(Default, Substrate)] +pub struct Base { + pub field_bool: bool, + pub field_string: String, + pub field_option: Option, +} + +impl Base { + pub fn has_bool(&self) -> bool { + self.field_bool + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + 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"}]}}}]}}}]}}}]}"# + ); + + let _fields: syn::Fields = syn_serde::json::from_str(&Base::expose_content()).unwrap(); + } +} diff --git a/derive/Cargo.toml b/derive/Cargo.toml index 29f7b62..ef1e6db 100644 --- a/derive/Cargo.toml +++ b/derive/Cargo.toml @@ -18,12 +18,14 @@ proc-macro = true proc-macro2 = "1.0" quote = "1.0" syn = { version = "2.0", features = ["parsing"] } +syn-serde = { version = "0.3.2", features = ["json"], optional = true } [features] status = [] op = [] merge = [] nesting = [] +catalyst = [ "syn-serde" ] [dev-dependencies] pretty_assertions_sorted = "1.2.3" diff --git a/derive/src/catalyst.rs b/derive/src/catalyst.rs new file mode 100644 index 0000000..43c4973 --- /dev/null +++ b/derive/src/catalyst.rs @@ -0,0 +1,273 @@ +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}; + +pub(crate) struct Catalyst { + visibility: syn::Visibility, + struct_name: Ident, + complex_struct_name: Ident, + generics: syn::Generics, + attributes: Vec, + fields: syn::Fields, + bind: String, +} + +struct Field { + ident: Option, + ty: Type, +} + +const CATALYST: &str = "catalyst"; +const COMPLEX: &str = "complex"; +const BIND: &str = "bind"; +const NAME: &str = "name"; +const ATTRIBUTE: &str = "attribute"; + +impl Catalyst { + /// let catalyst bind the substrate and generate the token stream for complex + pub fn to_token_stream(&self) -> Result { + let Catalyst { + visibility, + struct_name, + complex_struct_name, + generics, + attributes, + fields, + bind, + } = self; + + let substrate_name: Ident = { + let lit = LitStr::new(&bind, Span::call_site()); + lit.parse()? + }; + + // TODO refact + let mut raw_complex_fields: Vec = Vec::new(); + let mut substrate_fields: Vec = Vec::new(); + let mut catalyst_fields: Vec = Vec::new(); + + let substrate_str = std::env::var(bind).expect("field information of substrate is absent, please expose it in build.rs"); + let raw_substrate_fields: syn::Fields = syn_serde::json::from_str(&substrate_str).unwrap(); + + for field in raw_substrate_fields.into_iter() { + raw_complex_fields.push(Field::from_ast(field.clone())); + substrate_fields.push(Field::from_ast(field)); + } + + for field in fields.iter() { + raw_complex_fields.push(Field::from_ast(field.clone())); + catalyst_fields.push(Field::from_ast(field.clone())); + } + + let complex_fields = raw_complex_fields + .iter() + .map(|f| f.to_token_stream()) + .collect::>>()?; + + let unpack_complex_fields = raw_complex_fields + .iter() + .map(|f| f.to_unpack_stream()) + .collect::>>()?; + + let catalyst_fields = catalyst_fields + .iter() + .map(|f| f.to_unpack_stream()) + .collect::>>()?; + + let substrate_fields = substrate_fields + .iter() + .map(|f| f.to_unpack_stream()) + .collect::>>()?; + + let mapped_attributes = attributes + .iter() + .map(|a| { + quote! { + #[#a] + } + }) + .collect::>(); + let catalyst_impl = quote! { + impl struct_patch::traits::Catalyst < #substrate_name, #complex_struct_name > for #struct_name { + fn bind(self, s: #substrate_name) -> #complex_struct_name { + let #substrate_name { + #(#substrate_fields)* + } = s; + let #struct_name { + #(#catalyst_fields)* + } = self; + #complex_struct_name { + #(#unpack_complex_fields)* + } + } + } + }; + let complex_impl = quote! { + #(#mapped_attributes)* + #visibility struct #complex_struct_name #generics { + #(#complex_fields)* + } + + impl struct_patch::traits::Complex < #struct_name, #substrate_name > for #complex_struct_name { + fn decouple(self) -> (#struct_name, #substrate_name) { + let #complex_struct_name { + #(#unpack_complex_fields)* + } = self; + ( + #struct_name { + #(#catalyst_fields)* + }, + #substrate_name { + #(#substrate_fields)* + } + ) + } + } + }; + + Ok(quote! { + #catalyst_impl + #complex_impl + }) + } + /// Parse the Catalyst struct + pub fn from_ast( + DeriveInput { + ident, + data, + generics, + attrs, + vis, + }: syn::DeriveInput, + ) -> Result { + let fields = if let syn::Data::Struct(syn::DataStruct { fields, .. }) = data { + fields + } else { + return Err(syn::Error::new( + ident.span(), + "Catalyst derive only use for struct", + )); + }; + let mut name = None; + let mut attributes = vec![]; + let mut bind = String::new(); + + for attr in attrs { + let attr_str = attr.path().to_string(); + if attr_str != CATALYST && attr_str != COMPLEX { + continue; + } + + if let syn::Meta::List(meta) = &attr.meta { + if meta.tokens.is_empty() { + continue; + } + } + + attr.parse_nested_meta(|meta| { + let path = meta.path.to_string(); + match path.as_str() { + NAME if attr_str == COMPLEX => { + // #[complex(name = "PatchStruct")] + if let Some(lit) = crate::get_lit_str(path, &meta)? { + if name.is_some() { + return Err(meta + .error("The name attribute can't be defined more than once")); + } + name = Some(lit.parse()?); + } + } + ATTRIBUTE if attr_str == COMPLEX => { + // #[complex(attribute(derive(Deserialize)))] + let content; + parenthesized!(content in meta.input); + let attribute: TokenStream = content.parse()?; + attributes.push(attribute); + } + BIND if attr_str == CATALYST => { + // #[catalyst(bind = SubstrateStruct)] + if let Some(lit) = get_struct(&meta)? { + if bind.is_empty() { + bind = lit; + } + } + } + _ => { + return Err(meta.error(format_args!( + "unknown catalyst/complex attribute `{}`", + path.replace(' ', "") + ))); + } + } + Ok(()) + })?; + } + + if bind.is_empty() { + return Err(syn::Error::new(ident.span(), "No substrate for Catalyst, please specify with #[catalyst(bind = ...)]")); + } + let complex_struct_name = name.unwrap_or({ + let ts = TokenStream::from_str(&format!("{}Complex", &ident,)).unwrap(); + let lit = LitStr::new(&ts.to_string(), Span::call_site()); + lit.parse()? + }); + + Ok(Catalyst { + visibility: vis, + struct_name: ident, + complex_struct_name, + generics, + attributes, + fields, + bind, + }) + } +} + +impl Field { + /// Generate the token stream for the Complex struct fields + pub fn to_token_stream(&self) -> Result { + let Field { ident, ty } = self; + Ok(quote! { + pub #ident: #ty, + }) + } + + /// Generate the token stream for unpack Complex struct fields + pub fn to_unpack_stream(&self) -> Result { + let Field { ident, ty: _ } = self; + Ok(quote! { + #ident, + }) + } + + /// Parse the Catalyst struct field + pub fn from_ast(syn::Field { ident, ty, .. }: syn::Field) -> Field { + Field { ident, ty } + } +} + +trait ToStr { + fn to_string(&self) -> String; +} + +impl ToStr for syn::Path { + fn to_string(&self) -> String { + self.to_token_stream().to_string() + } +} + +fn get_struct(meta: &ParseNestedMeta) -> syn::Result> { + let expr: syn::Expr = meta.value()?.parse()?; + let mut value = &expr; + while let syn::Expr::Group(e) = value { + value = &e.expr; + } + if let syn::Expr::Path(syn::ExprPath { path, .. }) = value { + Ok(path.segments.last().map(|seg| seg.ident.to_string())) + } else { + Ok(None) + } +} diff --git a/derive/src/lib.rs b/derive/src/lib.rs index c7b6853..e41ce64 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -1,9 +1,17 @@ extern crate proc_macro; +#[cfg(feature = "catalyst")] +mod catalyst; mod filler; mod patch; +#[cfg(feature = "catalyst")] +mod substrate; +#[cfg(feature = "catalyst")] +use catalyst::Catalyst; use filler::Filler; use patch::Patch; +#[cfg(feature = "catalyst")] +use substrate::Substrate; use syn::meta::ParseNestedMeta; use syn::spanned::Spanned; @@ -34,6 +42,26 @@ pub fn derive_filler(item: proc_macro::TokenStream) -> proc_macro::TokenStream { .into() } +#[cfg(feature = "catalyst")] +#[proc_macro_derive(Substrate, attributes(substrate))] +pub fn derive_substrate(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + Substrate::from_ast(syn::parse_macro_input!(item as syn::DeriveInput)) + .unwrap() + .to_token_stream() + .unwrap() + .into() +} + +#[cfg(feature = "catalyst")] +#[proc_macro_derive(Catalyst, attributes(catalyst, complex))] +pub fn derive_catalyst(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + Catalyst::from_ast(syn::parse_macro_input!(item as syn::DeriveInput)) + .unwrap() + .to_token_stream() + .unwrap() + .into() +} + fn get_lit(attr_name: String, meta: &ParseNestedMeta) -> syn::Result> { let expr: syn::Expr = meta.value()?.parse()?; let mut value = &expr; @@ -52,3 +80,33 @@ fn get_lit(attr_name: String, meta: &ParseNestedMeta) -> syn::Result syn::Result> { + let expr: syn::Expr = meta.value()?.parse()?; + let mut value = &expr; + while let syn::Expr::Group(e) = value { + value = &e.expr; + } + if let syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Str(lit), + .. + }) = value + { + let suffix = lit.suffix(); + if !suffix.is_empty() { + return Err(Error::new( + lit.span(), + format!("unexpected suffix `{}` on string literal", suffix), + )); + } + Ok(Some(lit.clone())) + } else { + Err(Error::new( + expr.span(), + format!( + "expected serde {} attribute to be a string: `{} = \"...\"`", + attr_name, attr_name + ), + )) + } +} diff --git a/derive/src/patch.rs b/derive/src/patch.rs index af6c581..76eec67 100644 --- a/derive/src/patch.rs +++ b/derive/src/patch.rs @@ -2,10 +2,9 @@ extern crate proc_macro; use proc_macro2::{Ident, Span, TokenStream}; use quote::{quote, ToTokens}; use std::str::FromStr; -use syn::{ - meta::ParseNestedMeta, parenthesized, spanned::Spanned, DeriveInput, Error, Lit, LitStr, - Result, Type, -}; +#[cfg(not(feature = "op"))] +use syn::spanned::Spanned; +use syn::{parenthesized, DeriveInput, Lit, LitStr, Result, Type}; #[cfg(feature = "op")] use crate::Addable; @@ -592,7 +591,7 @@ impl Patch { match path.as_str() { NAME => { // #[patch(name = "PatchStruct")] - if let Some(lit) = get_lit_str(path, &meta)? { + if let Some(lit) = crate::get_lit_str(path, &meta)? { if name.is_some() { return Err(meta .error("The name attribute can't be defined more than once")); @@ -872,36 +871,6 @@ impl ToStr for syn::Path { } } -fn get_lit_str(attr_name: String, meta: &ParseNestedMeta) -> syn::Result> { - let expr: syn::Expr = meta.value()?.parse()?; - let mut value = &expr; - while let syn::Expr::Group(e) = value { - value = &e.expr; - } - if let syn::Expr::Lit(syn::ExprLit { - lit: syn::Lit::Str(lit), - .. - }) = value - { - let suffix = lit.suffix(); - if !suffix.is_empty() { - return Err(Error::new( - lit.span(), - format!("unexpected suffix `{}` on string literal", suffix), - )); - } - Ok(Some(lit.clone())) - } else { - Err(Error::new( - expr.span(), - format!( - "expected serde {} attribute to be a string: `{} = \"...\"`", - attr_name, attr_name - ), - )) - } -} - #[cfg(test)] mod tests { use pretty_assertions_sorted::assert_eq_sorted; diff --git a/derive/src/substrate.rs b/derive/src/substrate.rs new file mode 100644 index 0000000..e482de1 --- /dev/null +++ b/derive/src/substrate.rs @@ -0,0 +1,48 @@ +use proc_macro2::{Ident, TokenStream}; +use quote::quote; +use syn::{DeriveInput, Result}; +use syn_serde::json; + +pub(crate) struct Substrate { + struct_name: Ident, + fields: syn::Fields, +} + +impl Substrate { + /// Generate the token stream which provide expose for Substrate + pub fn to_token_stream(&self) -> Result { + let Substrate { + struct_name, + fields, + } = self; + + let active_site = json::to_string(fields); + + Ok(quote! { + impl struct_patch::traits::Substrate for #struct_name { + fn expose_content() -> &'static str { + #active_site + } + fn expose() { + println!("cargo:rustc-env={}={}", stringify!(#struct_name), Self::expose_content()); + } + } + }) + } + /// Parse the substrate struct + pub fn from_ast(DeriveInput { ident, data, .. }: syn::DeriveInput) -> Result { + let fields = if let syn::Data::Struct(syn::DataStruct { fields, .. }) = data { + fields + } else { + return Err(syn::Error::new( + ident.span(), + "Substrate derive only use for struct", + )); + }; + + Ok(Substrate { + struct_name: ident, + fields, + }) + } +} diff --git a/flake.nix b/flake.nix index 6b56f0a..4c693e6 100644 --- a/flake.nix +++ b/flake.nix @@ -22,15 +22,25 @@ sleep 10 cargo publish -p struct-patch ''; + checkCatalystScript = pkgs.writeShellScriptBin "check-catalyst" '' + cd $(git rev-parse --show-toplevel 2>/dev/null) + cd complex-example + cargo test -p substrate + cargo test -p catalyst + ''; updateDependencyScript = pkgs.writeShellScriptBin "update-dependency" '' dr ./Cargo.toml cd no-std-examples dr ./Cargo.toml - if [[ -f "Cargo.toml.old" || -f "no-std-examples/Cargo.toml.old" ]]; then + cd ../complex-example + dr ./Cargo.toml + + if [[ -f "Cargo.toml.old" || -f "no-std-examples/Cargo.toml.old" || -f "complex-example/Cargo.toml.old" ]]; then rm -f Cargo.toml.old rm -f no-std-examples/Cargo.toml.old + rm -f complex-example/Cargo.toml.old exit 1 fi ''; @@ -51,6 +61,8 @@ rust-bin.stable.latest.minimal openssl pkg-config + + checkCatalystScript ]; }; @@ -63,6 +75,8 @@ dr publishScript updateDependencyScript + + checkCatalystScript ]; }; diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 795af15..7616d8c 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -43,4 +43,6 @@ nesting = [ ] none_as_default = ["option"] keep_none = ["option"] - +catalyst = [ + "struct-patch-derive/catalyst" +] diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 61df30e..df03015 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -78,10 +78,16 @@ #[cfg(feature = "alloc")] extern crate alloc; +#[cfg(feature = "catalyst")] +#[doc(hidden)] +pub use struct_patch_derive::Catalyst; #[doc(hidden)] pub use struct_patch_derive::Filler; #[doc(hidden)] pub use struct_patch_derive::Patch; +#[cfg(feature = "catalyst")] +#[doc(hidden)] +pub use struct_patch_derive::Substrate; pub mod r#box; pub mod option; pub mod traits; diff --git a/lib/src/traits.rs b/lib/src/traits.rs index 87d8ea9..609e51c 100644 --- a/lib/src/traits.rs +++ b/lib/src/traits.rs @@ -111,3 +111,26 @@ pub trait Status { pub trait Merge { fn merge(self, other: Self) -> Self; } + +#[cfg(feature = "catalyst")] +/// A substrate struct that can expose the fields information thereof +pub trait Substrate { + fn expose_content() -> &'static str; + + /// Expose the field information, by call this function in Build.rs + fn expose(); +} + +#[cfg(feature = "catalyst")] +/// A catalyst struct that can expose the fields information thereof +pub trait Catalyst { + /// catalyst bind on substrate and generate complex + fn bind(self, substrate: S) -> Cpx; +} + +#[cfg(feature = "catalyst")] +/// A complex struct that can decouple return catalyst and substrate +pub trait Complex { + /// complex decouple to catalyst and substrate + fn decouple(self) -> (Cat, S); +}