pub mod group;
pub use group::{Group, Task};
use std::fmt::Display;
use std::future::Future;
use std::io::Write;
use std::pin::Pin;
use std::task::{Context, Poll};
use futures_lite::stream::Pending;
use futures_lite::{Stream, stream};
use crate::Theme;
use crate::bar::Bar;
use crate::spinner::Ticks;
use crate::term::clear_line;
pub struct ProgressBuilder<'a, F, M, P> {
inner: F,
bar: Bar<'a>,
bar_width: usize,
ticks: Ticks<'a>,
messages: M,
fraction: P,
spinner_char: Option<char>,
message: Option<String>,
current_fraction: f64,
dirty: bool,
}
impl<'a, F, M, P> ProgressBuilder<'a, F, M, P> {
pub fn with_message(mut self, message: impl Display) -> Self {
self.message = Some(message.to_string());
self.dirty = true;
self
}
pub fn with_messages<S>(self, messages: S) -> ProgressBuilder<'a, F, S, P>
where
S: Stream + Unpin,
S::Item: Display,
{
ProgressBuilder {
inner: self.inner,
bar: self.bar,
bar_width: self.bar_width,
ticks: self.ticks,
messages,
fraction: self.fraction,
spinner_char: self.spinner_char,
message: self.message,
current_fraction: self.current_fraction,
dirty: self.dirty,
}
}
pub fn with_fraction<S>(self, fraction: S) -> ProgressBuilder<'a, F, M, S>
where
S: Stream<Item = f64> + Unpin,
{
ProgressBuilder {
inner: self.inner,
bar: self.bar,
bar_width: self.bar_width,
ticks: self.ticks,
messages: self.messages,
fraction,
spinner_char: self.spinner_char,
message: self.message,
current_fraction: self.current_fraction,
dirty: self.dirty,
}
}
}
impl<F, M, P> Future for ProgressBuilder<'_, F, M, P>
where
F: Future + Unpin,
M: Stream + Unpin,
M::Item: Display,
P: Stream<Item = f64> + Unpin,
{
type Output = F::Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
if let Poll::Ready(spinner) = Pin::new(&mut this.ticks).poll_next(cx) {
this.spinner_char = spinner;
this.dirty = true;
}
while let Poll::Ready(Some(msg)) = Pin::new(&mut this.messages).poll_next(cx) {
this.message = Some(msg.to_string());
this.dirty = true;
}
while let Poll::Ready(Some(f)) = Pin::new(&mut this.fraction).poll_next(cx) {
this.current_fraction = f.clamp(0.0, 1.0);
this.dirty = true;
}
let item = Pin::new(&mut this.inner).poll(cx);
match item {
Poll::Pending if this.dirty => {
this.dirty = false;
let _ = clear_line(&mut std::io::stdout());
if let Some(spinner) = &this.spinner_char {
print!("{spinner} ");
}
let bar = this.bar.render(this.bar_width, this.current_fraction);
if !bar.is_empty() {
print!("{bar} ");
}
if let Some(message) = &this.message {
print!("{message}");
}
std::io::stdout().flush().expect("flushing");
}
Poll::Ready(_) => {
let _ = clear_line(&mut std::io::stdout());
std::io::stdout().flush().expect("flushing");
}
_ => {}
}
item
}
}
pub trait FutureExt: Future {
fn progress<'a>(
self,
theme: impl Into<Theme<'a>>,
) -> ProgressBuilder<'a, Self, Pending<&'static str>, Pending<f64>>
where
Self: Sized,
{
let theme = theme.into();
let bar_width = theme.effective_bar_width();
ProgressBuilder {
inner: self,
bar: theme.bar,
bar_width,
ticks: theme.spinner.ticks(),
messages: stream::pending(),
fraction: stream::pending(),
spinner_char: None,
message: None,
current_fraction: 0.0,
dirty: true,
}
}
fn with_label<'a>(self, label: impl Into<String>) -> Task<'a, Self>
where
Self: Sized,
{
Task::from(self).with_label(label)
}
fn with_messages<'a, S, D>(self, messages: S) -> Task<'a, Self>
where
Self: Sized,
S: Stream<Item = D> + Unpin + 'a,
D: Display + 'a,
{
Task::from(self).with_messages(messages)
}
fn with_progress<'a, S>(self, progress: S) -> Task<'a, Self>
where
Self: Sized,
S: Stream<Item = f64> + Unpin + 'a,
{
Task::from(self).with_progress(progress)
}
}
impl<F> FutureExt for F where F: Future {}