manyfmt/lib.rs
1//! Adapters with which you can easily create more varieties of formatting than the [`std::fmt`]
2//! traits ([`fmt::Display`], [`fmt::Debug`], [`fmt::Binary`], [`fmt::Pointer`], etc.) offer,
3//! without having to write any more boilerplate than absolutely necessary.
4//! You can also easily pass additional data down through the formatting recursion.
5//!
6//! To create a new format, declare a struct (`struct MyFormat;`) and implement
7//! [`Fmt<MyFormat>`](Fmt) for the type you want to be able to format. Then call [`Refmt::refmt()`]
8//! to apply the format as a wrapper type around your data.
9//!
10//! # Example
11//!
12//! The following code implements a minimal, non-validating TOML emitter.
13//! This demonstrates how `manyfmt` can be used to produce complex formatting, that operates
14//! within the [`std::fmt`] system without allocating, without writing a new by-reference wrapper
15//! type for each case.
16//!
17//! ```
18//! use std::collections::BTreeMap;
19//! use std::fmt;
20//!
21//! use manyfmt::{Fmt, Refmt};
22//!
23//! struct TomlFile;
24//! struct TomlTable;
25//!
26//! impl<S: AsRef<str>, T: Fmt<TomlTable>> Fmt<TomlFile> for BTreeMap<S, T> {
27//! fn fmt(&self, fmt: &mut fmt::Formatter<'_>, _: &TomlFile) -> fmt::Result {
28//! for (key, table) in self {
29//! writeln!(
30//! fmt,
31//! "[{key}]\n{table}",
32//! key = key.as_ref(),
33//! table = table.refmt(&TomlTable)
34//! )?;
35//! }
36//! Ok(())
37//! }
38//! }
39//!
40//! impl<S: AsRef<str>, T: fmt::Debug> Fmt<TomlTable> for BTreeMap<S, T> {
41//! fn fmt(&self, fmt: &mut fmt::Formatter<'_>, _: &TomlTable) -> fmt::Result {
42//! for (key, value) in self {
43//! // A real implementation would use TOML-specific value formatting
44//! // rather than `Debug`, which promises nothing.
45//! writeln!(fmt, "{key} = {value:?}", key = key.as_ref())?;
46//! }
47//! Ok(())
48//! }
49//! }
50//!
51//! let data = BTreeMap::from([
52//! ("package", BTreeMap::from([("name", "manyfmt"), ("edition", "2021")])),
53//! ("lib", BTreeMap::from([("name", "manyfmt")])),
54//! ]);
55//!
56//! let text = data.refmt(&TomlFile).to_string();
57//!
58//! assert_eq!(text,
59//! r#"[lib]
60//! name = "manyfmt"
61//!
62//! [package]
63//! edition = "2021"
64//! name = "manyfmt"
65//!
66//! "#);
67//! ```
68
69#![no_std]
70#![forbid(elided_lifetimes_in_paths)]
71#![forbid(unsafe_code)]
72#![warn(clippy::cast_lossless)]
73#![warn(clippy::exhaustive_enums)]
74#![warn(clippy::exhaustive_structs)]
75#![warn(clippy::missing_panics_doc)]
76#![warn(clippy::return_self_not_must_use)]
77#![warn(clippy::wrong_self_convention)]
78#![warn(missing_docs)]
79#![warn(unused_lifetimes)]
80
81#[cfg(any(doc, test))]
82#[macro_use]
83extern crate std;
84
85use core::fmt;
86
87pub mod formats;
88
89/// Implement this trait to provide a new kind of formatting, `F`, for values of type `Self`.
90///
91/// The type `F` may be used to carry formatting options, or it may be a simple unit struct
92/// which merely serves to select the implementation. See [the crate documentation](crate) for
93/// examples.
94pub trait Fmt<F: ?Sized> {
95 /// Formats `self` as specified by `fopt` into destination `fmt`.
96 ///
97 /// # Errors
98 ///
99 /// Returns [`Err`] if and only if `fmt` returns [`Err`]. Implementations should never return an
100 /// error in any other circumstance, as this would, for example, cause uses of [`ToString`] or
101 /// [`format!`] to panic.
102 ///
103 /// [`ToString`]: std::string::ToString
104 fn fmt(&self, fmt: &mut fmt::Formatter<'_>, fopt: &F) -> fmt::Result;
105}
106
107/// Wrap `value` so that when formatted with [`fmt::Debug`] or [`fmt::Display`], it uses
108/// the given [`Fmt`] custom format type instead.
109///
110/// This operation is also available as the extension trait method [`Refmt::refmt()`].
111#[inline]
112pub fn refmt<'a, F: ?Sized, T: ?Sized>(fopt: &'a F, value: &'a T) -> Wrapper<'a, F, T>
113where
114 T: Fmt<F>,
115{
116 Wrapper { fopt, value }
117}
118
119/// Extension trait providing the [`.refmt()`](Self::refmt) convenience method.
120//---
121// Design note: `F` is a parameter of the trait rather than the function so that method lookup will
122// propagate through dereferencing.
123pub trait Refmt<F: ?Sized>
124where
125 Self: Fmt<F>,
126{
127 /// Wrap this value so that when formatted with [`fmt::Debug`] or [`fmt::Display`], it uses
128 /// the given [`Fmt`] custom format type instead.
129 ///
130 /// This operation is also available as the non-trait function [`refmt()`].
131 fn refmt<'a>(&'a self, fopt: &'a F) -> Wrapper<'a, F, Self>;
132}
133impl<F: ?Sized, T: ?Sized + Fmt<F>> Refmt<F> for T {
134 #[inline]
135 fn refmt<'a>(&'a self, fopt: &'a F) -> Wrapper<'a, F, Self> {
136 Wrapper { fopt, value: self }
137 }
138}
139
140/// Wrapper type to replace the [`fmt::Display`] and [`fmt::Debug`] behavior of its contents with
141/// a [`Fmt`] implementation.
142///
143/// * `F` is the [`Fmt`] formatting type to use.
144/// * `T` is the type of value to be printed.
145///
146/// You can use [`refmt()`] or [`Refmt::refmt()`] to construct this.
147///
148/// To enable using this wrapper inside [`assert_eq`], it implements [`PartialEq`]
149/// (comparing both value and format).
150#[derive(Eq, PartialEq)]
151pub struct Wrapper<'a, F: ?Sized, T: ?Sized> {
152 value: &'a T,
153 fopt: &'a F,
154}
155
156impl<'a, F: ?Sized, T: ?Sized + Fmt<F>> fmt::Debug for Wrapper<'a, F, T> {
157 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
158 <T as Fmt<F>>::fmt(self.value, fmt, self.fopt)
159 }
160}
161impl<'a, F: ?Sized, T: ?Sized + Fmt<F>> fmt::Display for Wrapper<'a, F, T> {
162 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
163 <T as Fmt<F>>::fmt(self.value, fmt, self.fopt)
164 }
165}
166
167mod impls {
168 use super::*;
169 /// Forwards to the referent.
170 impl<F, T: Fmt<F>> Fmt<F> for &'_ T {
171 fn fmt(&self, fmt: &mut fmt::Formatter<'_>, fopt: &F) -> fmt::Result {
172 <T as Fmt<F>>::fmt(&**self, fmt, fopt)
173 }
174 }
175 /// Forwards to the referent.
176 impl<F, T: Fmt<F>> Fmt<F> for &'_ mut T {
177 fn fmt(&self, fmt: &mut fmt::Formatter<'_>, fopt: &F) -> fmt::Result {
178 <T as Fmt<F>>::fmt(&**self, fmt, fopt)
179 }
180 }
181}