summaryrefslogtreecommitdiff
path: root/src/progress.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/progress.rs')
-rw-r--r--src/progress.rs200
1 files changed, 200 insertions, 0 deletions
diff --git a/src/progress.rs b/src/progress.rs
new file mode 100644
index 0000000..a5509ef
--- /dev/null
+++ b/src/progress.rs
@@ -0,0 +1,200 @@
+use std::{collections::HashMap, fmt::Display, sync::OnceLock};
+use tokio::sync::watch;
+
+pub static PROGRESS_CENTER: OnceLock<ProgressCenter> = OnceLock::new();
+
+const SPECIAL_FLAG_CLEAR: &str = "_clear";
+const SPECIAL_FLAG_CLOSE: &str = "_close";
+
+#[derive(Debug)]
+pub struct ProgressCenter {
+ tx: watch::Sender<HashMap<String, ProgressState>>,
+ rx: watch::Receiver<HashMap<String, ProgressState>>,
+}
+
+/// Initialize `ProgressCenter` early in the program
+///
+/// ``` rust
+/// use just_progress::progress;
+///
+/// fn main() {
+/// // Initialize
+/// let progress_center = progress::init();
+/// }
+/// ```
+pub fn init() -> &'static ProgressCenter {
+ let (tx, rx) = watch::channel::<HashMap<String, ProgressState>>(HashMap::new());
+ PROGRESS_CENTER.get_or_init(|| ProgressCenter { tx, rx })
+}
+
+/// Bind a callback to ProgressCenter and create a Future for receiving and rendering messages
+///
+///
+/// ```rust
+/// use just_progress::progress;
+///
+/// #[tokio::main]
+/// async fn main() {
+/// // Initialize
+/// let progress_center = progress::init();
+/// let bind_future = progress::bind(progress_center, |name, state| {
+/// println!("Update progress `{}` state to `{}`", name, state);
+/// });
+/// }
+/// ```
+pub fn bind<F>(
+ center: &'static ProgressCenter,
+ mut callback: F,
+) -> impl std::future::Future<Output = ()> + Send
+where
+ F: FnMut(String, ProgressState) + Send + 'static,
+{
+ let mut rx = center.rx.clone();
+ async move {
+ while rx.changed().await.is_ok() {
+ let state = rx.borrow().clone();
+
+ // If it's a close flag, break
+ if let Some(_) = state.get(SPECIAL_FLAG_CLOSE) {
+ break;
+ }
+ // Clear flag
+ if let Some(_) = state.get(SPECIAL_FLAG_CLEAR) {
+ // Send all known names + progress 0 + EmptyState
+ for (name, _) in state.iter().filter(|(k, _)| *k != SPECIAL_FLAG_CLEAR) {
+ callback(
+ name.clone(),
+ ProgressState {
+ progress: 0.0,
+ info: ProgressInfo::Empty,
+ },
+ );
+ }
+ // Clear the entire HashMap
+ let _ = center.tx.send(HashMap::new());
+ } else {
+ for (name, progress_state) in state {
+ callback(name, progress_state);
+ }
+ }
+ }
+ }
+}
+
+#[derive(Debug, Default, Clone, Copy)]
+pub struct ProgressState {
+ pub(crate) progress: f32,
+ pub(crate) info: ProgressInfo,
+}
+
+impl ProgressState {
+ /// Get the progress value (0.0 to 1.0)
+ pub fn progress(&self) -> f32 {
+ self.progress
+ }
+
+ /// Get the progress info
+ pub fn info(&self) -> ProgressInfo {
+ self.info
+ }
+}
+
+impl Display for ProgressState {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let space = if !matches!(self.info, ProgressInfo::Empty) {
+ " "
+ } else {
+ ""
+ };
+ write!(f, "{}{}({:.1}%)", self.info, space, self.progress * 100.0)
+ }
+}
+
+#[derive(Debug, Default, Clone, Copy)]
+pub enum ProgressInfo {
+ #[default]
+ Empty,
+ Info(&'static str),
+ Warning(&'static str),
+ Error(&'static str),
+}
+
+impl Display for ProgressInfo {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ ProgressInfo::Empty => write!(f, ""),
+ ProgressInfo::Info(msg) => write!(f, "Info: `{}`", msg),
+ ProgressInfo::Warning(msg) => write!(f, "Warning: `{}`", msg),
+ ProgressInfo::Error(msg) => write!(f, "Error: `{}`", msg),
+ }
+ }
+}
+
+/// Complete an item
+///
+/// Set an item's progress to 100% so it disappears from progress rendering
+pub fn complete(name: &str) {
+ update_progress(name, 1.);
+}
+
+/// Clear all
+///
+/// Send a message to clear all items, ensuring no progress remains
+pub fn clear_all() {
+ update(SPECIAL_FLAG_CLEAR, 0., ProgressInfo::Empty);
+}
+
+/// Close the channel
+///
+/// Send a close message to stop bind() from running
+pub fn close() {
+ update(SPECIAL_FLAG_CLOSE, 0., ProgressInfo::Empty);
+}
+
+/// Update
+///
+/// Update a progress item's information
+pub fn update(name: &str, progress: f32, info: ProgressInfo) {
+ let Some(center) = PROGRESS_CENTER.get() else {
+ return;
+ };
+ let mut state = center.tx.borrow().clone();
+ state.insert(name.to_string(), ProgressState { progress, info });
+ let _ = center.tx.send(state);
+}
+
+/// Update progress
+///
+/// Modify a progress item's value
+pub fn update_progress(name: &str, progress: f32) {
+ let Some(center) = PROGRESS_CENTER.get() else {
+ return;
+ };
+ let mut state = center.tx.borrow().clone();
+ let entry = state
+ .entry(name.to_string())
+ .or_insert_with(|| ProgressState {
+ progress: 0.0,
+ info: ProgressInfo::default(),
+ });
+ entry.progress = progress;
+ let _ = center.tx.send(state);
+}
+
+/// Update info
+///
+/// Modify a progress item's status information
+pub fn update_info(name: &str, info: ProgressInfo) {
+ let Some(center) = PROGRESS_CENTER.get() else {
+ return;
+ };
+ let mut state = center.tx.borrow().clone();
+ let entry = state
+ .entry(name.to_string())
+ .or_insert_with(|| ProgressState {
+ progress: 0.0,
+ info: ProgressInfo::default(),
+ });
+ entry.info = info;
+ let _ = center.tx.send(state);
+}