spinners_jdxcode/
lib.rs

1use std::thread::JoinHandle;
2use std::time::Instant;
3use std::{
4    sync::mpsc::{channel, Sender, TryRecvError},
5    thread,
6    time::Duration,
7};
8
9pub use crate::utils::spinner_names::SpinnerNames as Spinners;
10use crate::utils::spinners_data::SPINNERS as SpinnersMap;
11pub use crate::utils::stream::Stream;
12
13mod utils;
14
15pub struct Spinner {
16    sender: Sender<(Instant, Option<String>)>,
17    join: Option<JoinHandle<()>>,
18    stream: Stream
19}
20
21impl Drop for Spinner {
22    fn drop(&mut self) {
23        if self.join.is_some() {
24            self.sender.send((Instant::now(), None)).unwrap();
25            self.join.take().unwrap().join().unwrap();
26        }
27    }
28}
29
30impl Spinner {
31    /// Create a new spinner along with a message
32    ///
33    /// # Examples
34    ///
35    /// Basic Usage:
36    ///
37    /// ```
38    /// use spinners::{Spinner, Spinners};
39    ///
40    /// let sp = Spinner::new(Spinners::Dots, "Loading things into memory...".into());
41    /// ```
42    ///
43    /// No Message:
44    ///
45    /// ```
46    /// use spinners::{Spinner, Spinners};
47    ///
48    /// let sp = Spinner::new(Spinners::Dots, String::new());
49    /// ```
50    pub fn new(spinner: Spinners, message: String) -> Self {
51        Self::new_inner(spinner, message, None, None)
52    }
53
54    /// Create a new spinner that logs the time since it was created
55    pub fn with_timer(spinner: Spinners, message: String) -> Self {
56        Self::new_inner(spinner, message, Some(Instant::now()), None)
57    }
58
59    /// Creates a new spinner along with a message with a specified output stream
60    ///
61    /// # Examples
62    ///
63    /// Basic Usage:
64    ///
65    /// ```
66    /// use spinners::{Spinner, Spinners, Stream};
67    /// 
68    /// let sp = Spinner::with_stream(Spinners::Dots, String::new(), Stream::Stderr);
69    /// ```
70    pub fn with_stream(spinner: Spinners, message: String, stream: Stream) -> Self {
71        Self::new_inner(spinner, message, None, Some(stream))
72    }
73
74    /// Creates a new spinner that logs the time since it was created with a specified output stream
75    ///
76    /// # Examples
77    ///
78    /// Basic Usage:
79    ///
80    /// ```
81    /// use spinners::{Spinner, Spinners, Stream};
82    /// 
83    /// let sp = Spinner::with_timer_and_stream(Spinners::Dots, String::new(), Stream::Stderr);
84    /// ```
85    pub fn with_timer_and_stream(spinner: Spinners, message: String, stream: Stream) -> Self {
86        Self::new_inner(spinner, message, Some(Instant::now()), Some(stream))
87    }
88
89    fn new_inner(spinner: Spinners, message: String, start_time: Option<Instant>, stream: Option<Stream>) -> Self 
90    {
91        let spinner_name = spinner.to_string();
92        let spinner_data = SpinnersMap
93            .get(&spinner_name)
94            .unwrap_or_else(|| panic!("No Spinner found with the given name: {}", spinner_name));
95
96        let stream = if let Some(stream) = stream { stream } else { Stream::default() };
97
98        let (sender, recv) = channel::<(Instant, Option<String>)>();
99
100        let join = thread::spawn(move || 'outer: loop {
101
102            for frame in spinner_data.frames.iter() {
103                let (do_stop, stop_time, stop_symbol) = match recv.try_recv() {
104                    Ok((stop_time, stop_symbol)) => (true, Some(stop_time), stop_symbol),
105                    Err(TryRecvError::Disconnected) => (true, None, None),
106                    Err(TryRecvError::Empty) => (false, None, None),
107                };
108
109                let frame = stop_symbol.unwrap_or_else(|| frame.to_string());
110
111                stream.write(&frame, &message, start_time, stop_time).expect("IO Error");
112
113                if do_stop {
114                    break 'outer;
115                }
116
117                thread::sleep(Duration::from_millis(spinner_data.interval as u64));
118            }
119        });
120
121        Self {
122            sender,
123            join: Some(join),
124            stream
125        }
126    }
127
128    /// Stops the spinner
129    ///
130    /// Stops the spinner that was created with the [`Spinner::new`] function.
131    ///
132    /// Optionally call [`stop_with_newline`] to print a newline after the spinner is stopped,
133    /// or the [`stop_with_message`] function to print a message after the spinner is stopped.
134    ///
135    /// [`Spinner::new`]: struct.Spinner.html#method.new
136    /// [`stop_with_newline`]: struct.Spinner.html#method.stop_with_newline
137    /// [`stop_with_message`]: struct.Spinner.html#method.stop_with_message
138    ///
139    /// # Examples
140    ///
141    /// Basic Usage:
142    ///
143    /// ```
144    /// use spinners::{Spinner, Spinners};
145    ///
146    /// let mut sp = Spinner::new(Spinners::Dots, "Loading things into memory...".into());
147    ///
148    /// sp.stop();
149    /// ```
150    pub fn stop(&mut self) {
151        self.stop_inner(Instant::now(), None);
152    }
153
154    /// Stop with a symbol that replaces the spinner
155    ///
156    /// The symbol is a String rather than a Char to allow for more flexibility, such as using ANSI color codes.
157    ///
158    /// # Examples
159    ///
160    /// Basic Usage:
161    ///
162    /// ```
163    /// use spinners::{Spinner, Spinners};
164    ///
165    /// let mut sp = Spinner::new(Spinners::Dots, "Loading things into memory...".into());
166    ///
167    /// sp.stop_with_symbol("🗸");
168    /// ```
169    ///
170    /// ANSI colors (green checkmark):
171    ///
172    /// ```
173    /// use spinners::{Spinner, Spinners};
174    ///
175    /// let mut sp = Spinner::new(Spinners::Dots, "Loading things into memory...".into());
176    ///
177    /// sp.stop_with_symbol("\x1b[32m🗸\x1b[0m");
178    /// ```
179    pub fn stop_with_symbol(&mut self, symbol: &str) {
180        self.stop_inner(Instant::now(), Some(symbol.to_owned()));
181        self.stream.stop(None, Some(symbol)).expect("IO error");
182    }
183
184    /// Stops the spinner and prints a new line
185    ///
186    /// # Examples
187    ///
188    /// Basic Usage:
189    ///
190    /// ```
191    /// use spinners::{Spinner, Spinners};
192    ///
193    /// let mut sp = Spinner::new(Spinners::Dots, "Loading things into memory...".into());
194    ///
195    /// sp.stop_with_newline();
196    /// ```
197    pub fn stop_with_newline(&mut self) {
198        self.stop();
199        self.stream.stop(None, None).expect("IO error");
200    }
201
202    /// Stops the spinner and prints the provided message
203    ///
204    /// # Examples
205    ///
206    /// Basic Usage:
207    ///
208    /// ```
209    /// use spinners::{Spinner, Spinners};
210    ///
211    /// let mut sp = Spinner::new(Spinners::Dots, "Loading things into memory...".into());
212    ///
213    /// sp.stop_with_message("Finished loading things into memory!".into());
214    /// ```
215    pub fn stop_with_message(&mut self, msg: String) {
216        self.stop();
217        self.stream.stop(Some(&msg), None).expect("IO Error");
218    }
219
220    /// Stops the spinner with a provided symbol and message
221    ///
222    /// # Examples
223    ///
224    /// Basic Usage:
225    ///
226    /// ```
227    /// use spinners::{Spinner, Spinners};
228    ///
229    /// let mut sp = Spinner::new(Spinners::Dots, "Loading things into memory...".into());
230    ///
231    /// sp.stop_and_persist("✔", "Finished loading things into memory!".into());
232    /// ```
233    pub fn stop_and_persist(&mut self, symbol: &str, msg: String) {
234        self.stop();
235        self.stream.stop(Some(&msg), Some(symbol)).expect("IO Error");
236    }
237
238    fn stop_inner(&mut self, stop_time: Instant, stop_symbol: Option<String>) {
239        self.sender
240            .send((stop_time, stop_symbol))
241            .expect("Could not stop spinner thread.");
242        self.join.take().unwrap().join().unwrap();
243    }
244}