use crate::{
NiceU8,
NiceU16,
NiceU32,
NiceU64,
};
use std::{
fmt,
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<T>: Inflection {
fn nice_inflect<'a>(self, singular: &'a str, plural: &'a str) -> NiceInflected<'a, T>;
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct NiceInflected<'a, T> {
neg: bool,
nice: T,
unit: &'a str,
}
impl<T: Copy> NiceInflected<'_, T> {
pub const fn is_negative(&self) -> bool { self.neg }
pub const fn nice(&self) -> T { self.nice }
pub const fn unit(&self) -> &str { self.unit }
}
macro_rules! inflect {
(@signed $($ty:ty)+) => ($(
impl Inflection for $ty {
#[inline]
fn inflect<'a>(self, singular: &'a str, plural: &'a str) -> &'a str {
if self.unsigned_abs() == 1 { singular } else { plural }
}
}
)+);
(@unsigned $($ty:ty)+) => ($(
impl Inflection for $ty {
#[inline]
fn inflect<'a>(self, singular: &'a str, plural: &'a str) -> &'a str {
if self == 1 { singular } else { plural }
}
}
)+);
(@nonzero $($ty:ty)+) => ($(
impl Inflection for $ty {
#[inline]
fn inflect<'a>(self, singular: &'a str, plural: &'a str) -> &'a str {
if self == Self::MIN { singular } else { plural }
}
}
)+);
}
inflect!(@unsigned u8 u16 u32 u64 u128 usize);
inflect!(@nonzero NonZeroU8 NonZeroU16 NonZeroU32 NonZeroU64 NonZeroU128 NonZeroUsize);
inflect!(@signed i8 i16 i32 i64 i128 isize);
impl Inflection for f32 {
#[inline]
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 {
#[inline]
fn inflect<'a>(self, singular: &'a str, plural: &'a str) -> &'a str {
if self.abs().eq(&1.0) { singular } else { plural }
}
}
macro_rules! inflect_nice {
(@signed $nice:ty: $($ty:ty)+) => ($(
impl NiceInflection<$nice> for $ty {
#[inline]
fn nice_inflect<'a>(self, singular: &'a str, plural: &'a str) -> NiceInflected<'a, $nice> {
let neg = self < 0;
let nice = <$nice>::from(self.unsigned_abs());
let unit = self.inflect(singular, plural);
NiceInflected { neg, nice, unit }
}
}
)+);
($($nice:ty)+) => ($(
impl fmt::Display for NiceInflected<'_, $nice> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.neg { f.write_str("-")?; }
f.write_str(self.nice.as_str())?;
f.write_str(" ")?;
f.write_str(self.unit)
}
}
impl NiceInflected<'_, $nice> {
pub const fn len(&self) -> usize {
self.neg as usize + self.nice.len() + 1 + self.unit.len()
}
}
impl<T: Inflection> NiceInflection<$nice> for T
where $nice: From<T> {
#[inline]
fn nice_inflect<'a>(self, singular: &'a str, plural: &'a str) -> NiceInflected<'a, $nice> {
let nice = <$nice>::from(self);
let unit = self.inflect(singular, plural);
NiceInflected { neg: false, nice, unit }
}
}
)+);
}
inflect_nice!(NiceU8 NiceU16 NiceU32 NiceU64);
inflect_nice!(@signed NiceU8: i8);
inflect_nice!(@signed NiceU16: i16);
inflect_nice!(@signed NiceU32: i32);
inflect_nice!(@signed NiceU64: i64 isize);
#[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 = 250;
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").to_string(),
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"); }
}
#[cfg(not(miri))]
#[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"); }
}
#[cfg(miri)]
#[test]
fn t_u16() {
t_nice_basics!(u16, NonZeroU16, i16);
let mut rng = fastrand::Rng::new();
for i in std::iter::repeat_with(|| rng.u16(2..=u16::MAX)).take(SAMPLE_SIZE) {
t_nice_inflect!(i, "books");
t_nice_inflect!(NonZeroU16::new(i).unwrap(), "books");
}
for i in std::iter::repeat_with(|| rng.i16(i16::MIN..-1)).take(SAMPLE_SIZE.wrapping_div(2)) {
t_nice_inflect!(i, "books");
}
for i in std::iter::repeat_with(|| rng.i16(2..i16::MAX)).take(SAMPLE_SIZE.wrapping_div(2)) {
t_nice_inflect!(i, "books");
}
}
#[test]
fn t_u32() {
t_nice_basics!(u32, NonZeroU32, i32);
let mut 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 mut 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 mut 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 mut 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");
}
}