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}
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#[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 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 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 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 pub fn sign(&mut self, sign: impl Into<Option<Sign>>) -> &mut Self {
226 self.sign = sign.into();
227 self
228 }
229
230 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 pub fn alternate(&mut self, alternate: bool) -> &mut Self {
260 self.alternate = alternate;
261 self
262 }
263
264 #[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 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 pub fn width(&mut self, width: impl Into<Option<u16>>) -> &mut Self {
324 self.width = width.into();
325 self
326 }
327
328 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 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}