#![deny(unused_imports)]
#![deny(missing_docs)]
use std::{
borrow::Cow,
fmt::{Debug, Display, Write},
};
use pest::{Parser, iterators::Pair};
use thiserror::Error;
mod imp;
mod parser;
use parser::{FmtParser, Rule};
#[derive(Debug, Error)]
pub enum Error {
#[error("unsupported format spec {0}")]
UnsupportedSpec(FormatSpec),
#[error(
"number of arguments doesn't match number of format specifications expected={0} found={1}"
)]
ArgumentCountMismatch(usize, usize),
#[error("format parsing error: {0}")]
Parse(#[from] Box<pest::error::Error<Rule>>),
#[error("format write error")]
Write(#[from] std::fmt::Error),
#[error("named/positional arguments are not supported: {0}")]
NamedArgument(String),
}
pub type Result = std::result::Result<(), Error>;
pub struct Formatter<'a> {
spec: &'a FormatSpec,
out: &'a mut dyn Write,
default_align: Align,
buf: String,
}
impl<'a> Formatter<'a> {
#[inline]
pub fn new(spec: &'a FormatSpec, out: &'a mut dyn Write) -> Self {
Self {
spec,
out,
default_align: Align::Left,
buf: String::new(),
}
}
#[inline]
pub fn spec(&self) -> &FormatSpec {
self.spec
}
#[inline]
pub fn fill(&self) -> char {
self.spec.fill.unwrap_or(' ')
}
#[inline]
pub fn align(&self) -> Align {
self.spec.align.unwrap_or(self.default_align)
}
#[inline]
pub fn width(&self) -> Option<usize> {
self.spec.width
}
#[inline]
pub fn precision(&self) -> Option<usize> {
self.spec.precision
}
#[inline]
pub fn sign(&self) -> Option<Sign> {
self.spec.sign
}
#[inline]
pub fn alternate(&self) -> bool {
self.spec.alternate
}
#[inline]
pub fn zero(&self) -> bool {
self.spec.zero
}
#[inline]
pub fn fmt_type(&self) -> FmtType {
self.spec.ty
}
#[inline]
pub fn set_default_align(&mut self, align: Align) {
self.default_align = align
}
pub(crate) fn finish(&mut self) -> Result {
if !self.buf.is_empty() {
let buf = std::mem::take(&mut self.buf);
self.spec.write_aligned(
&buf,
self.spec.align.unwrap_or(self.default_align),
self.out,
)?;
}
Ok(())
}
}
impl std::fmt::Write for Formatter<'_> {
fn write_str(&mut self, s: &str) -> std::fmt::Result {
self.buf.push_str(s);
Ok(())
}
fn write_char(&mut self, c: char) -> std::fmt::Result {
self.buf.push(c);
Ok(())
}
}
pub trait DynDisplay {
fn dyn_fmt(&self, f: &mut Formatter<'_>) -> Result;
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy)]
pub enum FmtType {
Default,
Debug,
DebugLowHex,
DebugUpHex,
LowerHex,
UpperHex,
Octal,
Ptr,
Bin,
LowExp,
UpperExp,
}
impl Display for FmtType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FmtType::Default => Ok(()),
FmtType::Debug => write!(f, "?"),
FmtType::DebugLowHex => write!(f, "x?"),
FmtType::DebugUpHex => write!(f, "X?"),
FmtType::LowerHex => write!(f, "x"),
FmtType::UpperHex => write!(f, "X"),
FmtType::Octal => write!(f, "o"),
FmtType::Ptr => write!(f, "p"),
FmtType::Bin => write!(f, "b"),
FmtType::LowExp => write!(f, "e"),
FmtType::UpperExp => write!(f, "E"),
}
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy)]
pub enum Align {
Left,
Center,
Right,
}
impl Display for Align {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Align::Center => write!(f, "^"),
Align::Left => write!(f, "<"),
Align::Right => write!(f, ">"),
}
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy)]
pub enum Sign {
Positive,
Negative,
}
impl Display for Sign {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Sign::Positive => write!(f, "+"),
Sign::Negative => write!(f, "-"),
}
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone)]
pub struct FormatSpec {
pub fill: Option<char>,
pub align: Option<Align>,
pub sign: Option<Sign>,
pub alternate: bool,
pub zero: bool,
pub width: Option<usize>,
pub precision: Option<usize>,
pub ty: FmtType,
}
impl Display for FormatSpec {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(c) = self.fill {
write!(f, "{c}")?;
}
if let Some(a) = self.align {
write!(f, "{a}")?;
}
if let Some(s) = self.sign {
write!(f, "{s}")?;
}
if self.alternate {
write!(f, "#")?;
}
if self.zero {
write!(f, "0")?;
}
if let Some(w) = self.width {
write!(f, "{w}")?;
}
if let Some(p) = self.precision {
write!(f, ".{p}")?;
}
write!(f, "{}", self.ty)
}
}
impl Default for FormatSpec {
fn default() -> Self {
Self {
fill: None,
align: None,
sign: None,
alternate: false,
zero: false,
width: None,
precision: None,
ty: FmtType::Default,
}
}
}
impl FormatSpec {
fn from_pair(p: Pair<'_, Rule>) -> Self {
let mut out = Self::default();
for p in p.into_inner() {
match p.as_rule() {
Rule::fill => out.fill = p.as_str().chars().nth(0),
Rule::align => {
out.align = match p.as_str() {
"^" => Some(Align::Center),
"<" => Some(Align::Left),
">" => Some(Align::Right),
_ => None,
}
}
Rule::sign => {
out.sign = match p.as_str() {
"-" => Some(Sign::Negative),
"+" => Some(Sign::Positive),
_ => None,
}
}
Rule::alternate => out.alternate = true,
Rule::zero_pad => out.zero = true,
Rule::width => out.width = p.as_str().parse::<usize>().ok(),
Rule::precision => out.precision = p.as_str().parse::<usize>().ok(),
Rule::type_fmt => {
if let Some(type_fmt) = p.into_inner().next() {
match type_fmt.as_rule() {
Rule::type_debug => out.ty = FmtType::Debug,
Rule::type_debug_low_hex => out.ty = FmtType::DebugLowHex,
Rule::type_debug_up_hex => out.ty = FmtType::DebugUpHex,
Rule::type_oct => out.ty = FmtType::Octal,
Rule::type_low_hex => out.ty = FmtType::LowerHex,
Rule::type_up_hex => out.ty = FmtType::UpperHex,
Rule::type_ptr => out.ty = FmtType::Ptr,
Rule::type_bin => out.ty = FmtType::Bin,
Rule::type_low_exp => out.ty = FmtType::LowExp,
Rule::type_upper_exp => out.ty = FmtType::UpperExp,
_ => {}
}
}
}
_ => {}
}
}
out
}
fn is_empty(&self) -> bool {
self.fill.is_none()
&& self.align.is_none()
&& self.sign.is_none()
&& !self.alternate
&& !self.zero
&& self.width.is_none()
&& self.precision.is_none()
&& matches!(self.ty, FmtType::Default)
}
pub fn write_aligned(&self, s: &str, default_align: Align, out: &mut dyn Write) -> Result {
let Some(width) = self.width else {
out.write_str(s)?;
return Ok(());
};
if width <= s.len() {
out.write_str(s)?;
return Ok(());
}
let pad = width - s.len();
let fill = self.fill.unwrap_or(' ');
let align = self.align.unwrap_or(default_align);
match align {
Align::Left => {
out.write_str(s)?;
for _ in 0..pad {
out.write_char(fill)?;
}
}
Align::Center => {
let r_pad = pad / 2;
let l_pad = pad.div_ceil(2);
for _ in 0..r_pad {
out.write_char(fill)?;
}
out.write_str(s)?;
for _ in 0..l_pad {
out.write_char(fill)?;
}
}
Align::Right => {
for _ in 0..pad {
out.write_char(fill)?;
}
out.write_str(s)?;
}
}
Ok(())
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone)]
struct Format {
start: usize,
end: usize,
arg: Option<String>,
spec: FormatSpec,
}
impl Display for Format {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{{")?;
if let Some(a) = self.arg.as_ref() {
write!(f, "{a}")?;
}
if !self.spec.is_empty() {
write!(f, ":")?;
}
write!(f, "{}", self.spec)?;
write!(f, "}}")
}
}
impl Format {
fn from_pair(pairs: Pair<'_, Rule>) -> Self {
let start = pairs.as_span().start();
let end = pairs.as_span().end();
let mut spec = None;
let mut arg = None;
for p in pairs.into_inner() {
match p.as_rule() {
Rule::argument => {
arg = Some(p.as_str().to_string())
}
Rule::format_spec => spec = Some(FormatSpec::from_pair(p)),
_ => {}
}
}
Self {
start,
end,
arg,
spec: spec.unwrap_or_default(),
}
}
fn dyn_fmt_arg<D: DynDisplay>(&self, arg: &D, out: &mut dyn Write) -> Result {
let mut f = Formatter::new(&self.spec, out);
DynDisplay::dyn_fmt(arg, &mut f)?;
f.finish()
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone)]
pub struct FormatString {
s: String,
fmts: Vec<Format>,
}
impl FormatString {
#[inline]
fn new_from_str<S: AsRef<str>>(s: S) -> std::result::Result<Self, Error> {
let pairs = FmtParser::parse(Rule::format_string, s.as_ref())
.map_err(|e| Error::from(Box::new(e)))?;
let mut fmts = vec![];
for p in pairs {
if p.as_rule() == Rule::format {
fmts.push(Format::from_pair(p))
}
}
Ok(Self {
s: s.as_ref().to_string(),
fmts,
})
}
pub fn from_string(s: String) -> std::result::Result<Self, Error> {
Self::new_from_str(s)
}
pub fn into_string(self) -> String {
self.s
}
pub fn to_string_lossy(&self) -> Cow<'_, str> {
Cow::Borrowed(&self.s)
}
pub fn contains_format(&self) -> bool {
!self.fmts.is_empty()
}
}
pub struct Writer<'s> {
i: usize,
format_string: &'s FormatString,
args: Vec<&'s dyn DynDisplay>,
out: String,
}
impl<'s> From<&'s FormatString> for Writer<'s> {
fn from(value: &'s FormatString) -> Self {
Self {
i: 0,
format_string: value,
args: vec![],
out: String::new(),
}
}
}
impl<'s> Writer<'s> {
pub fn push_arg<A>(&mut self, arg: &'s A) -> &mut Self
where
A: DynDisplay,
{
self.args.push(arg);
self
}
pub fn format(&mut self) -> std::result::Result<&mut Self, Error> {
if self.args.len() != self.format_string.fmts.len() {
return Err(Error::ArgumentCountMismatch(
self.format_string.fmts.len(),
self.args.len(),
));
}
for fmt in &self.format_string.fmts {
if let Some(arg) = &fmt.arg {
return Err(Error::NamedArgument(arg.clone()));
}
}
for (i, a) in self.args.iter().enumerate() {
let arg_fmt = &self.format_string.fmts[i];
let slice = &self.format_string.s.as_str()[self.i..];
self.out.push_str(&slice[..arg_fmt.start - self.i]);
arg_fmt.dyn_fmt_arg(a, &mut self.out)?;
self.i = arg_fmt.end;
}
let slice = &self.format_string.s.as_str()[self.i..];
self.out.push_str(slice);
Ok(self)
}
pub fn to_string_lossy(&self) -> Cow<'_, str> {
Cow::Borrowed(&self.out)
}
pub fn into_string(self) -> String {
self.out
}
}
#[macro_export]
macro_rules! dformat {
($fmt: expr, $($arg: expr),*) => {
{
let mut w = $crate::Writer::from($fmt);
$(
w.push_arg(&$arg);
)*
match w.format() {
Err(e) => Err(e),
Ok(_) => Ok(w.into_string()),
}
}
};
}
#[cfg(test)]
mod tests {
use std::{
ffi::{OsStr, OsString},
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6},
path::{Path, PathBuf},
rc::Rc,
sync::Arc,
time::{Duration, Instant, SystemTime},
};
use pest::Parser;
use super::*;
macro_rules! dformat_lit {
($fmt: literal, $($arg: expr),*) => {
{
let fs = FormatString::new_from_str($fmt).unwrap();
dformat!(&fs, $($arg),*)
}
};
}
#[test]
fn test_rule_format() {
for f in [
"{}", "{0}", "{name}", "{:>5}", "{:<5}", "{:^5}", "{:05}", "{:+}", "{:-}", "{:#b}",
"{:#o}", "{:#x}", "{:.2}", "{:08.2}", "{:x}", "{:X}", "{:o}", "{:b}", "{:e}", "{:E}",
"{:?}", "{:#?}", "{:p}",
] {
let fmt = Format::from_pair(
FmtParser::parse(Rule::format, f)
.inspect_err(|e| println!("{e}"))
.unwrap()
.next()
.unwrap(),
);
assert_eq!(f, format!("{fmt}"));
}
}
#[test]
fn test_integer_formatting() {
assert_eq!(format!("{}", 42), dformat_lit!("{}", 42).unwrap());
assert_eq!(format!("{:5}", 42), dformat_lit!("{:5}", 42).unwrap());
assert_eq!(format!("{:05}", 42), dformat_lit!("{:05}", 42).unwrap());
assert_eq!(format!("{:+}", 42), dformat_lit!("{:+}", 42).unwrap());
assert_eq!(format!("{: }", 42), dformat_lit!("{: }", 42).unwrap());
assert_eq!(format!("{:#}", 42), dformat_lit!("{:#}", 42).unwrap());
assert_eq!(format!("{:x}", 42), dformat_lit!("{:x}", 42).unwrap());
assert_eq!(format!("{:X}", 42), dformat_lit!("{:X}", 42).unwrap());
assert_eq!(format!("{:#x}", 42), dformat_lit!("{:#x}", 42).unwrap());
assert_eq!(format!("{:#X}", 42), dformat_lit!("{:#X}", 42).unwrap());
assert_eq!(format!("{:o}", 42), dformat_lit!("{:o}", 42).unwrap());
assert_eq!(format!("{:#o}", 42), dformat_lit!("{:#o}", 42).unwrap());
assert_eq!(format!("{:b}", 42), dformat_lit!("{:b}", 42).unwrap());
assert_eq!(format!("{:#b}", 42), dformat_lit!("{:#b}", 42).unwrap());
assert_eq!(format!("{:5}", 42), dformat_lit!("{:5}", 42).unwrap());
assert_eq!(format!("{:<5}", 42), dformat_lit!("{:<5}", 42).unwrap());
assert_eq!(format!("{:>5}", 42), dformat_lit!("{:>5}", 42).unwrap());
assert_eq!(format!("{:^5}", 42), dformat_lit!("{:^5}", 42).unwrap());
assert_eq!(format!("{:05}", 42), dformat_lit!("{:05}", 42).unwrap());
assert_eq!(format!("{:+<5}", 42), dformat_lit!("{:+<5}", 42).unwrap());
assert_eq!(format!("{:->5}", 42), dformat_lit!("{:->5}", 42).unwrap());
assert_eq!(format!("{:+^5}", 42), dformat_lit!("{:+^5}", 42).unwrap());
}
#[test]
fn test_float_formatting() {
assert_eq!(format!("{}", 42.0), dformat_lit!("{}", 42.0).unwrap());
assert_eq!(format!("{:e}", 42.0), dformat_lit!("{:e}", 42.0).unwrap());
assert_eq!(format!("{:E}", 42.0), dformat_lit!("{:E}", 42.0).unwrap());
assert_eq!(format!("{:.2}", 42.0), dformat_lit!("{:.2}", 42.0).unwrap());
assert_eq!(
format!("{:.2}", 42.1234),
dformat_lit!("{:.2}", 42.1234).unwrap()
);
assert_eq!(
format!("{:10.2}", 42.1234),
dformat_lit!("{:10.2}", 42.1234).unwrap()
);
assert_eq!(
format!("{:<10.2}", 42.1234),
dformat_lit!("{:<10.2}", 42.1234).unwrap()
);
assert_eq!(
format!("{:>10.2}", 42.1234),
dformat_lit!("{:>10.2}", 42.1234).unwrap()
);
assert_eq!(
format!("{:^10.2}", 42.1234),
dformat_lit!("{:^10.2}", 42.1234).unwrap()
);
assert_eq!(format!("{:+}", 42.0), dformat_lit!("{:+}", 42.0).unwrap());
assert_eq!(
format!("{:+<10.2}", 42.1234),
dformat_lit!("{:+<10.2}", 42.1234).unwrap()
);
assert_eq!(
format!("{:->10.2}", 42.1234),
dformat_lit!("{:->10.2}", 42.1234).unwrap()
);
assert_eq!(
format!("{:+^10.2}", 42.1234),
dformat_lit!("{:+^10.2}", 42.1234).unwrap()
);
}
#[test]
fn test_string_formatting() {
assert_eq!(format!("{}", "hello"), dformat_lit!("{}", "hello").unwrap());
assert_eq!(
format!("Hello, {}!", "world"),
dformat_lit!("Hello, {}!", "world").unwrap()
);
assert_eq!(
format!("{:10}", "hello"),
dformat_lit!("{:10}", "hello").unwrap()
);
assert_eq!(
format!("{:<10}", "hello"),
dformat_lit!("{:<10}", "hello").unwrap()
);
assert_eq!(
format!("{:>10}", "hello"),
dformat_lit!("{:>10}", "hello").unwrap()
);
assert_eq!(
format!("{:^10}", "hello"),
dformat_lit!("{:^10}", "hello").unwrap()
);
assert_eq!(
format!("{:.3}", "hello"),
dformat_lit!("{:.3}", "hello").unwrap()
);
assert_eq!(
format!("{:10.3}", "hello"),
dformat_lit!("{:10.3}", "hello").unwrap()
);
assert_eq!(
format!("{:<10.3}", "hello"),
dformat_lit!("{:<10.3}", "hello").unwrap()
);
assert_eq!(
format!("{:>10.3}", "hello"),
dformat_lit!("{:>10.3}", "hello").unwrap()
);
assert_eq!(
format!("{:^10.3}", "hello"),
dformat_lit!("{:^10.3}", "hello").unwrap()
);
}
#[test]
fn test_char_formatting() {
assert_eq!(format!("{}", 'A'), dformat_lit!("{}", 'A').unwrap());
assert_eq!(format!("{:5}", 'A'), dformat_lit!("{:5}", 'A').unwrap());
assert_eq!(format!("{:<5}", 'A'), dformat_lit!("{:<5}", 'A').unwrap());
assert_eq!(format!("{:>5}", 'A'), dformat_lit!("{:>5}", 'A').unwrap());
assert_eq!(format!("{:^5}", 'A'), dformat_lit!("{:^5}", 'A').unwrap());
}
#[test]
fn test_bool_formatting() {
assert_eq!(format!("{}", true), dformat_lit!("{}", true).unwrap());
assert_eq!(format!("{}", false), dformat_lit!("{}", false).unwrap());
assert_eq!(format!("{:5}", true), dformat_lit!("{:5}", true).unwrap());
assert_eq!(format!("{:<5}", true), dformat_lit!("{:<5}", true).unwrap());
assert_eq!(format!("{:>5}", true), dformat_lit!("{:>5}", true).unwrap());
assert_eq!(format!("{:^5}", true), dformat_lit!("{:^5}", true).unwrap());
}
#[test]
fn test_pointer_formatting() {
let x = 42;
let ptr = &x as *const i32;
assert_eq!(format!("{ptr:p}"), dformat_lit!("{:p}", ptr).unwrap());
assert_eq!(format!("{ptr:10p}"), dformat_lit!("{:10p}", ptr).unwrap());
assert_eq!(format!("{ptr:<10p}"), dformat_lit!("{:<10p}", ptr).unwrap());
assert_eq!(format!("{ptr:>10p}"), dformat_lit!("{:>10p}", ptr).unwrap());
assert_eq!(format!("{ptr:^10p}"), dformat_lit!("{:^10p}", ptr).unwrap());
}
#[test]
fn test_multiple_arguments() {
assert_eq!(
format!("{} {}", "hello", 42),
dformat_lit!("{} {}", "hello", 42).unwrap()
);
assert_eq!(
format!("{:5} {:<10.2}", 42, 42.1234),
dformat_lit!("{:5} {:<10.2}", 42, 42.1234).unwrap()
);
assert_eq!(
format!("{:>5} {:^10.2}", 42, 42.1234),
dformat_lit!("{:>5} {:^10.2}", 42, 42.1234).unwrap()
);
}
#[test]
fn test_complex_formatting() {
assert_eq!(
format!("{:05} {:<10.2} {:^10}", 42, 42.1234, "hello"),
dformat_lit!("{:05} {:<10.2} {:^10}", 42, 42.1234, "hello").unwrap()
);
assert_eq!(
format!("{:+<5} {:^10.2} {:>10}", 42, 42.1234, "hello"),
dformat_lit!("{:+<5} {:^10.2} {:>10}", 42, 42.1234, "hello").unwrap()
);
}
#[test]
fn test_network_types() {
let ipv4_addr = Ipv4Addr::new(127, 0, 0, 1);
let ipv6_addr = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1);
let ip_addr_v4 = IpAddr::V4(ipv4_addr);
let ip_addr_v6 = IpAddr::V6(ipv6_addr);
assert_eq!(
format!("{ipv4_addr}"),
dformat_lit!("{}", ipv4_addr).unwrap()
);
assert_eq!(
format!("{ipv4_addr:?}"),
dformat_lit!("{:?}", ipv4_addr).unwrap()
);
assert_eq!(
format!("{ipv6_addr}"),
dformat_lit!("{}", ipv6_addr).unwrap()
);
assert_eq!(
format!("{ipv6_addr:?}"),
dformat_lit!("{:?}", ipv6_addr).unwrap()
);
assert_eq!(
format!("{ip_addr_v4}"),
dformat_lit!("{}", ip_addr_v4).unwrap()
);
assert_eq!(
format!("{ip_addr_v4:?}"),
dformat_lit!("{:?}", ip_addr_v4).unwrap()
);
assert_eq!(
format!("{ip_addr_v6}"),
dformat_lit!("{}", ip_addr_v6).unwrap()
);
assert_eq!(
format!("{ip_addr_v6:?}"),
dformat_lit!("{:?}", ip_addr_v6).unwrap()
);
let socket_addr_v4 = SocketAddrV4::new(ipv4_addr, 8080);
let socket_addr_v6 = SocketAddrV6::new(ipv6_addr, 8080, 0, 0);
let socket_addr = SocketAddr::V4(socket_addr_v4);
assert_eq!(
format!("{socket_addr_v4}"),
dformat_lit!("{}", socket_addr_v4).unwrap()
);
assert_eq!(
format!("{socket_addr_v4:?}"),
dformat_lit!("{:?}", socket_addr_v4).unwrap()
);
assert_eq!(
format!("{socket_addr_v6}"),
dformat_lit!("{}", socket_addr_v6).unwrap()
);
assert_eq!(
format!("{socket_addr_v6:?}"),
dformat_lit!("{:?}", socket_addr_v6).unwrap()
);
assert_eq!(
format!("{socket_addr}"),
dformat_lit!("{}", socket_addr).unwrap()
);
assert_eq!(
format!("{socket_addr:?}"),
dformat_lit!("{:?}", socket_addr).unwrap()
);
}
#[test]
fn test_time_types() {
let duration = Duration::from_secs(3600);
let system_time = SystemTime::now();
let instant = Instant::now();
assert_eq!(
format!("{duration:?}"),
dformat_lit!("{:?}", duration).unwrap()
);
assert_eq!(
format!("{system_time:?}"),
dformat_lit!("{:?}", system_time).unwrap()
);
assert_eq!(
format!("{instant:?}"),
dformat_lit!("{:?}", instant).unwrap()
);
}
#[test]
fn test_path_types() {
let path = Path::new("/path/to/file");
let path_buf = PathBuf::from("/path/to/file");
assert_eq!(format!("{path:?}"), dformat_lit!("{:?}", path).unwrap());
assert_eq!(
format!("{path_buf:?}"),
dformat_lit!("{:?}", path_buf).unwrap()
);
}
#[test]
fn test_ffi_types() {
let os_string = OsString::from("OS String");
let os_str: &OsStr = os_string.as_os_str();
assert_eq!(
format!("{os_string:?}"),
dformat_lit!("{:?}", os_string).unwrap()
);
assert_eq!(format!("{os_str:?}"), dformat_lit!("{:?}", os_str).unwrap());
}
#[test]
fn test_smart_pointers() {
let boxed = Box::new(42);
let rc = Rc::new(42);
let arc = Arc::new(42);
let cow_str: Cow<'_, str> = Cow::Borrowed("Hello");
assert_eq!(format!("{boxed}"), dformat_lit!("{}", boxed).unwrap());
assert_eq!(format!("{rc}"), dformat_lit!("{}", rc).unwrap());
assert_eq!(format!("{arc}"), dformat_lit!("{}", arc).unwrap());
assert_eq!(format!("{cow_str}"), dformat_lit!("{}", cow_str).unwrap());
}
#[test]
#[cfg(feature = "serde")]
fn test_serde() {
let fs = FormatString::new_from_str("{}").unwrap();
let js_fs = serde_json::to_string(&fs).unwrap();
let fs: FormatString = serde_json::from_str(&js_fs).unwrap();
assert_eq!(format!("{}", 42), dformat!(&fs, 42).unwrap())
}
}