use std::io::{stderr, stdout, Result, Stderr, Stdout, 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::with_stdout(Spinner::default())
}
}
impl Loading {
pub fn with_stdout(spinner: Spinner) -> Self {
Self::create(spinner, Output::Stdout(stdout()))
}
pub fn with_stderr(spinner: Spinner) -> Self {
Self::create(spinner, Output::Stderr(stderr()))
}
fn create(spinner: Spinner, output: Output) -> Self {
let (sender, receiver) = mpsc::channel();
Self::update_output(receiver, output);
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()));
}
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_output(receiver: Receiver<Signal>, mut output: Output) {
thread::spawn(move || {
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();
};
}
while let Ok(signal) = receiver.recv() {
match signal {
Signal::Frame(s) => {
frame = s;
write_content!("{} {}", frame, text);
}
Signal::Text(s) => {
write_content!("{} {}", frame, s);
text = s;
}
Signal::Next(status, s) => {
write_content!("{} {}\n", status.as_str(), s);
}
Signal::Exit(sender) => {
write_content!();
let _ = sender.send(());
break;
}
}
}
});
}
}
#[derive(Debug)]
enum Output {
Stdout(Stdout),
Stderr(Stderr),
}
impl Write for Output {
#[inline]
fn write(&mut self, buf: &[u8]) -> Result<usize> {
match self {
Self::Stdout(out) => out.write(buf),
Self::Stderr(out) => out.write(buf),
}
}
#[inline]
fn flush(&mut self) -> Result<()> {
match self {
Self::Stdout(out) => out.flush(),
Self::Stderr(out) => out.flush(),
}
}
}
#[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,
}
impl Status {
fn as_str(&self) -> &'static str {
match self {
Status::Success => "\x1B[32m✔\x1B[0m",
Status::Fail => "\x1B[31m✖\x1B[0m",
Status::Warn => "\x1B[33m⚠\x1B[0m",
Status::Info => "\x1B[34mℹ\x1B[0m",
}
}
}