use std::io::{stderr, Write};
use std::sync::mpsc::{self, Receiver, Sender};
use std::thread;
use std::time::Duration;
#[derive(Debug)]
pub struct Loading {
sender: Sender<Signal>,
}
impl Default for Loading {
fn default() -> Self {
Self::new(Spinner::default())
}
}
impl Loading {
pub fn new(spinner: Spinner) -> Self {
let (sender, receiver) = mpsc::channel();
Self::update_stdout(receiver);
Self::update_animation(sender.clone(), spinner);
Self { sender }
}
pub fn end(&self) {
let (sender, receiver) = mpsc::channel();
let _ = self.sender.send(Signal::Exit(sender));
let _ = receiver.recv();
}
pub fn text<T: ToString>(&self, text: T) {
let _ = self.sender.send(Signal::Text(text.to_string()));
}
pub fn success<T: ToString>(&self, text: T) {
let _ = self
.sender
.send(Signal::Next(Status::Success, text.to_string()));
}
pub fn fail<T: ToString>(&self, text: T) {
let _ = self
.sender
.send(Signal::Next(Status::Fail, text.to_string()));
}
pub fn warn<T: ToString>(&self, text: T) {
let _ = self
.sender
.send(Signal::Next(Status::Warn, text.to_string()));
}
pub fn info<T: ToString>(&self, text: T) {
let _ = self
.sender
.send(Signal::Next(Status::Info, text.to_string()));
}
pub fn debug<T: ToString>(&self, text: T) {
let text = format!("\x1B[90m{}\x1B[0m", text.to_string());
let _ = self.sender.send(Signal::Next(Status::Debug, text));
}
fn update_animation(sender: Sender<Signal>, mut spinner: Spinner) {
thread::spawn(move || {
while sender.send(Signal::Frame(spinner.next())).is_ok() {
thread::sleep(spinner.interval);
}
});
}
fn update_stdout(receiver: Receiver<Signal>) {
thread::spawn(move || {
let mut output = stderr();
let mut frame = "";
let mut text = String::new();
macro_rules! write_content {
() => {
let _ = output.write(b"\x1B[2K\x1B[0G");
let _ = output.flush();
};
($($arg:tt)*) => {
let _ = output.write(b"\x1B[2K\x1B[0G");
let _ = output.write(format!($($arg)*).as_bytes());
let _ = output.flush();
};
}
let mut show_loader = true;
while let Ok(signal) = receiver.recv() {
match signal {
Signal::Frame(s) => {
frame = s;
if show_loader {
write_content!("[{}] {}", frame, text);
}
}
Signal::Text(s) => {
if show_loader {
write_content!("[{}] {}", frame, s);
}
text = s;
}
Signal::Next(status, s) => {
write_content!("[{}] {}\n", status.as_str(), s);
}
Signal::Exit(sender) => {
write_content!();
show_loader = false;
let _ = sender.send(());
}
}
}
});
}
}
#[derive(Debug)]
enum Signal {
Frame(&'static str),
Text(String),
Next(Status, String),
Exit(Sender<()>),
}
#[derive(Debug, Clone)]
pub struct Spinner {
index: usize,
frames: Vec<&'static str>,
interval: Duration,
}
impl Default for Spinner {
fn default() -> Self {
Self::new(vec!["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"])
}
}
impl Spinner {
pub fn new(frames: Vec<&'static str>) -> Self {
Self {
index: 0,
frames,
interval: Duration::from_millis(80),
}
}
pub fn interval(&mut self, interval: Duration) {
self.interval = interval
}
fn next(&mut self) -> &'static str {
match self.frames.get(self.index) {
Some(s) => {
self.index += 1;
s
}
None => {
self.index = 1;
self.frames[0]
}
}
}
}
#[derive(Debug)]
enum Status {
Success,
Fail,
Warn,
Info,
Debug,
}
impl Status {
fn as_str(&self) -> &'static str {
match self {
Status::Success => "\x1B[92m+\x1B[0m",
Status::Fail => "\x1B[91mFAIL\x1B[0m",
Status::Warn => "\x1B[93m!\x1B[0m",
Status::Info => "\x1B[94m*\x1B[0m",
Status::Debug => "\x1B[90m \x1B[0m",
}
}
}