deep_struct_update/
lib.rs

1//! [Struct update syntax](https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-instances-from-other-instances-with-struct-update-syntax) with support for nested structs.
2//!
3//! ```
4//! use deep_struct_update::update;
5//!
6//! struct Person {
7//!     name: String,
8//!     job: Job,
9//!     age: u8
10//! }
11//!
12//! struct Job {
13//!     position: String,
14//!     company: String
15//! }
16//!
17//! let bob = Person {
18//!     name: String::from("Bob"),
19//!     job: Job {
20//!         position: String::from("Programmer"),
21//!         company: String::from("Evil Inc.")
22//!     },
23//!     age: 29
24//! };
25//!
26//! let alice = update! {
27//!     name: String::from("Alice"),
28//!     age: 31,
29//!     job: {
30//!         position: String::from("Senior Programmer")
31//!     }
32//!     ..bob
33//! };
34//!
35//! assert_eq!(alice.age, 31);
36//! assert_eq!(alice.name, "Alice");
37//! assert_eq!(alice.job.position, "Senior Programmer");
38//! assert_eq!(alice.job.company, "Evil Inc.");
39//! ```
40
41/// Update a struct value.
42///
43/// Can be called with a `..<expr>` at the end (like normal struct update syntax) or a `<expr>,` at the beginning.
44///
45/// ```
46/// use deep_struct_update::update;
47///
48/// #[derive(Default)]
49/// struct A {
50///     b: B
51/// }
52///
53/// #[derive(Default)]
54/// struct B {
55///     c: C
56/// }
57///
58/// #[derive(Default)]
59/// struct C {
60///     num: i32
61/// }
62///
63/// let a = update! {
64///     b: { c: { num: 42 }}
65///     ..A::default()
66/// };
67///
68/// assert_eq!(a.b.c.num, 42);
69/// ```
70#[macro_export]
71macro_rules! update {
72    {$($t:tt)*} => {
73        $crate::find_expr! { {} $($t)* }
74    };
75    {$e:expr, $($t:tt)*} => {{
76        let mut temp = $e;
77        $crate::update_inner! {
78            @main temp, $($t)*
79        }
80        temp
81    }};
82}
83
84#[doc(hidden)]
85#[macro_export]
86macro_rules! find_expr {
87    {{$($update:tt)*} ..$e:expr} => {{
88        let mut temp = $e;
89        $crate::update_inner! {
90            @main temp, { $($update)* }
91        }
92        temp
93    }};
94    {{$($update:tt)*} $next:tt $($t:tt)*} => {
95        $crate::find_expr! {
96            {$($update)* $next} $($t)*
97        }
98    };
99    {{$($update:tt)*}} => {
100        compile_error!(concat!("Expected `..<expr>` at the end or `<expr>,` at the beginning. Got: \n\n", stringify!($($update)*), "\n\n"))
101    };
102}
103
104#[doc(hidden)]
105#[macro_export]
106macro_rules! update_inner {
107    (@main $e:expr, { $($t:tt)* }) => {
108        $crate::update_inner! {
109            @helper {$e} {} {} $($t)*
110        }
111    };
112    (@main $e:expr, $value:expr $(,)?) => {
113        $e = $value;
114    };
115    (@helper {$e:expr} {$($parsed_name:ident {$($parsed_inner:tt)*})*} {$($current:tt)*} $name:ident:) => {
116        $(
117            $crate::update_inner! { @main $e.$parsed_name, $($parsed_inner)* }
118        )*
119        $crate::update_inner! { @main $e.$name, $($current)* }
120    };
121    (@helper {$e:expr} {$($parsed_name:ident {$($parsed_inner:tt)*})*} {$($current:tt)*} $name:ident: , $other:ident $($rest:tt)*) => {
122        $crate::update_inner! {
123            @helper {$e} {$($parsed_name {$($parsed_inner)*})* $name {$($current)*}} {} $other $($rest)*
124        }
125    };
126    (@helper {$e:expr} {$($parsed_name:ident {$($parsed_inner:tt)*})*} {$($current:tt)*} $name:ident: $t:tt $($rest:tt)*) => {
127        $crate::update_inner! {
128            @helper {$e} {$($parsed_name {$($parsed_inner)*})* } {$($current)* $t} $name: $($rest)*
129        }
130    };
131}