mycelium_util/
fmt.rs

1//! Text formatting utilities.
2pub use core::fmt::*;
3pub use tracing::field::{debug, display, DebugValue};
4
5/// A wrapper type that formats the wrapped value using a provided function.
6///
7/// This is used to implement the [`fmt::alt`](alt), [`fmt::bin`](bin),
8/// [`fmt::hex`](hex), and [`fmt::ptr`](ptr) functions.
9pub struct FormatWith<T, F = fn(&T, &mut Formatter<'_>) -> Result>
10where
11    F: Fn(&T, &mut Formatter<'_>) -> Result,
12{
13    value: T,
14    fmt: F,
15}
16
17/// Wraps a type implementing [`core::fmt::Write`] so that every newline written to
18/// that writer is indented a given amount.
19#[derive(Debug)]
20pub struct WithIndent<'writer, W> {
21    writer: &'writer mut W,
22    indent: usize,
23}
24
25/// Extension trait adding additional methods to types implementing [`core::fmt::Write`].
26pub trait WriteExt: Write {
27    /// Wraps `self` in a [`WithIndent`] writer that indents every new line
28    /// that's written to it by `indent` spaces.
29    #[must_use]
30    #[inline]
31    fn with_indent(&mut self, indent: usize) -> WithIndent<'_, Self>
32    where
33        Self: Sized,
34    {
35        WithIndent {
36            writer: self,
37            indent,
38        }
39    }
40}
41
42/// A utility to assist with formatting [`Option`] values.
43///
44/// This wraps a reference to an [`Option`] value, and implements formatting
45/// traits by formatting the `Option`'s contents if it is [`Some`]. If the
46/// `Option` is [`None`], the formatting trait implementations emit no text by
47/// default, or a string provided using the [`or_else`](Self::or_else) method.
48///
49/// A `FmtOption` will implement the [`core::fmt::Display`],
50/// [`core::fmt::Debug`], [`core::fmt::Binary`], [`core::fmt::UpperHex`],
51/// [`core::fmt::LowerHex`], and [`core::fmt::Pointer`] formatting traits, if
52/// the inner type implements the corresponding trait.
53///
54/// The [`fmt::opt`](opt) method can be used as shorthand to borrow an `Option`
55/// as a `FmtOption`.
56///
57/// # Examples
58///
59/// Formatting a [`Some`] value emits that value's [`Debug`] and [`Display`] output:
60///
61/// ```
62/// use mycelium_util::fmt;
63///
64/// let value = Some("hello world");
65/// let debug = format!("{:?}", fmt::opt(&value));
66/// assert_eq!(debug, "\"hello world\"");
67///
68/// let display = format!("{}", fmt::opt(&value));
69/// assert_eq!(display, "hello world");
70/// ```
71///
72/// Formatting a [`None`] value generates no text by default:
73///
74/// ```
75/// use mycelium_util::fmt;
76///
77/// let value: Option<&str> = None;
78/// let debug = format!("{:?}", fmt::opt(&value));
79/// assert_eq!(debug, "");
80///
81/// let display = format!("{}", fmt::opt(&value));
82/// assert_eq!(display, "");
83/// ```
84///
85/// The [`or_else`](Self::or_else) method can be used to customize the text that
86/// is emitted when the value is [`None`]:
87///
88/// ```
89/// use mycelium_util::fmt;
90/// use core::ptr::NonNull;
91///
92/// let value: Option<NonNull<u8>> = None;
93/// let debug = format!("{:?}", fmt::opt(&value).or_else("null"));
94/// assert_eq!(debug, "null");
95/// ```
96#[derive(Clone)]
97pub struct FmtOption<'a, T> {
98    opt: Option<&'a T>,
99    or_else: &'a str,
100}
101
102/// Format the provided value using its [`core::fmt::Pointer`] implementation.
103///
104/// # Examples
105/// ```
106/// use mycelium_util::fmt;
107/// use tracing::debug;
108///
109/// let something = "a string";
110/// let some_ref = &something;
111///
112/// debug!(x = ?some_ref);            // will format the pointed value ("a string")
113/// debug!(x = fmt::ptr(some_ref)); // will format the address.
114///
115/// ```
116#[inline]
117#[must_use]
118pub fn ptr<T: Pointer>(value: T) -> DebugValue<FormatWith<T>> {
119    tracing::field::debug(FormatWith {
120        value,
121        fmt: Pointer::fmt,
122    })
123}
124
125/// Format the provided value using its [`core::fmt::LowerHex`] implementation.
126///
127/// # Examples
128/// ```
129/// use mycelium_util::fmt;
130/// use tracing::debug;
131///
132/// let n = 0xf00;
133///
134/// debug!(some_number = ?n);            // will be formatted as "some_number=3840"
135/// debug!(some_number = fmt::hex(n)); //will be formatted as "some_number=0xf00"
136///
137/// ```
138#[inline]
139#[must_use]
140pub fn hex<T: LowerHex>(value: T) -> DebugValue<FormatWith<T>> {
141    tracing::field::debug(FormatWith {
142        value,
143        fmt: |value, f: &mut Formatter<'_>| write!(f, "{value:#x}"),
144    })
145}
146
147/// Format the provided value using its [`core::fmt::Binary`] implementation.
148///
149/// # Examples
150/// ```
151/// use mycelium_util::fmt;
152/// use tracing::debug;
153///
154/// let n = 42;
155///
156/// debug!(some_number = ?n);            // will be formatted as "some_number=42"
157/// debug!(some_number = fmt::bin(n)); //will be formatted as "some_number=0b101010"
158///
159/// ```
160#[must_use]
161#[inline]
162pub fn bin<T: Binary>(value: T) -> DebugValue<FormatWith<T>> {
163    tracing::field::debug(FormatWith {
164        value,
165        fmt: |value, f: &mut Formatter<'_>| write!(f, "{value:#b}"),
166    })
167}
168
169/// Format the provided value using its [`core::fmt::Debug`] implementation,
170/// with "alternate mode" set
171///
172/// # Examples
173/// ```
174/// use mycelium_util::fmt;
175/// use tracing::debug;
176///
177/// #[derive(Debug)]
178/// struct Thing {
179///     question: &'static str,
180///     answer: usize,
181/// }
182/// let thing = Thing {
183///     question: "life, the universe, and everything",
184///     answer: 42,
185/// };
186///
187/// debug!(something = ?thing);             // will be formatted on the current line
188/// debug!(something = fmt::alt(&thing)); // will be formatted with newlines and indentation
189///
190/// ```
191#[must_use]
192#[inline]
193pub fn alt<T: Debug>(value: T) -> DebugValue<FormatWith<T>> {
194    tracing::field::debug(FormatWith {
195        value,
196        fmt: |value, f: &mut Formatter<'_>| write!(f, "{value:#?}"),
197    })
198}
199
200/// Borrows an [`Option`] as a [`FmtOption`] that formats the inner value if
201/// the [`Option`] is [`Some`], or emits a customizable string if the [`Option`]
202/// is [`None`].
203///
204/// # Examples
205///
206/// Formatting a [`Some`] value emits that value's [`Debug`] and [`Display`] output:
207///
208/// ```
209/// use mycelium_util::fmt;
210///
211/// let value = Some("hello world");
212/// let debug = format!("{:?}", fmt::opt(&value));
213/// assert_eq!(debug, "\"hello world\"");
214///
215/// let display = format!("{}", fmt::opt(&value));
216/// assert_eq!(display, "hello world");
217/// ```
218///
219/// Formatting a [`None`] value generates no text by default:
220///
221/// ```
222/// use mycelium_util::fmt;
223///
224/// let value: Option<&str> = None;
225/// let debug = format!("{:?}", fmt::opt(&value));
226/// assert_eq!(debug, "");
227///
228/// let display = format!("{}", fmt::opt(&value));
229/// assert_eq!(display, "");
230/// ```
231///
232/// The [`or_else`](FmtOption::or_else) method can be used to customize the text that
233/// is emitted when the value is [`None`]:
234///
235/// ```
236/// use mycelium_util::fmt;
237/// use core::ptr::NonNull;
238///
239/// let value: Option<NonNull<u8>> = None;
240/// let debug = format!("{:?}", fmt::opt(&value).or_else("null"));
241/// assert_eq!(debug, "null");
242/// ```
243#[must_use]
244#[inline]
245pub const fn opt<T>(value: &Option<T>) -> FmtOption<'_, T> {
246    FmtOption::new(value)
247}
248
249/// Formats a list of `F`-typed values to the provided `writer`, delimited with commas.
250pub fn comma_delimited<F: Display>(
251    mut writer: impl Write,
252    values: impl IntoIterator<Item = F>,
253) -> Result {
254    let mut values = values.into_iter();
255    if let Some(value) = values.next() {
256        write!(writer, "{value}")?;
257        for value in values {
258            write!(writer, ", {value}")?;
259        }
260    }
261
262    Ok(())
263}
264
265impl<T, F> Debug for FormatWith<T, F>
266where
267    F: Fn(&T, &mut Formatter<'_>) -> Result,
268{
269    #[inline]
270    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
271        (self.fmt)(&self.value, f)
272    }
273}
274
275impl<W: Write> Write for WithIndent<'_, W> {
276    fn write_str(&mut self, mut s: &str) -> Result {
277        while !s.is_empty() {
278            let (split, nl) = match s.find('\n') {
279                Some(pos) => (pos + 1, true),
280                None => (s.len(), false),
281            };
282            self.writer.write_str(&s[..split])?;
283            if nl {
284                for _ in 0..self.indent {
285                    self.writer.write_char(' ')?;
286                }
287            }
288            s = &s[split..];
289        }
290
291        Ok(())
292    }
293}
294
295impl<W> WriteExt for W where W: Write {}
296
297// === impl FmtOption ===
298
299impl<'a, T> FmtOption<'a, T> {
300    /// Returns a new `FmtOption` that formats the provided [`Option`] value.
301    ///
302    /// The [`fmt::opt`](opt) function can be used as shorthand for this.
303    #[must_use]
304    #[inline]
305    pub const fn new(opt: &'a Option<T>) -> Self {
306        Self {
307            opt: opt.as_ref(),
308            or_else: "",
309        }
310    }
311
312    /// Set the text to emit when the value is [`None`].
313    ///
314    /// # Examples
315    ///
316    /// If the value is [`None`], the `or_else` text is emitted:
317    ///
318    /// ```
319    /// use mycelium_util::fmt;
320    /// use core::ptr::NonNull;
321    ///
322    /// let value: Option<NonNull<u8>> = None;
323    /// let debug = format!("{:?}", fmt::opt(&value).or_else("null"));
324    /// assert_eq!(debug, "null");
325    /// ```
326    ///
327    /// If the value is [`Some`], this function does nothing:
328    ///
329    /// ```
330    /// # use mycelium_util::fmt;
331    /// # use core::ptr::NonNull;
332    /// let value = Some("hello world");
333    /// let debug = format!("{}", fmt::opt(&value).or_else("no string"));
334    /// assert_eq!(debug, "hello world");
335    /// ```
336    #[must_use]
337    #[inline]
338    pub fn or_else(self, or_else: &'a str) -> Self {
339        Self { or_else, ..self }
340    }
341}
342
343impl<T: Debug> Debug for FmtOption<'_, T> {
344    #[inline]
345    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
346        match self.opt {
347            Some(val) => val.fmt(f),
348            None => f.write_str(self.or_else),
349        }
350    }
351}
352
353impl<T: Display> Display for FmtOption<'_, T> {
354    #[inline]
355    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
356        match self.opt {
357            Some(val) => val.fmt(f),
358            None => f.write_str(self.or_else),
359        }
360    }
361}
362
363impl<T: Binary> Binary for FmtOption<'_, T> {
364    #[inline]
365    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
366        match self.opt {
367            Some(val) => val.fmt(f),
368            None => f.write_str(self.or_else),
369        }
370    }
371}
372
373impl<T: UpperHex> UpperHex for FmtOption<'_, T> {
374    #[inline]
375    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
376        match self.opt {
377            Some(val) => val.fmt(f),
378            None => f.write_str(self.or_else),
379        }
380    }
381}
382
383impl<T: LowerHex> LowerHex for FmtOption<'_, T> {
384    #[inline]
385    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
386        match self.opt {
387            Some(val) => val.fmt(f),
388            None => f.write_str(self.or_else),
389        }
390    }
391}
392
393impl<T: Pointer> Pointer for FmtOption<'_, T> {
394    #[inline]
395    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
396        match self.opt {
397            Some(val) => val.fmt(f),
398            None => f.write_str(self.or_else),
399        }
400    }
401}