1 // SPDX-FileCopyrightText: 2020 Robin Krahl <[email protected]>
2 // SPDX-License-Identifier: Apache-2.0 or MIT
3 
4 //! Provides [`Merge`][], a trait for objects that can be merged.
5 //!
6 //! # Usage
7 //!
8 //! ```
9 //! trait Merge {
10 //!     fn merge(&mut self, other: Self);
11 //! }
12 //! ```
13 //!
14 //! The [`Merge`][] trait can be used to merge two objects of the same type into one.  The intended
15 //! use case is merging configuration from different sources, for example environment variables,
16 //! multiple configuration files and command-line arguments, see the [`args.rs`][] example.
17 //!
18 //! `Merge` is implemented for `Option` and can be derived for structs.  When deriving the `Merge`
19 //! trait for a struct, you can provide custom merge strategies for the fields that don’t implement
20 //! `Merge`.  A merge strategy is a function with the signature `fn merge<T>(left: &mut T, right:
21 //! T)` that merges `right` into `left`.  The submodules of this crate provide strategies for the
22 //! most common types, but you can also define your own strategies.
23 //!
24 //! ## Features
25 //!
26 //! This crate has the following features:
27 //!
28 //! - `derive` (default):  Enables the derive macro for the `Merge` trait using the `merge_derive`
29 //!   crate.
30 //! - `num` (default): Enables the merge strategies in the `num` module that require the
31 //!   `num_traits` crate.
32 //! - `std` (default): Enables the merge strategies in the `vec` module that require the standard
33 //!   library.  If this feature is not set, `merge` is a `no_std` library.
34 //!
35 //! # Example
36 //!
37 //! ```
38 //! use merge::Merge;
39 //!
40 //! #[derive(Merge)]
41 //! struct User {
42 //!     // Fields with the skip attribute are skipped by Merge
43 //!     #[merge(skip)]
44 //!     pub name: &'static str,
45 //!
46 //!     // The Merge implementation for Option replaces its value if it is None
47 //!     pub location: Option<&'static str>,
48 //!
49 //!     // The strategy attribute is used to customize the merge behavior
50 //!     #[merge(strategy = merge::vec::append)]
51 //!     pub groups: Vec<&'static str>,
52 //! }
53 //!
54 //! let defaults = User {
55 //!     name: "",
56 //!     location: Some("Internet"),
57 //!     groups: vec!["rust"],
58 //! };
59 //! let mut ferris = User {
60 //!     name: "Ferris",
61 //!     location: None,
62 //!     groups: vec!["mascot"],
63 //! };
64 //! ferris.merge(defaults);
65 //!
66 //! assert_eq!("Ferris", ferris.name);
67 //! assert_eq!(Some("Internet"), ferris.location);
68 //! assert_eq!(vec!["mascot", "rust"], ferris.groups);
69 //! ```
70 //!
71 //! [`Merge`]: trait.Merge.html
72 //! [`args.rs`]: https://git.sr.ht/~ireas/merge-rs/tree/master/examples/args.rs
73 
74 #![cfg_attr(not(feature = "std"), no_std)]
75 
76 #[cfg(feature = "derive")]
77 pub use merge_derive::*;
78 
79 /// A trait for objects that can be merged.
80 ///
81 /// # Deriving
82 ///
83 /// `Merge` can be derived for structs if the `derive` feature is enabled.  The generated
84 /// implementation calls the `merge` method for all fields, or the merge strategy function if set.
85 /// You can use these field attributes to configure the generated implementation:
86 /// - `skip`: Skip this field in the `merge` method.
87 /// - `strategy = f`: Call `f(self.field, other.field)` instead of calling the `merge` function for
88 ///    this field.
89 ///
90 /// # Examples
91 ///
92 /// Using the `Merge` implementation for `Option`:
93 ///
94 /// ```
95 /// use merge::Merge as _;
96 ///
97 /// let mut val = None;
98 /// val.merge(Some(42));
99 /// assert_eq!(Some(42), val);
100 /// ```
101 ///
102 /// Deriving `Merge` for a struct:
103 ///
104 /// ```
105 /// use merge::Merge;
106 ///
107 /// #[derive(Debug, PartialEq, Merge)]
108 /// struct S {
109 ///     option: Option<usize>,
110 ///
111 ///     #[merge(skip)]
112 ///     s: String,
113 ///
114 ///     #[merge(strategy = merge::bool::overwrite_false)]
115 ///     flag: bool,
116 /// }
117 ///
118 /// let mut val = S {
119 ///     option: None,
120 ///     s: "some ignored value".to_owned(),
121 ///     flag: false,
122 /// };
123 /// val.merge(S {
124 ///     option: Some(42),
125 ///     s: "some other ignored value".to_owned(),
126 ///     flag: true,
127 /// });
128 /// assert_eq!(S {
129 ///     option: Some(42),
130 ///     s: "some ignored value".to_owned(),
131 ///     flag: true,
132 /// }, val);
133 /// ```
134 pub trait Merge {
135     /// Merge another object into this object.
merge(&mut self, other: Self)136     fn merge(&mut self, other: Self);
137 }
138 
139 impl<T> Merge for Option<T> {
merge(&mut self, mut other: Self)140     fn merge(&mut self, mut other: Self) {
141         if !self.is_some() {
142             *self = other.take();
143         }
144     }
145 }
146 
147 /// Merge strategies for boolean types.
148 pub mod bool {
149     /// Overwrite left with right if the value of left is false.
overwrite_false(left: &mut bool, right: bool)150     pub fn overwrite_false(left: &mut bool, right: bool) {
151         if !*left {
152             *left = right;
153         }
154     }
155 
156     /// Overwrite left with right if the value of left is true.
overwrite_true(left: &mut bool, right: bool)157     pub fn overwrite_true(left: &mut bool, right: bool) {
158         if *left {
159             *left = right;
160         }
161     }
162 }
163 
164 /// Merge strategies for numeric types.
165 ///
166 /// These strategies are only available if the `num` feature is enabled.
167 #[cfg(feature = "num")]
168 pub mod num {
169     /// Set left to the saturated some of left and right.
saturating_add<T: num_traits::SaturatingAdd>(left: &mut T, right: T)170     pub fn saturating_add<T: num_traits::SaturatingAdd>(left: &mut T, right: T) {
171         *left = left.saturating_add(&right);
172     }
173 
174     /// Overwrite left with right if the value of left is zero.
overwrite_zero<T: num_traits::Zero>(left: &mut T, right: T)175     pub fn overwrite_zero<T: num_traits::Zero>(left: &mut T, right: T) {
176         if left.is_zero() {
177             *left = right;
178         }
179     }
180 }
181 
182 /// Merge strategies for types that form a total order.
183 pub mod ord {
184     use core::cmp;
185 
186     /// Set left to the maximum of left and right.
max<T: cmp::Ord>(left: &mut T, right: T)187     pub fn max<T: cmp::Ord>(left: &mut T, right: T) {
188         if cmp::Ord::cmp(left, &right) == cmp::Ordering::Less {
189             *left = right;
190         }
191     }
192 
193     /// Set left to the minimum of left and right.
min<T: cmp::Ord>(left: &mut T, right: T)194     pub fn min<T: cmp::Ord>(left: &mut T, right: T) {
195         if cmp::Ord::cmp(left, &right) == cmp::Ordering::Greater {
196             *left = right;
197         }
198     }
199 }
200 
201 /// Merge strategies for vectors.
202 ///
203 /// These strategies are only available if the `std` feature is enabled.
204 #[cfg(feature = "std")]
205 pub mod vec {
206     /// Overwrite left with right if left is empty.
overwrite_empty<T>(left: &mut Vec<T>, mut right: Vec<T>)207     pub fn overwrite_empty<T>(left: &mut Vec<T>, mut right: Vec<T>) {
208         if left.is_empty() {
209             left.append(&mut right);
210         }
211     }
212 
213     /// Append the contents of right to left.
append<T>(left: &mut Vec<T>, mut right: Vec<T>)214     pub fn append<T>(left: &mut Vec<T>, mut right: Vec<T>) {
215         left.append(&mut right);
216     }
217 
218     /// Prepend the contents of right to left.
prepend<T>(left: &mut Vec<T>, mut right: Vec<T>)219     pub fn prepend<T>(left: &mut Vec<T>, mut right: Vec<T>) {
220         right.append(left);
221         *left = right;
222     }
223 }
224