#![deny(warnings)]
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(not(feature = "std"))]
pub(crate) mod std {
pub use core::*;
}
use std::fmt::{self, Display};
use std::hint::{unreachable_unchecked};
#[cfg(feature = "std")]
pub trait AsStrFormatExt: AsRef<str> {
fn format<'a, T: Display + ?Sized + 'a>(&self, args: impl IntoIterator<Item=&'a T> + Clone) -> String {
format!("{}", Arguments::new(self, args))
}
}
#[cfg(feature = "std")]
impl<T: AsRef<str>> AsStrFormatExt for T { }
#[macro_export]
macro_rules! dyn_write {
($dst:expr, $($arg:tt)*) => {
write!($dst, "{}", $crate::Arguments::new($($arg)*))
}
}
#[derive(Clone, Debug)]
pub struct Arguments<'a, F: AsRef<str>, T: Display + ?Sized + 'a, I: IntoIterator<Item=&'a T> + Clone> {
fmt: F,
args: I
}
impl<'a, F: AsRef<str>, T: Display + ?Sized + 'a, I: IntoIterator<Item=&'a T> + Clone> Arguments<'a, F, T, I> {
pub fn new(fmt: F, args: I) -> Self { Arguments { fmt, args } }
}
impl<'a, F: AsRef<str>, T: Display + ?Sized + 'a, I: IntoIterator<Item=&'a T> + Clone> Display for Arguments<'a, F, T, I> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
#[derive(Eq, PartialEq)]
enum Brace { Left, Right };
fn as_brace(c: u8) -> Option<Brace> {
match c {
b'{' => Some(Brace::Left),
b'}' => Some(Brace::Right),
_ => None
}
}
let mut args = self.args.clone().into_iter();
let mut fmt = self.fmt.as_ref();
let mut piece_end = 0;
enum State { Piece, Arg };
let mut state = State::Piece;
loop {
match state {
State::Piece => match fmt.as_bytes()[piece_end ..].first() {
None => {
fmt.fmt(f)?;
break;
},
Some(&b) => match as_brace(b) {
Some(b) => {
fmt[.. piece_end].fmt(f)?;
fmt = &fmt[(piece_end + 1) ..];
if fmt.is_empty() { break; }
match b {
Brace::Left => {
piece_end = 0;
state = State::Arg;
},
Brace::Right => {
piece_end = 1;
state = State::Piece;
}
};
},
None => {
piece_end += 1;
}
}
},
State::Arg => match fmt.as_bytes().first() {
None => unsafe { unreachable_unchecked() },
Some(&b'}') => {
if let Some(arg) = args.next() {
arg.fmt(f)?;
}
fmt = &fmt[1 ..];
state = State::Piece;
},
Some(_) => {
piece_end = 1;
state = State::Piece;
}
},
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate as dyn_fmt;
#[cfg(feature = "std")]
use AsStrFormatExt;
use std::fmt::{self, Write, Display};
use std::str::{self};
#[cfg(feature = "std")]
#[test]
fn test_format() {
assert_eq!("{}a{}b{}c".format(&[1, 2, 3]), "1a2b3c");
assert_eq!("{}a{}b{}c".format(&[1, 2, 3, 4]), "1a2b3c");
assert_eq!("{}a{}b{}c".format(&[1, 2]), "1a2bc");
assert_eq!("{{}}{}".format(&[1, 2]), "{}1");
}
#[cfg(feature = "std")]
#[test]
fn test_format_with_string_format() {
let format: String = "{}a{}b{}c".into();
assert_eq!(format.format(&[1, 2, 3]), "1a2b3c");
assert_eq!(format.format(&[2, 3, 4]), "2a3b4c");
}
struct Writer<'a> {
buf: &'a mut str,
len: usize,
}
impl<'a> fmt::Write for Writer<'a> {
fn write_str(&mut self, s: &str) -> fmt::Result {
let buf = &mut self.buf[self.len ..];
assert!(buf.len() >= s.len());
let buf = &mut buf[.. s.len()];
unsafe { buf.as_bytes_mut() }.copy_from_slice(s.as_bytes());
self.len += s.len();
Ok(())
}
}
#[test]
fn test_write() {
let mut buf = [0u8; 128];
let buf = str::from_utf8_mut(&mut buf).unwrap();
let mut writer = Writer { buf, len: 0 };
dyn_write!(&mut writer, "{}a{}b{}c", &[1, 2, 3]).unwrap();
let len = writer.len;
assert_eq!("1a2b3c", &buf[.. len]);
}
#[test]
fn write_args() {
let args_format = dyn_fmt::Arguments::new("{}{}{}", &[1, 2, 3]);
let mut buf = [0u8; 128];
let buf = str::from_utf8_mut(&mut buf).unwrap();
let mut writer = Writer { buf, len: 0 };
write!(&mut writer, "{}", args_format).unwrap();
let len = writer.len;
assert_eq!("123", &buf[.. len]);
}
#[test]
fn write_unsized_args() {
let args: &'static [&'static dyn Display] = &[&1, &2, &3];
let args_format = dyn_fmt::Arguments::new("{}{}{}", args.iter().copied());
let mut buf = [0u8; 128];
let buf = str::from_utf8_mut(&mut buf).unwrap();
let mut writer = Writer { buf, len: 0 };
write!(&mut writer, "{}", args_format).unwrap();
let len = writer.len;
assert_eq!("123", &buf[.. len]);
}
#[cfg(feature = "std")]
#[test]
fn format_unsized_args() {
let args: &'static [&'static dyn Display] = &[&1, &2, &3];
let args_format = "{}{}{}".format(args.iter().copied());
let mut buf = [0u8; 128];
let buf = str::from_utf8_mut(&mut buf).unwrap();
let mut writer = Writer { buf, len: 0 };
write!(&mut writer, "{}", args_format).unwrap();
let len = writer.len;
assert_eq!("123", &buf[.. len]);
}
#[test]
fn write_str() {
let args_format = dyn_fmt::Arguments::new("abcd{}абвгд{}{}", &[1, 2, 3]);
let mut buf = [0u8; 128];
let buf = str::from_utf8_mut(&mut buf).unwrap();
let mut writer = Writer { buf, len: 0 };
write!(&mut writer, "{}", args_format).unwrap();
let len = writer.len;
assert_eq!("abcd1абвгд23", &buf[.. len]);
}
#[test]
fn complex_case_1() {
let args_format = dyn_fmt::Arguments::new("{{}}x{{}{}}y{", &[1, 2, 3]);
let mut buf = [0u8; 128];
let buf = str::from_utf8_mut(&mut buf).unwrap();
let mut writer = Writer { buf, len: 0 };
write!(&mut writer, "{}", args_format).unwrap();
let len = writer.len;
assert_eq!("{}x{{}y", &buf[.. len]);
}
#[test]
fn complex_case_2() {
let args_format = dyn_fmt::Arguments::new("{{{}}}x{y}", &[1, 2, 3]);
let mut buf = [0u8; 128];
let buf = str::from_utf8_mut(&mut buf).unwrap();
let mut writer = Writer { buf, len: 0 };
write!(&mut writer, "{}", args_format).unwrap();
let len = writer.len;
assert_eq!("{1}xy", &buf[.. len]);
}
#[test]
fn complex_case_3() {
let args_format = dyn_fmt::Arguments::new("{{{}}}x{{}", &[1, 2, 3]);
let mut buf = [0u8; 128];
let buf = str::from_utf8_mut(&mut buf).unwrap();
let mut writer = Writer { buf, len: 0 };
write!(&mut writer, "{}", args_format).unwrap();
let len = writer.len;
assert_eq!("{1}x{", &buf[.. len]);
}
#[test]
fn fmt_lifetime() {
fn display<'a, 'b>(f: &'a str, i: &'a [u8], buf: &'b mut str) -> &'b str {
let args_format = dyn_fmt::Arguments::new(f, i);
let mut writer = Writer { buf, len: 0 };
write!(&mut writer, "{}", args_format).unwrap();
let len = writer.len;
&buf[.. len]
}
let mut buf = [0u8; 128];
let buf = str::from_utf8_mut(&mut buf).unwrap();
let res = display("{}", &[0], buf);
assert_eq!("0", res);
}
}