1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
#![no_std]
#![forbid(unsafe_code)]
#![deny(missing_docs)]
#![cfg_attr(docsrs, feature(doc_cfg))]

//! This crate extends the standard formatting syntax with custom format specifiers, by providing custom formatting macros.
//!
//! 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.
//! It also supports [format args capture](https://blog.rust-lang.org/2022/01/13/Rust-1.58.0.html#captured-identifiers-in-format-strings)
//! even on older versions of Rust, since it manually adds the named parameter if missing.
//!
//! This library comes in two flavors, corresponding to the following features:
//!
//! - `compile-time` (*enabled by default*)
//!
//!     The set of possible custom format specifiers is defined at compilation, so invalid specifiers can be checked at compile-time.
//!     This allows the library to have the same performance as when using the standard library formatting traits.
//!     See the [`compile_time::CustomFormat`](crate::compile_time::CustomFormat) trait.
//!
//! - `runtime` (*enabled by default*)
//!
//!     The formatting method dynamically checks the format specifier at runtime for each invocation.
//!     This is a slower version, but has a lower MSRV for greater compatibility.
//!     See the [`runtime::CustomFormat`](crate::runtime::CustomFormat) trait.

#[cfg(feature = "compile-time")]
#[cfg_attr(docsrs, doc(cfg(feature = "compile-time")))]
pub mod compile_time;

#[cfg(feature = "runtime")]
#[cfg_attr(docsrs, doc(cfg(feature = "runtime")))]
pub mod runtime;

#[doc(hidden)]
pub use custom_format_macros;

#[doc(hidden)]
#[macro_export]
macro_rules! parse_args {
    ([$($macro:tt)*], [$($first_arg:expr)?], [$($result:expr),*], $id:ident = $expr:expr, $($arg:tt)*) => {{
        $crate::parse_args!([$($macro)*], [$($first_arg)?], [$($result,)* ($id) = $expr], $($arg)*)
    }};
    ([$($macro:tt)*], [$($first_arg:expr)?], [$($result:expr),*], $expr:expr, $($arg:tt)*) => {{
        $crate::parse_args!([$($macro)*], [$($first_arg)?], [$($result,)* $expr], $($arg)*)
    }};
    ([$($macro:tt)*], [$($first_arg:expr)?], [$($result:expr),*], $(,)?) => {{
        $crate::custom_format_macros::fmt!($crate, [$($macro)*], [$($first_arg)?], [$($result),*])
    }};
}

#[doc(hidden)]
#[macro_export]
macro_rules! fmt_inner {
    ([$($macro:tt)*], [$($first_arg:expr)?], ) => {{
        compile_error!("requires at least a format string argument")
    }};
    ([$($macro:tt)*], [$($first_arg:expr)?], $fmt:literal) => {{
        $crate::custom_format_macros::fmt!($crate, [$($macro)*], [$($first_arg)?], [$fmt])
    }};
    ([$($macro:tt)*], [$($first_arg:expr)?], $fmt:literal, $($arg:tt)*) => {{
        $crate::parse_args!([$($macro)*], [$($first_arg)?], [$fmt], $($arg)*,)
    }};
}

/// Constructs parameters for the other string-formatting macros.
///
/// ## Important note
///
/// The other macros in this crate use an inner `match` to avoid reevaluating the input arguments several times.
///
/// For example, the following `println!` call:
///
/// ```rust
/// use custom_format as cfmt;
/// use core::fmt;
///
/// #[derive(Debug)]
/// struct Hex(u8);
///
/// impl cfmt::runtime::CustomFormat for Hex {
///     fn fmt(&self, f: &mut fmt::Formatter, _: &str) -> fmt::Result {
///         write!(f, "{:#02x}", self.0)
///     }
/// }
///
/// fn call() -> Hex {
///     Hex(42)
/// }
///
/// cfmt::println!("{0:?}, {res :<x>}", res = call());
/// ```
///
/// is expanded to:
///
/// ```rust
/// # use custom_format as cfmt;
/// # use core::fmt;
/// # #[derive(Debug)]
/// # struct Hex(u8);
/// # impl cfmt::runtime::CustomFormat for Hex {
/// #     fn fmt(&self, f: &mut fmt::Formatter, _: &str) -> fmt::Result {
/// #         write!(f, "{:#02x}", self.0)
/// #     }
/// # }
/// # fn call() -> Hex { Hex(42) }
/// match (&(call())) {
///     (arg0) => ::std::println!("{0:?}, {1}", arg0, cfmt::runtime::CustomFormatter::new("x", arg0)),
/// }
/// ```
///
/// This method doesn't work with the `format_args!` macro, since it returns a value of type [`core::fmt::Arguments`]
/// which borrows the temporary values of the `match`. Since these temporary values are dropped before returning,
/// the return value cannot be used at all if the format string contains format specifiers.
///
/// For this reason, the `format_args!` macro is expanded in another way. The following call:
///
/// ```rust
/// # use custom_format as cfmt;
/// # use core::fmt;
/// # #[derive(Debug)]
/// # struct Hex(u8);
/// # impl cfmt::runtime::CustomFormat for Hex {
/// #     fn fmt(&self, f: &mut fmt::Formatter, _: &str) -> fmt::Result {
/// #         write!(f, "{:#02x}", self.0)
/// #     }
/// # }
/// # fn call() -> Hex { Hex(42) }
/// println!("{}", cfmt::format_args!("{0:?}, {res :<x>}", res = call()));
/// ```
///
/// must be expanded to:
///
/// ```rust
/// # use custom_format as cfmt;
/// # use core::fmt;
/// # #[derive(Debug)]
/// # struct Hex(u8);
/// # impl cfmt::runtime::CustomFormat for Hex {
/// #     fn fmt(&self, f: &mut fmt::Formatter, _: &str) -> fmt::Result {
/// #         write!(f, "{:#02x}", self.0)
/// #     }
/// # }
/// # fn call() -> Hex { Hex(42) }
/// println!("{}", ::core::format_args!("{0:?}, {1}", &(call()), cfmt::runtime::CustomFormatter::new("x", &(call()))));
/// ```
///
/// which reevaluates the input arguments if they are used several times in the format string.
///
/// To avoid unnecessary reevaluations, we can store the expression result in a variable beforehand:
///
/// ```rust
/// # use custom_format as cfmt;
/// # use core::fmt;
/// # #[derive(Debug)]
/// # struct Hex(u8);
/// # impl cfmt::runtime::CustomFormat for Hex {
/// #     fn fmt(&self, f: &mut fmt::Formatter, _: &str) -> fmt::Result {
/// #         write!(f, "{:#02x}", self.0)
/// #     }
/// # }
/// # fn call() -> Hex { Hex(42) }
/// let res = call();
/// println!("{}", cfmt::format_args!("{res:?}, {res :<x>}"));
/// ```
///
/// is expanded to:
///
/// ```rust
/// # use custom_format as cfmt;
/// # use core::fmt;
/// # #[derive(Debug)]
/// # struct Hex(u8);
/// # impl cfmt::runtime::CustomFormat for Hex {
/// #     fn fmt(&self, f: &mut fmt::Formatter, _: &str) -> fmt::Result {
/// #         write!(f, "{:#02x}", self.0)
/// #     }
/// # }
/// # fn call() -> Hex { Hex(42) }
/// # let res = call();
/// println!("{}", ::core::format_args!("{0:?}, {1}", &res, cfmt::runtime::CustomFormatter::new("x", &res)))
/// ```
#[macro_export]
macro_rules! format_args {
    ($($arg:tt)*) => {{
        $crate::fmt_inner!([::core::format_args!], [], $($arg)*)
    }};
}

/// Creates a `String` using interpolation of runtime expressions
#[macro_export]
macro_rules! format {
    ($($arg:tt)*) => {{
        $crate::fmt_inner!([::std::format!], [], $($arg)*)
    }};
}

/// Prints to the standard output
#[macro_export]
macro_rules! print {
    ($($arg:tt)*) => {{
        $crate::fmt_inner!([::std::print!], [], $($arg)*)
    }};
}

/// Prints to the standard output, with a newline
#[macro_export]
macro_rules! println {
    () => {{
        ::std::println!()
    }};
    ($($arg:tt)*) => {{
        $crate::fmt_inner!([::std::println!], [], $($arg)*)
    }};
}

/// Prints to the standard error
#[macro_export]
macro_rules! eprint {
    ($($arg:tt)*) => {{
        $crate::fmt_inner!([::std::eprint!], [], $($arg)*)
    }};
}

/// Prints to the standard error, with a newline
#[macro_export]
macro_rules! eprintln {
    () => {{
        ::std::eprintln!()
    }};
    ($($arg:tt)*) => {{
        $crate::fmt_inner!([::std::eprintln!], [], $($arg)*)
    }};
}

/// Writes formatted data into a buffer
#[macro_export]
macro_rules! write {
    ($dst:expr, $($arg:tt)*) => {{
        $crate::fmt_inner!([::core::write!], [$dst], $($arg)*)
    }};
}

/// Write formatted data into a buffer, with a newline appended
#[macro_export]
macro_rules! writeln {
    ($dst:expr) => {{
        ::core::writeln!($dst)
    }};
    ($dst:expr, $($arg:tt)*) => {{
        $crate::fmt_inner!([::core::writeln!], [$dst], $($arg)*)
    }};
}

/// Panics the current thread
#[macro_export]
macro_rules! panic {
    () => {{
        ::core::panic!()
    }};
    ($($arg:tt)*) => {{
        $crate::fmt_inner!([::core::panic!], [], $($arg)*)
    }};
}