loaders 0.0.0

A fully-featured, customisable progress bar and loading indicator library for Rust CLI and terminal applications
Documentation
//! Iterator adapters that advance progress bars while consuming items.

use crate::bar::progress::ProgressBar;
use crate::style::style::ProgressStyle;

/// Iterator adapter that increments a progress bar for each item.
///
/// The adapter finishes the bar when the underlying iterator is exhausted.
#[derive(Clone, Debug)]
pub struct ProgressBarIter<I> {
    iter: I,
    pub(crate) bar: ProgressBar,
}

impl<I: Iterator> ProgressBarIter<I> {
    pub(crate) fn new(iter: I, bar: ProgressBar) -> Self {
        Self { iter, bar }
    }
}

impl<I: Iterator> Iterator for ProgressBarIter<I> {
    type Item = I::Item;

    fn next(&mut self) -> Option<I::Item> {
        let item = self.iter.next();
        match &item {
            Some(_) => self.bar.inc(1),
            None => {
                if !self.bar.is_finished() {
                    self.bar.finish();
                }
            }
        }
        item
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        self.iter.size_hint()
    }
}

impl<I: ExactSizeIterator> ExactSizeIterator for ProgressBarIter<I> {
    fn len(&self) -> usize {
        self.iter.len()
    }
}

impl<I: DoubleEndedIterator> DoubleEndedIterator for ProgressBarIter<I> {
    fn next_back(&mut self) -> Option<Self::Item> {
        let item = self.iter.next_back();
        match &item {
            Some(_) => self.bar.inc(1),
            None => {
                if !self.bar.is_finished() {
                    self.bar.finish();
                }
            }
        }
        item
    }
}

/// Extension trait for adding progress bars to iterators.
///
/// Import this trait to call `.progress()` directly on iterator values.
pub trait ProgressIterator: Iterator + Sized {
    /// Wraps this iterator with an automatically configured progress bar.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use loaders::ProgressIterator;
    /// let values: Vec<_> = (0..3).progress().collect();
    /// assert_eq!(values, vec![0, 1, 2]);
    /// ```
    fn progress(self) -> ProgressBarIter<Self>;

    /// Wraps this iterator with an existing progress bar.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use loaders::ProgressIterator;
    /// let pb = loaders::ProgressBar::hidden();
    /// let values: Vec<_> = (0..2).progress_with(pb).collect();
    /// assert_eq!(values, vec![0, 1]);
    /// ```
    fn progress_with(self, pb: ProgressBar) -> ProgressBarIter<Self>;

    /// Wraps this iterator with a style.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use loaders::ProgressIterator;
    /// let style = loaders::Theme::Minimal.style();
    /// let values: Vec<_> = (0..2).progress_with_style(style).collect();
    /// assert_eq!(values, vec![0, 1]);
    /// ```
    fn progress_with_style(self, style: ProgressStyle) -> ProgressBarIter<Self>;

    /// Wraps this iterator with an explicit length.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use loaders::ProgressIterator;
    /// let values: Vec<_> = (0..2).progress_count(2).collect();
    /// assert_eq!(values, vec![0, 1]);
    /// ```
    fn progress_count(self, len: u64) -> ProgressBarIter<Self>;
}

impl<I: Iterator> ProgressIterator for I {
    fn progress(self) -> ProgressBarIter<Self> {
        let lower = self.size_hint().0;
        if lower > 0 {
            self.progress_count(lower as u64)
        } else {
            ProgressBarIter::new(self, ProgressBar::new_spinner())
        }
    }

    fn progress_with(self, pb: ProgressBar) -> ProgressBarIter<Self> {
        ProgressBarIter::new(self, pb)
    }

    fn progress_with_style(self, style: ProgressStyle) -> ProgressBarIter<Self> {
        let lower = self.size_hint().0;
        let pb = if lower > 0 {
            ProgressBar::builder()
                .length(lower as u64)
                .style(style)
                .build()
        } else {
            ProgressBar::builder().style(style).build()
        };
        ProgressBarIter::new(self, pb)
    }

    fn progress_count(self, len: u64) -> ProgressBarIter<Self> {
        ProgressBarIter::new(self, ProgressBar::new(len))
    }
}

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

    #[test]
    fn test_progress_iter_advances_bar() {
        let pb = ProgressBar::hidden();
        let mut iter = (0..3).progress_with(pb.clone());
        assert_eq!(iter.next(), Some(0));
        assert_eq!(pb.position(), 1);
    }

    #[test]
    fn test_progress_iter_finishes_at_end() {
        let pb = ProgressBar::hidden();
        let mut iter = (0..1).progress_with(pb.clone());
        assert_eq!(iter.next(), Some(0));
        assert_eq!(iter.next(), None);
        assert!(pb.is_finished());
    }

    #[test]
    fn test_progress_iter_count_sets_length() {
        let iter = (0..3).progress_count(3);
        assert_eq!(iter.bar.length(), Some(3));
    }

    #[test]
    fn test_progress_iter_size_hint_used() {
        let iter = (0..5).progress();
        assert_eq!(iter.bar.length(), Some(5));
    }

    #[test]
    fn test_progress_iter_with_custom_bar() {
        let pb = ProgressBar::hidden();
        let iter = (0..5).progress_with(pb.clone());
        assert!(iter.bar.same_bar(&pb));
    }

    #[test]
    fn test_progress_iter_collects_all_items() {
        let pb = ProgressBar::hidden();
        let values: Vec<_> = (0..4).progress_with(pb).collect();
        assert_eq!(values, vec![0, 1, 2, 3]);
    }

    #[test]
    fn test_progress_iter_on_empty() {
        let pb = ProgressBar::hidden();
        let values: Vec<i32> = std::iter::empty().progress_with(pb.clone()).collect();
        assert!(values.is_empty());
        assert!(pb.is_finished());
    }

    #[test]
    fn test_progress_iter_double_ended() {
        let pb = ProgressBar::hidden();
        let mut iter = (0..3).progress_with(pb.clone());
        assert_eq!(iter.next_back(), Some(2));
        assert_eq!(pb.position(), 1);
    }
}