custom_format/lib.rs
1#![no_std]
2#![forbid(unsafe_code)]
3#![deny(missing_docs)]
4#![cfg_attr(docsrs, feature(doc_cfg))]
5
6//! This crate extends the standard formatting syntax with custom format specifiers, by providing custom formatting macros.
7//!
8//! It uses ` :` (a space and a colon) as a separator before the format specifier, which is not a syntax currently accepted and allows supporting standard specifiers in addition to custom specifiers.
9//! It also supports [format args capture](https://blog.rust-lang.org/2022/01/13/Rust-1.58.0.html#captured-identifiers-in-format-strings)
10//! even on older versions of Rust, since it manually adds the named parameter if missing.
11//!
12//! This library comes in two flavors, corresponding to the following features:
13//!
14//! - `compile-time` (*enabled by default*)
15//!
16//! The set of possible custom format specifiers is defined at compilation, so invalid specifiers can be checked at compile-time.
17//! This allows the library to have the same performance as when using the standard library formatting traits.
18//! See the [`compile_time::CustomFormat`](crate::compile_time::CustomFormat) trait.
19//!
20//! - `runtime` (*enabled by default*)
21//!
22//! The formatting method dynamically checks the format specifier at runtime for each invocation.
23//! This is a slower version, but has a lower MSRV for greater compatibility.
24//! See the [`runtime::CustomFormat`](crate::runtime::CustomFormat) trait.
25
26#[cfg(feature = "compile-time")]
27#[cfg_attr(docsrs, doc(cfg(feature = "compile-time")))]
28pub mod compile_time;
29
30#[cfg(feature = "runtime")]
31#[cfg_attr(docsrs, doc(cfg(feature = "runtime")))]
32pub mod runtime;
33
34#[doc(hidden)]
35pub use custom_format_macros;
36
37#[doc(hidden)]
38#[macro_export]
39macro_rules! parse_args {
40 ([$($macro:tt)*], [$($first_arg:expr)?], [$($result:expr),*], $id:ident = $expr:expr, $($arg:tt)*) => {{
41 $crate::parse_args!([$($macro)*], [$($first_arg)?], [$($result,)* ($id) = $expr], $($arg)*)
42 }};
43 ([$($macro:tt)*], [$($first_arg:expr)?], [$($result:expr),*], $expr:expr, $($arg:tt)*) => {{
44 $crate::parse_args!([$($macro)*], [$($first_arg)?], [$($result,)* $expr], $($arg)*)
45 }};
46 ([$($macro:tt)*], [$($first_arg:expr)?], [$($result:expr),*], $(,)?) => {{
47 $crate::custom_format_macros::fmt!($crate, [$($macro)*], [$($first_arg)?], [$($result),*])
48 }};
49}
50
51#[doc(hidden)]
52#[macro_export]
53macro_rules! fmt_inner {
54 ([$($macro:tt)*], [$($first_arg:expr)?], ) => {{
55 compile_error!("requires at least a format string argument")
56 }};
57 ([$($macro:tt)*], [$($first_arg:expr)?], $fmt:literal) => {{
58 $crate::custom_format_macros::fmt!($crate, [$($macro)*], [$($first_arg)?], [$fmt])
59 }};
60 ([$($macro:tt)*], [$($first_arg:expr)?], $fmt:literal, $($arg:tt)*) => {{
61 $crate::parse_args!([$($macro)*], [$($first_arg)?], [$fmt], $($arg)*,)
62 }};
63}
64
65/// Constructs parameters for the other string-formatting macros.
66///
67/// ## Important note
68///
69/// The other macros in this crate use an inner `match` to avoid reevaluating the input arguments several times.
70///
71/// For example, the following `println!` call:
72///
73/// ```rust
74/// use custom_format as cfmt;
75/// use core::fmt;
76///
77/// #[derive(Debug)]
78/// struct Hex(u8);
79///
80/// impl cfmt::runtime::CustomFormat for Hex {
81/// fn fmt(&self, f: &mut fmt::Formatter, _: &str) -> fmt::Result {
82/// write!(f, "{:#02x}", self.0)
83/// }
84/// }
85///
86/// fn call() -> Hex {
87/// Hex(42)
88/// }
89///
90/// cfmt::println!("{0:?}, {res :<x>}", res = call());
91/// ```
92///
93/// is expanded to:
94///
95/// ```rust
96/// # use custom_format as cfmt;
97/// # use core::fmt;
98/// # #[derive(Debug)]
99/// # struct Hex(u8);
100/// # impl cfmt::runtime::CustomFormat for Hex {
101/// # fn fmt(&self, f: &mut fmt::Formatter, _: &str) -> fmt::Result {
102/// # write!(f, "{:#02x}", self.0)
103/// # }
104/// # }
105/// # fn call() -> Hex { Hex(42) }
106/// match (&(call())) {
107/// (arg0) => ::std::println!("{0:?}, {1}", arg0, cfmt::runtime::CustomFormatter::new("x", arg0)),
108/// }
109/// ```
110///
111/// This method doesn't work with the `format_args!` macro, since it returns a value of type [`core::fmt::Arguments`]
112/// which borrows the temporary values of the `match`. Since these temporary values are dropped before returning,
113/// the return value cannot be used at all if the format string contains format specifiers.
114///
115/// For this reason, the `format_args!` macro is expanded in another way. The following call:
116///
117/// ```rust
118/// # use custom_format as cfmt;
119/// # use core::fmt;
120/// # #[derive(Debug)]
121/// # struct Hex(u8);
122/// # impl cfmt::runtime::CustomFormat for Hex {
123/// # fn fmt(&self, f: &mut fmt::Formatter, _: &str) -> fmt::Result {
124/// # write!(f, "{:#02x}", self.0)
125/// # }
126/// # }
127/// # fn call() -> Hex { Hex(42) }
128/// println!("{}", cfmt::format_args!("{0:?}, {res :<x>}", res = call()));
129/// ```
130///
131/// must be expanded to:
132///
133/// ```rust
134/// # use custom_format as cfmt;
135/// # use core::fmt;
136/// # #[derive(Debug)]
137/// # struct Hex(u8);
138/// # impl cfmt::runtime::CustomFormat for Hex {
139/// # fn fmt(&self, f: &mut fmt::Formatter, _: &str) -> fmt::Result {
140/// # write!(f, "{:#02x}", self.0)
141/// # }
142/// # }
143/// # fn call() -> Hex { Hex(42) }
144/// println!("{}", ::core::format_args!("{0:?}, {1}", &(call()), cfmt::runtime::CustomFormatter::new("x", &(call()))));
145/// ```
146///
147/// which reevaluates the input arguments if they are used several times in the format string.
148///
149/// To avoid unnecessary reevaluations, we can store the expression result in a variable beforehand:
150///
151/// ```rust
152/// # use custom_format as cfmt;
153/// # use core::fmt;
154/// # #[derive(Debug)]
155/// # struct Hex(u8);
156/// # impl cfmt::runtime::CustomFormat for Hex {
157/// # fn fmt(&self, f: &mut fmt::Formatter, _: &str) -> fmt::Result {
158/// # write!(f, "{:#02x}", self.0)
159/// # }
160/// # }
161/// # fn call() -> Hex { Hex(42) }
162/// let res = call();
163/// println!("{}", cfmt::format_args!("{res:?}, {res :<x>}"));
164/// ```
165///
166/// is expanded to:
167///
168/// ```rust
169/// # use custom_format as cfmt;
170/// # use core::fmt;
171/// # #[derive(Debug)]
172/// # struct Hex(u8);
173/// # impl cfmt::runtime::CustomFormat for Hex {
174/// # fn fmt(&self, f: &mut fmt::Formatter, _: &str) -> fmt::Result {
175/// # write!(f, "{:#02x}", self.0)
176/// # }
177/// # }
178/// # fn call() -> Hex { Hex(42) }
179/// # let res = call();
180/// println!("{}", ::core::format_args!("{0:?}, {1}", &res, cfmt::runtime::CustomFormatter::new("x", &res)))
181/// ```
182#[macro_export]
183macro_rules! format_args {
184 ($($arg:tt)*) => {{
185 $crate::fmt_inner!([::core::format_args!], [], $($arg)*)
186 }};
187}
188
189/// Creates a `String` using interpolation of runtime expressions
190#[macro_export]
191macro_rules! format {
192 ($($arg:tt)*) => {{
193 $crate::fmt_inner!([::std::format!], [], $($arg)*)
194 }};
195}
196
197/// Prints to the standard output
198#[macro_export]
199macro_rules! print {
200 ($($arg:tt)*) => {{
201 $crate::fmt_inner!([::std::print!], [], $($arg)*)
202 }};
203}
204
205/// Prints to the standard output, with a newline
206#[macro_export]
207macro_rules! println {
208 () => {{
209 ::std::println!()
210 }};
211 ($($arg:tt)*) => {{
212 $crate::fmt_inner!([::std::println!], [], $($arg)*)
213 }};
214}
215
216/// Prints to the standard error
217#[macro_export]
218macro_rules! eprint {
219 ($($arg:tt)*) => {{
220 $crate::fmt_inner!([::std::eprint!], [], $($arg)*)
221 }};
222}
223
224/// Prints to the standard error, with a newline
225#[macro_export]
226macro_rules! eprintln {
227 () => {{
228 ::std::eprintln!()
229 }};
230 ($($arg:tt)*) => {{
231 $crate::fmt_inner!([::std::eprintln!], [], $($arg)*)
232 }};
233}
234
235/// Writes formatted data into a buffer
236#[macro_export]
237macro_rules! write {
238 ($dst:expr, $($arg:tt)*) => {{
239 $crate::fmt_inner!([::core::write!], [$dst], $($arg)*)
240 }};
241}
242
243/// Write formatted data into a buffer, with a newline appended
244#[macro_export]
245macro_rules! writeln {
246 ($dst:expr) => {{
247 ::core::writeln!($dst)
248 }};
249 ($dst:expr, $($arg:tt)*) => {{
250 $crate::fmt_inner!([::core::writeln!], [$dst], $($arg)*)
251 }};
252}
253
254/// Panics the current thread
255#[macro_export]
256macro_rules! panic {
257 () => {{
258 ::core::panic!()
259 }};
260 ($($arg:tt)*) => {{
261 $crate::fmt_inner!([::core::panic!], [], $($arg)*)
262 }};
263}