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;