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    pub fn with<W, F>(&self, mut writer: W, f: F) -> fmt::Result
125    where
126        W: Write,
127        F: FnOnce(&mut Formatter<'_>) -> fmt::Result,
128    {
129        let width = self.width.unwrap_or(0) as usize;
130        let precision = self.precision.unwrap_or(0) as usize;
131
132        let format_with = FormatWith(Some(f).into());
133
134        builder!(self, writer, format_with {
135            fill_align [
136                Some((None, Alignment::Left)) => "<",
137                Some((None, Alignment::Right)) => ">",
138                Some((None, Alignment::Center)) => "^",
139                Some((Some(Fill::Zero), Alignment::Left)) => "0<",
140                Some((Some(Fill::Zero), Alignment::Right)) => "0>",
141                Some((Some(Fill::Zero), Alignment::Center)) => "0^",
142                Some((Some(Fill::Space), Alignment::Left)) => " <",
143                Some((Some(Fill::Space), Alignment::Right)) => " >",
144                Some((Some(Fill::Space), Alignment::Center)) => " ^",
145                None => "",
146            ],
147            sign [
148                Some(Sign::Plus) => "+",
149                Some(Sign::Minus) => "-",
150                None => "",
151            ],
152            alternate [
153                true => "#",
154                false => "",
155            ],
156            sign_aware_zero_pad [
157                true => "0",
158                false => "",
159            ],
160            width [
161                Some(_) => "width$" width,
162                None => "",
163            ],
164            precision [
165                Some(_) => ".precision$" precision,
166                None => "",
167            ],
168        });
169
170        Ok(())
171    }
172}
173
174impl FormatterBuilder {
175    pub fn new() -> Self {
176        Self::default()
177    }
178
179    /// Rebuild from [`Formatter`], but maybe lose some fields
180    ///
181    /// Incomplete list of loses:
182    ///
183    /// - Only support partial fill char ([`Fill`])
184    /// - About DebugHex (`x?` `X?`)
185    pub fn from_formatter_lossy(f: &Formatter<'_>) -> Self {
186        let mut b = Self::new();
187
188        if f.sign_plus() {
189            b.sign(Sign::Plus);
190        } else if f.sign_minus() {
191            b.sign(Sign::Minus);
192        }
193
194        b.sign_aware_zero_pad(f.sign_aware_zero_pad())
195            .alternate(f.alternate())
196            .width(f.width().map(|it| it as u16))
197            .precision(f.precision().map(|it| it as u16));
198
199        if let Some(align) = f.align() {
200            b.align(align);
201
202            if let Ok(fill) = Fill::try_from(f.fill()) {
203                b.fill(fill);
204            }
205        }
206
207        b
208    }
209
210    /// Format like `{:+}` and `{:-}`
211    ///
212    /// # Examples
213    ///
214    /// ```
215    /// # use formatter_builder::*;
216    /// use std::fmt::Display;
217    /// let mut writter = String::new();
218    /// FormatterBuilder::new().sign(Sign::Plus).with(&mut writter, |f| {
219    ///     2i32.fmt(f);
220    ///     Ok(())
221    /// }).unwrap();
222    /// assert_eq!(writter, "+2");
223    /// ```
224    pub fn sign(&mut self, sign: impl Into<Option<Sign>>) -> &mut Self {
225        self.sign = sign.into();
226        self
227    }
228
229    /// Format like `{:0}`
230    ///
231    /// # Examples
232    ///
233    /// ```
234    /// # use formatter_builder::*;
235    /// # let writter = String::new();
236    /// FormatterBuilder::new().sign_aware_zero_pad(true).with(writter, |f| {
237    ///     assert!(f.sign_aware_zero_pad());
238    ///     Ok(())
239    /// }).unwrap();
240    /// ```
241    pub fn sign_aware_zero_pad(&mut self, sign_aware_zero_pad: bool) -> &mut Self {
242        self.sign_aware_zero_pad = sign_aware_zero_pad;
243        self
244    }
245
246    /// Format like `{:#}`
247    ///
248    /// # Examples
249    ///
250    /// ```
251    /// # use formatter_builder::*;
252    /// # let writter = String::new();
253    /// FormatterBuilder::new().alternate(true).with(writter, |f| {
254    ///     assert!(f.alternate());
255    ///     Ok(())
256    /// }).unwrap();
257    /// ```
258    pub fn alternate(&mut self, alternate: bool) -> &mut Self {
259        self.alternate = alternate;
260        self
261    }
262
263    /// Format like `{:0>}` `{: ^}` etc
264    ///
265    /// # Panics
266    ///
267    /// - panic when [`align`](FormatterBuilder::align) is unset
268    ///
269    /// # Examples
270    ///
271    /// ```
272    /// # use formatter_builder::*;
273    /// # use Alignment::*;
274    /// # let writter = String::new();
275    /// FormatterBuilder::new().align(Left).fill(Fill::Space).with(writter, |f| {
276    ///     assert_eq!(f.fill(), ' ');
277    ///     Ok(())
278    /// }).unwrap();
279    /// ```
280    #[track_caller]
281    pub fn fill(&mut self, fill: impl Into<Option<Fill>>) -> &mut Self {
282        if let Some(fill_char) = fill.into() {
283            self.fill_align.as_mut().expect(".fill must setted align").0 = Some(fill_char);
284        }
285        self
286    }
287
288    /// Format like `{:<}` and  `{:^}` and `{:>}`
289    ///
290    /// # Examples
291    ///
292    /// ```
293    /// # use formatter_builder::*;
294    /// # use Alignment::*;
295    /// # let writter = String::new();
296    /// FormatterBuilder::new().align(Center).with(writter, |f| {
297    ///     assert_eq!(f.align(), Some(Center));
298    ///     Ok(())
299    /// }).unwrap();
300    /// ```
301    pub fn align(&mut self, align: impl Into<Option<Alignment>>) -> &mut Self {
302        if let Some(alignment) = align.into() {
303            self.fill_align.get_or_insert((None, alignment)).1 = alignment;
304        }
305        self
306    }
307
308    /// Format like `{:3}`
309    ///
310    /// # Examples
311    ///
312    /// ```
313    /// # use formatter_builder::*;
314    /// use std::fmt::Display;
315    /// let mut writter = String::new();
316    /// FormatterBuilder::new().width(3).with(&mut writter, |f| {
317    ///     2i32.fmt(f);
318    ///     Ok(())
319    /// }).unwrap();
320    /// assert_eq!(writter, "  2");
321    /// ```
322    pub fn width(&mut self, width: impl Into<Option<u16>>) -> &mut Self {
323        self.width = width.into();
324        self
325    }
326
327    /// Format like `{:.3}`
328    ///
329    /// # Examples
330    ///
331    /// ```
332    /// # use formatter_builder::*;
333    /// use std::fmt::Display;
334    /// let mut writter = String::new();
335    /// FormatterBuilder::new().precision(3).with(&mut writter, |f| {
336    ///     2f32.fmt(f);
337    ///     Ok(())
338    /// }).unwrap();
339    /// assert_eq!(writter, "2.000");
340    /// ```
341    pub fn precision(&mut self, precision: impl Into<Option<u16>>) -> &mut Self {
342        self.precision = precision.into();
343        self
344    }
345}
346
347struct FormatWith<F>(core::cell::Cell<Option<F>>)
348where
349    F: FnOnce(&mut Formatter<'_>) -> fmt::Result,
350;
351
352impl<F> fmt::Display for FormatWith<F>
353where
354    F: FnOnce(&mut Formatter<'_>) -> fmt::Result,
355{
356    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
357        self.0.take().unwrap()(f)
358    }
359}
360
361#[cfg(test)]
362mod tests {
363    use super::*;
364    use Alignment::*;
365    use Fill::*;
366
367    const W: String = String::new();
368
369    #[test]
370    fn basic_work() {
371        let mut out = String::new();
372        FormatterBuilder::new()
373            .with(&mut out, |f| {
374                write!(f, "foo")?;
375                write!(f, "bar")?;
376                Ok(())
377            }).unwrap();
378        assert_eq!(out, "foobar");
379    }
380
381    #[test]
382    fn align() {
383        let aligns = [ Left, Right, Center ];
384        for align in aligns {
385            FormatterBuilder::new()
386                .align(align)
387                .with(W, |f| {
388                    assert_eq!(f.align(), Some(align));
389                    Ok(())
390                }).unwrap();
391        }
392    }
393
394    #[test]
395    fn fill() {
396        let fills = [Zero, Space];
397        for fill in fills {
398            FormatterBuilder::new()
399                .align(Left)
400                .fill(fill)
401                .with(W, |f| {
402                    assert_eq!(f.fill(), fill.as_char());
403                    Ok(())
404                }).unwrap();
405        }
406    }
407
408    #[test]
409    fn alternate() {
410        for alt in [true, false] {
411            FormatterBuilder::new()
412                .alternate(alt)
413                .with(W, |f| {
414                    assert_eq!(f.alternate(), alt);
415                    Ok(())
416                }).unwrap();
417        }
418    }
419
420    #[test]
421    fn width() {
422        for width in [None, Some(0), Some(1), Some(2), Some(4), Some(256)] {
423            FormatterBuilder::new()
424                .width(width)
425                .with(W, |f| {
426                    assert_eq!(f.width(), width.map(Into::into));
427                    Ok(())
428                }).unwrap();
429        }
430    }
431
432    #[test]
433    fn precision() {
434        for precision in [None, Some(0), Some(1), Some(2), Some(4), Some(256)] {
435            FormatterBuilder::new()
436                .precision(precision)
437                .with(W, |f| {
438                    assert_eq!(f.precision(), precision.map(Into::into));
439                    Ok(())
440                }).unwrap();
441        }
442    }
443
444    #[test]
445    fn sign_aware_zero_pad() {
446        for sazp in [true, false] {
447            FormatterBuilder::new()
448                .sign_aware_zero_pad(sazp)
449                .with(W, |f| {
450                    assert_eq!(f.sign_aware_zero_pad(), sazp);
451                    Ok(())
452                }).unwrap();
453        }
454    }
455
456    #[test]
457    fn sign() {
458        // Sign::Minus unused
459        for (sign, exp) in [(Sign::Plus, "+1")] {
460            let mut out = String::new();
461            FormatterBuilder::new()
462                .sign(sign)
463                .with(&mut out, |f| {
464                    std::fmt::Display::fmt(&1, f)?;
465                    Ok(())
466                }).unwrap();
467            assert_eq!(out, exp);
468        }
469    }
470
471    #[test]
472    fn core_default_fill() {
473        struct Foo;
474        impl fmt::Display for Foo {
475            fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
476                assert_eq!(f.fill(), ' ');
477                Ok(())
478            }
479        }
480        assert_eq!(Foo.to_string(), "");
481    }
482
483    #[test]
484    fn from_formatter_lossy() {
485        struct Foo;
486        impl fmt::Display for Foo {
487            fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
488                FormatterBuilder::from_formatter_lossy(f)
489                    .with(W, |g| {
490                        assert_eq!(f.fill(), g.fill());
491                        assert_eq!(f.sign_plus(), g.sign_plus());
492                        assert_eq!(f.sign_minus(), g.sign_minus());
493                        assert_eq!(f.alternate(), g.alternate());
494                        assert_eq!(f.width(), g.width());
495                        assert_eq!(f.precision(), g.precision());
496                        Ok(())
497                    })
498            }
499        }
500        let _ = format!("{Foo}");
501        let _ = format!("{Foo:#}");
502        let _ = format!("{Foo:#0}");
503        let _ = format!("{Foo:0<#}");
504        let _ = format!("{Foo: <#}");
505        let _ = format!("{Foo:#.0}");
506        let _ = format!("{Foo:#.}");
507        let _ = format!("{Foo:#0.}");
508        let _ = format!("{Foo:#1.}");
509        let _ = format!("{Foo:#.0}");
510        let _ = format!("{Foo:#.1}");
511        let _ = format!("{Foo:#2.1}");
512        let _ = format!("{Foo:2.1}");
513        let _ = format!("{Foo:+2.1}");
514        let _ = format!("{Foo:-2.1}");
515        let _ = format!("{Foo:-#2.1}");
516        let _ = format!("{Foo:0^-#2.1}");
517    }
518}