use crate::{
NiceU8,
NiceU16,
NiceU32,
NiceU64,
};
use std::num::{
NonZeroU8,
NonZeroU16,
NonZeroU32,
NonZeroU64,
NonZeroUsize,
NonZeroU128,
};
pub trait Inflection: Sized + Copy + PartialEq {
fn inflect<'a>(self, singular: &'a str, plural: &'a str) -> &'a str;
}
pub trait NiceInflection: Inflection {
fn nice_inflect<S>(self, singular: S, plural: S) -> String
where S: AsRef<str>;
}
macro_rules! inflect {
($ty:ty, $one:literal) => (
impl Inflection for $ty {
fn inflect<'a>(self, singular: &'a str, plural: &'a str) -> &'a str {
if self == $one { singular } else { plural }
}
}
);
($ty:ty, $one:expr) => (
#[allow(unsafe_code)]
impl Inflection for $ty {
fn inflect<'a>(self, singular: &'a str, plural: &'a str) -> &'a str {
if self == $one { singular } else { plural }
}
}
);
($ty:ty, $one:literal, $cast:ident) => (
impl Inflection for $ty {
fn inflect<'a>(self, singular: &'a str, plural: &'a str) -> &'a str {
if self.$cast() == $one { singular } else { plural }
}
}
);
}
macro_rules! inflect_nice {
($ty:ty, $nice:ty) => (
impl NiceInflection for $ty {
fn nice_inflect<S>(self, singular: S, plural: S) -> String
where S: AsRef<str> {
[
<$nice>::from(self).as_str(),
" ",
self.inflect(singular.as_ref(), plural.as_ref()),
].concat()
}
}
);
($ty:ty, $nice:ty, $cast:ident) => (
impl NiceInflection for $ty {
fn nice_inflect<S>(self, singular: S, plural: S) -> String
where S: AsRef<str> {
[
if self < 0 { "-" } else { "" },
<$nice>::from(self.$cast()).as_str(),
" ",
self.inflect(singular.as_ref(), plural.as_ref()),
].concat()
}
}
);
($ty:ty, $nice:ty, $one:literal) => (
inflect!($ty, $one);
inflect_nice!($ty, $nice);
);
($ty:ty, $nice:ty, $one:expr) => (
inflect!($ty, $one);
inflect_nice!($ty, $nice);
);
($ty:ty, $nice:ty, $one:literal, $cast:ident) => (
inflect!($ty, $one, $cast);
inflect_nice!($ty, $nice, $cast);
);
}
inflect_nice!(u8, NiceU8, 1);
inflect_nice!(u16, NiceU16, 1);
inflect_nice!(u32, NiceU32, 1);
inflect_nice!(u64, NiceU64, 1);
inflect_nice!(usize, NiceU64, 1);
inflect_nice!(NonZeroU8, NiceU8, unsafe { Self::new_unchecked(1) });
inflect_nice!(NonZeroU16, NiceU16, unsafe { Self::new_unchecked(1) });
inflect_nice!(NonZeroU32, NiceU32, unsafe { Self::new_unchecked(1) });
inflect_nice!(NonZeroU64, NiceU64, unsafe { Self::new_unchecked(1) });
inflect_nice!(NonZeroUsize, NiceU64, unsafe { Self::new_unchecked(1) });
inflect_nice!(i8, NiceU8, 1, unsigned_abs);
inflect_nice!(i16, NiceU16, 1, unsigned_abs);
inflect_nice!(i32, NiceU32, 1, unsigned_abs);
inflect_nice!(i64, NiceU64, 1, unsigned_abs);
inflect_nice!(isize, NiceU64, 1, unsigned_abs);
inflect!(u128, 1);
inflect!(i128, 1, unsigned_abs);
inflect!(NonZeroU128, unsafe { Self::new_unchecked(1) });
impl Inflection for f32 {
fn inflect<'a>(self, singular: &'a str, plural: &'a str) -> &'a str {
if self.abs().eq(&1.0) { singular } else { plural }
}
}
impl Inflection for f64 {
fn inflect<'a>(self, singular: &'a str, plural: &'a str) -> &'a str {
if self.abs().eq(&1.0) { singular } else { plural }
}
}
#[cfg(test)]
mod tests {
use super::*;
use num_format::{ToFormattedString, Locale};
#[cfg(not(miri))]
const SAMPLE_SIZE: usize = 1_000_000;
#[cfg(miri)]
const SAMPLE_SIZE: usize = 1000;
macro_rules! t_inflect {
($num:expr, $str:literal) => (
assert_eq!($num.inflect("book", "books"), $str, "{}.inflect()", $num);
);
}
macro_rules! t_nice_inflect {
($num:expr, $str:literal) => (
t_inflect!($num, $str);
assert_eq!(
$num.nice_inflect("book", "books"),
format!(concat!("{} ", $str), $num.to_formatted_string(&Locale::en)),
"{}.nice_inflect()", $num
);
);
}
macro_rules! t_nice_basics {
($ty:ty, $nz:ty, $i:ty) => (
let num: $ty = 0;
t_nice_inflect!(num, "books");
let num: $ty = 1;
t_nice_inflect!(num, "book");
t_nice_inflect!(<$nz>::new(num).unwrap(), "book");
let num: $i = 0;
t_nice_inflect!(num, "books");
let num: $i = 1;
t_nice_inflect!(num, "book");
let num: $i = -1;
t_nice_inflect!(num, "book");
);
}
#[test]
fn t_u8() {
t_nice_basics!(u8, NonZeroU8, i8);
for i in 2..=u8::MAX {
t_nice_inflect!(i, "books");
t_nice_inflect!(NonZeroU8::new(i).unwrap(), "books");
}
for i in 2..=i8::MAX { t_nice_inflect!(i, "books"); }
for i in i8::MIN..-1 { t_nice_inflect!(i, "books"); }
}
#[test]
fn t_u16() {
t_nice_basics!(u16, NonZeroU16, i16);
for i in 2..=u16::MAX {
t_nice_inflect!(i, "books");
t_nice_inflect!(NonZeroU16::new(i).unwrap(), "books");
}
for i in 2..=i16::MAX { t_nice_inflect!(i, "books"); }
for i in i16::MIN..-1 { t_nice_inflect!(i, "books"); }
}
#[test]
fn t_u32() {
t_nice_basics!(u32, NonZeroU32, i32);
let rng = fastrand::Rng::new();
for i in std::iter::repeat_with(|| rng.u32(2..=u32::MAX)).take(SAMPLE_SIZE) {
t_nice_inflect!(i, "books");
t_nice_inflect!(NonZeroU32::new(i).unwrap(), "books");
}
for i in std::iter::repeat_with(|| rng.i32(i32::MIN..-1)).take(SAMPLE_SIZE.wrapping_div(2)) {
t_nice_inflect!(i, "books");
}
for i in std::iter::repeat_with(|| rng.i32(2..i32::MAX)).take(SAMPLE_SIZE.wrapping_div(2)) {
t_nice_inflect!(i, "books");
}
}
#[test]
fn t_u64() {
t_nice_basics!(u64, NonZeroU64, i64);
let rng = fastrand::Rng::new();
for i in std::iter::repeat_with(|| rng.u64(2..=u64::MAX)).take(SAMPLE_SIZE) {
t_nice_inflect!(i, "books");
t_nice_inflect!(NonZeroU64::new(i).unwrap(), "books");
}
for i in std::iter::repeat_with(|| rng.i64(i64::MIN..-1)).take(SAMPLE_SIZE.wrapping_div(2)) {
t_nice_inflect!(i, "books");
}
for i in std::iter::repeat_with(|| rng.i64(2..i64::MAX)).take(SAMPLE_SIZE.wrapping_div(2)) {
t_nice_inflect!(i, "books");
}
}
#[test]
fn t_usize() {
t_nice_basics!(usize, NonZeroUsize, isize);
let rng = fastrand::Rng::new();
for i in std::iter::repeat_with(|| rng.usize(2..=usize::MAX)).take(SAMPLE_SIZE) {
t_nice_inflect!(i, "books");
t_nice_inflect!(NonZeroUsize::new(i).unwrap(), "books");
}
for i in std::iter::repeat_with(|| rng.isize(isize::MIN..-1)).take(SAMPLE_SIZE.wrapping_div(2)) {
t_nice_inflect!(i, "books");
}
for i in std::iter::repeat_with(|| rng.isize(2..isize::MAX)).take(SAMPLE_SIZE.wrapping_div(2)) {
t_nice_inflect!(i, "books");
}
}
#[test]
fn t_u128() {
t_inflect!(0_u128, "books");
t_inflect!(1_u128, "book");
t_inflect!(NonZeroU128::new(1).unwrap(), "book");
t_inflect!((-1_i128), "book");
t_inflect!(0_i128, "books");
t_inflect!(1_i128, "book");
let rng = fastrand::Rng::new();
for i in std::iter::repeat_with(|| rng.u128(2..=u128::MAX)).take(SAMPLE_SIZE) {
t_inflect!(i, "books");
t_inflect!(NonZeroU128::new(i).unwrap(), "books");
}
for i in std::iter::repeat_with(|| rng.i128(i128::MIN..-1)).take(SAMPLE_SIZE.wrapping_div(2)) {
t_inflect!(i, "books");
}
for i in std::iter::repeat_with(|| rng.i128(2..i128::MAX)).take(SAMPLE_SIZE.wrapping_div(2)) {
t_inflect!(i, "books");
}
}
#[test]
fn t_f32() {
t_inflect!(0_f32, "books");
t_inflect!(1_f32, "book");
t_inflect!((-1_f32), "book");
t_inflect!(1.05_f32, "books");
}
#[test]
fn t_f64() {
t_inflect!(0_f64, "books");
t_inflect!(1_f64, "book");
t_inflect!((-1_f64), "book");
t_inflect!(1.05_f64, "books");
}
}