//! Progress integration for streams. //! //! Four entry points on [`StreamExt`]: //! //! - [`progress`](StreamExt::progress) takes a fraction closure; appropriate when each item //! already carries enough information to compute completion in `0.0..=1.0`. Sugar for //! `self.progressive(fraction_fn).with_theme(theme)`. //! - [`progress_count`](StreamExt::progress_count) counts items internally; pair with //! [`with_len`](ProgressCountStream::with_len) to derive the bar fraction from `count / total`. //! The closure-free path for "I know how many items will flow through". //! - [`progress_bytes`](StreamExt::progress_bytes) takes a byte-delta closure. The builder owns //! the cumulative counter, EWMA rate and (when //! [`with_len`](ProgressBytesStream::with_len) is set) the derived progress fraction. Pair with //! [`Segment::bytes`](crate::layout::Segment::bytes), //! [`Segment::rate`](crate::layout::Segment::rate) and [`Segment::eta`](crate::layout::Segment::eta) //! in a custom [`Layout`](crate::layout::Layout) for byte / throughput / ETA columns. //! - [`progressive`](StreamExt::progressive), [`progressive_count`](StreamExt::progressive_count) //! and [`progressive_bytes`](StreamExt::progressive_bytes) produce unconfigured adapters. Without //! [`with_theme`](ProgressStream::with_theme) they inherit the parent [`Group`]'s theme; with //! `with_theme` they render standalone or override the Group's theme per-row. //! //! Dynamic messages compose on top of any builder via //! [`with_messages`](ProgressStream::with_messages). pub mod group; pub use group::Group; use std::borrow::Cow; use std::fmt::Display; use std::pin::Pin; use std::task::{Context, Poll}; use futures_lite::stream::Pending; use futures_lite::{stream, Stream}; use owo_colors::Style; use pin_project_lite::pin_project; use crate::progress::Progress; use crate::progressive::Progressive; use crate::Theme; pin_project! { /// A [`Stream`] wrapped to track progress derived from a fraction closure. pub struct ProgressStream<'a, S, F, M = Pending<&'static str>> { #[pin] inner: S, fraction_fn: F, #[pin] messages: M, core: Progress<'a>, current: usize, } } impl ProgressStream<'_, S, F> { fn new(inner: S, fraction_fn: F) -> Self { Self { inner, fraction_fn, messages: stream::pending(), core: Progress::new(), current: 0, } } } impl<'a, S, F, M> ProgressStream<'a, S, F, M> { /// Set the static label shown in the [`Label`](crate::layout::Segment::Label) segment. pub fn with_label(mut self, label: impl Display) -> Self { self.core.set_label(label.to_string()); self } /// Prepend the elapsed time to the line. pub fn with_elapsed_time(mut self) -> Self { self.core.enable_elapsed_time(); self } /// Render this row with `theme`. Drives standalone rendering when the stream is polled /// directly; overrides the parent [`Group`]'s theme when pushed. pub fn with_theme(mut self, theme: impl Into>) -> Self { self.core.set_theme(theme.into()); self } /// Apply `style` to the spinner character on this row, overriding the parent Group's default. pub fn with_spinner_style(mut self, style: Style) -> Self { self.core.set_spinner_style(style); self } /// Apply `style` to the annotation (label) text on this row, overriding the parent Group's /// default. pub fn with_annotation_style(mut self, style: Style) -> Self { self.core.set_annotation_style(style); self } /// Replace the displayed message each time `messages` yields a value. The item type is /// anything that converts into a `Cow<'static, str>`: `&'static str` and `String` are /// zero-copy; other formatted values should be `format!`'d at the call site. pub fn with_messages(self, messages: S2) -> ProgressStream<'a, S, F, S2> where S2: Stream, S2::Item: Into>, { ProgressStream { inner: self.inner, fraction_fn: self.fraction_fn, messages, core: self.core, current: self.current, } } } impl<'a, S, F, M> Progressive<'a> for ProgressStream<'a, S, F, M> { fn label(&self) -> Option<&str> { self.core.label() } fn message(&self) -> Option<&str> { self.core.message() } fn progress(&self) -> Option { self.core.progress() } fn bytes_done(&self) -> u64 { self.core.bytes_done() } fn bytes_total(&self) -> Option { self.core.bytes_total() } fn rate(&self) -> Option { self.core.rate() } fn detach_rendering(&mut self) { self.core.detach_rendering(); } fn theme(&self) -> Option<&Theme<'a>> { self.core.theme() } fn spinner_style(&self) -> Option