wincounter 0.1.6

Binary win counter that supports ties
Documentation
use std::borrow::Cow;
use std::str::Utf8Error;

#[cfg(not(target_arch = "wasm32"))]
use std::fs::File;
#[cfg(not(target_arch = "wasm32"))]
use std::io::{self, BufRead};
#[cfg(not(target_arch = "wasm32"))]
use std::path::Path;

#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Percentage {
    pub number: usize,
    pub total: usize,
}

impl Percentage {
    #[must_use]
    pub fn new(number: usize, total: usize) -> Self {
        Self { number, total }
    }

    #[must_use]
    pub fn calculate(&self) -> f32 {
        Util::calculate_percentage(self.number, self.total)
    }
}

/// Blank struct that is home to misfit utility functions.
///
/// There is a whole school that argues against util functions and modules like
/// this. Obviously, I am not one of them.
pub struct Util;

impl Util {
    /// Forgiving percentage calculator. It will return zero if you try
    /// to divide by zero.
    #[must_use]
    #[allow(clippy::cast_precision_loss)]
    pub fn calculate_percentage(number: usize, total: usize) -> f32 {
        match total {
            0 => 0_f32,
            _ => (number as f32 * 100.0) / total as f32,
        }
    }

    ///
    ///
    /// # Errors
    ///
    /// Returns `Utf8Error` if the `&str` is not valid UTF-8.
    pub fn percent_decode(s: &str) -> Result<String, Utf8Error> {
        Ok(percent_encoding::percent_decode_str(s)
            .decode_utf8()?
            .to_string())
    }

    /// # Errors
    ///
    /// Returns `io::Error` if the file cannot be opened or read.
    ///
    /// Note: This function is not available when targeting WASM.
    #[cfg(not(target_arch = "wasm32"))]
    pub fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
    where
        P: AsRef<Path>,
    {
        let file = File::open(filename)?;
        Ok(io::BufReader::new(file).lines())
    }

    /// I need to study the ideas behind `Cow`.
    ///
    /// Usage:
    /// ```
    /// use wincounter::util::Util;
    /// assert_eq!(Util::replace_plus("A♠+J♦+6♥+6♣".into()), "A♠ J♦ 6♥ 6♣");
    /// ```
    #[must_use]
    #[allow(clippy::needless_pass_by_value)]
    pub fn replace_plus(s: Cow<str>) -> String {
        s.replace('+', " ")
    }

    #[must_use]
    pub fn str_remove_spaces(s: &str) -> String {
        s.replace(' ', "")
    }

    #[must_use]
    pub fn str_splitter(s: &str, splitter: &str) -> Vec<String> {
        s.split(splitter)
            .map(std::string::ToString::to_string)
            .collect()
    }

    /// Code from [stackoverflow](https://stackoverflow.com/questions/57029974/how-to-split-string-into-chunks-in-rust-to-insert-spaces)
    #[must_use]
    pub fn str_len_splitter(s: &str, on: usize) -> String {
        s.chars()
            .enumerate()
            .flat_map(|(i, c)| {
                if i != 0 && i % on == 0 {
                    Some(' ')
                } else {
                    None
                }
                .into_iter()
                .chain(std::iter::once(c))
            })
            .collect::<String>()
    }
}

#[cfg(test)]
#[allow(non_snake_case)]
mod util__tests {
    use super::*;

    #[test]
    fn percent() {
        let percentage = Util::calculate_percentage(48, 2_598_960);

        assert_eq!("0.00185%", format!("{:.5}%", percentage));
        assert_eq!(
            "0.00000%",
            format!("{:.5}%", Util::calculate_percentage(0, 0))
        );
    }

    #[test]
    fn percent__zero_numerator() {
        let percentage = Util::calculate_percentage(0, 2_598_960);

        assert_eq!("0.00000%", format!("{:.5}%", percentage));
    }

    #[test]
    fn percent__zero_denominator() {
        let percentage = Util::calculate_percentage(48, 0);

        assert_eq!("0.00000%", format!("{:.5}%", percentage));
    }

    #[test]
    fn str_remove_spaces() {
        let index = "JJ-22, AQs -    ATs,KJs+";

        let expected = "JJ-22,AQs-ATs,KJs+".to_string();

        assert_eq!(Util::str_remove_spaces(index), expected);
    }

    #[test]
    fn percentage_calculate() {
        assert_eq!(25.0_f32, Percentage::new(1, 4).calculate());
        assert_eq!(0.0_f32, Percentage::new(0, 0).calculate());
        assert_eq!(0.0_f32, Percentage::new(0, 100).calculate());
    }

    #[test]
    fn percent_decode() {
        assert_eq!(
            "hello world",
            Util::percent_decode("hello%20world").unwrap()
        );
        assert_eq!(
            "A♠ J♦",
            Util::percent_decode("A%E2%99%A0%20J%E2%99%A6").unwrap()
        );
        assert_eq!("", Util::percent_decode("").unwrap());
    }

    #[test]
    fn str_splitter() {
        assert_eq!(vec!["a", "b", "c"], Util::str_splitter("a,b,c", ","));
        assert_eq!(vec!["hello"], Util::str_splitter("hello", ","));
        assert_eq!(vec!["", ""], Util::str_splitter(",", ","));
    }

    #[test]
    fn str_len_splitter() {
        assert_eq!("AB CD EF", Util::str_len_splitter("ABCDEF", 2));
        assert_eq!("ABC DEF", Util::str_len_splitter("ABCDEF", 3));
        assert_eq!("ABCDEF", Util::str_len_splitter("ABCDEF", 6));
        assert_eq!("A B C", Util::str_len_splitter("ABC", 1));
    }
}