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}