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}
37impl TryFrom<char> for Fill {
38    type Error = TryFromCharError;
39
40    fn try_from(value: char) -> Result<Self, Self::Error> {
41        match value {
42            ' ' => Ok(Self::Space),
43            '0' => Ok(Self::Zero),
44            ch => Err(TryFromCharError(ch)),
45        }
46    }
47}
48#[derive(Debug)]
49pub struct TryFromCharError(char);
50impl fmt::Display for TryFromCharError {
51    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
52        write!(f, "unsupported fill char {:?}", self.0)
53    }
54}
55impl core::error::Error for TryFromCharError {
56    fn description(&self) -> &str {
57        "unsupported fill char"
58    }
59}
60
61/// [`Formatter`] safe builder.
62///
63#[doc = include_str!("../README.md")]
64#[derive(Debug, Default, Clone)]
65pub struct FormatterBuilder {
66    sign: Option<Sign>,
67    sign_aware_zero_pad: bool,
68    alternate: bool,
69    fill_align: Option<(Option<Fill>, Alignment)>,
70    width: Option<u16>,
71    precision: Option<u16>,
72}
73
74macro_rules! pack {
75    ($name:ident $bang:tt $args:tt $($t:tt)*) => {
76        pack!(@run {$name $bang $args} () $($t)*)
77    };
78    (@run $cfg:tt $coll:tt $arg:tt $($t:tt)*) => {
79        pack!(@run $cfg ($coll $arg) $($t)*)
80    };
81    (@run $cfg:tt $coll:tt) => {
82        pack!(@back $cfg () $coll)
83    };
84    (@back $cfg:tt $coll:tt ($a:tt $b:tt)) => {
85        pack!(@back $cfg ($b $coll) $a)
86    };
87    (@back {$name:tt $bang:tt ($($args:tt)*)} $coll:tt ()) => {
88        $name $bang ($($args)* $coll)
89    };
90}
91
92macro_rules! builder {
93    (@run[$self:tt $f:tt $caps:tt $cfg:tt]
94        (($field:ident [$($pat:pat => $lit:literal $($cap:ident)?),*]) $rec:tt)
95    ) => {
96        match $self.$field {
97            $(
98                $pat => builder!(@run[$self (concat!$f, $lit) ($caps $($cap)?) $cfg] $rec),
99            )*
100        }
101    };
102    (@run[$self:tt $f:tt ((((((())))) $($cap1:tt)?) $($cap2:tt)?) $cfg:tt] ()) => {
103        //                 ^ 之后的代码, 分支数每多一个就添加一个括号
104        builder!(@fin[$self $f ($($cap1)? $($cap2)?) $cfg])
105    };
106    (@fin[$self:tt $f:tt ($($caps:tt)*) {
107        $writer:ident,
108        $format_with:ident
109    }]) => {
110        //compile_error!(concat!("{:", concat!$f, "}"))
111        write!($writer, concat!("{:", concat!$f, "}"), $format_with, $($caps = $caps,)*)?
112    };
113    ($self:ident, $writer:ident, $format_with:ident {
114        $($field:ident [$($pat:pat => $lit:literal $($cap:ident)?),* $(,)?]),+ $(,)?
115    }) => {
116        pack!(builder!(@run[$self ("") () {
117            $writer,
118            $format_with
119        }]) $(($field [$($pat => $lit $($cap)?),*]))*)
120    };
121}
122
123impl FormatterBuilder {
124    #[inline]
125    pub fn with<W, F>(&self, mut writer: W, f: F) -> fmt::Result
126    where
127        W: Write,
128        F: FnOnce(&mut Formatter<'_>) -> fmt::Result,
129    {
130        let width = self.width.unwrap_or(0) as usize;
131        let precision = self.precision.unwrap_or(0) as usize;
132
133        let format_with = FormatWith(Some(f).into());
134
135        builder!(self, writer, format_with {
136            fill_align [
137                Some((None, Alignment::Left)) => "<",
138                Some((None, Alignment::Right)) => ">",
139                Some((None, Alignment::Center)) => "^",
140                Some((Some(Fill::Zero), Alignment::Left)) => "0<",
141                Some((Some(Fill::Zero), Alignment::Right)) => "0>",
142                Some((Some(Fill::Zero), Alignment::Center)) => "0^",
143                Some((Some(Fill::Space), Alignment::Left)) => " <",
144                Some((Some(Fill::Space), Alignment::Right)) => " >",
145                Some((Some(Fill::Space), Alignment::Center)) => " ^",
146                None => "",
147            ],
148            sign [
149                Some(Sign::Plus) => "+",
150                Some(Sign::Minus) => "-",
151                None => "",
152            ],
153            alternate [
154                true => "#",
155                false => "",
156            ],
157            sign_aware_zero_pad [
158                true => "0",
159                false => "",
160            ],
161            width [
162                Some(_) => "width$" width,
163                None => "",
164            ],
165            precision [
166                Some(_) => ".precision$" precision,
167                None => "",
168            ],
169        });
170
171        Ok(())
172    }
173}
174
175impl FormatterBuilder {
176    pub fn new() -> Self {
177        Self::default()
178    }
179
180    /// Rebuild from [`Formatter`], but maybe lose some fields
181    ///
182    /// Incomplete list of loses:
183    ///
184    /// - Only support partial fill char ([`Fill`])
185    /// - About DebugHex (`x?` `X?`)
186    pub fn from_formatter_lossy(f: &Formatter<'_>) -> Self {
187        let mut b = Self::new();
188
189        if f.sign_plus() {
190            b.sign(Sign::Plus);
191        } else if f.sign_minus() {
192            b.sign(Sign::Minus);
193        }
194
195        b.sign_aware_zero_pad(f.sign_aware_zero_pad())
196            .alternate(f.alternate())
197            .width(f.width().map(|it| it as u16))
198            .precision(f.precision().map(|it| it as u16));
199
200        if let Some(align) = f.align() {
201            b.align(align);
202
203            if let Ok(fill) = Fill::try_from(f.fill()) {
204                b.fill(fill);
205            }
206        }
207
208        b
209    }
210
211    /// Format like `{:+}` and `{:-}`
212    ///
213    /// # Examples
214    ///
215    /// ```
216    /// # use formatter_builder::*;
217    /// use std::fmt::Display;
218    /// let mut writter = String::new();
219    /// FormatterBuilder::new().sign(Sign::Plus).with(&mut writter, |f| {
220    ///     2i32.fmt(f);
221    ///     Ok(())
222    /// }).unwrap();
223    /// assert_eq!(writter, "+2");
224    /// ```
225    pub fn sign(&mut self, sign: impl Into<Option<Sign>>) -> &mut Self {
226        self.sign = sign.into();
227        self
228    }
229
230    /// Format like `{:0}`
231    ///
232    /// # Examples
233    ///
234    /// ```
235    /// # use formatter_builder::*;
236    /// # let writter = String::new();
237    /// FormatterBuilder::new().sign_aware_zero_pad(true).with(writter, |f| {
238    ///     assert!(f.sign_aware_zero_pad());
239    ///     Ok(())
240    /// }).unwrap();
241    /// ```
242    pub fn sign_aware_zero_pad(&mut self, sign_aware_zero_pad: bool) -> &mut Self {
243        self.sign_aware_zero_pad = sign_aware_zero_pad;
244        self
245    }
246
247    /// Format like `{:#}`
248    ///
249    /// # Examples
250    ///
251    /// ```
252    /// # use formatter_builder::*;
253    /// # let writter = String::new();
254    /// FormatterBuilder::new().alternate(true).with(writter, |f| {
255    ///     assert!(f.alternate());
256    ///     Ok(())
257    /// }).unwrap();
258    /// ```
259    pub fn alternate(&mut self, alternate: bool) -> &mut Self {
260        self.alternate = alternate;
261        self
262    }
263
264    /// Format like `{:0>}` `{: ^}` etc
265    ///
266    /// # Panics
267    ///
268    /// - panic when [`align`](FormatterBuilder::align) is unset
269    ///
270    /// # Examples
271    ///
272    /// ```
273    /// # use formatter_builder::*;
274    /// # use Alignment::*;
275    /// # let writter = String::new();
276    /// FormatterBuilder::new().align(Left).fill(Fill::Space).with(writter, |f| {
277    ///     assert_eq!(f.fill(), ' ');
278    ///     Ok(())
279    /// }).unwrap();
280    /// ```
281    #[track_caller]
282    pub fn fill(&mut self, fill: impl Into<Option<Fill>>) -> &mut Self {
283        if let Some(fill_char) = fill.into() {
284            self.fill_align.as_mut().expect(".fill must setted align").0 = Some(fill_char);
285        }
286        self
287    }
288
289    /// Format like `{:<}` and  `{:^}` and `{:>}`
290    ///
291    /// # Examples
292    ///
293    /// ```
294    /// # use formatter_builder::*;
295    /// # use Alignment::*;
296    /// # let writter = String::new();
297    /// FormatterBuilder::new().align(Center).with(writter, |f| {
298    ///     assert_eq!(f.align(), Some(Center));
299    ///     Ok(())
300    /// }).unwrap();
301    /// ```
302    pub fn align(&mut self, align: impl Into<Option<Alignment>>) -> &mut Self {
303        if let Some(alignment) = align.into() {
304            self.fill_align.get_or_insert((None, alignment)).1 = alignment;
305        }
306        self
307    }
308
309    /// Format like `{:3}`
310    ///
311    /// # Examples
312    ///
313    /// ```
314    /// # use formatter_builder::*;
315    /// use std::fmt::Display;
316    /// let mut writter = String::new();
317    /// FormatterBuilder::new().width(3).with(&mut writter, |f| {
318    ///     2i32.fmt(f);
319    ///     Ok(())
320    /// }).unwrap();
321    /// assert_eq!(writter, "  2");
322    /// ```
323    pub fn width(&mut self, width: impl Into<Option<u16>>) -> &mut Self {
324        self.width = width.into();
325        self
326    }
327
328    /// Format like `{:.3}`
329    ///
330    /// # Examples
331    ///
332    /// ```
333    /// # use formatter_builder::*;
334    /// use std::fmt::Display;
335    /// let mut writter = String::new();
336    /// FormatterBuilder::new().precision(3).with(&mut writter, |f| {
337    ///     2f32.fmt(f);
338    ///     Ok(())
339    /// }).unwrap();
340    /// assert_eq!(writter, "2.000");
341    /// ```
342    pub fn precision(&mut self, precision: impl Into<Option<u16>>) -> &mut Self {
343        self.precision = precision.into();
344        self
345    }
346}
347
348struct FormatWith<F>(core::cell::Cell<Option<F>>)
349where
350    F: FnOnce(&mut Formatter<'_>) -> fmt::Result,
351;
352
353impl<F> fmt::Display for FormatWith<F>
354where
355    F: FnOnce(&mut Formatter<'_>) -> fmt::Result,
356{
357    #[inline]
358    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
359        self.0.take().unwrap()(f)
360    }
361}
362
363#[cfg(test)]
364mod tests {
365    use super::*;
366    use Alignment::*;
367    use Fill::*;
368
369    const W: String = String::new();
370
371    #[test]
372    fn basic_work() {
373        let mut out = String::new();
374        FormatterBuilder::new()
375            .with(&mut out, |f| {
376                write!(f, "foo")?;
377                write!(f, "bar")?;
378                Ok(())
379            }).unwrap();
380        assert_eq!(out, "foobar");
381    }
382
383    #[test]
384    fn align() {
385        let aligns = [ Left, Right, Center ];
386        for align in aligns {
387            FormatterBuilder::new()
388                .align(align)
389                .with(W, |f| {
390                    assert_eq!(f.align(), Some(align));
391                    Ok(())
392                }).unwrap();
393        }
394    }
395
396    #[test]
397    fn fill() {
398        let fills = [Zero, Space];
399        for fill in fills {
400            FormatterBuilder::new()
401                .align(Left)
402                .fill(fill)
403                .with(W, |f| {
404                    assert_eq!(f.fill(), fill.as_char());
405                    Ok(())
406                }).unwrap();
407        }
408    }
409
410    #[test]
411    fn alternate() {
412        for alt in [true, false] {
413            FormatterBuilder::new()
414                .alternate(alt)
415                .with(W, |f| {
416                    assert_eq!(f.alternate(), alt);
417                    Ok(())
418                }).unwrap();
419        }
420    }
421
422    #[test]
423    fn width() {
424        for width in [None, Some(0), Some(1), Some(2), Some(4), Some(256)] {
425            FormatterBuilder::new()
426                .width(width)
427                .with(W, |f| {
428                    assert_eq!(f.width(), width.map(Into::into));
429                    Ok(())
430                }).unwrap();
431        }
432    }
433
434    #[test]
435    fn precision() {
436        for precision in [None, Some(0), Some(1), Some(2), Some(4), Some(256)] {
437            FormatterBuilder::new()
438                .precision(precision)
439                .with(W, |f| {
440                    assert_eq!(f.precision(), precision.map(Into::into));
441                    Ok(())
442                }).unwrap();
443        }
444    }
445
446    #[test]
447    fn sign_aware_zero_pad() {
448        for sazp in [true, false] {
449            FormatterBuilder::new()
450                .sign_aware_zero_pad(sazp)
451                .with(W, |f| {
452                    assert_eq!(f.sign_aware_zero_pad(), sazp);
453                    Ok(())
454                }).unwrap();
455        }
456    }
457
458    #[test]
459    fn sign() {
460        // Sign::Minus unused
461        for (sign, exp) in [(Sign::Plus, "+1")] {
462            let mut out = String::new();
463            FormatterBuilder::new()
464                .sign(sign)
465                .with(&mut out, |f| {
466                    std::fmt::Display::fmt(&1, f)?;
467                    Ok(())
468                }).unwrap();
469            assert_eq!(out, exp);
470        }
471    }
472
473    #[test]
474    fn core_default_fill() {
475        struct Foo;
476        impl fmt::Display for Foo {
477            fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
478                assert_eq!(f.fill(), ' ');
479                Ok(())
480            }
481        }
482        assert_eq!(Foo.to_string(), "");
483    }
484
485    #[test]
486    fn from_formatter_lossy() {
487        struct Foo;
488        impl fmt::Display for Foo {
489            fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
490                FormatterBuilder::from_formatter_lossy(f)
491                    .with(W, |g| {
492                        assert_eq!(f.fill(), g.fill());
493                        assert_eq!(f.sign_plus(), g.sign_plus());
494                        assert_eq!(f.sign_minus(), g.sign_minus());
495                        assert_eq!(f.alternate(), g.alternate());
496                        assert_eq!(f.width(), g.width());
497                        assert_eq!(f.precision(), g.precision());
498                        Ok(())
499                    })
500            }
501        }
502        let _ = format!("{Foo}");
503        let _ = format!("{Foo:#}");
504        let _ = format!("{Foo:#0}");
505        let _ = format!("{Foo:0<#}");
506        let _ = format!("{Foo: <#}");
507        let _ = format!("{Foo:#.0}");
508        let _ = format!("{Foo:#.}");
509        let _ = format!("{Foo:#0.}");
510        let _ = format!("{Foo:#1.}");
511        let _ = format!("{Foo:#.0}");
512        let _ = format!("{Foo:#.1}");
513        let _ = format!("{Foo:#2.1}");
514        let _ = format!("{Foo:2.1}");
515        let _ = format!("{Foo:+2.1}");
516        let _ = format!("{Foo:-2.1}");
517        let _ = format!("{Foo:-#2.1}");
518        let _ = format!("{Foo:0^-#2.1}");
519    }
520}