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