runtime_format/
lib.rs

1//! Formatting, but processed at runtime.
2//!
3//! ```
4//! use runtime_format::{FormatArgs, FormatKey, FormatKeyError};
5//! use core::fmt;
6//! # struct DateTime;
7//! # impl DateTime { fn now() -> Self { Self } }
8//! # impl DateTime { fn day(&self) -> i32 { 25 } fn short_month_name(&self) -> &'static str { "Jan" } fn year(&self) -> i32 { 2023 } }
9//! # impl DateTime { fn hours(&self) -> i32 { 16 } fn minutes(&self) -> i32 { 27 } fn seconds(&self) -> i32 { 53 } }
10//! impl FormatKey for DateTime {
11//!     fn fmt(&self, key: &str, f: &mut fmt::Formatter<'_>) -> Result<(), FormatKeyError> {
12//!         use core::fmt::Write;
13//!         match key {
14//!             "year"    => write!(f, "{}", self.year()).map_err(FormatKeyError::Fmt),
15//!             "month"   => write!(f, "{}", self.short_month_name()).map_err(FormatKeyError::Fmt),
16//!             "day"     => write!(f, "{}", self.day()).map_err(FormatKeyError::Fmt),
17//!             "hours"   => write!(f, "{}", self.hours()).map_err(FormatKeyError::Fmt),
18//!             "minutes" => write!(f, "{}", self.minutes()).map_err(FormatKeyError::Fmt),
19//!             "seconds" => write!(f, "{}", self.seconds()).map_err(FormatKeyError::Fmt),
20//!             _ => Err(FormatKeyError::UnknownKey),
21//!         }
22//!     }
23//! }
24//!
25//! let now = DateTime::now();
26//! let fmt = "{month} {day} {year} {hours}:{minutes}:{seconds}";
27//! let args = FormatArgs::new(fmt, &now);
28//! let expected = "Jan 25 2023 16:27:53";
29//! assert_eq!(args.to_string(), expected);
30//! ```
31//!
32//! See [`ParsedFmt`] if you need to repeatedly format a given string, but with
33//! different args.
34#![cfg_attr(not(feature = "std"), no_std)]
35
36#[cfg(feature = "std")]
37mod alloc_impls;
38#[cfg(feature = "std")]
39pub use alloc_impls::*;
40
41#[cfg(feature = "std")]
42mod compiled;
43#[cfg(feature = "std")]
44pub use compiled::ParsedFmt;
45
46mod parse;
47pub use parse::{FromStr, ParseSegment};
48
49use core::cell::Cell;
50use core::fmt;
51
52#[derive(Debug, Clone, PartialEq)]
53/// Error produced when formatting
54pub enum FormatKeyError {
55    /// The formatter had an error
56    Fmt(fmt::Error),
57    /// The requested key is unknown
58    UnknownKey,
59}
60
61#[cfg(feature = "std")]
62impl std::error::Error for FormatKeyError {
63    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
64        match self {
65            FormatKeyError::Fmt(f) => Some(f),
66            FormatKeyError::UnknownKey => None,
67        }
68    }
69}
70
71impl fmt::Display for FormatKeyError {
72    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73        match self {
74            FormatKeyError::Fmt(_) => f.write_str("There was an error writing to the formatter"),
75            FormatKeyError::UnknownKey => f.write_str("The requested key is unknown"),
76        }
77    }
78}
79
80impl From<fmt::Error> for FormatKeyError {
81    fn from(value: fmt::Error) -> Self {
82        FormatKeyError::Fmt(value)
83    }
84}
85
86/// A trait like [`fmt::Display`] or [`fmt::Debug`] by with a keyed field.
87///
88/// It has a `fmt` method that accepts a [`fmt::Formatter`] argument. The important feature is the
89/// `key` field which indicates what value should be written to the formatter.
90///
91/// ```
92/// use runtime_format::{FormatArgs, FormatKey, FormatKeyError};
93/// use core::fmt;
94/// # struct DateTime;
95/// # impl DateTime { fn now() -> Self { Self } }
96/// # impl DateTime { fn day(&self) -> i32 { 25 } fn short_month_name(&self) -> &'static str { "Jan" } fn year(&self) -> i32 { 2023 } }
97/// # impl DateTime { fn hours(&self) -> i32 { 16 } fn minutes(&self) -> i32 { 27 } fn seconds(&self) -> i32 { 53 } }
98/// impl FormatKey for DateTime {
99///     fn fmt(&self, key: &str, f: &mut fmt::Formatter<'_>) -> Result<(), FormatKeyError> {
100///         use core::fmt::Write;
101///         match key {
102///             "year"    => write!(f, "{}", self.year()).map_err(FormatKeyError::Fmt),
103///             "month"   => write!(f, "{}", self.short_month_name()).map_err(FormatKeyError::Fmt),
104///             "day"     => write!(f, "{}", self.day()).map_err(FormatKeyError::Fmt),
105///             "hours"   => write!(f, "{}", self.hours()).map_err(FormatKeyError::Fmt),
106///             "minutes" => write!(f, "{}", self.minutes()).map_err(FormatKeyError::Fmt),
107///             "seconds" => write!(f, "{}", self.seconds()).map_err(FormatKeyError::Fmt),
108///             _ => Err(FormatKeyError::UnknownKey),
109///         }
110///     }
111/// }
112///
113/// let now = DateTime::now();
114/// let fmt = "{month} {day} {year} {hours}:{minutes}:{seconds}";
115/// let args = FormatArgs::new(fmt, &now);
116/// let expected = "Jan 25 2023 16:27:53";
117/// assert_eq!(args.to_string(), expected);
118/// ```
119pub trait FormatKey {
120    /// Write the value with the associated with the given `key` to the formatter.
121    ///
122    /// # Errors
123    /// If the formatter returns an error, or if the key is unknown.
124    fn fmt(&self, key: &str, f: &mut fmt::Formatter<'_>) -> Result<(), FormatKeyError>;
125}
126
127/// Turn a value into parsed formatting segments on the fly.
128pub trait ToFormatParser<'a> {
129    /// The Parser type that returns the [`ParseSegment`]s
130    type Parser: Iterator<Item = ParseSegment<'a>>;
131
132    /// Turn this value into the parser
133    fn to_parser(&'a self) -> Self::Parser;
134    /// Get the unparsed str from this parser.
135    /// Used to determine if there was an error while parsing.
136    fn unparsed(iter: Self::Parser) -> &'a str;
137}
138
139#[derive(Debug, Clone, PartialEq)]
140#[non_exhaustive]
141/// Error returned when formatting or parsing.
142pub enum FormatError<'a> {
143    /// The key was invalid
144    Key(&'a str),
145    /// Could not parse the string
146    Parse(&'a str),
147}
148
149#[cfg(feature = "std")]
150impl std::error::Error for FormatError<'_> {}
151
152impl fmt::Display for FormatError<'_> {
153    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154        match self {
155            FormatError::Key(key) => write!(f, "The requested key {key:?} is unknown"),
156            FormatError::Parse(rest) => write!(f, "Failed to parse {rest:?}"),
157        }
158    }
159}
160
161/// Performs formatting.
162pub struct FormatArgs<'fs, 'fk, FS: ?Sized, FK: ?Sized> {
163    format_segments: &'fs FS,
164    format_keys: &'fk FK,
165    error: Cell<Option<FormatError<'fs>>>,
166}
167
168impl<'fs, 'fk, FS: ?Sized, FK: ?Sized> FormatArgs<'fs, 'fk, FS, FK> {
169    /// Create a new `FormatArgs` using the format specifier and the format keys
170    pub fn new(format_specified: &'fs FS, format_keys: &'fk FK) -> Self {
171        FormatArgs {
172            format_segments: format_specified,
173            format_keys,
174            error: Cell::new(None),
175        }
176    }
177
178    /// If there was an error when formatting, then that error is available here.
179    pub fn status(&self) -> Result<(), FormatError<'fs>> {
180        match self.error.take() {
181            Some(err) => Err(err),
182            None => Ok(()),
183        }
184    }
185}
186
187impl<'fs, 'fk, FS, FK> fmt::Display for FormatArgs<'fs, 'fk, FS, FK>
188where
189    FS: ?Sized + ToFormatParser<'fs>,
190    FK: ?Sized + FormatKey,
191{
192    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
193        let mut segments = self.format_segments.to_parser();
194        for segment in &mut segments {
195            match segment {
196                ParseSegment::Literal(s) => f.write_str(s)?,
197                ParseSegment::Key(key) => match self.format_keys.fmt(key, f) {
198                    Ok(_) => {}
199                    Err(FormatKeyError::Fmt(e)) => return Err(e),
200                    Err(FormatKeyError::UnknownKey) => {
201                        self.error.set(Some(FormatError::Key(key)));
202                        return Err(fmt::Error);
203                    }
204                },
205            }
206        }
207        let remaining = FS::unparsed(segments);
208        if !remaining.is_empty() {
209            self.error.set(Some(FormatError::Parse(remaining)));
210            Err(fmt::Error)
211        } else {
212            Ok(())
213        }
214    }
215}
216
217impl<'fs, 'fk, FS, FK> fmt::Debug for FormatArgs<'fs, 'fk, FS, FK>
218where
219    FS: ?Sized + ToFormatParser<'fs>,
220    FK: ?Sized + FormatKey,
221{
222    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
223        fmt::Display::fmt(self, f)
224    }
225}
226
227#[cfg(test)]
228mod tests {
229    use core::fmt::{self, Write};
230
231    use crate::{FormatArgs, FormatError, FormatKey, FormatKeyError};
232
233    struct WriteShim<'a> {
234        w: &'a mut [u8],
235        n: usize,
236    }
237    impl fmt::Write for WriteShim<'_> {
238        fn write_str(&mut self, s: &str) -> fmt::Result {
239            let remaining = self.w.len() - self.n;
240            if let Some(prefix) = s.as_bytes().get(..remaining) {
241                self.w[self.n..].copy_from_slice(prefix);
242                self.n = self.w.len();
243                Err(fmt::Error)
244            } else {
245                let n = self.n + s.len();
246                self.w[self.n..n].copy_from_slice(s.as_bytes());
247                self.n = n;
248                Ok(())
249            }
250        }
251    }
252
253    fn format<'a, F: FormatKey>(
254        s: &'a str,
255        fmt: &'a F,
256        f: impl FnOnce(&[u8]),
257    ) -> Result<(), FormatError<'a>> {
258        let mut bytes = WriteShim {
259            w: &mut [0; 1024],
260            n: 0,
261        };
262        let fmt = FormatArgs::new(s, fmt);
263        let _ = write!(bytes, "{}", fmt);
264        if let Some(err) = fmt.error.take() {
265            return Err(err);
266        }
267
268        f(&bytes.w[..bytes.n]);
269        Ok(())
270    }
271
272    struct Message;
273    impl FormatKey for Message {
274        fn fmt(&self, key: &str, f: &mut fmt::Formatter<'_>) -> Result<(), FormatKeyError> {
275            match key {
276                "recipient" => f.write_str("World").map_err(FormatKeyError::Fmt),
277                "time_descriptor" => f.write_str("morning").map_err(FormatKeyError::Fmt),
278                _ => Err(FormatKeyError::UnknownKey),
279            }
280        }
281    }
282
283    #[test]
284    fn happy_path() {
285        let format_str = "Hello, {recipient}. Hope you are having a nice {time_descriptor}.";
286        let expected = "Hello, World. Hope you are having a nice morning.";
287        format(format_str, &Message, |output| {
288            assert_eq!(output, expected.as_bytes())
289        })
290        .unwrap();
291    }
292
293    #[test]
294    fn missing_key() {
295        let format_str = "Hello, {recipient}. Hope you are having a nice {time_descriptr}.";
296        assert_eq!(
297            format(format_str, &Message, |_| {}),
298            Err(FormatError::Key("time_descriptr"))
299        );
300    }
301
302    #[test]
303    fn failed_parsing() {
304        let format_str = "Hello, {recipient}. Hope you are having a nice {time_descriptor.";
305        assert_eq!(
306            format(format_str, &Message, |_| {}),
307            Err(FormatError::Parse("time_descriptor."))
308        );
309    }
310
311    #[test]
312    fn escape_brackets() {
313        let format_str = "You can make custom formatting terms using {{foo}!";
314        let expected = "You can make custom formatting terms using {foo}!";
315        format(format_str, &Message, |output| {
316            assert_eq!(output, expected.as_bytes())
317        })
318        .unwrap();
319    }
320}