dyn_fmt/
lib.rs

1#![deny(warnings)]
2#![allow(clippy::needless_doctest_main)]
3
4#![cfg_attr(not(feature = "std"), no_std)]
5
6//! |        Static format macro         |           Dynamic analog           |
7//! |:----------------------------------:|:----------------------------------:|
8//! |      [`format!`](std::format )     | [`format`](AsStrFormatExt::format) |
9//! | [`format_args!`](std::format_args) | [`Arguments::new`](Arguments::new) |
10//! |       [`write!`](std::write)       |      [`dyn_write!`](dyn_write)     |
11//!
12//! ## Feature flags
13#![doc=document_features::document_features!()]
14
15#[doc=include_str!("../README.md")]
16type _DocTestReadme = ();
17
18#[cfg(feature = "std")]
19extern crate core;
20
21use core::fmt::{self, Display};
22use core::hint::{unreachable_unchecked};
23
24#[doc(hidden)]
25pub use core::write as std_write;
26
27/// Extends strings with the `format` method, which is a runtime analog of the [`format!`](std::format) macro.
28/// Unavailable in `no_std` environment.
29#[cfg(feature = "std")]
30pub trait AsStrFormatExt: AsRef<str> {
31    /// Creates a [`String`] replacing the {}s within `self` using provided parameters in the order given.
32    /// A runtime analog of [`format!`](std::format) macro. In contrast with the macro format string have not be a string literal.
33    /// # Examples:
34    /// ```rust
35    /// use dyn_fmt::AsStrFormatExt;
36    /// assert_eq!("{}a{}b{}c".format(&[1, 2, 3]), "1a2b3c");
37    /// assert_eq!("{}a{}b{}c".format(&[1, 2, 3, 4]), "1a2b3c"); // extra arguments are ignored
38    /// assert_eq!("{}a{}b{}c".format(&[1, 2]), "1a2bc"); // missing arguments are replaced by empty string
39    /// assert_eq!("{{}}{}".format(&[1, 2]), "{}1");
40    fn format<'a, T: Display + ?Sized + 'a>(&self, args: impl IntoIterator<Item=&'a T> + Clone) -> String {
41        format!("{}", Arguments::new(self, args))
42    }
43}
44
45#[cfg(feature = "std")]
46impl<T: AsRef<str>> AsStrFormatExt for T { }
47
48/// Writes formatted data into a buffer. A runtime analog of [`write!`](std::write) macro.
49/// In contrast with the macro format string have not be a string literal.
50/// 
51/// This macro accepts three arguments: a writer, a format string, and an arguments iterator.
52/// Arguments will be formatted according to the specified format string by calling `Arguments::new(fmt, args)`,
53/// and the result will be passed to the writer.
54///
55/// The writer may be any value with a `write_fmt` method; generally this comes from an implementation of either
56/// the [`fmt::Write`](std::fmt::Write) or the [`Write`](std::io::Write) trait.
57/// The macro returns whatever the `write_fmt` method returns;
58/// commonly a [`fmt::Result`](std::fmt::Result), or an [`io::Result`](std::io::Result).
59///
60/// # Examples:
61/// ```rust
62/// use dyn_fmt::dyn_write;
63/// use std::fmt::Write;
64/// let mut buf = String::new();
65/// dyn_write!(buf, "{}a{}b{}c", &[1, 2, 3]);
66/// assert_eq!(buf, "1a2b3c");
67/// ```
68#[macro_export]
69macro_rules! dyn_write {
70    ($dst:expr, $fmt:expr, $args:expr $(,)?) => {
71        $crate::std_write!($dst, "{}", $crate::Arguments::new($fmt, $args))
72    }
73}
74
75/// This structure represents a format string combined with its arguments.
76/// In contrast with [`fmt::Arguments`] this structure can be easily and safely created at runtime.
77#[derive(Clone, Debug)]
78pub struct Arguments<'a, F: AsRef<str>, T: Display + ?Sized + 'a, I: IntoIterator<Item=&'a T> + Clone> {
79    fmt: F,
80    args: I
81}
82
83impl<'a, F: AsRef<str>, T: Display + ?Sized + 'a, I: IntoIterator<Item=&'a T> + Clone> Arguments<'a, F, T, I> {
84    /// Creates a new instance of a [`Display`]able structure, representing formatted arguments.
85    /// A runtime analog of [`format_args!`](std::format_args) macro.
86    /// Extra arguments are ignored, missing arguments are replaced by empty string.
87    /// # Examples:
88    /// ```rust
89    /// dyn_fmt::Arguments::new("{}a{}b{}c", &[1, 2, 3]); // "1a2b3c"
90    /// dyn_fmt::Arguments::new("{}a{}b{}c", &[1, 2, 3, 4]); // "1a2b3c"
91    /// dyn_fmt::Arguments::new("{}a{}b{}c", &[1, 2]); // "1a2bc"
92    /// dyn_fmt::Arguments::new("{{}}{}", &[1, 2]); // "{}1"
93    /// ```
94    pub fn new(fmt: F, args: I) -> Self { Arguments { fmt, args } }
95}
96
97impl<'a, F: AsRef<str>, T: Display + ?Sized + 'a, I: IntoIterator<Item=&'a T> + Clone> Display for Arguments<'a, F, T, I> {
98    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
99        #[derive(Eq, PartialEq)]
100        enum Brace { Left, Right }
101        fn as_brace(c: u8) -> Option<Brace> {
102            match c {
103                b'{' => Some(Brace::Left),
104                b'}' => Some(Brace::Right),
105                _ => None
106            }
107        }
108        let mut args = self.args.clone().into_iter();
109        let mut fmt = self.fmt.as_ref();
110        let mut piece_end = 0;
111        enum State { Piece, Arg }
112        let mut state = State::Piece;
113        loop {
114            match state {
115                State::Piece => match fmt.as_bytes()[piece_end ..].first() {
116                    None => {
117                        fmt.fmt(f)?;
118                        break;
119                    },
120                    Some(&b) => match as_brace(b) {
121                        Some(b) => {
122                            fmt[.. piece_end].fmt(f)?;
123                            fmt = &fmt[(piece_end + 1) ..];
124                            if fmt.is_empty() { break; }
125                            match b {
126                                Brace::Left => {
127                                    piece_end = 0;
128                                    state = State::Arg;
129                                },
130                                Brace::Right => {
131                                    piece_end = 1;
132                                    state = State::Piece;
133                                }
134                            };
135                        },
136                        None => {
137                            piece_end += 1;
138                        }
139                    }
140                },
141                State::Arg => match fmt.as_bytes().first() {
142                    None => unsafe { unreachable_unchecked() },
143                    Some(&b'}') => {
144                        if let Some(arg) = args.next() {
145                            arg.fmt(f)?;
146                        }
147                        fmt = &fmt[1 ..];
148                        state = State::Piece;
149                    },
150                    Some(_) => {
151                        piece_end = 1;
152                        state = State::Piece;
153                    }
154                },
155            }
156        }
157        Ok(())
158    }
159}
160
161#[cfg(test)]
162mod tests {
163    use crate as dyn_fmt;
164    #[cfg(feature = "std")]
165    use crate::AsStrFormatExt;
166    use core::fmt::{self, Write, Display};
167    use core::str::{self};
168
169    #[cfg(feature = "std")]
170    #[test]
171    fn test_format() {
172        assert_eq!("{}a{}b{}c".format(&[1, 2, 3]), "1a2b3c");
173        assert_eq!("{}a{}b{}c".format(&[1, 2, 3, 4]), "1a2b3c");
174        assert_eq!("{}a{}b{}c".format(&[1, 2]), "1a2bc");
175        assert_eq!("{{}}{}".format(&[1, 2]), "{}1");
176    }
177
178    #[cfg(feature = "std")]
179    #[test]
180    fn test_format_with_string_format() {
181        let format: String = "{}a{}b{}c".into();
182        assert_eq!(format.format(&[1, 2, 3]), "1a2b3c");
183        assert_eq!(format.format(&[2, 3, 4]), "2a3b4c");
184    }
185
186    struct Writer<'a> {
187        buf: &'a mut str,
188        len: usize,
189    }
190    
191    impl<'a> fmt::Write for Writer<'a> {
192        fn write_str(&mut self, s: &str) -> fmt::Result {
193            let buf = &mut self.buf[self.len ..];
194            assert!(buf.len() >= s.len());
195            let buf = &mut buf[.. s.len()];
196            unsafe { buf.as_bytes_mut() }.copy_from_slice(s.as_bytes());
197            self.len += s.len();
198            Ok(())
199        }
200    }
201
202    #[test]
203    fn test_write() {
204        let mut buf = [0u8; 128];
205        let buf = str::from_utf8_mut(&mut buf).unwrap();
206        let mut writer = Writer { buf, len: 0 };
207        dyn_write!(&mut writer, "{}a{}b{}c", &[1, 2, 3]).unwrap();
208        let len = writer.len;
209        assert_eq!("1a2b3c", &buf[.. len]);
210    }
211
212    #[test]
213    fn write_args() {
214        let args_format = dyn_fmt::Arguments::new("{}{}{}", &[1, 2, 3]);
215        let mut buf = [0u8; 128];
216        let buf = str::from_utf8_mut(&mut buf).unwrap();
217        let mut writer = Writer { buf, len: 0 };
218        write!(&mut writer, "{}", args_format).unwrap();
219        let len = writer.len;
220        assert_eq!("123", &buf[.. len]);
221    }
222
223    #[test]
224    fn write_unsized_args() {
225        let args: &'static [&'static dyn Display] = &[&1, &2, &3];
226        let args_format = dyn_fmt::Arguments::new("{}{}{}", args.iter().copied());
227        let mut buf = [0u8; 128];
228        let buf = str::from_utf8_mut(&mut buf).unwrap();
229        let mut writer = Writer { buf, len: 0 };
230        write!(&mut writer, "{}", args_format).unwrap();
231        let len = writer.len;
232        assert_eq!("123", &buf[.. len]);
233    }
234
235    #[cfg(feature = "std")]
236    #[test]
237    fn format_unsized_args() {
238        let args: &'static [&'static dyn Display] = &[&1, &2, &3];
239        let args_format = "{}{}{}".format(args.iter().copied());
240        let mut buf = [0u8; 128];
241        let buf = str::from_utf8_mut(&mut buf).unwrap();
242        let mut writer = Writer { buf, len: 0 };
243        write!(&mut writer, "{}", args_format).unwrap();
244        let len = writer.len;
245        assert_eq!("123", &buf[.. len]);
246    }
247
248    #[test]
249    fn write_str() {
250        let args_format = dyn_fmt::Arguments::new("abcd{}абвгд{}{}", &[1, 2, 3]);
251        let mut buf = [0u8; 128];
252        let buf = str::from_utf8_mut(&mut buf).unwrap();
253        let mut writer = Writer { buf, len: 0 };
254        write!(&mut writer, "{}", args_format).unwrap();
255        let len = writer.len;
256        assert_eq!("abcd1абвгд23", &buf[.. len]);
257    }
258
259    #[test]
260    fn complex_case_1() {
261        let args_format = dyn_fmt::Arguments::new("{{}}x{{}{}}y{", &[1, 2, 3]);
262        let mut buf = [0u8; 128];
263        let buf = str::from_utf8_mut(&mut buf).unwrap();
264        let mut writer = Writer { buf, len: 0 };
265        write!(&mut writer, "{}", args_format).unwrap();
266        let len = writer.len;
267        assert_eq!("{}x{{}y", &buf[.. len]);
268    }
269
270    #[test]
271    fn complex_case_2() {
272        let args_format = dyn_fmt::Arguments::new("{{{}}}x{y}", &[1, 2, 3]);
273        let mut buf = [0u8; 128];
274        let buf = str::from_utf8_mut(&mut buf).unwrap();
275        let mut writer = Writer { buf, len: 0 };
276        write!(&mut writer, "{}", args_format).unwrap();
277        let len = writer.len;
278        assert_eq!("{1}xy", &buf[.. len]);
279    }
280
281    #[test]
282    fn complex_case_3() {
283        let args_format = dyn_fmt::Arguments::new("{{{}}}x{{}", &[1, 2, 3]);
284        let mut buf = [0u8; 128];
285        let buf = str::from_utf8_mut(&mut buf).unwrap();
286        let mut writer = Writer { buf, len: 0 };
287        write!(&mut writer, "{}", args_format).unwrap();
288        let len = writer.len;
289        assert_eq!("{1}x{", &buf[.. len]);
290    }
291
292    #[test]
293    fn fmt_lifetime() {
294        fn display<'a, 'b>(f: &'a str, i: &'a [u8], buf: &'b mut str) -> &'b str {
295            let args_format = dyn_fmt::Arguments::new(f, i);
296            let mut writer = Writer { buf, len: 0 };
297            write!(&mut writer, "{}", args_format).unwrap();
298            let len = writer.len;
299            &buf[.. len]
300        }
301        let mut buf = [0u8; 128];
302        let buf = str::from_utf8_mut(&mut buf).unwrap();
303        let res = display("{}", &[0], buf);
304        assert_eq!("0", res);
305    }
306
307    #[test]
308    fn write_macros() {
309        let mut buf = [0u8; 128];
310        let buf = str::from_utf8_mut(&mut buf).unwrap();
311        let mut writer = Writer { buf, len: 0 };
312        dyn_write!(&mut writer, "abcd{}абвгд{}{}", &[1, 2, 3]).unwrap();
313        let len = writer.len;
314        assert_eq!("abcd1абвгд23", &buf[.. len]);
315    }
316}