Skip to main content

rskit_cli/
progress.rs

1//! Progress bar abstractions over `indicatif`.
2//!
3//! Provides preset styles for common CLI progress patterns.
4
5use indicatif::{
6    MultiProgress as IndicatifMultiProgress, ProgressBar as IndicatifBar,
7    ProgressStyle as IndicatifStyle,
8};
9use std::time::Duration;
10
11/// Re-export raw type so binaries can create shared instances for writer integration.
12pub use indicatif::MultiProgress as RawMultiProgress;
13
14/// Preset progress bar styles.
15#[derive(Clone, Copy)]
16pub enum ProgressStyle {
17    /// Standard bar with count and percentage.
18    Bar,
19    /// Spinner for indeterminate operations.
20    Spinner,
21    /// Download-style with bytes.
22    Download,
23    /// Finished bar — just shows prefix + message, no bar/timer.
24    Finished,
25}
26
27impl ProgressStyle {
28    fn to_indicatif(self) -> IndicatifStyle {
29        match self {
30            Self::Bar => style_or_default(
31                "{prefix:.bold} {wide_bar:.cyan/dim} {pos}/{len} {percent}% {elapsed}",
32                IndicatifStyle::default_bar,
33            )
34            .progress_chars("━╸─"),
35
36            Self::Spinner => style_or_default(
37                "{spinner:.green} {prefix} {wide_msg}",
38                IndicatifStyle::default_spinner,
39            ),
40
41            Self::Download => style_or_default(
42                "{prefix:.bold} {wide_bar:.green/dim} {bytes}/{total_bytes} {bytes_per_sec} {eta}",
43                IndicatifStyle::default_bar,
44            )
45            .progress_chars("━╸─"),
46
47            Self::Finished => {
48                style_or_default("{prefix} {wide_msg}", IndicatifStyle::default_spinner)
49            }
50        }
51    }
52}
53
54fn style_or_default(template: &str, fallback: impl FnOnce() -> IndicatifStyle) -> IndicatifStyle {
55    IndicatifStyle::with_template(template).unwrap_or_else(|_| fallback())
56}
57
58/// A single progress bar wrapping `indicatif`.
59pub struct ProgressBar {
60    inner: IndicatifBar,
61}
62
63impl ProgressBar {
64    /// Create a new progress bar with the given total and style.
65    pub fn new(total: u64, style: ProgressStyle) -> Self {
66        let bar = IndicatifBar::new(total);
67        bar.set_style(style.to_indicatif());
68        bar.enable_steady_tick(Duration::from_millis(100));
69        Self { inner: bar }
70    }
71
72    /// Create a spinner (indeterminate progress).
73    pub fn spinner() -> Self {
74        let bar = IndicatifBar::new_spinner();
75        bar.set_style(ProgressStyle::Spinner.to_indicatif());
76        bar.enable_steady_tick(Duration::from_millis(80));
77        Self { inner: bar }
78    }
79
80    /// Set the prefix text (typically appears before the bar).
81    pub fn set_prefix(&self, prefix: impl Into<String>) {
82        self.inner.set_prefix(prefix.into());
83    }
84
85    /// Change the bar style.
86    pub fn set_style(&self, style: ProgressStyle) {
87        self.inner.set_style(style.to_indicatif());
88    }
89
90    /// Set the message text.
91    pub fn set_message(&self, msg: impl Into<String>) {
92        self.inner.set_message(msg.into());
93    }
94
95    /// Enable steady tick (auto-redraw) at the given interval.
96    pub fn enable_steady_tick(&self, interval: Duration) {
97        self.inner.enable_steady_tick(interval);
98    }
99
100    /// Trigger a redraw without changing position.
101    pub fn tick(&self) {
102        self.inner.tick();
103    }
104
105    /// Set current position.
106    pub fn set_position(&self, pos: u64) {
107        self.inner.set_position(pos);
108    }
109
110    /// Increment position by delta.
111    pub fn inc(&self, delta: u64) {
112        self.inner.inc(delta);
113    }
114
115    /// Set total length.
116    pub fn set_length(&self, len: u64) {
117        self.inner.set_length(len);
118    }
119
120    /// Mark as finished.
121    pub fn finish(&self) {
122        self.inner.finish();
123    }
124
125    /// Mark as finished with a final message (bar stays visible).
126    pub fn finish_with_message(&self, msg: impl Into<std::borrow::Cow<'static, str>>) {
127        self.inner.finish_with_message(msg);
128    }
129
130    /// Reset the bar to its initial state (position 0, not finished).
131    pub fn reset(&self) {
132        self.inner.reset();
133    }
134
135    /// Finish and clear from display.
136    pub fn finish_and_clear(&self) {
137        self.inner.finish_and_clear();
138    }
139
140    /// Access the underlying indicatif bar.
141    pub const fn inner(&self) -> &IndicatifBar {
142        &self.inner
143    }
144}
145
146/// Multi-progress bar manager for concurrent operations.
147pub struct MultiProgress {
148    inner: IndicatifMultiProgress,
149}
150
151impl MultiProgress {
152    /// Create an empty multi-progress manager.
153    #[must_use]
154    pub fn new() -> Self {
155        Self {
156            inner: IndicatifMultiProgress::new(),
157        }
158    }
159
160    /// Wrap an existing `indicatif::MultiProgress`.
161    #[must_use]
162    pub const fn from_raw(mp: IndicatifMultiProgress) -> Self {
163        Self { inner: mp }
164    }
165
166    /// Access the underlying `indicatif::MultiProgress`.
167    pub const fn raw(&self) -> &IndicatifMultiProgress {
168        &self.inner
169    }
170
171    /// Add a bar-style progress with the given prefix and total.
172    pub fn add_bar(&self, prefix: impl Into<String>, total: u64) -> ProgressBar {
173        let bar = IndicatifBar::new(total);
174        bar.set_style(ProgressStyle::Bar.to_indicatif());
175        bar.set_prefix(prefix.into());
176        let bar = self.inner.add(bar);
177        ProgressBar { inner: bar }
178    }
179
180    /// Add a bar inserted after another bar (keeps ordering stable).
181    pub fn insert_bar_after(
182        &self,
183        after: &ProgressBar,
184        prefix: impl Into<String>,
185        total: u64,
186    ) -> ProgressBar {
187        let bar = IndicatifBar::new(total);
188        bar.set_style(ProgressStyle::Bar.to_indicatif());
189        bar.set_prefix(prefix.into());
190        let bar = self.inner.insert_after(after.inner(), bar);
191        ProgressBar { inner: bar }
192    }
193
194    /// Add a spinner-style progress.
195    pub fn add_spinner(&self, prefix: impl Into<String>) -> ProgressBar {
196        let bar = IndicatifBar::new_spinner();
197        bar.set_style(ProgressStyle::Spinner.to_indicatif());
198        bar.set_prefix(prefix.into());
199        bar.enable_steady_tick(Duration::from_millis(120));
200        let bar = self.inner.add(bar);
201        ProgressBar { inner: bar }
202    }
203
204    /// Add a static text line (no animation, no bar).
205    pub fn add_static_line(
206        &self,
207        prefix: impl Into<String>,
208        msg: impl Into<std::borrow::Cow<'static, str>>,
209    ) -> ProgressBar {
210        let bar = IndicatifBar::new(0);
211        bar.set_style(ProgressStyle::Finished.to_indicatif());
212        bar.set_prefix(prefix.into());
213        bar.finish_with_message(msg);
214        let bar = self.inner.add(bar);
215        ProgressBar { inner: bar }
216    }
217
218    /// Print a line above the progress bars (for completed items).
219    pub fn println(&self, msg: impl AsRef<str>) -> std::io::Result<()> {
220        self.inner.println(msg)
221    }
222
223    /// Remove a progress bar from the display.
224    pub fn remove(&self, bar: &ProgressBar) {
225        self.inner.remove(bar.inner());
226    }
227
228    /// Clear all bars.
229    pub fn clear(&self) -> std::io::Result<()> {
230        self.inner.clear()
231    }
232}
233
234impl Default for MultiProgress {
235    fn default() -> Self {
236        Self::new()
237    }
238}