Skip to main content

formatter_builder/
lib.rs

1#![cfg_attr(not(test), no_std)]
2#![doc = include_str!("../README.md")]
3
4use core::fmt::{self, Write, Formatter};
5pub use core::fmt::{Alignment};
6
7/// The signedness of a [`Formatter`].
8#[derive(Copy, Clone, Debug, PartialEq, Eq)]
9pub enum Sign {
10    /// Represents the `+` flag.
11    Plus,
12    /// Represents the `-` flag.
13    Minus,
14}
15
16// NOTE: 如果要添加更多字符, 是一个 break,
17// 并且哪怕声明非穷尽, 宏代码量也会爆炸, 体验会很大影响, 所以需要非同一个版本
18
19/// [`Formatter`] fill character.
20///
21/// Due to some limitations, only a small number of characters are supported.
22#[derive(Copy, Clone, Debug, PartialEq, Eq)]
23pub enum Fill {
24    /// Character `'0'`
25    Zero,
26    /// Character `' '`
27    Space,
28}
29impl Fill {
30    pub fn as_char(self) -> char {
31        match self {
32            Self::Zero => '0',
33            Self::Space => ' ',
34        }
35    }
36}
37
38/// [`Formatter`] safe builder.
39///
40#[doc = include_str!("../README.md")]
41#[derive(Debug, Default)]
42pub struct FormatterBuilder {
43    sign: Option<Sign>,
44    sign_aware_zero_pad: bool,
45    alternate: bool,
46    fill_align: Option<(Option<Fill>, Alignment)>,
47    width: Option<u16>,
48    precision: Option<u16>,
49}
50
51macro_rules! pack {
52    ($name:ident $bang:tt $args:tt $($t:tt)*) => {
53        pack!(@run {$name $bang $args} () $($t)*)
54    };
55    (@run $cfg:tt $coll:tt $arg:tt $($t:tt)*) => {
56        pack!(@run $cfg ($coll $arg) $($t)*)
57    };
58    (@run $cfg:tt $coll:tt) => {
59        pack!(@back $cfg () $coll)
60    };
61    (@back $cfg:tt $coll:tt ($a:tt $b:tt)) => {
62        pack!(@back $cfg ($b $coll) $a)
63    };
64    (@back {$name:tt $bang:tt ($($args:tt)*)} $coll:tt ()) => {
65        $name $bang ($($args)* $coll)
66    };
67}
68
69macro_rules! builder {
70    (@run[$self:tt $f:tt $caps:tt $cfg:tt]
71        (($field:ident [$($pat:pat => $lit:literal $($cap:ident)?),*]) $rec:tt)
72    ) => {
73        match $self.$field {
74            $(
75                $pat => builder!(@run[$self (concat!$f, $lit) ($caps $($cap)?) $cfg] $rec),
76            )*
77        }
78    };
79    (@run[$self:tt $f:tt ((((((())))) $($cap1:tt)?) $($cap2:tt)?) $cfg:tt] ()) => {
80        //                 ^ 之后的代码, 分支数每多一个就添加一个括号
81        builder!(@fin[$self $f ($($cap1)? $($cap2)?) $cfg])
82    };
83    (@fin[$self:tt $f:tt ($($caps:tt)*) {
84        $writer:ident,
85        $format_with:ident
86    }]) => {
87        //compile_error!(concat!("{:", concat!$f, "}"))
88        write!($writer, concat!("{:", concat!$f, "}"), $format_with, $($caps = $caps,)*)?
89    };
90    ($self:ident, $writer:ident, $format_with:ident {
91        $($field:ident [$($pat:pat => $lit:literal $($cap:ident)?),* $(,)?]),+ $(,)?
92    }) => {
93        pack!(builder!(@run[$self ("") () {
94            $writer,
95            $format_with
96        }]) $(($field [$($pat => $lit $($cap)?),*]))*)
97    };
98}
99
100impl FormatterBuilder {
101    pub fn with<W, F>(&self, mut writer: W, f: F) -> fmt::Result
102    where
103        W: Write,
104        F: FnOnce(&mut Formatter<'_>) -> fmt::Result,
105    {
106        let width = self.width.unwrap_or(0) as usize;
107        let precision = self.precision.unwrap_or(0) as usize;
108
109        let format_with = FormatWith(Some(f).into());
110
111        builder!(self, writer, format_with {
112            fill_align [
113                Some((None, Alignment::Left)) => "<",
114                Some((None, Alignment::Right)) => ">",
115                Some((None, Alignment::Center)) => "^",
116                Some((Some(Fill::Zero), Alignment::Left)) => "0<",
117                Some((Some(Fill::Zero), Alignment::Right)) => "0>",
118                Some((Some(Fill::Zero), Alignment::Center)) => "0^",
119                Some((Some(Fill::Space), Alignment::Left)) => " <",
120                Some((Some(Fill::Space), Alignment::Right)) => " >",
121                Some((Some(Fill::Space), Alignment::Center)) => " ^",
122                None => "",
123            ],
124            sign [
125                Some(Sign::Plus) => "+",
126                Some(Sign::Minus) => "-",
127                None => "",
128            ],
129            alternate [
130                true => "#",
131                false => "",
132            ],
133            sign_aware_zero_pad [
134                true => "0",
135                false => "",
136            ],
137            width [
138                Some(_) => "width$" width,
139                None => "",
140            ],
141            precision [
142                Some(_) => ".precision$" precision,
143                None => "",
144            ],
145        });
146
147        Ok(())
148    }
149}
150
151impl FormatterBuilder {
152    pub fn new() -> Self {
153        Self::default()
154    }
155
156    /// Format like `{:+}` and `{:-}`
157    ///
158    /// # Examples
159    ///
160    /// ```
161    /// # use formatter_builder::*;
162    /// use std::fmt::Display;
163    /// let mut writter = String::new();
164    /// FormatterBuilder::new().sign(Sign::Plus).with(&mut writter, |f| {
165    ///     2i32.fmt(f);
166    ///     Ok(())
167    /// }).unwrap();
168    /// assert_eq!(writter, "+2");
169    /// ```
170    pub fn sign(&mut self, sign: impl Into<Option<Sign>>) -> &mut Self {
171        self.sign = sign.into();
172        self
173    }
174
175    /// Format like `{:0}`
176    ///
177    /// # Examples
178    ///
179    /// ```
180    /// # use formatter_builder::*;
181    /// # let writter = String::new();
182    /// FormatterBuilder::new().sign_aware_zero_pad(true).with(writter, |f| {
183    ///     assert!(f.sign_aware_zero_pad());
184    ///     Ok(())
185    /// }).unwrap();
186    /// ```
187    pub fn sign_aware_zero_pad(&mut self, sign_aware_zero_pad: bool) -> &mut Self {
188        self.sign_aware_zero_pad = sign_aware_zero_pad;
189        self
190    }
191
192    /// Format like `{:#}`
193    ///
194    /// # Examples
195    ///
196    /// ```
197    /// # use formatter_builder::*;
198    /// # let writter = String::new();
199    /// FormatterBuilder::new().alternate(true).with(writter, |f| {
200    ///     assert!(f.alternate());
201    ///     Ok(())
202    /// }).unwrap();
203    /// ```
204    pub fn alternate(&mut self, alternate: bool) -> &mut Self {
205        self.alternate = alternate;
206        self
207    }
208
209    /// Format like `{:0>}` `{: ^}` etc
210    ///
211    /// # Panics
212    ///
213    /// - panic when [`align`](FormatterBuilder::align) is unset
214    ///
215    /// # Examples
216    ///
217    /// ```
218    /// # use formatter_builder::*;
219    /// # use Alignment::*;
220    /// # let writter = String::new();
221    /// FormatterBuilder::new().align(Left).fill(Fill::Space).with(writter, |f| {
222    ///     assert_eq!(f.fill(), ' ');
223    ///     Ok(())
224    /// }).unwrap();
225    /// ```
226    #[track_caller]
227    pub fn fill(&mut self, fill: impl Into<Option<Fill>>) -> &mut Self {
228        if let Some(fill_char) = fill.into() {
229            self.fill_align.as_mut().expect(".fill must setted align").0 = Some(fill_char);
230        }
231        self
232    }
233
234    /// Format like `{:<}` and  `{:^}` and `{:>}`
235    ///
236    /// # Examples
237    ///
238    /// ```
239    /// # use formatter_builder::*;
240    /// # use Alignment::*;
241    /// # let writter = String::new();
242    /// FormatterBuilder::new().align(Center).with(writter, |f| {
243    ///     assert_eq!(f.align(), Some(Center));
244    ///     Ok(())
245    /// }).unwrap();
246    /// ```
247    pub fn align(&mut self, align: impl Into<Option<Alignment>>) -> &mut Self {
248        if let Some(alignment) = align.into() {
249            self.fill_align.get_or_insert((None, alignment)).1 = alignment;
250        }
251        self
252    }
253
254    /// Format like `{:3}`
255    ///
256    /// # Examples
257    ///
258    /// ```
259    /// # use formatter_builder::*;
260    /// use std::fmt::Display;
261    /// let mut writter = String::new();
262    /// FormatterBuilder::new().width(3).with(&mut writter, |f| {
263    ///     2i32.fmt(f);
264    ///     Ok(())
265    /// }).unwrap();
266    /// assert_eq!(writter, "  2");
267    /// ```
268    pub fn width(&mut self, width: impl Into<Option<u16>>) -> &mut Self {
269        self.width = width.into();
270        self
271    }
272
273    /// Format like `{:.3}`
274    ///
275    /// # Examples
276    ///
277    /// ```
278    /// # use formatter_builder::*;
279    /// use std::fmt::Display;
280    /// let mut writter = String::new();
281    /// FormatterBuilder::new().precision(3).with(&mut writter, |f| {
282    ///     2f32.fmt(f);
283    ///     Ok(())
284    /// }).unwrap();
285    /// assert_eq!(writter, "2.000");
286    /// ```
287    pub fn precision(&mut self, precision: impl Into<Option<u16>>) -> &mut Self {
288        self.precision = precision.into();
289        self
290    }
291}
292
293struct FormatWith<F>(core::cell::Cell<Option<F>>)
294where
295    F: FnOnce(&mut Formatter<'_>) -> fmt::Result,
296;
297
298impl<F> fmt::Display for FormatWith<F>
299where
300    F: FnOnce(&mut Formatter<'_>) -> fmt::Result,
301{
302    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
303        self.0.take().unwrap()(f)
304    }
305}
306
307#[cfg(test)]
308mod tests {
309    use super::*;
310    use Alignment::*;
311    use Fill::*;
312
313    const W: String = String::new();
314
315    #[test]
316    fn basic_work() {
317        let mut out = String::new();
318        FormatterBuilder::new()
319            .with(&mut out, |f| {
320                write!(f, "foo")?;
321                write!(f, "bar")?;
322                Ok(())
323            }).unwrap();
324        assert_eq!(out, "foobar");
325    }
326
327    #[test]
328    fn align() {
329        let aligns = [ Left, Right, Center ];
330        for align in aligns {
331            FormatterBuilder::new()
332                .align(align)
333                .with(W, |f| {
334                    assert_eq!(f.align(), Some(align));
335                    Ok(())
336                }).unwrap();
337        }
338    }
339
340    #[test]
341    fn fill() {
342        let fills = [Zero, Space];
343        for fill in fills {
344            FormatterBuilder::new()
345                .align(Left)
346                .fill(fill)
347                .with(W, |f| {
348                    assert_eq!(f.fill(), fill.as_char());
349                    Ok(())
350                }).unwrap();
351        }
352    }
353
354    #[test]
355    fn alternate() {
356        for alt in [true, false] {
357            FormatterBuilder::new()
358                .alternate(alt)
359                .with(W, |f| {
360                    assert_eq!(f.alternate(), alt);
361                    Ok(())
362                }).unwrap();
363        }
364    }
365
366    #[test]
367    fn width() {
368        for width in [None, Some(0), Some(1), Some(2), Some(4), Some(256)] {
369            FormatterBuilder::new()
370                .width(width)
371                .with(W, |f| {
372                    assert_eq!(f.width(), width.map(Into::into));
373                    Ok(())
374                }).unwrap();
375        }
376    }
377
378    #[test]
379    fn precision() {
380        for precision in [None, Some(0), Some(1), Some(2), Some(4), Some(256)] {
381            FormatterBuilder::new()
382                .precision(precision)
383                .with(W, |f| {
384                    assert_eq!(f.precision(), precision.map(Into::into));
385                    Ok(())
386                }).unwrap();
387        }
388    }
389
390    #[test]
391    fn sign_aware_zero_pad() {
392        for sazp in [true, false] {
393            FormatterBuilder::new()
394                .sign_aware_zero_pad(sazp)
395                .with(W, |f| {
396                    assert_eq!(f.sign_aware_zero_pad(), sazp);
397                    Ok(())
398                }).unwrap();
399        }
400    }
401
402    #[test]
403    fn sign() {
404        // Sign::Minus unused
405        for (sign, exp) in [(Sign::Plus, "+1")] {
406            let mut out = String::new();
407            FormatterBuilder::new()
408                .sign(sign)
409                .with(&mut out, |f| {
410                    std::fmt::Display::fmt(&1, f)?;
411                    Ok(())
412                }).unwrap();
413            assert_eq!(out, exp);
414        }
415    }
416}