Skip to main content

context_trait/
macros.rs

1//! Declarative macros for scoped context usage and extensibility.
2
3/// Wraps items in [`WithContext<T, OrdContext<T>>`](crate::WithContext) with
4/// a custom comparator, runs a callback, and provides the wrapped slice.
5///
6/// # Examples
7///
8/// ```
9/// use context_trait::{with_ord, WithContext, OrdContext};
10///
11/// let items = vec![3i32, 1, 4, 1, 5];
12/// with_ord!(items, |a: &i32, b: &i32| b.cmp(a), |wrapped: &[WithContext<i32, OrdContext<i32>>]| {
13///     let mut sorted = wrapped.to_vec();
14///     sorted.sort();
15///     let values: Vec<i32> = sorted.into_iter().map(|w| w.inner).collect();
16///     assert_eq!(values, vec![5, 4, 3, 1, 1]);
17/// });
18/// ```
19#[macro_export]
20macro_rules! with_ord {
21    ($items:expr, $cmp:expr, $body:expr) => {{
22        let ctx = $crate::OrdContext { compare: $cmp };
23        let wrapped: Vec<$crate::WithContext<_, $crate::OrdContext<_>>> = $items
24            .iter()
25            .map(|v| $crate::WithContext {
26                inner: v.clone(),
27                ctx,
28            })
29            .collect();
30        let f: &dyn Fn(&[_]) -> _ = &$body;
31        f(&wrapped)
32    }};
33}
34
35/// Wraps items in [`WithContext<T, HashContext<T>>`](crate::WithContext) with
36/// a custom hash function, runs a callback, and provides the wrapped slice.
37///
38/// # Examples
39///
40/// ```
41/// use context_trait::{with_hash, WithContext, HashContext};
42/// use std::hash::Hasher;
43///
44/// let items = vec![(1, 100), (1, 200), (2, 300)];
45/// with_hash!(items, |v: &(i32, i32), h: &mut dyn Hasher| { h.write_i32(v.0); },
46///     |wrapped: &[WithContext<(i32, i32), HashContext<(i32, i32)>>]| {
47///     let _: Vec<_> = wrapped.to_vec();
48/// });
49/// ```
50#[macro_export]
51macro_rules! with_hash {
52    ($items:expr, $hash_fn:expr, $body:expr) => {{
53        let ctx = $crate::HashContext { hash: $hash_fn };
54        let wrapped: Vec<$crate::WithContext<_, $crate::HashContext<_>>> = $items
55            .iter()
56            .map(|v| $crate::WithContext {
57                inner: v.clone(),
58                ctx,
59            })
60            .collect();
61        let f: &dyn Fn(&[_]) -> _ = &$body;
62        f(&wrapped)
63    }};
64}
65
66/// Wraps items in [`WithContext<T, DisplayContext<T>>`](crate::WithContext) with
67/// a custom display function, runs a callback, and provides the wrapped slice.
68///
69/// # Examples
70///
71/// ```
72/// use context_trait::{with_display, WithContext, DisplayContext};
73///
74/// let items = vec![1i32, 2, 3];
75/// with_display!(
76///     items,
77///     |v: &i32, f: &mut std::fmt::Formatter| write!(f, "#{v}"),
78///     |wrapped: &[WithContext<i32, DisplayContext<i32>>]| {
79///         let strs: Vec<String> = wrapped.iter().map(|w| format!("{w}")).collect();
80///         assert_eq!(strs, vec!["#1", "#2", "#3"]);
81///     }
82/// );
83/// ```
84#[macro_export]
85macro_rules! with_display {
86    ($items:expr, $display_fn:expr, $body:expr) => {{
87        let ctx = $crate::DisplayContext {
88            display: $display_fn,
89        };
90        let wrapped: Vec<$crate::WithContext<_, $crate::DisplayContext<_>>> = $items
91            .iter()
92            .map(|v| $crate::WithContext {
93                inner: v.clone(),
94                ctx,
95            })
96            .collect();
97        let f: &dyn Fn(&[_]) -> _ = &$body;
98        f(&wrapped)
99    }};
100}
101
102/// Define a new context type and its trait implementation for [`WithContext`](crate::WithContext).
103///
104/// This macro generates a context struct holding a function pointer and
105/// implements the specified trait for `WithContext<T, YourContext<T>>`.
106///
107/// # Examples
108///
109/// ```
110/// use context_trait::{impl_context_trait, WithContext};
111/// use std::fmt;
112///
113/// // Define a trait we want to contextualize
114/// trait Summarize {
115///     fn summarize(&self) -> String;
116/// }
117///
118/// // Generate SummarizeContext and impl Summarize for WithContext
119/// impl_context_trait! {
120///     /// A context for custom summarization.
121///     context SummarizeContext<T> {
122///         field summarize_fn: fn(&T) -> String
123///     }
124///     impl Summarize for WithContext<T, SummarizeContext<T>> {
125///         fn summarize(&self) -> String {
126///             (self.ctx.summarize_fn)(&self.inner)
127///         }
128///     }
129/// }
130///
131/// struct Article { title: String, body: String }
132///
133/// let ctx = SummarizeContext {
134///     summarize_fn: |a: &Article| format!("{}...", &a.title),
135/// };
136/// let wrapped = WithContext {
137///     inner: Article { title: "Hello".into(), body: "World".into() },
138///     ctx,
139/// };
140/// assert_eq!(wrapped.summarize(), "Hello...");
141/// ```
142#[macro_export]
143macro_rules! impl_context_trait {
144    (
145        $(#[$ctx_meta:meta])*
146        context $ctx_name:ident<$t:ident> {
147            field $field:ident : $field_ty:ty
148        }
149        impl $trait_name:ident for WithContext<$t2:ident, $ctx_name2:ident<$t3:ident>> {
150            $(
151                fn $method:ident(&$self:ident $(, $arg:ident : $arg_ty:ty)* ) -> $ret:ty {
152                    $($body:tt)*
153                }
154            )+
155        }
156    ) => {
157        $(#[$ctx_meta])*
158        pub struct $ctx_name<$t> {
159            /// The function pointer providing the implementation.
160            pub $field: $field_ty,
161        }
162
163        impl<$t> Clone for $ctx_name<$t> {
164            fn clone(&self) -> Self { *self }
165        }
166        impl<$t> Copy for $ctx_name<$t> {}
167        impl<$t> std::fmt::Debug for $ctx_name<$t> {
168            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169                f.debug_struct(stringify!($ctx_name)).finish()
170            }
171        }
172
173        impl<$t2> $trait_name for $crate::WithContext<$t2, $ctx_name2<$t3>> {
174            $(
175                fn $method(&$self $(, $arg : $arg_ty)*) -> $ret {
176                    $($body)*
177                }
178            )+
179        }
180    };
181}
182
183#[cfg(test)]
184mod tests {
185    use crate::*;
186    use std::collections::BTreeSet;
187    use std::hash::Hasher;
188
189    #[test]
190    fn with_ord_sort() {
191        let items = [3i32, 1, 4, 1, 5, 9];
192        with_ord!(
193            items,
194            |a: &i32, b: &i32| b.cmp(a),
195            |wrapped: &[WithContext<i32, OrdContext<i32>>]| {
196                let mut sorted = wrapped.to_vec();
197                sorted.sort();
198                let values: Vec<i32> = sorted.into_iter().map(|w| w.inner).collect();
199                assert_eq!(values, vec![9, 5, 4, 3, 1, 1]);
200            }
201        );
202    }
203
204    #[test]
205    fn with_ord_btreeset() {
206        let items = [3i32, 1, 4, 1, 5];
207        with_ord!(
208            items,
209            |a: &i32, b: &i32| b.cmp(a),
210            |wrapped: &[WithContext<i32, OrdContext<i32>>]| {
211                let set: BTreeSet<_> = wrapped.iter().cloned().collect();
212                let values: Vec<i32> = set.into_iter().map(|w| w.inner).collect();
213                // BTreeSet deduplicates, reverse order
214                assert_eq!(values, vec![5, 4, 3, 1]);
215            }
216        );
217    }
218
219    #[test]
220    #[allow(clippy::type_complexity)]
221    fn with_hash_custom() {
222        use std::collections::hash_map::DefaultHasher;
223        use std::hash::Hash;
224
225        let items = [(1, 100), (1, 200)];
226        with_hash!(
227            items,
228            |v: &(i32, i32), h: &mut dyn Hasher| {
229                h.write_i32(v.0);
230            },
231            |wrapped: &[WithContext<(i32, i32), HashContext<(i32, i32)>>]| {
232                let hash_of = |w: &WithContext<(i32, i32), HashContext<(i32, i32)>>| -> u64 {
233                    let mut h = DefaultHasher::new();
234                    w.hash(&mut h);
235                    h.finish()
236                };
237                assert_eq!(hash_of(&wrapped[0]), hash_of(&wrapped[1]));
238            }
239        );
240    }
241
242    #[test]
243    fn with_display_custom() {
244        let items = [1i32, 2, 3];
245        with_display!(
246            items,
247            |v: &i32, f: &mut std::fmt::Formatter| write!(f, "[{v}]"),
248            |wrapped: &[WithContext<i32, DisplayContext<i32>>]| {
249                let strs: Vec<String> = wrapped.iter().map(|w| format!("{w}")).collect();
250                assert_eq!(strs, vec!["[1]", "[2]", "[3]"]);
251            }
252        );
253    }
254
255    // Test impl_context_trait! macro
256    trait Summarize {
257        fn summarize(&self) -> String;
258    }
259
260    impl_context_trait! {
261        /// Context for custom summarization.
262        context SummarizeContext<T> {
263            field summarize_fn: fn(&T) -> String
264        }
265        impl Summarize for WithContext<T, SummarizeContext<T>> {
266            fn summarize(&self) -> String {
267                (self.ctx.summarize_fn)(&self.inner)
268            }
269        }
270    }
271
272    #[test]
273    fn custom_context_trait() {
274        struct Article {
275            title: String,
276        }
277
278        let ctx = SummarizeContext {
279            summarize_fn: |a: &Article| format!("Title: {}", a.title),
280        };
281        let wrapped = WithContext {
282            inner: Article {
283                title: "Hello".into(),
284            },
285            ctx,
286        };
287        assert_eq!(wrapped.summarize(), "Title: Hello");
288    }
289}