custom_format/compile_time.rs
1//! Provides types associated to compile-time formatting.
2
3use core::fmt;
4
5/// Trait for custom formatting with compile-time format checking
6pub trait CustomFormat<const SPEC: u128> {
7 /// Formats the value using the given formatter.
8 ///
9 /// # Examples
10 ///
11 /// ```rust
12 /// use custom_format as cfmt;
13 /// use custom_format::compile_time::{spec, CustomFormat};
14 ///
15 /// use core::fmt;
16 ///
17 /// #[derive(Debug)]
18 /// struct Hex(u8);
19 ///
20 /// impl CustomFormat<{ spec("x") }> for Hex {
21 /// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
22 /// write!(f, "{:#02x}", self.0)
23 /// }
24 /// }
25 ///
26 /// impl CustomFormat<{ spec("X") }> for Hex {
27 /// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
28 /// write!(f, "{:#02X}", self.0)
29 /// }
30 /// }
31 ///
32 /// assert_eq!(cfmt::format!("{0:X?}, {0 :x}, {0 :X}", Hex(0xAB)), "Hex(AB), 0xab, 0xAB");
33 /// ```
34 ///
35 /// The following statement doesn't compile since `"z"` is not a valid format specifier:
36 ///
37 /// ```rust,compile_fail
38 /// # use custom_format as cfmt;
39 /// # use custom_format::compile_time::{spec, CustomFormat};
40 /// # use core::fmt;
41 /// # struct Hex(u8);
42 /// # impl CustomFormat<{ cfmt::spec("x") }> for Hex {
43 /// # fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
44 /// # write!(f, "{:#02x}", self.0)
45 /// # }
46 /// # }
47 /// # impl CustomFormat<{ cfmt::spec("X") }> for Hex {
48 /// # fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
49 /// # write!(f, "{:#02X}", self.0)
50 /// # }
51 /// # }
52 /// cfmt::println!("{ :z}", Hex(0));
53 /// ```
54 ///
55 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result;
56}
57
58/// Wrapper for custom formatting via its [`Display`](core::fmt::Display) trait.
59///
60/// The format specifier is a const-generic parameter and is part of the type.
61///
62#[derive(Debug, Clone)]
63pub struct CustomFormatter<'a, T, const SPEC: u128> {
64 /// Value to format
65 value: &'a T,
66}
67
68impl<'a, T, const SPEC: u128> CustomFormatter<'a, T, SPEC> {
69 /// Construct a new [`CustomFormatter`] value
70 pub fn new(value: &'a T) -> Self {
71 Self { value }
72 }
73}
74
75/// Helper macro for constructing a new [`compile_time::CustomFormatter`](CustomFormatter) value from a format specifier
76#[macro_export]
77macro_rules! custom_formatter {
78 ($spec:literal, $value:expr) => {{
79 $crate::compile_time::CustomFormatter::<_, { $crate::compile_time::spec($spec) }>::new($value)
80 }};
81}
82pub use custom_formatter;
83
84impl<T: CustomFormat<SPEC>, const SPEC: u128> fmt::Display for CustomFormatter<'_, T, SPEC> {
85 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
86 CustomFormat::fmt(self.value, f)
87 }
88}
89
90/// Convert a format specifier to a [`u128`], used as a const-generic parameter
91pub const fn spec(s: &str) -> u128 {
92 let bytes = s.as_bytes();
93 let len = s.len();
94
95 if len > 16 {
96 #[allow(unconditional_panic)]
97 let _ = ["format specifier is limited to 16 bytes"][usize::MAX];
98 }
99
100 let mut result = [0u8; 16];
101
102 let mut i = 0;
103 while i < len {
104 result[i] = bytes[i];
105 i += 1;
106 }
107
108 u128::from_le_bytes(result)
109}