#[cfg(test)] #[macro_use]
extern crate galvanic_assert;
#[cfg(test)] #[macro_use]
extern crate galvanic_test;
extern crate num;
use std::borrow::Borrow;
use std::cmp::Ordering;
use std::collections::HashMap;
use parse::{parse, ParseError, Piece};
mod parse;
pub mod extras;
pub mod util;
pub trait Fmt {
fn format(
&self,
full_name: &[String],
name: &[String],
args: &[String],
flags: &[char],
options: &HashMap<String, String>,
) -> Result<String, SingleFmtError>;
}
pub trait FormatTable<'a> {
type Item: Fmt;
fn get_fmt(&'a self, name: &str) -> Option<Self::Item>;
fn format(&'a self, input: &str) -> Result<String, FormattingError> {
format_one(self, &parse(input, 0, 0)?)
}
}
fn format_one<'a, 'b, T: FormatTable<'a> + ?Sized>(
table: &'a T,
piece: &'b Piece,
) -> Result<String, FormattingError> {
match piece {
Piece::Literal(s) => Ok(s.clone()),
Piece::Placeholder(name, args, flags, opts) => {
if let Some(root) = table.get_fmt(&name[0]) {
let mut processed_args = Vec::with_capacity(args.len());
for arg in args.iter() {
processed_args.push(format_one(table, arg)?);
}
let mut processed_opts = HashMap::new();
for (key, piece) in opts.iter() {
processed_opts.insert(key.clone(), format_one(table, piece)?);
}
Ok(root.format(name, &name[1..], &processed_args, flags, &processed_opts)?)
} else {
Err(FormattingError::UnknownFmt(util::join_name(&name)))
}
},
Piece::Multi(pieces) => {
let mut res = String::new();
for piece in pieces.iter() {
res.push_str(&format_one(table, piece)?);
}
Ok(res)
}
}
}
#[derive(Debug, PartialEq)]
pub enum SingleFmtError {
UnknownFlag(String, char),
UnknownOption(String, String),
InvalidOptionValue(String, String, String),
NamespaceOnlyFmt(String),
UnknownSubfmt(String),
WrongNumberOfArguments(String, Ordering, usize),
InvalidArgument(String, usize, String),
}
#[derive(Debug, PartialEq)]
pub enum FormattingError {
UnbalancedBrackets(usize),
EmptyNameSegment(usize),
EmptyOptionName(usize),
UnknownFlag(String, char),
UnknownOption(String, String),
InvalidOptionValue(String, String, String),
NamespaceOnlyFmt(String),
WrongNumberOfArguments(String, Ordering, usize),
InvalidArgument(String, usize, String),
UnknownFmt(String),
}
impl From<SingleFmtError> for FormattingError {
fn from(err: SingleFmtError) -> Self {
match err {
SingleFmtError::UnknownFlag(s, c) => FormattingError::UnknownFlag(s, c),
SingleFmtError::UnknownOption(p, o) => FormattingError::UnknownOption(p, o),
SingleFmtError::InvalidOptionValue(p, opt, val) =>
FormattingError::InvalidOptionValue(p, opt, val),
SingleFmtError::NamespaceOnlyFmt(s) => FormattingError::NamespaceOnlyFmt(s),
SingleFmtError::UnknownSubfmt(s) => FormattingError::UnknownFmt(s),
SingleFmtError::WrongNumberOfArguments(s, o, n) =>
FormattingError::WrongNumberOfArguments(s, o, n),
SingleFmtError::InvalidArgument(s, i, a) =>
FormattingError::InvalidArgument(s, i, a),
}
}
}
impl From<ParseError> for FormattingError {
fn from(err: ParseError) -> Self {
use parse::ParseError::*;
match err {
UnbalancedBrackets(i) => FormattingError::UnbalancedBrackets(i),
EmptyNameSegment(i) => FormattingError::EmptyNameSegment(i),
EmptyOptionName(i) => FormattingError::EmptyOptionName(i),
}
}
}
impl<'a, T: Fmt + ?Sized> Fmt for &'a T {
fn format(
&self,
full_name: &[String],
name: &[String],
args: &[String],
flags: &[char],
options: &HashMap<String, String>,
) -> Result<String, SingleFmtError> {
(*self).format(full_name, name, args, flags, options)
}
}
impl<'a, 'b, I: Fmt, T: FormatTable<'a, Item = I>> FormatTable<'a> for &'b T {
type Item = I;
fn get_fmt(&'a self, name: &str) -> Option<Self::Item> {
(*self).get_fmt(name)
}
}
impl<'a, B, H> FormatTable<'a> for HashMap<String, B, H>
where
B: Borrow<dyn Fmt>,
H: std::hash::BuildHasher,
{
type Item = &'a dyn Fmt;
fn get_fmt(&'a self, name: &str) -> Option<Self::Item> {
self.get(name).map(|b| b.borrow())
}
}
impl<'a, 'b, B, H> FormatTable<'a> for HashMap<&'b str, B, H>
where
B: Borrow<dyn Fmt>,
H: std::hash::BuildHasher,
{
type Item = &'a dyn Fmt;
fn get_fmt(&'a self, name: &str) -> Option<Self::Item> {
self.get(name).map(|b| b.borrow())
}
}
impl<'a, B: Borrow<dyn Fmt>> FormatTable<'a> for Vec<B> {
type Item = &'a dyn Fmt;
fn get_fmt(&'a self, name: &str) -> Option<Self::Item> {
if let Ok(index) = name.parse::<usize>() {
if index < self.len() {
Some(self[index].borrow())
} else {
None
}
} else {
None
}
}
}
impl<'a, F, A, B> FormatTable<'a> for (A, B)
where
F: Fmt,
A: FormatTable<'a, Item = F>,
B: FormatTable<'a, Item = F>,
{
type Item = F;
fn get_fmt(&'a self, name: &str) -> Option<Self::Item> {
self.0.get_fmt(name)
.or_else(|| self.1.get_fmt(name))
}
}
impl<'a, F, A, B, C> FormatTable<'a> for (A, B, C)
where
F: Fmt,
A: FormatTable<'a, Item = F>,
B: FormatTable<'a, Item = F>,
C: FormatTable<'a, Item = F>,
{
type Item = F;
fn get_fmt(&'a self, name: &str) -> Option<Self::Item> {
self.0
.get_fmt(name)
.or_else(|| self.1.get_fmt(name))
.or_else(|| self.2.get_fmt(name))
}
}
impl<'a, F, A, B, C, D> FormatTable<'a> for (A, B, C, D)
where
F: Fmt,
A: FormatTable<'a, Item = F>,
B: FormatTable<'a, Item = F>,
C: FormatTable<'a, Item = F>,
D: FormatTable<'a, Item = F>,
{
type Item = F;
fn get_fmt(&'a self, name: &str) -> Option<Self::Item> {
self.0
.get_fmt(name)
.or_else(|| self.1.get_fmt(name))
.or_else(|| self.2.get_fmt(name))
.or_else(|| self.3.get_fmt(name))
}
}
impl<'a, T, A, B, C, D, E> FormatTable<'a> for (A, B, C, D, E)
where
T: Fmt,
A: FormatTable<'a, Item = T>,
B: FormatTable<'a, Item = T>,
C: FormatTable<'a, Item = T>,
D: FormatTable<'a, Item = T>,
E: FormatTable<'a, Item = T>,
{
type Item = T;
fn get_fmt(&'a self, name: &str) -> Option<Self::Item> {
self.0
.get_fmt(name)
.or_else(|| self.1.get_fmt(name))
.or_else(|| self.2.get_fmt(name))
.or_else(|| self.3.get_fmt(name))
.or_else(|| self.4.get_fmt(name))
}
}
impl<'a, T, A, B, C, D, E, F> FormatTable<'a> for (A, B, C, D, E, F)
where
T: Fmt,
A: FormatTable<'a, Item = T>,
B: FormatTable<'a, Item = T>,
C: FormatTable<'a, Item = T>,
D: FormatTable<'a, Item = T>,
E: FormatTable<'a, Item = T>,
F: FormatTable<'a, Item = T>,
{
type Item = T;
fn get_fmt(&'a self, name: &str) -> Option<Self::Item> {
self.0
.get_fmt(name)
.or_else(|| self.1.get_fmt(name))
.or_else(|| self.2.get_fmt(name))
.or_else(|| self.3.get_fmt(name))
.or_else(|| self.4.get_fmt(name))
.or_else(|| self.5.get_fmt(name))
}
}
impl Fmt for bool {
fn format(
&self,
full_name: &[String],
name: &[String],
_args: &[String],
flags: &[char],
options: &HashMap<String, String>,
) -> Result<String, SingleFmtError> {
if !name.is_empty() {
return Err(SingleFmtError::UnknownSubfmt(util::join_name(full_name)));
}
let mut res = if *self {
if flags.contains(&'y') {
"yes".to_string()
} else if flags.contains(&'Y') {
"Y".to_string()
} else {
"true".to_string()
}
} else if flags.contains(&'y') {
"no".to_string()
} else if flags.contains(&'Y') {
"N".to_string()
} else {
"false".to_string()
};
util::apply_common_options(full_name, &mut res, options)?;
Ok(res)
}
}
impl Fmt for char {
fn format(
&self,
full_name: &[String],
name: &[String],
_args: &[String],
_flags: &[char],
options: &HashMap<String, String>,
) -> Result<String, SingleFmtError> {
if !name.is_empty() {
return Err(SingleFmtError::UnknownSubfmt(util::join_name(full_name)));
}
let mut s = self.to_string();
util::apply_common_options(full_name, &mut s, options)?;
Ok(s)
}
}
impl Fmt for f32 {
fn format(
&self,
full_name: &[String],
name: &[String],
_args: &[String],
flags: &[char],
options: &HashMap<String, String>,
) -> Result<String, SingleFmtError> {
if !name.is_empty() {
return Err(SingleFmtError::UnknownSubfmt(util::join_name(full_name)));
}
let mut res = if flags.contains(&'e') {
util::float_to_exp(full_name, *self, options)?
} else {
util::float_to_normal(full_name, *self, options)?
};
util::add_sign(&mut res, *self, flags)?;
util::apply_common_options(full_name, &mut res, options)?;
Ok(res)
}
}
impl Fmt for f64 {
fn format(
&self,
full_name: &[String],
name: &[String],
_args: &[String],
flags: &[char],
options: &HashMap<String, String>,
) -> Result<String, SingleFmtError> {
if !name.is_empty() {
return Err(SingleFmtError::UnknownSubfmt(util::join_name(full_name)));
}
let mut res = if flags.contains(&'e') {
util::float_to_exp(full_name, *self, options)?
} else {
util::float_to_normal(full_name, *self, options)?
};
util::add_sign(&mut res, *self, flags)?;
util::apply_common_options(full_name, &mut res, options)?;
Ok(res)
}
}
impl Fmt for i8 {
fn format(
&self,
full_name: &[String],
name: &[String],
_args: &[String],
flags: &[char],
options: &HashMap<String, String>,
) -> Result<String, SingleFmtError> {
if !name.is_empty() {
return Err(SingleFmtError::UnknownSubfmt(util::join_name(full_name)));
}
let mut s = util::int_to_str(full_name, *self, flags, options)?;
util::add_sign(&mut s, *self, flags)?;
util::apply_common_options(full_name, &mut s, options)?;
Ok(s)
}
}
impl Fmt for i16 {
fn format(
&self,
full_name: &[String],
name: &[String],
_args: &[String],
flags: &[char],
options: &HashMap<String, String>,
) -> Result<String, SingleFmtError> {
if !name.is_empty() {
return Err(SingleFmtError::UnknownSubfmt(util::join_name(full_name)));
}
let mut s = util::int_to_str(full_name, *self, flags, options)?;
util::add_sign(&mut s, *self, flags)?;
util::apply_common_options(full_name, &mut s, options)?;
Ok(s)
}
}
impl Fmt for i32 {
fn format(
&self,
full_name: &[String],
name: &[String],
_args: &[String],
flags: &[char],
options: &HashMap<String, String>,
) -> Result<String, SingleFmtError> {
if !name.is_empty() {
return Err(SingleFmtError::UnknownSubfmt(util::join_name(full_name)));
}
let mut s = util::int_to_str(full_name, *self, flags, options)?;
util::add_sign(&mut s, *self, flags)?;
util::apply_common_options(full_name, &mut s, options)?;
Ok(s)
}
}
impl Fmt for i64 {
fn format(
&self,
full_name: &[String],
name: &[String],
_args: &[String],
flags: &[char],
options: &HashMap<String, String>,
) -> Result<String, SingleFmtError> {
if !name.is_empty() {
return Err(SingleFmtError::UnknownSubfmt(util::join_name(full_name)));
}
let mut s = util::int_to_str(full_name, *self, flags, options)?;
util::add_sign(&mut s, *self, flags)?;
util::apply_common_options(full_name, &mut s, options)?;
Ok(s)
}
}
impl Fmt for i128 {
fn format(
&self,
full_name: &[String],
name: &[String],
_args: &[String],
flags: &[char],
options: &HashMap<String, String>,
) -> Result<String, SingleFmtError> {
if !name.is_empty() {
return Err(SingleFmtError::UnknownSubfmt(util::join_name(full_name)));
}
let mut s = util::int_to_str(full_name, *self, flags, options)?;
util::add_sign(&mut s, *self, flags)?;
util::apply_common_options(full_name, &mut s, options)?;
Ok(s)
}
}
impl Fmt for isize {
fn format(
&self,
full_name: &[String],
name: &[String],
_args: &[String],
flags: &[char],
options: &HashMap<String, String>,
) -> Result<String, SingleFmtError> {
if !name.is_empty() {
return Err(SingleFmtError::UnknownSubfmt(util::join_name(full_name)));
}
let mut s = util::int_to_str(full_name, *self, flags, options)?;
util::add_sign(&mut s, *self, flags)?;
util::apply_common_options(full_name, &mut s, options)?;
Ok(s)
}
}
impl<'a> Fmt for &'a str {
fn format(
&self,
full_name: &[String],
name: &[String],
_args: &[String],
_flags: &[char],
options: &HashMap<String, String>,
) -> Result<String, SingleFmtError> {
if !name.is_empty() {
return Err(SingleFmtError::UnknownSubfmt(util::join_name(full_name)));
}
let mut s = self.to_string();
util::apply_common_options(full_name, &mut s, options)?;
Ok(s)
}
}
impl Fmt for String {
fn format(
&self,
full_name: &[String],
name: &[String],
_args: &[String],
_flags: &[char],
options: &HashMap<String, String>,
) -> Result<String, SingleFmtError> {
if !name.is_empty() {
return Err(SingleFmtError::UnknownSubfmt(util::join_name(full_name)));
}
let mut s = self.clone();
util::apply_common_options(full_name, &mut s, options)?;
Ok(s)
}
}
impl Fmt for u8 {
fn format(
&self,
full_name: &[String],
name: &[String],
_args: &[String],
flags: &[char],
options: &HashMap<String, String>,
) -> Result<String, SingleFmtError> {
if !name.is_empty() {
return Err(SingleFmtError::UnknownSubfmt(util::join_name(full_name)));
}
let mut s = util::int_to_str(full_name, *self, flags, options)?;
if flags.contains(&'+') {
s.insert(0, '+');
}
util::apply_common_options(full_name, &mut s, options)?;
Ok(s)
}
}
impl Fmt for u16 {
fn format(
&self,
full_name: &[String],
name: &[String],
_args: &[String],
flags: &[char],
options: &HashMap<String, String>,
) -> Result<String, SingleFmtError> {
if !name.is_empty() {
return Err(SingleFmtError::UnknownSubfmt(util::join_name(full_name)));
}
let mut s = util::int_to_str(full_name, *self, flags, options)?;
if flags.contains(&'+') {
s.insert(0, '+');
}
util::apply_common_options(full_name, &mut s, options)?;
Ok(s)
}
}
impl Fmt for u32 {
fn format(
&self,
full_name: &[String],
name: &[String],
_args: &[String],
flags: &[char],
options: &HashMap<String, String>,
) -> Result<String, SingleFmtError> {
if !name.is_empty() {
return Err(SingleFmtError::UnknownSubfmt(util::join_name(full_name)));
}
let mut s = util::int_to_str(full_name, *self, flags, options)?;
if flags.contains(&'+') {
s.insert(0, '+');
}
util::apply_common_options(full_name, &mut s, options)?;
Ok(s)
}
}
impl Fmt for u64 {
fn format(
&self,
full_name: &[String],
name: &[String],
_args: &[String],
flags: &[char],
options: &HashMap<String, String>,
) -> Result<String, SingleFmtError> {
if !name.is_empty() {
return Err(SingleFmtError::UnknownSubfmt(util::join_name(full_name)));
}
let mut s = util::int_to_str(full_name, *self, flags, options)?;
if flags.contains(&'+') {
s.insert(0, '+');
}
util::apply_common_options(full_name, &mut s, options)?;
Ok(s)
}
}
impl Fmt for u128 {
fn format(
&self,
full_name: &[String],
name: &[String],
_args: &[String],
flags: &[char],
options: &HashMap<String, String>,
) -> Result<String, SingleFmtError> {
if !name.is_empty() {
return Err(SingleFmtError::UnknownSubfmt(util::join_name(full_name)));
}
let mut s = util::int_to_str(full_name, *self, flags, options)?;
if flags.contains(&'+') {
s.insert(0, '+');
}
util::apply_common_options(full_name, &mut s, options)?;
Ok(s)
}
}
impl Fmt for usize {
fn format(
&self,
full_name: &[String],
name: &[String],
_args: &[String],
flags: &[char],
options: &HashMap<String, String>,
) -> Result<String, SingleFmtError> {
if !name.is_empty() {
return Err(SingleFmtError::UnknownSubfmt(util::join_name(full_name)));
}
let mut s = util::int_to_str(full_name, *self, flags, options)?;
if flags.contains(&'+') {
s.insert(0, '+');
}
util::apply_common_options(full_name, &mut s, options)?;
Ok(s)
}
}
#[cfg(test)]
mod fmt_tests {
test_suite! {
name general;
use std::collections::HashMap;
use galvanic_assert::matchers::*;
use {FormatTable, Fmt, FormattingError};
test unknown_fmt() {
let table: HashMap<&str, &dyn Fmt> = HashMap::new();
let s = table.format("i = {i}");
assert_that!(&s, eq(Err(FormattingError::UnknownFmt("i".to_string()))));
}
test unknown_fmt_nested() {
let i = 1;
let mut table: HashMap<&str, &dyn Fmt> = HashMap::new();
table.insert("i", &i);
let s = table.format("{i.a}");
assert_that!(&s, eq(Err(FormattingError::UnknownFmt("i.a".to_string()))));
}
test integers_simple_1() {
let i = 1;
let j = 23;
let mut table: HashMap<&str, &dyn Fmt> = HashMap::new();
table.insert("i", &i);
table.insert("j", &j);
let s = table.format("i = {i}, j = {j}").unwrap();
assert_that!(&s.as_str(), eq("i = 1, j = 23"));
}
test separated_by_colons() {
let table: HashMap<&str, &dyn Fmt> = HashMap::new();
let s = table.format("a:b").unwrap();
assert_that!(&s.as_str(), eq("ab"));
}
}
test_suite! {
name boolean;
use std::collections::HashMap;
use galvanic_assert::matchers::*;
use {FormatTable, Fmt};
test flags() {
let a = true;
let b = false;
let mut table: HashMap<&str, &dyn Fmt> = HashMap::new();
table.insert("a", &a);
table.insert("b", &b);
let s = table.format("{a}, {b:y}, {b:Y}").unwrap();
assert_that!(&s.as_str(), eq("true, no, N"));
}
}
test_suite! {
name char;
use std::collections::HashMap;
use galvanic_assert::matchers::*;
use {FormatTable, Fmt};
test boring() {
let c = 'z';
let mut table: HashMap<&str, &dyn Fmt> = HashMap::new();
table.insert("c", &c);
let s = table.format("{c}, {c::width=l5}!").unwrap();
assert_that!(&s.as_str(), eq("z, z !"));
}
}
test_suite! {
name floats;
use std::collections::HashMap;
use galvanic_assert::matchers::*;
use {FormatTable, Fmt};
test exp_precision_neg() {
let f: f64 = 1_234_567.891;
let mut table: HashMap<String, &dyn Fmt> = HashMap::new();
table.insert("f".to_string(), &f);
let s = table.format("{f:e+:prec=-1}").expect("Failed to format");
assert_that!(&s.as_str(), eq("+1.23457e6"));
}
test exp_precision_pos() {
let f: f32 = 1000.123;
let mut table: HashMap<&str, &dyn Fmt> = HashMap::new();
table.insert("f", &f);
let s = table.format("{f:e:prec=2}").expect("Failed to format");
assert_that!(&s.as_str(), eq("1.00012e3"));
}
test exp_negative_power() {
let f: f32 = 0.0625;
let mut table: HashMap<&str, &dyn Fmt> = HashMap::new();
table.insert("f", &f);
let s = table.format("{f:e}").expect("Failed to format");
assert_that!(&s.as_str(), eq("6.25e-2"));
}
test norm_rounding_up() {
let f = 0.2;
let mut table: HashMap<&str, &dyn Fmt> = HashMap::new();
table.insert("f", &f);
let s = table.format("{f::round=up:prec=0}").expect("Failed to format");
assert_that!(&s.as_str(), eq("1"));
}
test norm_rounding_down() {
let f = 0.8;
let mut table: HashMap<&str, &dyn Fmt> = HashMap::new();
table.insert("f", &f);
let s = table.format("{f::round=down:prec=0}").expect("Failed to format");
assert_that!(&s.as_str(), eq("0"));
}
test norm_rounding_usual() {
let f = 0.5;
let mut table: HashMap<&str, &dyn Fmt> = HashMap::new();
table.insert("f", &f);
let s = table.format("{f::round=nearest:prec=0}").expect("Failed to format");
assert_that!(&s.as_str(), eq("1"));
}
test negative() {
let f = -1.0;
let mut table: HashMap<&str, &dyn Fmt> = HashMap::new();
table.insert("f", &f);
let s = table.format("{f}").expect("Failed to format");
assert_that!(&s.as_str(), eq("-1"));
}
}
test_suite! {
name integers;
use std::collections::HashMap;
use galvanic_assert::matchers::*;
use {FormatTable, Fmt};
test basic() {
let i = 10;
let mut table: HashMap<&str, &dyn Fmt> = HashMap::new();
table.insert("i", &i);
let s = table.format("{i}").expect("Failed to format");
assert_that!(&s.as_str(), eq("10"));
}
test different_bases() {
let i = 11;
let mut table: HashMap<&str, &dyn Fmt> = HashMap::new();
table.insert("i", &i);
let s = table.format("{i:b}, {i:o}, {i:x}").expect("Failed to format");
assert_that!(&s.as_str(), eq("1011, 13, b"));
}
test base_prefixes() {
let i = 1;
let mut table: HashMap<&str, &dyn Fmt> = HashMap::new();
table.insert("i", &i);
let s = table.format("{i:bp}, {i:op}, {i:xp}").expect("Failed to format");
assert_that!(&s.as_str(), eq("0b1, 0o1, 0x1"));
}
test bases_for_negative_numbers() {
let i = -11;
let mut table: HashMap<&str, &dyn Fmt> = HashMap::new();
table.insert("i", &i);
let s = table.format("{i:b}, {i:o}, {i:x}").expect("Failed to format");
assert_that!(&s.as_str(), eq("-1011, -13, -b"));
}
test rounding() {
let i = 1235;
let mut table: HashMap<&str, &dyn Fmt> = HashMap::new();
table.insert("i", &i);
let s = table.format("{i::prec=-1}, {i::prec=-2}").expect("Failed to format");
assert_that!(&s.as_str(), eq("1240, 1200"));
}
test rounding_for_negatives() {
let i = -1235;
let mut table: HashMap<&str, &dyn Fmt> = HashMap::new();
table.insert("i", &i);
let s = table.format("{i::prec=-1}, {i::prec=-2}").expect("Failed to format");
assert_that!(&s.as_str(), eq("-1240, -1200"));
}
test rounding_in_different_bases() {
let o = 0o124;
let b = 0b1101;
let x = 0x1a2;
let mut table: HashMap<&str, &dyn Fmt> = HashMap::new();
table.insert("o", &o);
table.insert("b", &b);
table.insert("x", &x);
let s1 = table.format("{o:op:prec=-1}, {o:op:prec=-2}").expect("Failed to parse 1");
let s2 = table.format("{b:bp:prec=-1}, {b:bp:prec=-2}").expect("Failed to parse 2");
let s3 = table.format("{x:xp:prec=-1}, {x:xp:prec=-2}").expect("Failed to parse 3");
assert_that!(&s1.as_str(), eq("0o130, 0o100"));
assert_that!(&s2.as_str(), eq("0b1110, 0b1100"));
assert_that!(&s3.as_str(), eq("0x1a0, 0x200"));
}
}
test_suite! {
name common_options;
use std::collections::HashMap;
use galvanic_assert::matchers::*;
use {FormatTable, Fmt};
test width_left() {
let string = "foobar";
let mut table: HashMap<&str, &dyn Fmt> = HashMap::new();
table.insert("s", &string);
let s = table.format("{s::width=l10}").unwrap();
assert_that!(&s.as_str(), eq("foobar "));
}
test width_right() {
let string = "foobar";
let mut table: HashMap<&str, &dyn Fmt> = HashMap::new();
table.insert("s", &string);
let s = table.format("{s::width=r10}").unwrap();
assert_that!(&s.as_str(), eq(" foobar"));
}
test width_center() {
let string = "foobar";
let mut table: HashMap<&str, &dyn Fmt> = HashMap::new();
table.insert("s", &string);
let s = table.format("{s::width=c10}").unwrap();
assert_that!(&s.as_str(), eq(" foobar "));
}
test truncate_left() {
let string = "1234567890";
let mut table: HashMap<&str, &dyn Fmt> = HashMap::new();
table.insert("s", &string);
let s = table.format("{s::truncate=l5}").unwrap();
assert_that!(&s.as_str(), eq("67890"));
}
test truncate_right() {
let string = "1234567890";
let mut table: HashMap<&str, &dyn Fmt> = HashMap::new();
table.insert("s", &string);
let s = table.format("{s::truncate=r5}").unwrap();
assert_that!(&s.as_str(), eq("12345"));
}
}
test_suite! {
name nested_fmts;
use std::collections::HashMap;
use galvanic_assert::matchers::*;
use {FormatTable, Fmt, FormattingError, SingleFmtError, util};
struct Point {
x: i32,
y: i32
}
struct Line {
start: Point,
end: Point
}
impl Fmt for Point {
fn format(&self,
full_name: &[String],
name: &[String],
args: &[String],
flags: &[char],
options: &HashMap<String, String>)
-> Result<String, SingleFmtError>
{
if name.is_empty() {
Err(SingleFmtError::NamespaceOnlyFmt(util::join_name(full_name)))
} else if name[0] == "x" {
self.x.format(full_name, &name[1..], args, flags, options)
} else if name[0] == "y" {
self.y.format(full_name, &name[1..], args, flags, options)
} else {
Err(SingleFmtError::UnknownSubfmt(util::join_name(full_name)))
}
}
}
impl Fmt for Line {
fn format(&self,
full_name: &[String],
name: &[String],
args: &[String],
flags: &[char],
options: &HashMap<String, String>)
-> Result<String, SingleFmtError>
{
if name.is_empty() {
Err(SingleFmtError::NamespaceOnlyFmt(util::join_name(full_name)))
} else if name[0] == "start" || name[0] == "a" {
self.start.format(full_name, &name[1..], args, flags, options)
} else if name[0] == "end" || name[0] == "b" {
self.end.format(full_name, &name[1..], args, flags, options)
} else {
Err(SingleFmtError::UnknownSubfmt(util::join_name(full_name)))
}
}
}
test single_nested() {
let a = Point { x: 0, y: 0 };
let b = Point { x: 2, y: 10 };
let mut table: HashMap<&str, &dyn Fmt> = HashMap::new();
table.insert("a", &a);
table.insert("b", &b);
let s = table.format("{a.x}, {b.y}").expect("Failed to format");
assert_that!(&s.as_str(), eq("0, 10"));
}
test double_nested() {
let line = Line {
start: Point { x: 0, y: 2 },
end: Point { x: 6, y: 10},
};
let mut table: HashMap<&str, &dyn Fmt> = HashMap::new();
table.insert("line", &line);
let s = table.format("{line.start.x}, {line.end.y}").expect("Failed to format");
assert_that!(&s.as_str(), eq("0, 10"));
}
test namespace_only() {
let p = Point { x: 1, y: 2 };
let mut table: HashMap<&str, &dyn Fmt> = HashMap::new();
table.insert("p", &p);
let s = table.format("{p}");
assert_that!(&s, eq(Err(FormattingError::NamespaceOnlyFmt("p".to_string()))));
}
}
test_suite! {
name placeholder_substitution;
use std::collections::HashMap;
use galvanic_assert::matchers::*;
use {FormatTable, Fmt};
test placeholder_in_options() {
let p1 = 3;
let p2 = 5;
let f = 0.765_432_1;
let mut table: HashMap<&str, &dyn Fmt> = HashMap::new();
table.insert("p1", &p1);
table.insert("p2", &p2);
table.insert("f", &f);
let s = table.format("{f::prec={p1}}, {f::prec={p2}}").expect("Failed to format");
assert_that!(&s.as_str(), eq("0.765, 0.76543"));
}
}
}
#[cfg(test)]
mod table_tests {
test_suite! {
name vec;
use galvanic_assert::matchers::*;
use {FormatTable, Fmt, FormattingError};
test unknown_fmt_1() {
let i = 1;
let j = 2;
let table: Vec<&dyn Fmt> = vec![&i, &j];
let err = table.format("{10}").expect_err("Unexpectedly found a fmt");
assert_that!(&err, eq(FormattingError::UnknownFmt("10".to_string())));
}
test unknown_fmt_2() {
let i = 1;
let j = 2;
let table: Vec<&dyn Fmt> = vec![&i, &j];
let err = table.format("{-3}").expect_err("Unexpectedly found a fmt");
assert_that!(&err, eq(FormattingError::UnknownFmt("-3".to_string())));
}
test boring() {
let i = 1;
let j = 2;
let table: Vec<&dyn Fmt> = vec![&i, &j];
let s = table.format("{0}, {1}").expect("Failed to format");
assert_that!(&s, eq("1, 2".to_string()));
}
}
test_suite! {
name tuples;
use galvanic_assert::matchers::*;
use {FormatTable, Fmt};
test defaulting() {
let a: Vec<Box<Fmt>> = vec![Box::new(-1), Box::new(-2)];
let b: Vec<Box<Fmt>> = (0..10_i32).map(|i| Box::new(i) as Box<Fmt>).collect();
let s = (a, b).format("{5}").expect("Failed");
assert_that!(&s, eq("5".to_string()));
}
test precedence() {
let a: Vec<Box<Fmt>> = vec![Box::new(1)];
let b: Vec<Box<Fmt>> = vec![Box::new(10)];
let s = (a, b).format("{0}").expect("Failed");
assert_that!(&s, eq("1".to_string()));
}
test lifetimes() {
let i = 10;
let j = 12;
let v1: Vec<&dyn Fmt> = vec![&i, &j];
{
let k = 13;
let v2: Vec<&dyn Fmt> = vec![&k];
let s1 = (&v1, &v2).format("{0}").expect("Failed to format");
let s2 = (&v2, &v1).format("{0}").expect("Failed to format");
assert_that!(&s1.as_str(), eq("10"));
assert_that!(&s2.as_str(), eq("13"));
}
}
}
}