#![warn(missing_docs)]
#![allow(clippy::result_unit_err)]
use std::borrow::Cow;
use std::fmt;
use std::io;
use erased_serde::Serialize as Serializable;
use serde::ser::Serialize;
use thiserror::Error;
mod formatter;
use crate::formatter::{FormatError, Formatter};
#[cfg(feature = "python")]
pub mod python;
#[cfg(feature = "python")]
pub use crate::python::PythonFormat;
#[cfg(feature = "curly")]
pub mod curly;
#[cfg(feature = "curly")]
pub use crate::curly::SimpleCurlyFormat;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Default)]
pub enum Position<'a> {
#[default]
Auto,
Index(usize),
Key(&'a str),
}
impl fmt::Display for Position<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Position::Auto => write!(f, "{{next}}"),
Position::Index(index) => write!(f, "{index}"),
Position::Key(key) => f.write_str(key),
}
}
}
#[derive(Debug, Error)]
pub enum Error<'a> {
#[error("unsupported format '{0}'")]
BadFormat(char),
#[error("error parsing format string: {0}")]
Parse(Cow<'a, str>),
#[error("format requires an argument list")]
ListRequired,
#[error("format requires an argument map")]
MapRequired,
#[error("missing argument: {0}")]
MissingArg(Position<'a>),
#[error("argument '{0}' cannot be formatted as {1}")]
BadArg(Position<'a>, FormatType),
#[error("error formatting argument '{0}': {1}")]
BadData(Position<'a>, String),
#[error("{0}")]
Io(#[source] io::Error),
}
impl<'a> Error<'a> {
fn from_serialize(error: FormatError, position: Position<'a>) -> Self {
match error {
FormatError::Type(ty) => Error::BadArg(position, ty),
FormatError::Serde(err) => Error::BadData(position, err),
FormatError::Io(err) => Error::Io(err),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum FormatType {
#[default]
Display,
Debug,
Object,
Octal,
LowerHex,
UpperHex,
Pointer,
Binary,
LowerExp,
UpperExp,
Literal(&'static str),
}
impl FormatType {
pub const fn name(self) -> &'static str {
match self {
FormatType::Display => "string",
FormatType::Debug => "debug",
FormatType::Octal => "octal",
FormatType::LowerHex => "lower hex",
FormatType::UpperHex => "upper hex",
FormatType::Pointer => "pointer",
FormatType::Binary => "binary",
FormatType::LowerExp => "lower exp",
FormatType::UpperExp => "upper exp",
FormatType::Object => "object",
FormatType::Literal(s) => s,
}
}
}
impl fmt::Display for FormatType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.name())
}
}
pub type Argument<'a> = &'a dyn Serializable;
pub trait FormatArgs {
#[allow(unused_variables)]
fn get_index(&self, index: usize) -> Result<Option<Argument<'_>>, ()> {
Err(())
}
#[allow(unused_variables)]
fn get_key(&self, key: &str) -> Result<Option<Argument<'_>>, ()> {
Err(())
}
}
impl<T> FormatArgs for Vec<T>
where
T: Serialize,
{
fn get_index(&self, index: usize) -> Result<Option<Argument<'_>>, ()> {
Ok(self.get(index).map(|arg| arg as Argument<'_>))
}
}
impl<T> FormatArgs for &'_ [T]
where
T: Serialize,
{
fn get_index(&self, index: usize) -> Result<Option<Argument<'_>>, ()> {
Ok(self.get(index).map(|arg| arg as Argument<'_>))
}
}
macro_rules! impl_args_array {
($num:expr) => {
impl<T> FormatArgs for [T; $num]
where
T: Serialize,
{
fn get_index(&self, index: usize) -> Result<Option<Argument<'_>>, ()> {
Ok(self.get(index).map(|arg| arg as Argument<'_>))
}
}
};
}
impl_args_array!(0);
impl_args_array!(1);
impl_args_array!(2);
impl_args_array!(3);
impl_args_array!(4);
impl_args_array!(5);
impl_args_array!(6);
impl_args_array!(7);
impl_args_array!(8);
impl_args_array!(9);
impl_args_array!(10);
impl_args_array!(11);
impl_args_array!(12);
impl_args_array!(13);
impl_args_array!(14);
impl_args_array!(15);
impl_args_array!(16);
impl<T> FormatArgs for std::collections::VecDeque<T>
where
T: Serialize,
{
fn get_index(&self, index: usize) -> Result<Option<Argument<'_>>, ()> {
Ok(self.get(index).map(|arg| arg as Argument<'_>))
}
}
impl<S, T> FormatArgs for std::collections::BTreeMap<S, T>
where
S: std::borrow::Borrow<str> + Ord,
T: Serialize,
{
fn get_key(&self, key: &str) -> Result<Option<Argument<'_>>, ()> {
Ok(self.get(key).map(|arg| arg as Argument<'_>))
}
}
impl<S, T> FormatArgs for std::collections::HashMap<S, T>
where
S: std::borrow::Borrow<str> + std::hash::Hash + Eq,
T: Serialize,
{
fn get_key(&self, key: &str) -> Result<Option<Argument<'_>>, ()> {
Ok(self.get(key).map(|arg| arg as Argument<'_>))
}
}
impl<A> FormatArgs for &A
where
A: FormatArgs,
{
fn get_index(&self, index: usize) -> Result<Option<Argument<'_>>, ()> {
(*self).get_index(index)
}
fn get_key(&self, key: &str) -> Result<Option<Argument<'_>>, ()> {
(*self).get_key(key)
}
}
struct ArgumentAccess<A> {
args: A,
index: usize,
}
impl<A> ArgumentAccess<A>
where
A: FormatArgs,
{
pub const fn new(args: A) -> Self {
ArgumentAccess { args, index: 0 }
}
pub fn get_pos<'a>(&mut self, mut position: Position<'a>) -> Result<Argument<'_>, Error<'a>> {
if position == Position::Auto {
position = Position::Index(self.index);
self.index += 1;
}
let result = match position {
Position::Auto => unreachable!("Position::Auto matched twice"),
Position::Index(index) => self.args.get_index(index).map_err(|()| Error::ListRequired),
Position::Key(key) => self.args.get_key(key).map_err(|()| Error::MapRequired),
};
result.and_then(|opt| opt.ok_or(Error::MissingArg(position)))
}
}
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Default)]
pub enum Alignment {
Left,
Center,
#[default]
Right,
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum Count<'a> {
Value(usize),
Ref(Position<'a>),
}
#[derive(Debug)]
pub struct ArgumentSpec<'a> {
range: (usize, usize),
position: Position<'a>,
format: FormatType,
alternate: bool,
add_sign: bool,
pad_zero: bool,
fill_char: char,
alignment: Alignment,
width: Option<Count<'a>>,
precision: Option<Count<'a>>,
precision_truncates: bool,
}
impl<'a> ArgumentSpec<'a> {
pub fn new(start: usize, end: usize) -> Self {
ArgumentSpec {
range: (start, end),
position: Position::default(),
format: FormatType::default(),
alternate: false,
add_sign: false,
pad_zero: false,
fill_char: ' ',
alignment: Alignment::default(),
width: None,
precision: None,
precision_truncates: false,
}
}
pub const fn with_position(mut self, position: Position<'a>) -> Self {
self.position = position;
self
}
pub const fn with_format(mut self, format: FormatType) -> Self {
self.format = format;
self
}
pub const fn with_alternate(mut self, alternate: bool) -> Self {
self.alternate = alternate;
self
}
pub const fn with_sign(mut self, sign: bool) -> Self {
self.add_sign = sign;
self
}
pub const fn with_zeros(mut self, pad_zero: bool) -> Self {
self.pad_zero = pad_zero;
self
}
pub const fn with_fill(mut self, fill_char: char) -> Self {
self.fill_char = fill_char;
self
}
pub const fn with_alignment(mut self, alignment: Alignment) -> Self {
self.alignment = alignment;
self
}
pub const fn with_width(mut self, width: Option<Count<'a>>) -> Self {
self.width = width;
self
}
pub const fn with_precision(mut self, precision: Option<Count<'a>>) -> Self {
self.precision = precision;
self
}
pub const fn with_precision_truncates(mut self, precision_truncates: bool) -> Self {
self.precision_truncates = precision_truncates;
self
}
pub const fn start(&self) -> usize {
self.range.0
}
pub const fn end(&self) -> usize {
self.range.1
}
fn format_into<W, A>(&self, mut write: W, args: &mut ArgumentAccess<A>) -> Result<(), Error<'a>>
where
W: io::Write,
A: FormatArgs,
{
if let FormatType::Literal(literal) = self.format {
return write!(write, "{literal}").map_err(Error::Io);
}
let width = self
.width
.map(|count| resolve_count(count, args, "width"))
.transpose()?;
let precision = self
.precision
.map(|count| resolve_count(count, args, "precision"))
.transpose()?;
let formatter_precision = if self.precision_truncates {
None
} else {
precision
};
let mut buffer = Vec::new();
Formatter::new(&mut buffer)
.with_type(self.format)
.with_alternate(self.alternate)
.with_precision(formatter_precision)
.format(args.get_pos(self.position)?)
.map_err(|e| Error::from_serialize(e, self.position))?;
let mut formatted = String::from_utf8(buffer)
.map_err(|e| Error::Io(io::Error::new(io::ErrorKind::InvalidData, e)))?;
if self.precision_truncates
&& let Some(precision_val) = precision
&& formatted.chars().count() > precision_val
{
formatted = formatted.chars().take(precision_val).collect();
}
if let Some(width_val) = width {
let formatted_chars = formatted.chars().count();
if formatted_chars < width_val {
let padding = width_val - formatted_chars;
let fill_char = if self.pad_zero && self.alignment != Alignment::Left {
'0'
} else {
self.fill_char
};
match self.alignment {
Alignment::Left => {
write!(write, "{}", formatted).map_err(Error::Io)?;
for _ in 0..padding {
write!(write, "{}", self.fill_char).map_err(Error::Io)?;
}
}
Alignment::Right => {
for _ in 0..padding {
write!(write, "{}", fill_char).map_err(Error::Io)?;
}
write!(write, "{}", formatted).map_err(Error::Io)?;
}
Alignment::Center => {
let left_padding = padding / 2;
let right_padding = padding - left_padding;
for _ in 0..left_padding {
write!(write, "{}", self.fill_char).map_err(Error::Io)?;
}
write!(write, "{}", formatted).map_err(Error::Io)?;
for _ in 0..right_padding {
write!(write, "{}", self.fill_char).map_err(Error::Io)?;
}
}
}
} else {
write!(write, "{}", formatted).map_err(Error::Io)?;
}
} else {
write!(write, "{}", formatted).map_err(Error::Io)?;
}
Ok(())
}
}
fn resolve_count<'a, A>(
count: Count<'a>,
args: &mut ArgumentAccess<A>,
what: &str,
) -> Result<usize, Error<'a>>
where
A: FormatArgs,
{
match count {
Count::Value(value) => Ok(value),
Count::Ref(pos) => {
let arg = args.get_pos(pos)?;
let mut buffer = Vec::new();
Formatter::new(&mut buffer)
.with_type(FormatType::Display)
.format(arg)
.map_err(|e| Error::from_serialize(e, pos))?;
let text = String::from_utf8(buffer)
.map_err(|e| Error::Io(io::Error::new(io::ErrorKind::InvalidData, e)))?;
text.parse::<usize>()
.map_err(|_| Error::BadData(pos, format!("{what} must be a positive integer")))
}
}
}
pub type ArgumentResult<'f> = Result<ArgumentSpec<'f>, Error<'f>>;
pub trait Format<'f> {
type Iter: Iterator<Item = ArgumentResult<'f>>;
fn iter_args(&self, format: &'f str) -> Result<Self::Iter, Error<'f>>;
fn format<A>(&self, format: &'f str, arguments: A) -> Result<Cow<'f, str>, Error<'f>>
where
A: FormatArgs,
{
let mut iter = self.iter_args(format)?.peekable();
if iter.peek().is_none() {
return Ok(Cow::Borrowed(format));
}
let mut access = ArgumentAccess::new(arguments);
let mut buffer = Vec::with_capacity(format.len());
let mut last_match = 0;
for spec in iter {
let spec = spec?;
buffer.extend(&format.as_bytes()[last_match..spec.start()]);
spec.format_into(&mut buffer, &mut access)?;
last_match = spec.end();
}
buffer.extend(&format.as_bytes()[last_match..]);
Ok(Cow::Owned(unsafe { String::from_utf8_unchecked(buffer) }))
}
}
#[derive(Debug)]
pub struct NoopFormat;
impl<'f> Format<'f> for NoopFormat {
type Iter = std::iter::Empty<ArgumentResult<'f>>;
fn iter_args(&self, _format: &'f str) -> Result<Self::Iter, Error<'f>> {
Ok(Default::default())
}
}