ml_progress/
lib.rs

1#![doc = include_str!(concat!(env!("OUT_DIR"), "/README-rustdocified.md"))]
2#![deny(missing_docs)]
3#![forbid(unsafe_code)]
4
5use std::{
6    borrow::Cow,
7    error::Error as StdError,
8    fmt,
9    sync::Arc,
10    thread::{self, JoinHandle},
11    time::Duration,
12};
13
14use parking_lot::Mutex;
15
16pub use crate::state::State;
17
18use crate::internal::Item;
19
20#[allow(missing_docs)]
21pub mod internal;
22mod macros;
23mod state;
24
25// ======================================================================
26// CONST - PRIVATE
27
28const DEFAULT_DRAW_RATE: usize = 20;
29const DEFAULT_DRAW_INTERVAL: Duration =
30    Duration::from_nanos(1_000_000_000 / DEFAULT_DRAW_RATE as u64);
31
32const DEFAULT_DRAW_DELAY: Duration = Duration::from_millis(5);
33
34const MIN_ETA_ELAPSED: Duration = Duration::from_millis(100);
35const MIN_SPEED_ELAPSED: Duration = Duration::from_millis(100);
36
37const BINARY_PREFIXES: &[&str] = &["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi"];
38const DECIMAL_PREFIXES: &[&str] = &["", "k", "M", "G", "T", "P", "E", "Z", "Y"];
39
40// ======================================================================
41// Error - PUBLIC
42
43/// Represents all possible errors that can occur in this library.
44#[derive(Debug, PartialEq)]
45pub enum Error {
46    /// Given items contain multiple `*_fill` items but at most one is allowed.
47    ///
48    /// # Examples
49    ///
50    /// ```rust
51    /// use ml_progress::{progress, Error};
52    ///
53    /// assert_eq!(
54    ///     progress!(10; bar_fill message_fill).err(),
55    ///     Some(Error::MultipleFillItems)
56    /// );
57    /// ```
58    MultipleFillItems,
59
60    /// Given `total` is out-of-range of `u64`.
61    ///
62    /// # Examples
63    ///
64    /// ```rust
65    /// use ml_progress::{progress, Error};
66    ///
67    /// assert_eq!(progress!(-1).err(), Some(Error::TotalIsOutOfRange));
68    /// ```
69    TotalIsOutOfRange,
70}
71
72// ======================================================================
73// Error - IMPL DISPLAY
74
75impl fmt::Display for Error {
76    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77        match self {
78            Error::MultipleFillItems => {
79                write!(f, "got multiple fill items, at most one is allowed")
80            }
81
82            Error::TotalIsOutOfRange => {
83                write!(f, "total is out-of-range of `u64`")
84            }
85        }
86    }
87}
88
89// ======================================================================
90// Error - IMPL ERROR
91
92impl StdError for Error {}
93
94// ======================================================================
95// Progress - PUBLIC
96
97/// Progress indicator.
98///
99/// `Progress` is created either
100/// - directly with [`progress!`] macro (if you don’t need custom configuration) or
101/// - by first creating [`ProgressBuilder`] with [`progress_builder!`] macro,
102///   setting custom options, and then creating `Progress` with [`build`].
103///
104/// `Progress` is drawn
105/// - using background thread to guarantee timely updates
106/// - only if terminal is detected
107/// - to `STDERR` starting with `"\r"`
108/// - from the moment `Progress` is created until `Progress` is finished or dropped
109///
110/// See crate index for [usage](crate#usage) and [examples](crate#examples).
111///
112/// [`build`]: crate::ProgressBuilder::build
113#[derive(Clone)]
114pub struct Progress {
115    // This is `None` only in `Drop::drop`.
116    drawer: Option<Arc<JoinHandle<()>>>,
117    state: Arc<Mutex<State>>,
118}
119
120impl Progress {
121    /// Finishes `Progress` with 100% completion.
122    ///
123    /// - Sets [`State`] of `Progress` to 100% completion.
124    /// - Draws `Progress` once with additional `"\n"`
125    ///   to move cursor to next line.
126    /// - Finishes `Progress`, i.e. there will be no further draws.
127    ///
128    /// # Examples
129    ///
130    /// ```rust
131    /// use ml_progress::progress;
132    ///
133    /// eprintln!("Begin");
134    /// let progress = progress!(10)?;
135    /// progress.finish();
136    /// eprintln!("End");
137    /// # Ok::<(), ml_progress::Error>(())
138    /// ```
139    ///
140    /// ```text
141    /// Begin
142    /// ################################################# 10/10 (0s)
143    /// End
144    /// ```
145    pub fn finish(&self) {
146        self.state.lock().finish(self.drawer.as_ref().unwrap());
147    }
148
149    /// Finishes and clears `Progress`.
150    ///
151    /// - Clears drawn `Progress` by overwriting with spaces + `"\r"`,
152    ///   leaving cursor at start of the cleared line.
153    /// - Finishes `Progress`, i.e. there will be no further draws.
154    ///
155    /// # Examples
156    ///
157    /// ```rust
158    /// use ml_progress::progress;
159    ///
160    /// eprintln!("Begin");
161    /// let progress = progress!(10)?;
162    /// progress.finish_and_clear();
163    /// eprintln!("End");
164    /// # Ok::<(), ml_progress::Error>(())
165    /// ```
166    ///
167    /// ```text
168    /// Begin
169    /// End
170    /// ```
171    pub fn finish_and_clear(&self) {
172        self.state
173            .lock()
174            .finish_and_clear(self.drawer.as_ref().unwrap());
175    }
176
177    /// Finishes `Progress`.
178    ///
179    /// - Draws `Progress` once with additional `"\n"`
180    ///   to move cursor to next line.
181    /// - Finishes `Progress`, i.e. there will be no further draws.
182    ///
183    /// # Examples
184    ///
185    /// ```rust
186    /// use ml_progress::progress;
187    ///
188    /// eprintln!("Begin");
189    /// let progress = progress!(10)?;
190    /// progress.inc(6);
191    /// progress.finish_at_current_pos();
192    /// eprintln!("End");
193    /// # Ok::<(), ml_progress::Error>(())
194    /// ```
195    ///
196    /// ```text
197    /// Begin
198    /// ##############################-------------------- 6/10 (0s)
199    /// End
200    /// ```
201    pub fn finish_at_current_pos(&self) {
202        self.state
203            .lock()
204            .finish_at_current_pos(self.drawer.as_ref().unwrap());
205    }
206
207    /// Increments position of `Progress`.
208    ///
209    /// # Examples
210    ///
211    /// ```rust
212    /// use ml_progress::progress;
213    ///
214    /// let progress = progress!(10)?;
215    /// progress.inc(6);
216    /// progress.finish_at_current_pos();
217    /// # Ok::<(), ml_progress::Error>(())
218    /// ```
219    ///
220    /// ```text
221    /// ##############################-------------------- 6/10 (0s)
222    /// ```
223    pub fn inc(&self, steps: u64) {
224        self.state.lock().inc(steps, self.drawer.as_ref().unwrap());
225    }
226
227    /// Sets the message shown by item `message_fill`.
228    ///
229    /// # Examples
230    ///
231    /// ```rust
232    /// use ml_progress::progress;
233    ///
234    /// let progress = progress!(10; pos "/" total " " message_fill)?;
235    /// progress.inc(6);
236    /// progress.message("Hello, World!");
237    /// progress.finish_at_current_pos();
238    /// # Ok::<(), ml_progress::Error>(())
239    /// ```
240    ///
241    /// ```text
242    /// 6/10 Hello, World!
243    /// ```
244    pub fn message(&self, message: impl Into<Cow<'static, str>>) {
245        self.state
246            .lock()
247            .message(message, self.drawer.as_ref().unwrap());
248    }
249
250    /// Returns current state of `Progress`.
251    ///
252    /// # Examples
253    ///
254    /// ```rust
255    /// use ml_progress::progress;
256    ///
257    /// let progress = progress!(10)?;
258    /// progress.inc(6);
259    /// assert_eq!(progress.state().lock().pos(), 6);
260    /// # Ok::<(), ml_progress::Error>(())
261    /// ```
262    pub fn state(&self) -> &Arc<Mutex<State>> {
263        &self.state
264    }
265}
266
267impl Drop for Progress {
268    fn drop(&mut self) {
269        if let Ok(drawer) = Arc::try_unwrap(self.drawer.take().unwrap()) {
270            let mut state = self.state.lock();
271            if !state.is_finished() {
272                state.finish_quietly(&drawer);
273            }
274            drop(state);
275            let _ = drawer.join();
276        }
277    }
278}
279
280// ======================================================================
281// Progress - CRATE
282
283impl Progress {
284    pub(crate) fn new(state: State) -> Self {
285        let state = Arc::new(Mutex::new(state));
286
287        let drawer = thread::spawn({
288            let state = state.clone();
289            move || loop {
290                let mut state = state.lock();
291
292                if state.is_finished() {
293                    break;
294                }
295
296                let timeout = match state.try_draw() {
297                    Ok(()) => None,
298                    Err(timeout) => timeout,
299                };
300
301                drop(state);
302
303                // NOTE: These may wake spuriously
304                if let Some(timeout) = timeout {
305                    thread::park_timeout(timeout);
306                } else {
307                    thread::park();
308                }
309            }
310        });
311
312        Self {
313            drawer: Some(Arc::new(drawer)),
314            state,
315        }
316    }
317}
318
319// ======================================================================
320// ProgressBuilder - PUBLIC
321
322/// A builder to create [`Progress`] with custom configuration.
323///
324/// See [custom configuration] for an example.
325///
326/// [custom configuration]: crate#custom-configuration
327pub struct ProgressBuilder {
328    total: Result<Option<u64>, Error>,
329    pre_inc: bool,
330    thousands_separator: String,
331    items: Vec<Item>,
332}
333
334impl ProgressBuilder {
335    /// Creates [`Progress`] using configuration of this `ProgressBuilder`.
336    ///
337    /// See [custom configuration] for an example.
338    ///
339    /// [custom configuration]: crate#custom-configuration
340    pub fn build(self) -> Result<Progress, Error> {
341        let state = State::new(
342            self.total?,
343            self.pre_inc,
344            self.thousands_separator,
345            self.items,
346        )?;
347
348        Ok(Progress::new(state))
349    }
350
351    /// Creates `ProgressBuilder` to configure [`Progress`].
352    ///
353    /// If `items` is empty then default items are used instead.
354    ///
355    /// [`progress_builder!`] macro should be used instead of this,
356    /// which is same as `ProgressBuilder::new(items!(ITEMS))`.
357    pub fn new(items: Vec<Item>) -> Self {
358        let items = if items.is_empty() {
359            // DEFAULT ITEMS
360            items!(bar_fill " " pos "/" total " (" eta ")")
361        } else {
362            items
363        };
364
365        Self {
366            total: Ok(None),
367            pre_inc: false,
368            thousands_separator: " ".to_owned(),
369            items,
370        }
371    }
372
373    /// Sets increment mode to `PreInc`.
374    ///
375    /// Increment mode can be `PostInc` (default) or `PreInc`.
376    ///
377    /// - `PostInc` means that progress position is
378    ///   incremented after the associated work.
379    ///     - For example incrementing position from 2 to 3 means that work
380    ///       of step 3 has been completed and work of step 4 is about to begin.
381    /// - `PreInc` means that progress position is
382    ///   incremented before the associated work.
383    ///     - For example incrementing position from 2 to 3 means that work
384    ///       of step 2 has been completed and work of step 3 is about to begin.
385    ///
386    /// # Examples
387    ///
388    /// Here first step has been completed and second is about to begin
389    /// so completion percentage is 33%.
390    ///
391    /// ```rust
392    /// use ml_progress::progress_builder;
393    ///
394    /// let progress = progress_builder!("[" percent "] " pos "/" total)
395    ///     .total(Some(3))
396    ///     .pre_inc()
397    ///     .build()?;
398    /// progress.inc(1);
399    /// progress.inc(1);
400    /// progress.finish_at_current_pos();
401    /// # Ok::<(), ml_progress::Error>(())
402    /// ```
403    ///
404    /// ```text
405    /// [ 33%] 2/3
406    /// ```
407    pub fn pre_inc(self) -> Self {
408        Self {
409            pre_inc: true,
410            ..self
411        }
412    }
413
414    /// Sets thousands separator, default is space.
415    ///
416    /// See [custom configuration] for an example.
417    ///
418    /// [custom configuration]: crate#custom-configuration
419    pub fn thousands_separator(self, separator: &str) -> Self {
420        Self {
421            thousands_separator: separator.to_owned(),
422            ..self
423        }
424    }
425
426    /// Sets progress total, default is `None`.
427    ///
428    /// See [custom configuration] for an example.
429    ///
430    /// [custom configuration]: crate#custom-configuration
431    pub fn total<T: TryInto<u64>>(self, total: Option<T>) -> Self {
432        let total = if let Some(total) = total {
433            match total.try_into() {
434                Ok(total) => Ok(Some(total)),
435                Err(_) => Err(Error::TotalIsOutOfRange),
436            }
437        } else {
438            Ok(None)
439        };
440
441        Self { total, ..self }
442    }
443}
444
445// ======================================================================
446// FUNCTIONS - PUBLIC
447
448/// Returns given value as binary prefix with corresponding value.
449///
450/// Uses 1024-based prefixes `Ki`, `Mi`, `Gi`, ..., `Yi`.
451///
452/// # Examples
453///
454/// ```rust
455/// assert_eq!(ml_progress::binary_prefix(2048.0), (2.0, "Ki"));
456/// ```
457pub fn binary_prefix(mut value: f64) -> (f64, &'static str) {
458    let mut scale = 0;
459    while value.abs() >= 1024.0 && scale < BINARY_PREFIXES.len() - 1 {
460        value /= 1024.0;
461        scale += 1;
462    }
463    (value, BINARY_PREFIXES[scale])
464}
465
466/// Returns given value as decimal prefix with corresponding value.
467///
468/// Uses 1000-based prefixes `k`, `M`, `G`, ..., `Y`.
469///
470/// # Examples
471///
472/// ```rust
473/// assert_eq!(ml_progress::decimal_prefix(2000.0), (2.0, "k"));
474/// ```
475pub fn decimal_prefix(mut value: f64) -> (f64, &'static str) {
476    let mut scale = 0;
477    while value.abs() >= 1000.0 && scale < DECIMAL_PREFIXES.len() - 1 {
478        value /= 1000.0;
479        scale += 1;
480    }
481    (value, DECIMAL_PREFIXES[scale])
482}
483
484/// Returns given duration in approximate format: amount and unit.
485///
486/// - Amount is the number of full units, i.e. it's not rounded.
487/// - Unit can be `h` (hours), `m` (minutes) or `s` (seconds)
488///
489/// # Examples
490///
491/// ```rust
492/// use std::time::Duration;
493/// assert_eq!(ml_progress::duration_approx(Duration::from_secs(234)), (3, "m"));
494/// ```
495pub fn duration_approx(duration: Duration) -> (u64, &'static str) {
496    let secs = duration.as_secs();
497    if secs < 60 {
498        (secs, "s")
499    } else if secs < 3600 {
500        (secs / 60, "m")
501    } else {
502        (secs / 3600, "h")
503    }
504}
505
506/// Returns given duration as hours, minutes and seconds.
507///
508/// Returned value is the number of full seconds, i.e. it's not rounded.
509///
510/// # Examples
511///
512/// ```rust
513/// use std::time::Duration;
514/// assert_eq!(ml_progress::duration_hms(Duration::from_secs(234)), (0, 3, 54));
515/// ```
516pub fn duration_hms(duration: Duration) -> (u64, u64, u64) {
517    let secs = duration.as_secs();
518    let h = secs / 3600;
519    let m = (secs % 3600) / 60;
520    let s = secs % 60;
521    (h, m, s)
522}
523
524/// Formats integer with digits in groups of three.
525///
526/// # Examples
527///
528/// ```rust
529/// assert_eq!(ml_progress::group_digits(12345, " "), "12 345");
530/// ```
531pub fn group_digits(mut value: u64, separator: &str) -> String {
532    // `u64` can have at most 7 3-digit groups
533    let mut groups = [0; 7];
534    let mut pos = 0;
535    while pos == 0 || value > 0 {
536        groups[pos] = value % 1000;
537        value /= 1000;
538        pos += 1;
539    }
540
541    let mut result = String::with_capacity(pos * 3 + (pos - 1) * separator.len());
542    pos -= 1;
543    result.push_str(&format!("{}", groups[pos]));
544    while pos > 0 {
545        pos -= 1;
546        result.push_str(separator);
547        result.push_str(&format!("{:03}", groups[pos]));
548    }
549    result
550}
551
552// ======================================================================
553// TESTS
554
555#[cfg(test)]
556mod tests {
557    use super::*;
558
559    // ============================================================
560    // binary_prefix
561
562    #[test]
563    fn binary_prefix_misc() {
564        assert_eq!(binary_prefix(0.0), (0.0, ""));
565
566        assert_eq!(binary_prefix(2560.0), (2.5, "Ki"));
567        assert_eq!(binary_prefix(2621440.0), (2.5, "Mi"));
568
569        assert_eq!(binary_prefix(-2560.0), (-2.5, "Ki"));
570        assert_eq!(binary_prefix(-2621440.0), (-2.5, "Mi"));
571    }
572
573    #[test]
574    fn binary_prefix_overflow() {
575        assert_eq!(binary_prefix(91.0f64.exp2()), (2048.0, "Yi"));
576    }
577
578    // ============================================================
579    // decimal_prefix
580
581    #[test]
582    fn decimal_prefix_misc() {
583        assert_eq!(decimal_prefix(0.0), (0.0, ""));
584
585        assert_eq!(decimal_prefix(2500.0), (2.5, "k"));
586        assert_eq!(decimal_prefix(2500000.0), (2.5, "M"));
587
588        assert_eq!(decimal_prefix(-2500.0), (-2.5, "k"));
589        assert_eq!(decimal_prefix(-2500000.0), (-2.5, "M"));
590    }
591
592    #[test]
593    fn decimal_prefix_overflow() {
594        // TODO: This shouldn't use exact comparison.
595        assert_eq!(decimal_prefix(2.0e27), (2000.0, "Y"));
596    }
597
598    // ============================================================
599    // duration_approx
600
601    #[test]
602    fn duration_approx_no_rounding() {
603        assert_eq!(duration_approx(Duration::from_millis(1800)), (1, "s"));
604    }
605
606    // ============================================================
607    // duration_hms
608
609    #[test]
610    fn duration_hms_no_rounding() {
611        assert_eq!(duration_hms(Duration::from_millis(1800)), (0, 0, 1));
612    }
613
614    // ============================================================
615    // group_digits
616
617    #[test]
618    fn group_digits_0() {
619        assert_eq!(group_digits(0, " "), "0");
620    }
621
622    #[test]
623    fn group_digits_max() {
624        assert_eq!(group_digits(u64::MAX, " "), "18 446 744 073 709 551 615");
625    }
626
627    #[test]
628    fn group_digits_long_separator() {
629        assert_eq!(group_digits(12_345, "abc"), "12abc345");
630    }
631
632    #[test]
633    fn group_digits_has_zero_padding() {
634        assert_eq!(group_digits(1_002_034, " "), "1 002 034");
635    }
636
637    #[test]
638    fn group_digits_no_zero_padding() {
639        assert_eq!(group_digits(1_234_567, " "), "1 234 567");
640    }
641}