fmt_interspersed/lib.rs
1#![no_std]
2#![doc = include_str!(concat!(env!("OUT_DIR"),"/", "docs.md"))]
3
4#[cfg(feature = "alloc")]
5extern crate alloc;
6
7#[cfg(feature = "std")]
8extern crate std;
9
10#[doc(hidden)]
11#[macro_export]
12macro_rules! __return_if_err {
13 ($ex:expr) => {{
14 if let err @ ::core::result::Result::Err(_) = $ex {
15 return err;
16 }
17 }};
18}
19
20// helper trait to force taking variable by `&mut Self` regardless of how it's passed,
21// similar to how `write_fmt` does. more or less copied from
22// https://stackoverflow.com/a/79153906
23#[doc(hidden)]
24pub trait WithMut {
25 fn with_mut<'a, R>(&'a mut self, f: impl FnOnce(&'a mut Self) -> R) -> R;
26}
27
28impl<T> WithMut for T {
29 fn with_mut<'a, R>(&'a mut self, f: impl FnOnce(&'a mut Self) -> R) -> R {
30 f(self)
31 }
32}
33
34/// Common underlying implementation of the various `write`-related macros
35#[doc(hidden)]
36#[macro_export]
37macro_rules! __write_interspersed_impl {
38 ($writer:expr, $iter:expr, $separator:expr, $arg:pat_param => $($fmt:tt)*) => {{
39 use $crate::WithMut;
40
41 let separator = $separator;
42 let mut iter = $iter.into_iter();
43
44 $writer.with_mut(move |w| {
45 if let ::core::option::Option::Some($arg) = iter.next() {
46 // can't use ? because we need to state that the error type is specifically
47 // that returned by `write!` and not merely `From` it
48 $crate::__return_if_err!(write!(w, $($fmt)*));
49 for $arg in iter {
50 $crate::__return_if_err!(write!(w, "{separator}"));
51 $crate::__return_if_err!(write!(w, $($fmt)*));
52 }
53 }
54 ::core::result::Result::Ok(())
55 })
56 }};
57 ($writer:expr, $iter:expr, $separator:expr $(,)?) => {
58 $crate::__write_interspersed_impl!($writer, $iter, $separator, x => "{x}")
59 };
60}
61
62/// An interspersing version of [`write!`]
63///
64/// Writes an iterable’s items, separated by a separator, to a destination. Like
65/// `write!`, this macro returns a [`Result`] and requires [`std::io::Write`] or
66/// [`std::fmt::Write`] to be in scope, depending on the destination.
67///
68/// Like all macros in this crate, `write_interspersed!` has two forms:
69/// `write_interspersed!(w, iterable, sep)` and `write_interspersed!(w, iterable, sep,
70/// pat => fmt_args)`. Both forms require that `sep` implements
71/// [`Display`](`std::fmt::Display`). The first also requires that the iterable’s items
72/// implement `Display`.
73///
74/// ```
75/// use fmt_interspersed::write_interspersed;
76/// use std::{fs, io::{Error, Write}};
77///
78/// let mut f = fs::File::create("test.txt")?;
79/// write_interspersed!(f, 1..=5, ";")?;
80/// assert_eq!("1;2;3;4;5", fs::read_to_string("test.txt")?);
81///
82/// let mut f = fs::File::create("test.txt")?;
83/// write_interspersed!(f, [("a", 1), ("b", 2)], ", ", (x, y) => "{x:?} => {y}")?;
84/// assert_eq!(r#""a" => 1, "b" => 2"#, fs::read_to_string("test.txt")?);
85///
86/// # fs::remove_file("test.txt")?;
87/// # Ok::<(), Error>(())
88/// ```
89#[macro_export]
90macro_rules! write_interspersed {
91 ($writer:expr, $($args:tt)*) => {{
92 (|| {
93 $crate::__return_if_err!($crate::__write_interspersed_impl!($writer, $($args)*));
94
95 ::core::result::Result::Ok(())
96 })()
97 }};
98
99}
100
101/// An interspersing version of [`writeln!`]
102///
103/// Writes an iterable’s items, separated by a separator, to a destination. Like
104/// `writeln!`, this macro returns a [`Result`] and requires [`std::io::Write`] or
105/// [`std::fmt::Write`] to be in scope, depending on the destination.
106///
107/// Like all macros in this crate, `writeln_interspersed!` has two forms:
108/// `writeln_interspersed!(w, iterable, sep)` and `writeln_interspersed!(w, iterable,
109/// sep, pat => fmt_args)`. Both forms require that `sep` implements
110/// [`Display`](`std::fmt::Display`). The first also requires that the iterable’s items
111/// implement `Display`.
112///
113/// ```
114/// use fmt_interspersed::writeln_interspersed;
115/// use std::{fs, io::{Error, Write}};
116///
117/// let mut f = fs::File::create("test.txt")?;
118/// writeln_interspersed!(f, 1..=5, ";")?;
119/// assert_eq!("1;2;3;4;5\n", fs::read_to_string("test.txt")?);
120///
121/// let mut f = fs::File::create("test.txt")?;
122/// writeln_interspersed!(f, [("a", 1), ("b", 2)], ", ", (x, y) => "{x:?} => {y}")?;
123/// assert_eq!("\"a\" => 1, \"b\" => 2\n", fs::read_to_string("test.txt")?);
124///
125/// # fs::remove_file("test.txt")?;
126/// # Ok::<(), Error>(())
127/// ```
128#[macro_export]
129macro_rules! writeln_interspersed {
130 ($writer:expr, $($args:tt)*) => {{
131 (|| {
132 $crate::__return_if_err!($crate::__write_interspersed_impl!($writer, $($args)*));
133 writeln!($writer)?;
134
135 ::core::result::Result::Ok(())
136 })()
137 }};
138}
139
140/// Underlying implementation of `__format_interspersed_impl`, simply exists to keep
141/// `format_interspersed`’s arguments in sync with other macros.
142#[cfg(feature = "alloc")]
143#[doc(hidden)]
144#[macro_export]
145macro_rules! __format_interspersed_impl {
146 ($iter:expr, $separator:expr $(, $($args:tt)*)?) => {{
147 use ::core::fmt::Write;
148
149 let iter = $iter.into_iter();
150 let separator = $separator;
151
152 // a reasonable heuristic, assuming the iterator's items and the separator both
153 // have length at least 1 when Display'd
154 let (lower_bd, _) = iter.size_hint();
155 let mut buf = ::alloc::string::String::with_capacity(lower_bd * 2);
156
157 $crate::write_interspersed!(buf, iter, separator $(, $($args)*)?).unwrap();
158
159 buf
160 }};
161}
162
163/// An interspersing version of [`format!`](std::format)
164///
165/// Make a string from an iterable’s items separated by a separator.
166///
167/// Like all macros in this crate, `format_interspersed!` has two forms:
168/// `format_interspersed!(w, iterable, sep)` and `format_interspersed!(w, iterable, sep,
169/// pat => fmt_args)`. Both forms require that `sep` implements [`Display`](`std::fmt::Display`). The first
170/// also requires that the iterable’s items implement `Display`.
171///
172/// ```
173/// # extern crate alloc;
174/// use fmt_interspersed::format_interspersed;
175///
176/// let s = format_interspersed!(1..=5, ";");
177/// assert_eq!("1;2;3;4;5", s);
178///
179/// let s = format_interspersed!([("a", 1), ("b", 2)], ", ", (x, y) => "{x:?} => {y}");
180/// assert_eq!(r#""a" => 1, "b" => 2"#, s);
181/// ```
182#[cfg(feature = "alloc")]
183#[macro_export]
184macro_rules! format_interspersed {
185 ($($args:tt)*) => {
186 $crate::__format_interspersed_impl!($($args)*)
187 };
188}
189
190/// Common implementation for various print-related macros
191#[cfg(feature = "std")]
192#[macro_export]
193#[doc(hidden)]
194macro_rules! __print_interspersed_impl {
195 (print = $print:path; $iter:expr, $separator:expr, $arg:pat_param => $($fmt:tt)*) => {
196 let separator = $separator;
197 let mut iter = $iter.into_iter();
198 if let ::core::option::Option::Some($arg) = iter.next() {
199 $print!($($fmt)*);
200 for $arg in iter {
201 $print!("{separator}");
202 $print!($($fmt)*);
203 }
204 }
205 };
206 (print = $print:path; $iter:expr, $separator:expr $(,)?) => {
207 $crate::__print_interspersed_impl!(print = $print; $iter, $separator, x => "{x}")
208 };
209}
210
211/// An interspersing version of [`print!`](std::print)
212///
213/// Prints the string made from an iterable’s items separated by a separator. Does not
214/// allocate.
215///
216/// Like all macros in this crate, `print!` has two forms: `print!(w, iterable, sep)`
217/// and `print!(w, iterable, sep, pat => fmt_args)`. Both forms require that `sep`
218/// implements [`Display`](`std::fmt::Display`). The first also requires that the
219/// iterable’s items implement `Display`.
220///
221/// ```
222/// use fmt_interspersed::print_interspersed;
223///
224/// print_interspersed!(1..=5, ";");
225/// // 1;2;3;4;5
226///
227/// print_interspersed!([("a", 1), ("b", 2)], ", ", (x, y) => "{x:?} => {y}");
228/// // "a" => 1, "b" => 2
229/// ```
230#[cfg(feature = "std")]
231#[macro_export]
232macro_rules! print_interspersed {
233 ($($args:tt)*) => {{
234 $crate::__print_interspersed_impl!(print = ::std::print; $($args)*);
235 }};
236}
237
238/// An interspersing version of [`println!`](std::println)
239///
240/// Prints the string made from an iterable’s items separated by a separator, followed
241/// by a newline. Does not allocate.
242///
243/// Like all macros in this crate, `println!` has two forms: `println!(w, iterable,
244/// sep)` and `println!(w, iterable, sep, pat => fmt_args)`. Both forms require that
245/// `sep` implements [`Display`](`std::fmt::Display`). The first also requires that the
246/// iterable’s items implement `Display`.
247///
248/// ```
249/// use fmt_interspersed::println_interspersed;
250///
251/// println_interspersed!(1..=5, ";");
252/// // 1;2;3;4;5
253/// // <newline>
254///
255/// println_interspersed!([("a", 1), ("b", 2)], ", ", (x, y) => "{x:?} => {y}");
256/// // "a" => 1, "b" => 2
257/// // <newline>
258/// ```
259#[cfg(feature = "std")]
260#[macro_export]
261macro_rules! println_interspersed {
262 ($($args:tt)*) => {{
263 $crate::__print_interspersed_impl!(print = ::std::print; $($args)*);
264 ::std::println!();
265 }};
266}
267
268/// An interspersing version of [`eprint!`](std::eprint)
269///
270/// Prints the string made from an iterable’s items separated by a separator to standard
271/// error. Does not allocate.
272///
273/// Like all macros in this crate, `eprint!` has two forms: `eprint!(w, iterable, sep)`
274/// and `eprint!(w, iterable, sep, pat => fmt_args)`. Both forms require that `sep`
275/// implements [`Display`](`std::fmt::Display`). The first also requires that the
276/// iterable’s items implement `Display`.
277///
278/// ```
279/// use fmt_interspersed::eprint_interspersed;
280///
281/// eprint_interspersed!(1..=5, ";");
282/// // (stderr) 1;2;3;4;5
283///
284/// eprint_interspersed!([("a", 1), ("b", 2)], ", ", (x, y) => "{x:?} => {y}");
285/// // (stderr) "a" => 1, "b" => 2
286/// ```
287#[cfg(feature = "std")]
288#[macro_export]
289macro_rules! eprint_interspersed {
290 ($($args:tt)*) => {{
291 $crate::__print_interspersed_impl!(print = ::std::eprint; $($args)*);
292 }};
293}
294
295/// An interspersing version of [`eprintln!`](std::eprintln)
296///
297/// Prints the string made from an iterable’s items separated by a separator, followed
298/// by a newline, to standard error. Does not allocate.
299///
300/// Like all macros in this crate, `eprintln!` has two forms: `eprintln!(w, iterable,
301/// sep)` and `eprintln!(w, iterable, sep, pat => fmt_args)`. Both forms require that
302/// `sep` implements [`Display`](`std::fmt::Display`). The first also requires that the
303/// iterable’s items implement `Display`.
304///
305/// ```
306/// use fmt_interspersed::eprintln_interspersed;
307///
308/// eprintln_interspersed!(1..=5, ";");
309/// // (stderr) 1;2;3;4;5
310/// // (stderr) <newline>
311///
312/// eprintln_interspersed!([("a", 1), ("b", 2)], ", ", (x, y) => "{x:?} => {y}");
313/// // (stderr) "a" => 1, "b" => 2
314/// // (stderr) <newline>
315/// ```
316#[cfg(feature = "std")]
317#[macro_export]
318macro_rules! eprintln_interspersed {
319 ($($args:tt)*) => {{
320 $crate::__print_interspersed_impl!(print = ::std::eprint; $($args)*);
321 ::std::eprintln!();
322 }};
323}
324
325pub mod prelude {
326 pub use crate::{write_interspersed, writeln_interspersed};
327
328 #[cfg(feature = "alloc")]
329 pub use crate::format_interspersed;
330
331 #[cfg(feature = "std")]
332 pub use crate::{
333 eprint_interspersed, eprintln_interspersed, print_interspersed, println_interspersed,
334 };
335}
336
337#[cfg(test)]
338mod test;