use std::process::Stdio;
use std::sync::Arc;
use spin::mutex::SpinMutex;
use tokio::process::{Child as TokioChild, ChildStderr, ChildStdout, Command as TokioCommand};
use crate::cli::{ProgressBar, ProgressBarBuilder};
use crate::lv::Lv;
use crate::{Atomic, BoxedFuture};
use super::{ChildOutConfig, ChildOutTask, Driver, DriverOutput};
#[inline(always)]
pub fn spinner(name: impl Into<String>) -> Spinner {
Spinner {
config: Arc::new(SpinnerInner {
lv: Atomic::new_u8(Lv::Off as u8),
bar: SpinMutex::new(Err(crate::progress(name))),
}),
}
}
#[derive(Clone)]
#[doc(hidden)]
pub struct Spinner {
config: Arc<SpinnerInner>,
}
#[rustfmt::skip]
impl Spinner {
pub fn error(self) -> Self { self.config.lv.set(crate::lv::E); self }
pub fn hint(self) -> Self { self.config.lv.set(crate::lv::H); self }
pub fn print(self) -> Self { self.config.lv.set(crate::lv::P); self }
pub fn warn(self) -> Self { self.config.lv.set(crate::lv::W); self }
pub fn info(self) -> Self { self.config.lv.set(crate::lv::I); self }
pub fn debug(self) -> Self { self.config.lv.set(crate::lv::D); self }
pub fn trace(self) -> Self { self.config.lv.set(crate::lv::T); self }
#[inline(always)]
pub fn configure_spinner<F: FnOnce(ProgressBarBuilder) -> ProgressBarBuilder>(
self,
f: F,
) -> Self {
let mut bar = self.config.bar.lock();
if let Err(builder) = bar.clone() {
*bar = Err(f(builder));
}
drop(bar);
self
}
}
struct SpinnerInner {
lv: Atomic<u8, Lv>,
bar: SpinMutex<Result<Arc<ProgressBar>, ProgressBarBuilder>>,
}
pub struct SpinnerTask {
lv: Lv,
prefix: String,
bar: Arc<ProgressBar>,
out: Option<ChildStdout>,
err: Option<ChildStderr>,
}
impl ChildOutConfig for Spinner {
type Task = SpinnerTask;
type __Null = super::__OCNonNull;
fn configure_stdout(&mut self, command: &mut TokioCommand) {
command.stdout(Stdio::piped());
}
fn configure_stderr(&mut self, command: &mut TokioCommand) {
command.stderr(Stdio::piped());
}
fn take(
self,
child: &mut TokioChild,
name: Option<&str>,
is_out: bool,
) -> crate::Result<Self::Task> {
let lv = self.config.lv.get();
let log_prefix = if lv.enabled() {
let name = name.unwrap_or_default();
if name.is_empty() {
String::new()
} else {
format!("[{name}] ")
}
} else {
String::new()
};
let bar = {
let mut bar_arc = self.config.bar.lock();
match bar_arc.as_mut() {
Ok(bar) => Arc::clone(bar),
Err(e) => {
let bar = e.clone().spawn();
*bar_arc = Ok(Arc::clone(&bar));
bar
}
}
};
Ok(SpinnerTask {
lv,
prefix: log_prefix,
bar,
out: if is_out { child.stdout.take() } else { None },
err: if !is_out { child.stderr.take() } else { None },
})
}
}
impl ChildOutTask for SpinnerTask {
type Output = Arc<ProgressBar>;
fn run(self) -> (Option<BoxedFuture<()>>, Self::Output) {
let bar = Arc::clone(&self.bar);
(Some(Box::pin(self.main())), bar)
}
}
impl SpinnerTask {
async fn main(self) {
let bar = self.bar;
let lv = self.lv;
let prefix = self.prefix;
let mut driver = Driver::new(self.out, self.err, lv == Lv::Off);
loop {
match driver.next().await {
DriverOutput::Line(line) => {
if lv != Lv::Off {
crate::cli::__print_with_level(lv, format_args!("{prefix}{line}"));
if lv.enabled() {
crate::progress!(bar, "")
} else {
crate::progress!(bar, "{line}")
}
} else {
crate::progress!(bar, "{line}")
}
}
DriverOutput::Progress(line) => {
crate::progress!(bar, "{line}")
}
DriverOutput::Done => break,
_ => {}
}
}
}
}