1use std::{
2 io::{self, Write},
3 sync::{atomic::{AtomicBool, Ordering}, Mutex}, thread, time::Duration,
4};
5
6#[derive(Clone, Copy, Debug)]
7struct Task {
8 pub row_offset: i32
9}
10
11static TASKS: Mutex<Vec<Task>> = Mutex::new(Vec::new());
12static SPINNING: AtomicBool = AtomicBool::new(false);
13
14#[macro_export]
16macro_rules! task {
17 ($($tokens:tt)*) => {
18 $crate::__start_task__(format!($($tokens)*));
19 }
20}
21
22#[macro_export]
25macro_rules! pass {
26 ($($tokens:tt)*) => {
27 $crate::__end_task__("\x1b[32;1m✔\x1b[0m", format!($($tokens)*));
28 }
29}
30
31#[macro_export]
34macro_rules! warn {
35 ($($tokens:tt)*) => {
36 $crate::__end_task__("\x1b[33;1m▲\x1b[0m", format!($($tokens)*));
37 }
38}
39
40#[macro_export]
43macro_rules! fail {
44 ($($tokens:tt)*) => {
45 $crate::__end_task__("\x1b[31;1m✘\x1b[0m", format!($($tokens)*));
46 }
47}
48
49#[doc(hidden)]
50pub fn __start_task__(message: String) {
51 let mut tasks = TASKS.lock().unwrap();
60
61 if tasks.len() > 0 {
62 for task in tasks.iter_mut() {
64 task.row_offset += 1;
65 }
66
67 println!();
68 }
69
70 if let Some(last_row) = tasks.last().map(|task| task.row_offset) {
71 print!("\x1b[s");
72
73 if last_row > 1 {
74 print!("\x1b[{}A\x1b[{}G┣", last_row - 1, (tasks.len() - 1) * 5 + 3);
75 }
76
77 for _ in 1..last_row {
78 print!("\x1b[1D\x1b[1B┃");
79 }
80
81 print!("\x1b[u");
82 }
83
84 tasks.push(Task { row_offset: 0 });
85
86 if tasks.len() > 1 {
87 print!("{}", " ".repeat((tasks.len() - 2) * 5 + 2) + "┗━ ");
88 }
89
90 print!("\x1b[33;1m-\x1b[0m {message}");
92 _ = io::stdout().flush();
93
94 if SPINNING.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) == Ok(false) {
97 thread::spawn(spin);
98 }
99}
100
101#[doc(hidden)]
102pub fn __end_task__(symbol: &str, message: String) {
103 let mut tasks = TASKS.lock().unwrap();
104
105 if let Some(Task { row_offset: row }) = tasks.pop() {
106 let column = tasks.len() * 5 + 1;
107 print!("\x1b[s");
116
117 if row > 0 {
118 print!("\x1b[{row}A");
119 }
120
121 print!("\x1b[{column}G{symbol} \x1b[K{message}");
122
123 if row != 0 {
125 print!("\x1b[u");
126 }
127
128 if tasks.len() == 0 {
129 println!();
130 }
131
132 _ = io::stdout().flush();
133 } else {
134 println!("{symbol} {message}");
136 }
137}
138
139fn spin() {
140 let mut spinner = '-';
141
142 loop {
143 let tasks = TASKS.lock().unwrap();
144
145 if tasks.len() == 0 {
147 break;
148 }
149
150 let mut column = 1;
151
152 for Task { row_offset: row } in tasks.iter() {
153 print!("\x1b[s");
163
164 if *row > 0 {
165 print!("\x1b[{row}A");
166 }
167
168 print!("\x1b[{column}G\x1b[33;1m{spinner}\x1b[0m\x1b[u");
169
170 column += 5;
171 }
172
173 _ = io::stdout().flush();
177
178 spinner = match spinner {
180 '-' => '\\',
181 '\\' => '|',
182 '|' => '/',
183 '/' => '-',
184 _ => '-', };
186
187 drop(tasks);
189
190 thread::sleep(Duration::from_millis(100));
192 }
193
194 SPINNING.store(false, Ordering::Relaxed);
197}