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#[derive(Copy, Clone, Debug, PartialEq, Eq)]
9pub enum Sign {
10 Plus,
12 Minus,
14}
15
16#[derive(Copy, Clone, Debug, PartialEq, Eq)]
23pub enum Fill {
24 Zero,
26 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#[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 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 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 pub fn sign(&mut self, sign: impl Into<Option<Sign>>) -> &mut Self {
171 self.sign = sign.into();
172 self
173 }
174
175 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 pub fn alternate(&mut self, alternate: bool) -> &mut Self {
205 self.alternate = alternate;
206 self
207 }
208
209 #[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 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 pub fn width(&mut self, width: impl Into<Option<u16>>) -> &mut Self {
269 self.width = width.into();
270 self
271 }
272
273 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 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}