#![cfg_attr(docsrs, feature(doc_cfg))]
mod ext;
pub use ext::ProgressExt;
#[cfg(feature = "std")]
mod updater;
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub use updater::{ProgressUpdater, progress};
use core::future::Future;
use futures_core::Stream;
pub trait Progress: Future {
fn progress(&self) -> impl Stream<Item = ProgressUpdate> + Unpin + Send + 'static;
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ProgressUpdate {
current: u64,
total: u64,
state: State,
message: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum State {
Working,
Completed,
Paused,
Cancelled,
}
impl State {
#[must_use]
pub const fn is_cancelled(&self) -> bool {
matches!(self, Self::Cancelled)
}
#[must_use]
pub const fn is_working(&self) -> bool {
matches!(self, Self::Working)
}
#[must_use]
pub const fn is_completed(&self) -> bool {
matches!(self, Self::Completed)
}
#[must_use]
pub const fn is_paused(&self) -> bool {
matches!(self, Self::Paused)
}
}
impl ProgressUpdate {
#[must_use]
pub const fn new(total: u64, current: u64, state: State, message: Option<String>) -> Self {
Self {
current,
total,
state,
message,
}
}
#[must_use]
pub const fn total(&self) -> u64 {
self.total
}
#[must_use]
pub const fn current(&self) -> u64 {
self.current
}
#[must_use]
pub fn completed_fraction(&self) -> f64 {
if self.total == 0 {
0.0
} else {
#[allow(clippy::cast_precision_loss)]
{
self.current as f64 / self.total as f64
}
}
}
#[must_use]
pub const fn remaining(&self) -> u64 {
self.total.saturating_sub(self.current)
}
#[must_use]
pub const fn is_cancelled(&self) -> bool {
matches!(self.state, State::Cancelled)
}
#[must_use]
pub const fn is_working(&self) -> bool {
matches!(self.state, State::Working)
}
#[must_use]
pub const fn is_completed(&self) -> bool {
matches!(self.state, State::Completed)
}
#[must_use]
pub const fn is_paused(&self) -> bool {
matches!(self.state, State::Paused)
}
#[must_use]
pub fn message(&self) -> Option<&str> {
self.message.as_deref()
}
#[must_use]
pub const fn state(&self) -> State {
self.state
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_progress_update_new() {
let update = ProgressUpdate::new(100, 0, State::Working, None);
assert_eq!(update.current(), 0);
assert_eq!(update.total(), 100);
assert!(!update.is_cancelled());
assert_eq!(update.message(), None);
}
#[test]
fn test_completed_fraction() {
let mut update = ProgressUpdate::new(100, 0, State::Working, None);
assert!((update.completed_fraction() - 0.0).abs() < f64::EPSILON);
update.current = 50;
assert!((update.completed_fraction() - 0.5).abs() < f64::EPSILON);
update.current = 100;
assert!((update.completed_fraction() - 1.0).abs() < f64::EPSILON);
}
#[test]
fn test_completed_fraction_zero_total() {
let update = ProgressUpdate::new(0, 0, State::Working, None);
assert!((update.completed_fraction() - 0.0).abs() < f64::EPSILON);
}
#[test]
fn test_builder_methods() {
let update =
ProgressUpdate::new(100, 50, State::Working, Some("Half complete".to_string()));
assert_eq!(update.current(), 50);
assert_eq!(update.message(), Some("Half complete"));
assert_eq!(update.state, State::Working);
assert!(update.is_working());
assert!(!update.is_cancelled());
assert!(!update.is_completed());
assert!(!update.is_paused());
}
#[test]
fn test_is_complete() {
let mut update = ProgressUpdate::new(100, 0, State::Working, None);
assert!(!update.is_completed());
assert!(update.is_working());
update.state = State::Completed;
assert!(update.is_completed());
update.state = State::Cancelled;
assert!(update.is_cancelled());
assert!(!update.is_completed());
update.state = State::Paused;
assert!(update.is_paused());
assert!(!update.is_completed());
}
#[test]
fn test_remaining() {
let mut update = ProgressUpdate::new(100, 0, State::Working, None);
assert_eq!(update.remaining(), 100);
update.current = 30;
assert_eq!(update.remaining(), 70);
update.current = 100;
assert_eq!(update.remaining(), 0);
update.current = 150; assert_eq!(update.remaining(), 0);
}
}