russian_numbers 0.2.0

crate for converting numbers into names in Russian language
Documentation
use std::iter::Enumerate;

use crate::{triplet::RevDigits, Triplets};

mod dict;

/// Iterator of triplet names with the names of these triplets
///
/// **Inverted order**
///
/// [[1, 0, 0], [1, 0, 2]] -> [["сто"], ["сто", "две", "тысячи"]]
pub struct Numerals<T>(Enumerate<Triplets<T>>);

impl<T> From<Triplets<T>> for Numerals<T>
where
    T: Iterator<Item = u8>,
{
    #[inline]
    fn from(triplets: Triplets<T>) -> Self {
        Self(triplets.enumerate())
    }
}

impl<T> Iterator for Numerals<T>
where
    T: Iterator<Item = u8>,
{
    type Item = Vec<&'static str>;

    fn next(&mut self) -> Option<Self::Item> {
        self.0.next().map(|(i, triplet)| {
            let mut words = Vec::with_capacity(4);
            let [hs, ds, us] = *triplet.as_ref();

            if hs != 0 {
                words.push(dict::hs2str(hs))
            };
            if ds != 0 && ds == 1 {
                words.push(dict::ds1us2str(us))
            };
            if ds != 0 && ds != 1 {
                words.push(dict::ds2str(ds))
            };
            if us != 0 && ds != 1 {
                words.push(dict::us2str(us, i == 1))
            };

            if i != 0 && !triplet.is_sero() {
                if let Some(grade) = dict::grade(i as u32) {
                    words.push(grade(triplet.ds_us()));
                } else {
                    dbg!(triplet);
                }
            }
            words
        })
    }
}

impl<T> Numerals<T>
where
    T: Iterator<Item = u8>,
{
    pub fn words(self) -> Vec<&'static str> {
        let mut words = self.collect::<Vec<_>>();
        words.reverse();
        words.into_iter().flatten().collect::<Vec<_>>()
    }
}

pub trait NumeralName {
    /// Returns a vec of words - the name of the number
    ///
    /// # Examples
    /// ```rust
    /// use russian_numbers::NumeralName;
    /// assert_eq!(123_321usize.numeral_name(), &["сто", "двадцать", "три", "тысячи", "триста", "двадцать", "один"]);
    /// assert_eq!(100_000usize.numeral_name(), &["сто", "тысяч"]);
    /// assert_eq!(101_000usize.numeral_name(), &["сто", "одна", "тысяча"]);
    /// assert_eq!(102_000usize.numeral_name(), &["сто", "две", "тысячи"]);
    /// assert_eq!(101_000_000usize.numeral_name(), &["сто", "один", "миллион"]);
    /// assert_eq!(102_000_000usize.numeral_name(), &["сто", "два", "миллиона"]);
    /// assert_eq!(102_102_000usize.numeral_name(), &["сто", "два", "миллиона", "сто", "две", "тысячи"]);
    /// assert_eq!(u128::MAX.numeral_name(), &["триста", "сорок", "анцедиллионов", "двести", "восемьдесят", "два", "дециллиона", "триста", "шестьдесят", "шесть", "нониллионов", "девятьсот", "двадцать", "октиллионов", "девятьсот", "тридцать", "восемь", "септиллионов", "четыреста", "шестьдесят", "три", "секстиллиона", "четыреста", "шестьдесят", "три", "квинтиллиона", "триста", "семьдесят", "четыре", "квадриллиона", "шестьсот", "семь", "триллионов", "четыреста", "тридцать", "один", "миллиард", "семьсот", "шестьдесят", "восемь", "миллионов", "двести", "одиннадцать", "тысяч", "четыреста", "пятьдесят", "пять"]);
    /// ```
    ///
    /// If you need to split triplets, use triplet iterators separately:
    ///
    /// ```rust
    /// use russian_numbers::{Numerals, RevDigits};
    ///
    /// let x = 120_210usize;
    /// assert_eq!(Numerals::from(x.into_triplets()).collect::<Vec<_>>(), vec![vec!["двести", "десять"], vec!["сто", "двадцать", "тысяч"]])
    /// ```
    fn numeral_name(&self) -> Vec<&'static str>;
}

macro_rules! impl_into_numeral {
    ( $to:ty, $digits:ident ) => {
        pub struct $digits($to);
        impl Iterator for $digits {
            type Item = u8;
            fn next(&mut self) -> Option<Self::Item> {
                if self.0 == 0 { None } else {
                    let num = self.0;
                    let digit = num.rem_euclid(10);
                    self.0 = num.div_euclid(10);
                    Some(digit as u8)
                }
            }
        }

        impl RevDigits<$digits> for $to {
            #[inline]
            fn reversed_digits(&self) -> $digits {
                debug_assert!(*self != 0, "number must not be equal zero");
                $digits(*self)
            }
        }

        impl NumeralName for $to {
            fn numeral_name(&self) -> Vec<&'static str> {
                let mut out = Vec::new();
                if *self == 0 {
                    return vec![dict::ZERO];
                }
                out.extend_from_slice(&Numerals::from(Triplets::from($digits(*self))).words());
                out
            }
        }

    };
    ( $( $type:ty, $digits:ident );+ ) => {
        $( impl_into_numeral!($type, $digits); )+
    }
}

impl_into_numeral!(
    u8, RevDigitsU8;
    u16, RevDigitsU16;
    u32, RevDigitsU32;
    u64, RevDigitsU64;
    u128, RevDigitsU128;
    usize, RevDigitsUsize
);