fav_utils 0.0.15

Fav's utils crate; A collection of utilities and data structures for the fav project
Documentation
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use std::{cmp::Ordering, sync::OnceLock};
use unicode_width::UnicodeWidthChar;

pub fn download_bar(size: usize, title: impl AsRef<str>) -> ProgressBar {
    static PBS: OnceLock<MultiProgress> = OnceLock::new();
    let pbs = PBS.get_or_init(MultiProgress::new);
    let pb = pbs.add(ProgressBar::new(size as u64));
    pb.set_message(take_by_unicode_width(title.as_ref(), 10));
    pb.set_style(ProgressStyle::with_template("{msg} {spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} ({bytes_per_sec}, {eta})")
            .unwrap()
            .progress_chars("#>-"));
    pb
}

fn take_by_unicode_width(s: &str, max_width: usize) -> String {
    let mut width = 0;
    let mut res = String::new();
    for ch in s.chars() {
        let ch_width = ch.width().unwrap_or_default();
        match (width + ch_width).cmp(&max_width) {
            Ordering::Less | Ordering::Equal => res.push(ch),
            Ordering::Greater => break,
        }
        width += ch_width;
    }
    if width < max_width {
        res.push_str(&" ".repeat(max_width - width));
    }
    res
}

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

    #[test]
    fn test_take_by_unicode_width() {
        assert_eq!(take_by_unicode_width("a", 1), "a");
        assert_eq!(take_by_unicode_width("", 1), " ");
        assert_eq!(take_by_unicode_width("", 2), "");
        assert_eq!(take_by_unicode_width("", 3), "");
        assert_eq!(take_by_unicode_width("中aa", 4), "中aa");
        assert_eq!(take_by_unicode_width("中aa", 5), "中aa ");
        assert_eq!(take_by_unicode_width("中国人不骗中国人", 6), "中国人");
    }

    #[test]
    #[ignore = "This test is for manual inspection"]
    fn test_download_bar() {
        fn download_bar_job(title: impl AsRef<str>) {
            let pb = download_bar(100, title);
            for _ in 0..100 {
                pb.inc(1);
                std::thread::sleep(std::time::Duration::from_millis(10));
            }
            pb.finish();
        }
        let jhs: Vec<_> = [
            "中国人不骗中国人中国人不骗中国人",
            "中国人不骗中国人",
            "Rust起飞啦",
            "Rust",
            "",
        ]
        .into_iter()
        .map(|title| std::thread::spawn(move || download_bar_job(title)))
        .collect();
        for jh in jhs {
            jh.join().unwrap();
        }
    }
}