Async-first terminal UI spinners and progress bars
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229//! strides is an *async-first* crate to support building command line tools which display progress//! to the user. The purpose is similar to that of the widely used indicatif crate but focuses on//! integrating with async futures and streams and drive progress animations based on polling state.//! For this strides provides utilities to integrate UI elements as part of the [`Future`] and//! [`Stream`] abstractions.//!//! ## Modes of operation//!//! Pick a mode by how many tasks you have and how many terminal rows you want to spend on them://!//! | Tasks | Rows | Futures | Streams |//! |-------|-----------|----------------------------------------------------------|---------|//! | 1 | 1 | `fut.progress(theme).await` | `s.progress(theme, ...)` / `s.progress_count(theme)` / `s.progress_bytes(theme, ...)` |//! | N | N | [`future::Group::new`] + [`push`](future::Group::push) per task | [`stream::Group::new`] + [`push`](stream::Group::push) per stream |//! | N | 1 | [`join`](future::join())`(futs).with_theme(theme).await` | n/a |//! | N | 1-of-many | [`group.push(join(futs).with_label(...))`](future::join()) | n/a |//!//! The last row collapses many futures into a single progress line that sits alongside other rows//! in a [`future::Group`]. Streams have no `join` collapse — push each stream as its own row.//!//! ## Spinners//!//! A spinner is a UI element that represents ongoing work. It is usually iconified as a circular//! motion but anything that streams Unicode characters can be used. To create a spinner, import the//! [`Spinner`] struct and pass it a string slice://!//! ```rust//! let abc = strides::spinner::Spinner::new("abc");//! ```//!//! The [`ticks()`](crate::spinner::Spinner::ticks) method returns an infinite stream that cycles//! through the characters of the string slice. The rate at which characters are cycled is set to//! every 80ms and can be changed with the//! [`with_interval()`](crate::spinner::Spinner::with_interval) function.//!//! The [`spinner::styles`] module provides a few pre-defined spinner//! styles.//!//! ## Bars//!//! A [`Bar`] renders fractional progress as a strip of characters. It is defined by two characters,//! one for the empty portion and one for the filled portion. In addition, optional borders,//! in-between separator, and per-portion colors can be configured via the builder methods on//! [`Bar`].//!//! Create a new bar with [`Bar::new()`](crate::bar::Bar::new) or pick a pre-defined variant from//! [`bar::styles`]://!//! ```rust//! // This customizes the pre-defined thin line style with additional borders and colors for the//! // filled portion.//! let bar = strides::bar::styles::THIN_LINE//! .with_border("[", "]")//! .with_filled_style(owo_colors::Style::new().bright_purple());//! ```//!//! The bar is attached to a [`Theme`] with [`with_bar()`](crate::Theme::with_bar). The bar width//! defaults to the terminal size and can be overridden with//! [`with_bar_width()`](crate::Theme::with_bar_width).//!//! ## Themes//!//! A [`Theme`] bundles a [`Spinner`](crate::spinner::Spinner) and a [`Bar`](crate::bar::Bar) into a//! single configuration object that can be passed to both the futures and streams progress APIs.//! Three constructors form a ladder: [`Theme::default`] for "sensible defaults, no bar",//! [`Theme::with`] to name a spinner and bar in one call, and [`Theme::new`] when building//! piece-by-piece (e.g. with a custom bar width or layout)://!//! ```rust//! let theme = strides::Theme::with(//! strides::spinner::styles::DOTS_3,//! strides::bar::styles::PARALLELOGRAM,//! );//! ```//!//! A bare [`Spinner`](crate::spinner::Spinner) can also be passed directly wherever a//! [`Theme`] is expected.//!//! ## Layout//!//! The order, spacing and per-element formatting of a progress line is controlled by a [`Layout`],//! an ordered list of [`Segment`]s. [`Layout::DEFAULT`] renders elapsed time, spinner, label, bar//! and message. Segments with nothing to show are skipped, so spacing stays correct. Attach a//! custom layout with [`Theme::with_layout`]://!//! ```rust//! use strides::layout::{Layout, Segment};//!//! let theme = strides::Theme::new().with_layout(Layout::from_segments([//! Segment::spinner(),//! Segment::elapsed().with_border("[", "]"),//! Segment::bar(),//! Segment::message(),//! ]));//! ```//!//! ## Futures//!//! Import the [`FutureExt`](crate::future::FutureExt) extension trait and call//! [`progress(theme)`](crate::future::FutureExt::progress) on any [`Future`] for standalone use,//! or [`progressive()`](crate::future::FutureExt::progressive) to lift it for inclusion in a//! [`future::Group`]. Both return a [`ProgressFuture`](crate::future::ProgressFuture) configured fluently//! with [`with_label`](crate::future::ProgressFuture::with_label),//! [`with_messages`](crate::future::ProgressFuture::with_messages) (a `Stream` whose values//! replace the message), and [`with_progress`](crate::future::ProgressFuture::with_progress) (a//! `Stream<Item = f64>` driving the bar).//!//! ```rust//! use strides::future::FutureExt;//! use strides::spinner::styles::DOTS_3;//!//! # futures_lite::future::block_on(//! # async {//! // Simulate work by waiting for three seconds.//! futures_timer::Delay::new(std::time::Duration::from_secs(3))//! .progress(DOTS_3)//! .with_label("this will take some time")//! .await;//! # }//! # );//! ```//!//! For multiple concurrent futures, push the future directly into a [`future::Group`]. The//! [`with_label`](crate::future::FutureExt::with_label),//! [`with_messages`](crate::future::FutureExt::with_messages),//! [`with_progress`](crate::future::FutureExt::with_progress) and//! [`with_elapsed_time`](crate::future::FutureExt::with_elapsed_time) setters on//! [`FutureExt`](crate::future::FutureExt) lift the future into a//! [`ProgressFuture`](crate::future::ProgressFuture) implicitly. Use `.progressive()` explicitly//! when pushing a bare future with no configuration. Per-row overrides//! ([`with_theme`](crate::future::ProgressFuture::with_theme),//! [`with_spinner_style`](crate::future::ProgressFuture::with_spinner_style),//! [`with_annotation_style`](crate::future::ProgressFuture::with_annotation_style)) take//! precedence over the Group's defaults for that row. See [`future::Group`]'s docs for an//! example.//!//! ## Streams//!//! Import the [`StreamExt`](crate::stream::StreamExt) extension trait. Standalone://! [`progress_count(theme)`](crate::stream::StreamExt::progress_count) for "count items, derive//! the bar from the total" — bounded streams like `iter(Vec)` and `iter(0..n)` pick up the total//! from [`Stream::size_hint`] automatically; pair with//! [`with_len(n)`](crate::stream::ProgressCountStream::with_len) when the hint is absent or//! inaccurate. [`progress(theme, fraction_fn)`](crate::stream::StreamExt::progress) when each item//! carries enough information to compute completion, or//! [`progress_bytes(theme, bytes_fn)`](crate::stream::StreamExt::progress_bytes) for byte-oriented//! streams (the wrapper accumulates a counter, derives a smoothed rate, and — when//! [`with_len`](crate::stream::ProgressBytesStream::with_len) is set — derives the progress//! fraction).//!//! ```rust//! use futures_lite::{StreamExt as _, stream};//! use strides::stream::StreamExt;//! use strides::Theme;//! use strides::{bar, spinner};//!//! let theme = Theme::new()//! .with_spinner(spinner::styles::DOTS_3)//! .with_bar(bar::styles::SHADED);//!//! # futures_lite::future::block_on(async {//! stream::iter(0..100)//! .progress_count(theme)//! .for_each(|_| {})//! .await;//! # });//! ```//!//! For multiple concurrent streams use//! [`progressive`](crate::stream::StreamExt::progressive) or//! [`progressive_bytes`](crate::stream::StreamExt::progressive_bytes) and push the result into a//! [`stream::Group`]. Pair with [`Segment::bytes`],//! [`Segment::rate`](crate::layout::Segment::rate) and [`Segment::eta`](crate::layout::Segment::eta)//! in a custom [`Layout`] to render the byte / throughput / ETA columns that downloads typically//! want.//!//! ## Reading from `AsyncRead`//!//! strides does not wrap [`AsyncRead`](futures_lite::AsyncRead) directly. Convert your reader to a//! byte stream first — for tokio,//! [`tokio_util::io::ReaderStream`](https://docs.rs/tokio-util/latest/tokio_util/io/struct.ReaderStream.html)//! is the canonical adapter — and feed it into//! [`progress_bytes`](crate::stream::StreamExt::progress_bytes)://!//! ```rust,ignore//! use tokio_util::io::ReaderStream;//! use futures_lite::StreamExt as _;//! use strides::stream::StreamExt as _;//!//! let mut stream = ReaderStream::new(reader)//! .progress_bytes(theme, |c| c.as_ref().map_or(0, |c| c.len() as u64))//! .with_label("download")//! .with_len(total);//!//! while let Some(chunk) = stream.next().await {//! writer.write_all(&chunk?).await?;//! }//! ```//!//! ## Output//!//! strides currently renders all progress output to `stdout`. When `stdout` is not a terminal (for//! example, when the program's output is redirected to a file or piped to another command) progress//! rendering is suppressed entirely. Futures and streams still run to completion, but no spinner,//! bar, or message bytes are written.//!//! [`Future`]: std::future::Future//! [`Stream`]: futures_lite::Stream//! [`future::Group`]: crate::future::Group//! [`stream::Group`]: crate::stream::Group//! [`Spinner`]: crate::spinner::Spinner//! [`Bar`]: crate::bar::Bar
pub mod bar;pub mod future;pub mod layout;pub mod progressive;pub mod spinner;pub mod stream;pub mod term;pub mod theme;
pub(crate) mod line;pub(crate) mod state;
pub use layout::{Layout, RenderContext, Segment};pub use progressive::{Progressive, ProgressiveFuture, ProgressiveStream};pub use theme::Theme;