circle_rs/
lib.rs

1//! [![version](https://img.shields.io/crates/v/circle-rs)](https:://github.com/alekspickle)
2//!
3//! # Minimalistic modern infinite terminal progress indicator
4//!
5//! This is slightly changed version of [rustbar](https://crates.io/crates/rustbar) crate, which is simple and minimalistic,
6//! but i needed another infinite bar animation, hence this crate.
7//!
8//! ### The goal also was to be able to use it as simple as:
9//!
10//! # Example
11//! ```rust,no_run
12//! use std::{io::Result, thread, time::{Duration, Instant}};
13//! use circle_rs::{Infinite, Progress};
14//!
15//! pub fn main() -> Result<()> {
16//!     println!("\n100 ms delay");
17//!     let mut loader = Infinite::new().to_stderr();
18//!     loader.set_msg("Polling");
19//!     let start_thread = loader.start()?;
20//!     let now = Instant::now();
21//!     thread::sleep(Duration::from_secs(2));
22//!     loader.stop()?;
23//!     println!("elapsed {} {:?}",start_thread, now.elapsed());
24//!     Ok(())
25//! }
26//! ```
27//! # Features:
28//! 1. set custom loading message
29//! 2. set loader speed without reconstructing it
30//! 3. add cute greeny "done" message after loader is done
31//! 
32//! ## Note: 
33//!  In (3) case you'll need to enable feature, because it requires termion to be added.
34//!  Because you **dont pay for what you dont want**, right?
35//! ```toml
36//! [dependencies]
37//! circle-rs = {version = "*", features = ["end"]}
38//! ```
39//!
40use std::{
41    io::{stderr, stdout, Result, Write},
42    thread,
43    time::Duration,
44};
45
46#[cfg(feature = "end")]
47mod utils;
48
49/// loader pattern
50const PATTERN: &str = "⠁⠁⠂⠂⠄⠄⡀⡀⡀⠠⠠⠐⠐⠈";
51
52fn clear_stdout() -> Result<()> {
53    stdout().flush()?;
54    Ok(())
55}
56
57fn write_to_stdout(buf: String) -> Result<()> {
58    let mut output = stdout();
59    output.write(buf.as_bytes())?;
60    output.flush()?;
61    Ok(())
62}
63
64fn write_to_stderr(buf: String) -> Result<()> {
65    let mut output = stderr();
66    output.write(buf.as_bytes())?;
67    output.flush()?;
68    Ok(())
69}
70
71/// Main trait
72pub trait Progress<T> {
73    fn new() -> T;
74    fn to_stderr(&mut self) -> T;
75    fn write(&self, buf: String) -> Result<()>;
76    #[cfg(feature = "end")]
77    fn write_done(&self) -> Result<()>;
78    fn clear(&self) -> Result<()>;
79}
80
81/// Struct for storing state
82#[derive(Clone)]
83pub struct Infinite {
84    msg: String,
85    marker_position: u8,
86    step: u8,
87    delay: Duration,
88    write_fn: fn(String) -> Result<()>,
89    clear_fn: fn() -> Result<()>,
90    rolling: bool,
91    done: bool,
92}
93
94impl Default for Infinite {
95    fn default() -> Infinite {
96        Infinite {
97            step: 1,
98            delay: Duration::from_millis(100),
99            msg: "".to_owned(),
100            marker_position: 0,
101            write_fn: write_to_stdout,
102            clear_fn: clear_stdout,
103            rolling: false,
104            done: false,
105        }
106    }
107}
108
109impl Progress<Infinite> for Infinite {
110    fn new() -> Infinite {
111        Infinite {
112            ..Default::default()
113        }
114    }
115
116    fn to_stderr(&mut self) -> Infinite {
117        self.write_fn = write_to_stderr;
118        self.clone()
119    }
120
121    fn write(&self, buf: String) -> Result<()> {
122        (self.write_fn)(buf)
123    }
124    #[cfg(feature = "end")]
125    fn write_done(&self) -> Result<()> {
126        let buf = utils::print_green("done\n");
127        (self.write_fn)(buf)
128    }
129    fn clear(&self) -> Result<()> {
130        (self.clear_fn)()?;
131        Ok(())
132    }
133}
134
135impl Infinite {
136    pub fn set_msg(&mut self, msg: &str) {
137        self.msg = msg.to_owned()
138    }
139    pub fn set_delay(&mut self, d: Duration) {
140        self.delay = d
141    }
142    pub fn set_done(&mut self, done: bool) {
143        self.done = done
144    }
145
146    pub fn get_msg(&self) -> &str {
147        self.msg.as_ref()
148    }
149    pub fn get_delay(&self) -> Duration {
150        self.delay
151    }
152
153    pub fn stop(&mut self) -> Result<()> {
154        self.rolling = false;
155        self.clear()?;
156        self.render_end()?;
157        Ok(())
158    }
159    pub fn start<'a>(&'a mut self) -> Result<String> {
160        self.rolling = true;
161        let mut bar = self.clone();
162        let sleep = self.delay;
163        let thread_name = "rolling";
164        thread::Builder::new()
165            .name(thread_name.clone().into())
166            .spawn(move || loop {
167                bar.render().unwrap();
168                thread::sleep(sleep);
169            })?;
170        Ok(thread_name.into())
171    }
172
173    #[cfg(feature = "end")]
174    pub fn render_end(&mut self) -> Result<()> {
175        if self.done {
176            self.write_done()
177        } else {
178            self.write("\r".into())
179        }
180    }
181    #[cfg(not(feature = "end"))]
182    pub fn render_end(&mut self) -> Result<()> {
183        self.write("\r".into())
184    }
185    pub fn render(&mut self) -> Result<()> {
186        if self.marker_position == 0 {
187            self.marker_position = 0;
188            self.step = 1;
189        } else if self.marker_position == 12 {
190            self.marker_position = 0;
191            self.step = 1;
192        }
193        self.marker_position = self.marker_position + self.step;
194
195        let bar = PATTERN
196            .chars()
197            .nth(usize::from(self.marker_position))
198            .expect("out of bounds");
199
200        self.write(format!(
201            "\r{msg} {bar}{bar}{bar} ",
202            msg = self.msg,
203            bar = bar
204        ))?;
205        Ok(())
206    }
207}