use alloc::string::String;
use core::fmt;
use core::fmt::Write as _;
use crate::arg::{FormatArg, FormatType};
use crate::error::FormatError;
use crate::spec::{Align, Spec};
struct Core<'a> {
arg: &'a dyn FormatArg,
spec: Spec,
}
impl fmt::Display for Core<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.arg
.write(self.spec.ty, self.spec.pretty(), self.spec.precision, f)
}
}
fn radix_prefix(ty: FormatType) -> &'static str {
match ty {
FormatType::LowerHex | FormatType::UpperHex => "0x",
FormatType::Octal => "0o",
FormatType::Binary => "0b",
_ => "",
}
}
fn note_write(result: fmt::Result) -> Result<(), FormatError> {
result.map_err(|_| FormatError::WriteFailed)
}
fn write_fill(w: &mut dyn fmt::Write, fill: char, n: usize) -> Result<(), FormatError> {
let mut buf = [0u8; 4];
let piece = fill.encode_utf8(&mut buf);
for _ in 0..n {
note_write(w.write_str(piece))?;
}
Ok(())
}
fn write_padded(
w: &mut dyn fmt::Write,
spec: Spec,
default: Align,
total_len: usize,
body: impl FnOnce(&mut dyn fmt::Write) -> Result<(), FormatError>,
) -> Result<(), FormatError> {
let Some(width) = spec.width else {
return body(w);
};
if total_len >= width {
return body(w);
}
let pad = width - total_len;
match spec.align.unwrap_or(default) {
Align::Left => {
body(w)?;
write_fill(w, spec.fill, pad)
}
Align::Right => {
write_fill(w, spec.fill, pad)?;
body(w)
}
Align::Center => {
let left = pad / 2;
write_fill(w, spec.fill, left)?;
body(w)?;
write_fill(w, spec.fill, pad - left)
}
}
}
fn truncate_chars(s: &mut String, max: usize) {
if s.chars().count() > max
&& let Some((end, _)) = s.char_indices().nth(max)
{
s.truncate(end);
}
}
pub(crate) fn render_arg(
arg: &dyn FormatArg,
spec: Spec,
w: &mut dyn fmt::Write,
scratch: &mut String,
) -> Result<(), FormatError> {
scratch.clear();
write!(scratch, "{}", Core { arg, spec }).map_err(|_| FormatError::UnsupportedFormatType)?;
if !arg.is_numeric() {
if let Some(p) = spec.precision {
truncate_chars(scratch, p);
}
let total = scratch.chars().count();
return write_padded(w, spec, Align::Left, total, |w| {
note_write(w.write_str(scratch))
});
}
let (sign, digits) = if let Some(rest) = scratch.strip_prefix('-') {
("-", rest)
} else if spec.sign_plus && scratch != "NaN" {
("+", scratch.as_str())
} else {
("", scratch.as_str())
};
let prefix = if spec.alternate {
radix_prefix(spec.ty)
} else {
""
};
if spec.zero {
note_write(w.write_str(sign))?;
note_write(w.write_str(prefix))?;
if let Some(width) = spec.width {
let cur = sign.len() + prefix.len() + digits.len();
if cur < width {
write_fill(w, '0', width - cur)?;
}
}
return note_write(w.write_str(digits));
}
let total = sign.len() + prefix.len() + digits.len();
write_padded(w, spec, Align::Right, total, |w| {
note_write(w.write_str(sign))?;
note_write(w.write_str(prefix))?;
note_write(w.write_str(digits))
})
}