use core::fmt;
use crate::{Currency, Gender, int_to_words_gender, money, ordinal};
#[derive(Debug, Clone, Copy)]
pub struct Number(i64);
impl Number {
pub const fn new(n: i64) -> Self {
Self(n)
}
pub const fn masculine(self) -> GenderedNumber {
GenderedNumber {
n: self.0,
gender: Gender::Masculine,
}
}
pub const fn feminine(self) -> GenderedNumber {
GenderedNumber {
n: self.0,
gender: Gender::Feminine,
}
}
pub const fn neuter(self) -> GenderedNumber {
GenderedNumber {
n: self.0,
gender: Gender::Neuter,
}
}
pub const fn gender(self, gender: Gender) -> GenderedNumber {
GenderedNumber { n: self.0, gender }
}
pub const fn ordinal(self, gender: Gender) -> OrdinalNumber {
OrdinalNumber { n: self.0, gender }
}
pub const fn with_noun<'a>(
self,
one: &'a str,
two: &'a str,
five: &'a str,
) -> NumberWithNoun<'a> {
NumberWithNoun {
n: self.0,
forms: (one, two, five),
gender: Gender::Masculine,
}
}
pub const fn with_noun_gender<'a>(
self,
one: &'a str,
two: &'a str,
five: &'a str,
gender: Gender,
) -> NumberWithNoun<'a> {
NumberWithNoun {
n: self.0,
forms: (one, two, five),
gender,
}
}
pub const fn money<'a>(self, cents: u32, currency: &'a Currency<'a>) -> MoneyAmount<'a> {
MoneyAmount {
whole: self.0,
cents,
currency,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct GenderedNumber {
n: i64,
gender: Gender,
}
impl fmt::Display for GenderedNumber {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&int_to_words_gender(self.n, self.gender))
}
}
#[derive(Debug, Clone, Copy)]
pub struct OrdinalNumber {
n: i64,
gender: Gender,
}
impl fmt::Display for OrdinalNumber {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&ordinal(self.n, self.gender))
}
}
#[derive(Debug, Clone, Copy)]
pub struct NumberWithNoun<'a> {
n: i64,
forms: (&'a str, &'a str, &'a str),
gender: Gender,
}
impl fmt::Display for NumberWithNoun<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let words = crate::int_to_words_gender(self.n, self.gender);
let noun = crate::decline(self.n, self.forms.0, self.forms.1, self.forms.2);
write!(f, "{words} {noun}")
}
}
#[derive(Debug, Clone, Copy)]
pub struct MoneyAmount<'a> {
whole: i64,
cents: u32,
currency: &'a Currency<'a>,
}
impl fmt::Display for MoneyAmount<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&money(self.whole, self.cents, self.currency))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::RUB;
#[cfg(not(feature = "std"))]
use alloc::string::{String, ToString};
#[test]
fn test_number_builder_cardinals() {
assert_eq!(Number::new(42).masculine().to_string(), "сорок два");
assert_eq!(Number::new(42).feminine().to_string(), "сорок две");
assert_eq!(Number::new(1).neuter().to_string(), "одно");
}
#[test]
fn test_number_builder_ordinal() {
assert_eq!(
Number::new(42).ordinal(Gender::Masculine).to_string(),
"сорок второй"
);
assert_eq!(
Number::new(2026).ordinal(Gender::Masculine).to_string(),
"две тысячи двадцать шестой"
);
}
#[test]
fn test_number_builder_noun() {
assert_eq!(
Number::new(5)
.with_noun("рубль", "рубля", "рублей")
.to_string(),
"пять рублей"
);
assert_eq!(
Number::new(21)
.with_noun("рубль", "рубля", "рублей")
.to_string(),
"двадцать один рубль"
);
assert_eq!(
Number::new(1)
.with_noun_gender("копейка", "копейки", "копеек", Gender::Feminine)
.to_string(),
"одна копейка"
);
assert_eq!(
Number::new(1)
.with_noun_gender("место", "места", "мест", Gender::Neuter)
.to_string(),
"одно место"
);
}
#[test]
fn test_number_builder_money() {
assert_eq!(
Number::new(1234).money(56, &RUB).to_string(),
"одна тысяча двести тридцать четыре рубля пятьдесят шесть копеек"
);
}
#[test]
fn test_display_no_alloc_in_caller() {
use core::fmt::Write;
let mut s = String::new();
write!(&mut s, "{}", Number::new(42).masculine()).unwrap();
assert_eq!(s, "сорок два");
}
}