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