use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
use crate::arg::{FormatArg, FormatType};
use crate::error::FormatError;
use crate::template::Template;
pub struct FormatBuilder<'template, 'arg> {
template: &'template str,
slots: Vec<ArgSlot<'arg>>,
}
impl<'template, 'arg> FormatBuilder<'template, 'arg> {
pub(crate) fn new(template: &'template str) -> Self {
Self {
template,
slots: Vec::new(),
}
}
pub fn arg<A: FormatArgRef<'arg>>(mut self, arg: A) -> Self {
self.slots.push(arg.into_slot());
self
}
pub fn build(self) -> String {
self.try_build()
.unwrap_or_else(|error| panic!("format failed: {error}"))
}
pub fn try_build(self) -> Result<String, FormatError> {
let mut out = String::with_capacity(self.template.len());
self.try_build_to(&mut out)?;
Ok(out)
}
pub fn build_to(self, sink: &mut dyn fmt::Write) {
self.try_build_to(sink)
.unwrap_or_else(|error| panic!("format failed: {error}"))
}
pub fn try_build_to(self, sink: &mut dyn fmt::Write) -> Result<(), FormatError> {
let args: Vec<&dyn FormatArg> = self
.slots
.iter()
.map(|slot| slot as &dyn FormatArg)
.collect();
Template::parse(self.template)?.render(&args, sink)
}
}
pub trait FormatArgRef<'a> {
#[doc(hidden)]
fn into_slot(self) -> ArgSlot<'a>;
}
impl<'a, T: FormatArg + 'a> FormatArgRef<'a> for &'a T {
fn into_slot(self) -> ArgSlot<'a> {
ArgSlot::Dyn(self)
}
}
impl<'a> FormatArgRef<'a> for &'a str {
fn into_slot(self) -> ArgSlot<'a> {
ArgSlot::Str(self)
}
}
#[doc(hidden)]
pub enum ArgSlot<'a> {
Dyn(&'a dyn FormatArg),
Str(&'a str),
}
impl FormatArg for ArgSlot<'_> {
fn write(
&self,
ty: FormatType,
pretty: bool,
precision: Option<usize>,
f: &mut fmt::Formatter<'_>,
) -> fmt::Result {
match self {
ArgSlot::Dyn(arg) => arg.write(ty, pretty, precision, f),
ArgSlot::Str(arg) => arg.write(ty, pretty, precision, f),
}
}
fn is_numeric(&self) -> bool {
match self {
ArgSlot::Dyn(arg) => arg.is_numeric(),
ArgSlot::Str(arg) => arg.is_numeric(),
}
}
fn is_integer(&self) -> bool {
match self {
ArgSlot::Dyn(arg) => arg.is_integer(),
ArgSlot::Str(arg) => arg.is_integer(),
}
}
fn as_usize(&self) -> Option<usize> {
match self {
ArgSlot::Dyn(arg) => arg.as_usize(),
ArgSlot::Str(arg) => arg.as_usize(),
}
}
}