use std::{fmt, sync::Arc};
use compact_str::CompactString;
use parking_lot::RwLock;
use crate::{Progress, ProgressSnapshot};
#[derive(Clone, Default)]
pub struct ProgressStack {
inner: Arc<RwLock<Vec<Progress>>>,
}
impl fmt::Debug for ProgressStack {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ProgressStack")
.field("count", &self.len())
.finish()
}
}
impl ProgressStack {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn push(&self, progress: Progress) {
self.inner.write().push(progress);
}
#[must_use]
pub fn add_pb(&self, name: impl Into<CompactString>, total: impl Into<u64>) -> Progress {
let progress = Progress::new_pb(name, total);
self.push(progress.clone());
progress
}
#[must_use]
pub fn add_spinner(&self, name: impl Into<CompactString>) -> Progress {
let progress = Progress::new_spinner(name);
self.push(progress.clone());
progress
}
#[must_use]
pub fn snapshot(&self) -> ProgressStackSnapshot {
let items: Vec<Progress> = self.inner.read().clone();
let snapshots = items.iter().map(Progress::snapshot).collect();
ProgressStackSnapshot(snapshots)
}
#[must_use]
pub fn items(&self) -> Vec<Progress> {
self.inner.read().clone()
}
#[must_use]
pub fn is_all_finished(&self) -> bool {
self.inner.read().iter().all(Progress::is_finished)
}
pub fn clear(&self) {
self.inner.write().clear();
}
#[must_use]
pub fn len(&self) -> usize {
self.inner.read().len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.inner.read().is_empty()
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[cfg_attr(
feature = "rkyv",
derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "rkyv", rkyv(derive(Debug, Eq, PartialEq)))]
pub struct ProgressStackSnapshot(pub Vec<ProgressSnapshot>);
#[cfg(test)]
mod tests {
use super::ProgressStack;
#[test]
fn test_stack_operations() {
let stack = ProgressStack::new();
assert!(stack.is_empty());
let _pb = stack.add_pb("bar", 100u64);
let _sp = stack.add_spinner("spin");
assert_eq!(stack.len(), 2);
assert!(!stack.is_all_finished());
let items = stack.items();
items[0].finish();
items[1].finish();
assert!(stack.is_all_finished());
}
#[test]
fn test_snapshot_isolation() {
let stack = ProgressStack::new();
let pb = stack.add_pb("test", 100u64);
pb.inc(10u64);
let snap_1 = stack.snapshot();
pb.inc(20u64);
let snap_2 = stack.snapshot();
assert_eq!(
snap_1.0[0].position(),
10,
"Old snapshot should remain immutable"
);
assert_eq!(snap_2.0[0].position(), 30, "New snapshot reflects updates");
}
}