use std::borrow::Cow; use std::io::{self, IoSliceMut}; use std::iter::FusedIterator; #[cfg(feature = "tokio")] use std::pin::Pin; #[cfg(feature = "tokio")] use std::task::{Context, Poll}; use std::time::Duration; #[cfg(feature = "tokio")] use tokio::io::{ReadBuf, SeekFrom}; use crate::progress_bar::ProgressBar; use crate::state::ProgressFinish; use crate::style::ProgressStyle; /// Wraps an iterator to display its progress. pub trait ProgressIterator where Self: Sized + Iterator, { /// Wrap an iterator with default styling. Uses `Iterator::size_hint` to get length. /// Returns `Some(..)` only if `size_hint.1` is `Some`. If you want to create a progress bar /// even if `size_hint.1` returns `None` use `progress_count` or `progress_with` instead. fn try_progress(self) -> Option> { self.size_hint() .1 .map(|len| self.progress_count(u64::try_from(len).unwrap())) } /// Wrap an iterator with default styling. fn progress(self) -> ProgressBarIter where Self: ExactSizeIterator, { let len = u64::try_from(self.len()).unwrap(); self.progress_count(len) } /// Wrap an iterator with an explicit element count. fn progress_count(self, len: u64) -> ProgressBarIter { self.progress_with(ProgressBar::new(len)) } /// Wrap an iterator with a custom progress bar. fn progress_with(self, progress: ProgressBar) -> ProgressBarIter; /// Wrap an iterator with a progress bar and style it. fn progress_with_style(self, style: crate::ProgressStyle) -> ProgressBarIter where Self: ExactSizeIterator, { let len = u64::try_from(self.len()).unwrap(); let bar = ProgressBar::new(len).with_style(style); self.progress_with(bar) } } /// Wraps an iterator to display its progress. #[derive(Debug)] pub struct ProgressBarIter { pub(crate) it: T, pub progress: ProgressBar, } impl ProgressBarIter { /// Builder-like function for setting underlying progress bar's style. /// /// See [ProgressBar::with_style]. pub fn with_style(mut self, style: ProgressStyle) -> Self { self.progress = self.progress.with_style(style); self } /// Builder-like function for setting underlying progress bar's prefix. /// /// See [ProgressBar::with_prefix]. pub fn with_prefix(mut self, prefix: impl Into>) -> Self { self.progress = self.progress.with_prefix(prefix); self } /// Builder-like function for setting underlying progress bar's message. /// /// See [ProgressBar::with_message]. pub fn with_message(mut self, message: impl Into>) -> Self { self.progress = self.progress.with_message(message); self } /// Builder-like function for setting underlying progress bar's position. /// /// See [ProgressBar::with_position]. pub fn with_position(mut self, position: u64) -> Self { self.progress = self.progress.with_position(position); self } /// Builder-like function for setting underlying progress bar's elapsed time. /// /// See [ProgressBar::with_elapsed]. pub fn with_elapsed(mut self, elapsed: Duration) -> Self { self.progress = self.progress.with_elapsed(elapsed); self } /// Builder-like function for setting underlying progress bar's finish behavior. /// /// See [ProgressBar::with_finish]. pub fn with_finish(mut self, finish: ProgressFinish) -> Self { self.progress = self.progress.with_finish(finish); self } } impl> Iterator for ProgressBarIter { type Item = S; fn next(&mut self) -> Option { let item = self.it.next(); if item.is_some() { self.progress.inc(1); } else if !self.progress.is_finished() { self.progress.finish_using_style(); } item } } impl ExactSizeIterator for ProgressBarIter { fn len(&self) -> usize { self.it.len() } } impl DoubleEndedIterator for ProgressBarIter { fn next_back(&mut self) -> Option { let item = self.it.next_back(); if item.is_some() { self.progress.inc(1); } else if !self.progress.is_finished() { self.progress.finish_using_style(); } item } } impl FusedIterator for ProgressBarIter {} impl io::Read for ProgressBarIter { fn read(&mut self, buf: &mut [u8]) -> io::Result { let inc = self.it.read(buf)?; self.progress.inc(inc as u64); Ok(inc) } fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { let inc = self.it.read_vectored(bufs)?; self.progress.inc(inc as u64); Ok(inc) } fn read_to_string(&mut self, buf: &mut String) -> io::Result { let inc = self.it.read_to_string(buf)?; self.progress.inc(inc as u64); Ok(inc) } fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { self.it.read_exact(buf)?; self.progress.inc(buf.len() as u64); Ok(()) } } impl io::BufRead for ProgressBarIter { fn fill_buf(&mut self) -> io::Result<&[u8]> { self.it.fill_buf() } fn consume(&mut self, amt: usize) { self.it.consume(amt); self.progress.inc(amt as u64); } } impl io::Seek for ProgressBarIter { fn seek(&mut self, f: io::SeekFrom) -> io::Result { self.it.seek(f).map(|pos| { self.progress.set_position(pos); pos }) } // Pass this through to preserve optimizations that the inner I/O object may use here // Also avoid sending a set_position update when the position hasn't changed fn stream_position(&mut self) -> io::Result { self.it.stream_position() } } #[cfg(feature = "tokio")] #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] impl tokio::io::AsyncWrite for ProgressBarIter { fn poll_write( mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { Pin::new(&mut self.it).poll_write(cx, buf).map(|poll| { poll.map(|inc| { self.progress.inc(inc as u64); inc }) }) } fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Pin::new(&mut self.it).poll_flush(cx) } fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Pin::new(&mut self.it).poll_shutdown(cx) } } #[cfg(feature = "tokio")] #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] impl tokio::io::AsyncRead for ProgressBarIter { fn poll_read( mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>, ) -> Poll> { let prev_len = buf.filled().len() as u64; if let Poll::Ready(e) = Pin::new(&mut self.it).poll_read(cx, buf) { self.progress.inc(buf.filled().len() as u64 - prev_len); Poll::Ready(e) } else { Poll::Pending } } } #[cfg(feature = "tokio")] #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] impl tokio::io::AsyncSeek for ProgressBarIter { fn start_seek(mut self: Pin<&mut Self>, position: SeekFrom) -> io::Result<()> { Pin::new(&mut self.it).start_seek(position) } fn poll_complete(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Pin::new(&mut self.it).poll_complete(cx) } } #[cfg(feature = "tokio")] #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] impl tokio::io::AsyncBufRead for ProgressBarIter { fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let this = self.get_mut(); let result = Pin::new(&mut this.it).poll_fill_buf(cx); if let Poll::Ready(Ok(buf)) = &result { this.progress.inc(buf.len() as u64); } result } fn consume(mut self: Pin<&mut Self>, amt: usize) { Pin::new(&mut self.it).consume(amt); } } #[cfg(feature = "futures")] #[cfg_attr(docsrs, doc(cfg(feature = "futures")))] impl futures_core::Stream for ProgressBarIter { type Item = S::Item; fn poll_next( self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> std::task::Poll> { let this = self.get_mut(); let item = std::pin::Pin::new(&mut this.it).poll_next(cx); match &item { std::task::Poll::Ready(Some(_)) => this.progress.inc(1), std::task::Poll::Ready(None) => this.progress.finish_using_style(), std::task::Poll::Pending => {} } item } } impl io::Write for ProgressBarIter { fn write(&mut self, buf: &[u8]) -> io::Result { self.it.write(buf).map(|inc| { self.progress.inc(inc as u64); inc }) } fn write_vectored(&mut self, bufs: &[io::IoSlice]) -> io::Result { self.it.write_vectored(bufs).map(|inc| { self.progress.inc(inc as u64); inc }) } fn flush(&mut self) -> io::Result<()> { self.it.flush() } // write_fmt can not be captured with reasonable effort. // as it uses write_all internally by default that should not be a problem. // fn write_fmt(&mut self, fmt: fmt::Arguments) -> io::Result<()>; } impl> ProgressIterator for T { fn progress_with(self, progress: ProgressBar) -> ProgressBarIter { ProgressBarIter { it: self, progress } } } #[cfg(test)] mod test { use crate::iter::{ProgressBarIter, ProgressIterator}; use crate::progress_bar::ProgressBar; use crate::ProgressStyle; #[test] fn it_can_wrap_an_iterator() { let v = [1, 2, 3]; let wrap = |it: ProgressBarIter<_>| { assert_eq!(it.map(|x| x * 2).collect::>(), vec![2, 4, 6]); }; wrap(v.iter().progress()); wrap(v.iter().progress_count(3)); wrap({ let pb = ProgressBar::new(v.len() as u64); v.iter().progress_with(pb) }); wrap({ let style = ProgressStyle::default_bar() .template("{wide_bar:.red} {percent}/100%") .unwrap(); v.iter().progress_with_style(style) }); } }