custom-display 0.1.0

A trait for implementing custom formatting logic for types
Documentation
// SPDX-FileCopyrightText: 2026 Marissa (cuddle puddle) <dev@princess.lgbt>
//
// SPDX-License-Identifier: MPL-2.0

use std::cell::RefCell;
use std::fmt::{self, Arguments, Display, Formatter};

use crate::CustomDisplay;

/**
 * A helper for asserting that [`CustomDisplay::width_in_chars()`] returns the
 * number of characters that [`CustomDisplay::fmt()`] writes.
 *
 * This type implements [`Display`] by calling
 * [`CustomDisplay::width_in_chars()`] using formatter provided to
 * [`Display::fmt()`], storing the result of that call such that it can be
 * retrieved by calling [`last_computed_width()`], and then writing to the
 * [`Formatter`] with [`CustomDisplay::fmt()`].
 *
 * # Examples
 *
 * ```
 * //#[cfg(test)]
 * mod tests {
 *     use std::fmt::Display;
 *     use custom_display::CustomDisplay;
 *     use custom_display::testkit::AssertConsistentWidth;
 *
 *     // `CD: Display` is not required, but may be convenient for assert
 *     // messages if testing more that one `CustomDisplay` implementation with
 *     // one method.
 *     fn assert_consistent_width<CD>(display: &CD, value: &CD::Value)
 *     where
 *         CD: CustomDisplay + Display,
 *     {
 *         let helper = AssertConsistentWidth::new(display, value);
 *
 *         let formatted = format!("{helper}");
 *         if let Some(computed) = helper.last_computed_width() {
 *             assert_eq!(
 *                 computed,
 *                 formatted.chars().count(),
 *                 "[no options] `{display}` for value `{formatted}`",
 *             );
 *         }
 *
 *         let displayed = format!("{helper:.6}");
 *         if let Some(computed) = helper.last_computed_width() {
 *             assert_eq!(
 *                 computed,
 *                 formatted.chars().count(),
 *                 "[precision] `{display}` for value `{formatted}`",
 *             );
 *         }
 *
 *         let formatted = format!("{helper:.>10.6}");
 *         if let Some(computed) = helper.last_computed_width() {
 *             assert_eq!(
 *                 computed,
 *                 formatted.chars().count(),
 *                 "[width, fill, align, precision] `{display}` for value `{formatted}`",
 *             );
 *         }
 *     }
 * }
 *
 * ```
 *
 * [`last_computed_width()`]: Self::last_computed_width()
 */
#[derive(Debug)]
pub struct AssertConsistentWidth<'multi, CD>
// TODO: wrap `Displayable`?
where
    CD: CustomDisplay,
{
    display: &'multi CD,
    value: &'multi CD::Value,
    last_computed_width: RefCell<Result<Option<usize>, ()>>,
}

impl<'multi, CD> AssertConsistentWidth<'multi, CD>
where
    CD: CustomDisplay,
{
    /**
     * Creates a new `AssertConsistentWidth` from a [`CustomDisplay`] and a
     * value.
     */
    #[inline]
    pub fn new(custom_display: &'multi CD, value: &'multi CD::Value) -> Self {
        Self {
            display: custom_display,
            value,
            last_computed_width: RefCell::new(Err(())),
        }
    }

    /**
     * Returns the value computed by [`CustomDisplay::width_in_chars()`] in the
     * last call to [`Display::fmt()`] on this value.
     *
     * The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL
     * NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED",  "MAY", and
     * "OPTIONAL" in this document are to be interpreted as described in
     * RFC 2119.
     *
     * This method MUST NOT be called without having previously called
     * [`Display::fmt()`] on this value (usually indirectly by using it in a
     * [`format!`]-type macro).
     *
     * # Panics
     *
     * Panics if [`Display::fmt()`] has not been called on this value.
     */
    #[expect(
        clippy::panic,
        reason = "more ergonomic in test code than forcing `Result` handling"
    )]
    #[inline]
    pub fn last_computed_width(&self) -> Option<usize> {
        self.last_computed_width
            .borrow()
            .unwrap_or_else(|()| panic!("`Display::fmt()` has not been called on this value"))
    }
}

impl<CD> Display for AssertConsistentWidth<'_, CD>
where
    CD: CustomDisplay,
{
    #[inline]
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        let _discarded_previous = self
            .last_computed_width
            .replace(Ok(self.display.width_in_chars(self.value, f)));
        self.display.fmt(self.value, f)
    }
}

/**
 * Helper function for the [`assert_consistent_width!`] macro.
 *
 * Using this function directly may lead to incorrect or misleading errors.
 *
 * # Panics
 *
 * Panics if the width computed by [`CustomDisplay::width_in_chars()`] differs
 * from the width of the value written by [`CustomDisplay::fmt()`].
 *
 * [`assert_consistent_width!`]: crate::assert_consistent_width!
 */
#[inline]
pub fn assert_consistent_width_impl<CD>(
    helper: &AssertConsistentWidth<CD>,
    fmt_str: &'static str,
    fmt_args: Arguments<'_>,
) where
    CD: CustomDisplay,
{
    let formatted = fmt::format(fmt_args);
    if let Some(computed) = helper.last_computed_width() {
        assert_eq!(
            computed,
            formatted.chars().count(),
            "format `{fmt_str}` yielding `{formatted}`",
        );
    }
}