1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317
#![allow(clippy::needless_doctest_main)]
//! # Automation of Destructure Pattern
//! `destructure` is a automation library for `destructure pattern`.
//!
//! ## Usage
//! ```rust
//! use destructure::Destructure;
//!
//! #[derive(Destructure)]
//! pub struct Book {
//! id: u64,
//! name: String,
//! stocked_at: String,
//! author: String,
//! // ... too many fields...
//! }
//!
//! fn main() {
//! let book = Book {
//! id: 1234_5678_9999_0000u64,
//! name: "name".to_string(),
//! stocked_at: "2023/01/03".to_string(),
//! author: "author".to_string()
//! };
//!
//! // Auto generate
//! let des: DestructBook = book.into_destruct();
//!
//! println!("{:?}", des.id);
//! }
//! ```
//!
//! ### What is `destructure pattern`?
//! A structure with too many fields makes it hard to call constructors,
//! but it is also hard work to prepare a `Getter/Setter` for each one.
//! There are macros for this purpose, but even so, a large number of macros reduces readability.
//! This is especially true when using `From<T>` Trait.
//!
//! So how can this be simplified? It is the technique of "converting all fields to public".
//!
//! This allows for a simplified representation, as in the following example
//!
//! ```rust
//! pub struct Book {
//! id: u64,
//! name: String,
//! stocked_at: String,
//! author: String,
//! // ... too many fields...
//! }
//!
//! impl Book {
//! pub fn into_destruct(self) -> DestructBook {
//! DestructBook {
//! id: self.id,
//! name: self.name,
//! stocked_at: self.stocked_at,
//! author: self.author,
//! }
//! }
//! }
//!
//! pub struct DestructBook {
//! pub id: u64,
//! pub name: String,
//! pub stocked_at: String,
//! pub author: String,
//! // ... too many fields (All `public`.)...
//! }
//!
//! fn main() {
//! let book = Book {
//! id: 1234_5678_9999_0000u64,
//! name: "name".to_string(),
//! stocked_at: "2023/01/03".to_string(),
//! author: "author".to_string()
//! };
//!
//! let des = book.into_destruct();
//!
//! println!("{:?}", des.id);
//! }
//! ```
//!
//! There are several problems with this method, the most serious of which is the increase in boilerplate.
//! Using the multi-cursor feature of the editor, this can be done by copy-pasting, but it is still a hassle.
//!
//! Therefore, I created a *Procedural Macro* that automatically generates structures and methods:
//!
//! ```rust
//! use destructure::Destructure;
//!
//! #[derive(Destructure)]
//! pub struct Book {
//! id: u64,
//! name: String,
//! stocked_at: String,
//! author: String,
//! // ... too many fields...
//! }
//!
//! fn main() {
//! let book = Book {
//! id: 1234_5678_9999_0000u64,
//! name: "name".to_string(),
//! stocked_at: "2023/01/03".to_string(),
//! author: "author".to_string()
//! };
//!
//! // Auto generate
//! let des: DestructBook = book.into_destruct();
//!
//! println!("{:?}", des.id);
//! }
//!```
//!
//! You can also perform safe value substitution by using `reconstruct()`,
//! which performs the same role as the following usage.
//! ```rust
//! use destructure::Destructure;
//!
//! #[derive(Debug, Eq, PartialEq, Clone, Destructure)]
//! pub struct Book {
//! id: u64,
//! name: String,
//! stocked_at: String,
//! author: String,
//! // ... too many fields...
//! }
//!
//! fn main() {
//! let before = Book {
//! id: 1234_5678_9999_0000u64,
//! name: "name".to_string(),
//! stocked_at: "2023/01/03".to_string(),
//! author: "author".to_string()
//! };
//!
//! let author = "After".to_string();
//!
//! // Auto generate
//! let after = before.clone().reconstruct(|before| {
//! before.author = author;
//! });
//!
//! assert_ne!(before, after);
//! }
//! ```
use proc_macro::TokenStream;
use quote::{quote, quote_spanned};
use syn::{
parse_macro_input,
DeriveInput,
Ident,
Data,
DataStruct,
Fields,
FieldsNamed,
};
/// Automatically implements `into_destruct()` and `freeze()` methods.
//noinspection DuplicatedCode
#[proc_macro_derive(Destructure)]
pub fn derive_destructure(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let name = &ast.ident;
let generate = format!("Destruct{}", name);
let generate_ident = Ident::new(&generate, name.span());
let fields = if let Data::Struct(DataStruct { fields: Fields::Named(FieldsNamed { ref named, ..}), .. }) = ast.data {
named
} else {
return quote_spanned! { name.span() => compile_error!("Only structures with named fields are supported.") }.into()
};
let destruction = fields.iter().map(|field| {
let name = &field.ident;
let ty = &field.ty;
quote! {
pub #name: #ty
}
});
let expanded = fields.iter().map(|field| {
let name = &field.ident;
quote! {
#name: self.#name
}
});
let freeze = expanded.clone();
let q = quote::quote! {
/// Do not have an explicit implementation for this structure.
pub struct #generate_ident {
#(#destruction,)*
}
impl #name {
/// Convert the field value to a fully disclosed Destruct structure.
///
/// If you wish to revert the Destruct structure back to the original structure, see `freeze()`.
pub fn into_destruct(self) -> #generate_ident {
#generate_ident { #(#expanded,)* }
}
/// It provides a mechanism for replacing the contents by [`into_destruct()`]
/// and changing the actual value by [`freeze()`] using a limited closure.
///
/// If you wish to use Result, see [`try_reconstruct()`].
pub fn reconstruct(self, f: impl FnOnce(&mut #generate_ident)) -> #name {
let mut dest = self.into_destruct();
f(&mut dest);
dest.freeze()
}
pub fn try_reconstruct<E>(self, f: impl FnOnce(&mut #generate_ident) -> Result<(), E>) -> Result<#name, E> {
let mut dest = self.into_destruct();
f(&mut dest)?;
Ok(dest.freeze())
}
}
impl #generate_ident {
/// Restore the Destruct structure to its original structure again.
pub fn freeze(self) -> #name {
#name { #(#freeze,)* }
}
}
};
q.into()
}
/// Automatically implements `substitute()` methods.
///
/// When performing loop processing, and so on,
/// it is more efficient than using [`reconstruct()`].
/// ## Usage
/// ```rust
/// use destructure::Mutation;
///
/// #[derive(Debug, Mutation)]
/// pub struct Book {
/// id: String,
/// name: String,
/// }
///
/// # fn main() {
/// # let mut book = Book { id: "123456789-abc".to_string(), name: "name".to_string() };
/// book.substitute(|book| {
/// *book.name = "new name".to_string();
/// });
///
/// book.try_substitute(|book| -> Result<(), std::io::Error> {
/// *book.name = "new name".to_string();
/// Ok(())
/// }).expect("Error");
/// # }
//noinspection DuplicatedCode
#[proc_macro_derive(Mutation)]
pub fn derive_mutation(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let name = &ast.ident;
let generate = format!("{}Mut", name);
let generate_ident = Ident::new(&generate, name.span());
let fields = if let Data::Struct(DataStruct { fields: Fields::Named(FieldsNamed { ref named, ..}), .. }) = ast.data {
named
} else {
return quote_spanned! { name.span() => compile_error!("Only structures with named fields are supported.") }.into()
};
let destruction = fields.iter().map(|field| {
let name = &field.ident;
let ty = &field.ty;
quote! {
pub #name: &'mutation mut #ty
}
});
let expanded = fields.iter().map(|field| {
let name = &field.ident;
quote! {
#name: &mut self.#name
}
});
let expanded_cloned = expanded.clone();
let q = quote::quote! {
/// Do not have an explicit implementation for this structure.
pub struct #generate_ident<'mutation> {
#(#destruction,)*
}
impl #name {
pub fn substitute(&mut self, mut f: impl FnOnce(&mut #generate_ident)) {
f(&mut #generate_ident {
#(#expanded,)*
})
}
pub fn try_substitute<E>(&mut self, mut f: impl FnOnce(&mut #generate_ident) -> Result<(), E>) -> Result<(), E> {
f(&mut #generate_ident {
#(#expanded_cloned,)*
})
}
}
};
q.into()
}