From fc3fcd4b205c88b0f82dfbfb1533bde07a46a5b1 Mon Sep 17 00:00:00 2001 From: rustN00b Date: Thu, 14 Jul 2022 20:05:49 +0200 Subject: [PATCH] Add a configurable drop behavior for Spinner This commit adds an `on_drop` method to Spinner, the method is a "Builder method" (called on a Spinner taking ownership and returns a new Spinner instance). The existing Spinner API therefore continue to work as they did before with no changes to methods or their parameters. By default, i.e. when `on_drop` is not used to set the default behavior of a Spinner, its behavior remains the same as prior to this commit. When `on_drop` is called on a Spinner, a new Spinner instance is returned. If this instance is dropped without any of its `stop*` methods having been called, then the `StopBehavior` set in the `on_drop` call is triggered. This change allows the following simple construction: ``` { let sp = Spinner::new(...).on_drop(StopBehavior::Message("Failed!"); do_something_fallible()?; sp.stop_with_message("Yay, it worked); } ``` If `do_something_fallible()` returns a `Result::Ok` the Spinner is stopped with the "success" message. Conversely, if `do_something_fallible()` returns a `Result::Err`, the Spinner is stopped with the "Failed!" message when it is dropped. --- examples/on_drop.rs | 46 ++++++++++++++++++++ src/lib.rs | 102 ++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 139 insertions(+), 9 deletions(-) create mode 100644 examples/on_drop.rs diff --git a/examples/on_drop.rs b/examples/on_drop.rs new file mode 100644 index 0000000..a23fa04 --- /dev/null +++ b/examples/on_drop.rs @@ -0,0 +1,46 @@ +use spinners::{Spinner, Spinners, StopBehavior}; + +fn frobnicate(should_work: bool) -> Result<(), String> { + if should_work { + Ok(()) + } else { + Err("Error!".to_string()) + } +} + +fn main() -> Result<(), String> { + // Successful variant shows the message from [Spinner::stop_with] + // and not the one from [Spinner::on_drop] + { + println!("Spinner that is stopped after some action is successfully performed:"); + let mut sp = Spinner::new(Spinners::Dots, "Frobincating...".into()).on_drop( + StopBehavior::SymbolAndMessage( + "✗", + "Ouch, something went wrong with the Frobnicator!?".into(), + ), + ); + frobnicate(true)?; + sp.stop_with(&StopBehavior::SymbolAndMessage( + "✔", + "Frobnication successful!".into(), + )); + } + + // Failure variant in which the Spinner is dropped before having been explicitly + // stopped, the behavior passed into [Spinner::on_drop] is used. + { + println!("Spinner that never reaches a stop call because an error is hit before:"); + let mut sp = Spinner::new(Spinners::Dots, "Frobincating...".into()).on_drop( + StopBehavior::SymbolAndMessage( + "✗", + "Ouch, something went wrong with the Frobnicator!?".into(), + ), + ); + frobnicate(false)?; + sp.stop_with(&StopBehavior::SymbolAndMessage( + "✔", + "Frobnication successful!".into(), + )); + } + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs index 05d07cb..aaa605e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,16 +12,21 @@ use crate::utils::spinners_data::SPINNERS as SpinnersMap; mod utils; -pub struct Spinner { +struct SpinnerHandle { sender: Sender<(Instant, Option)>, - join: Option>, + join: JoinHandle<()>, +} + +pub struct Spinner { + handle: Option, + on_drop: Option, } impl Drop for Spinner { fn drop(&mut self) { - if self.join.is_some() { - self.sender.send((Instant::now(), None)).unwrap(); - self.join.take().unwrap().join().unwrap(); + if self.handle.is_some() { + let behavior = self.on_drop.take().unwrap(); + self.stop_with(&behavior); } } } @@ -95,8 +100,8 @@ impl Spinner { }); Self { - sender, - join: Some(join), + handle: Some(SpinnerHandle { sender, join }), + on_drop: Some(StopBehavior::NewLine), } } @@ -211,9 +216,88 @@ impl Spinner { } fn stop_inner(&mut self, stop_time: Instant, stop_symbol: Option) { - self.sender + let hdl = self.handle.take().unwrap(); + + hdl.sender .send((stop_time, stop_symbol)) .expect("Could not stop spinner thread."); - self.join.take().unwrap().join().unwrap(); + + hdl.join.join().unwrap(); + } + + /// Builder method to convert a Spinner to a one with a new [StopBehavior]. + /// + /// If the spinner is dropped without any prior call to one of its `Spinner::stop*` + /// methods, then the [StopBehavior] from the `behavior` parameter is triggered. + /// + /// Example: + /// ``` + /// use spinners::{Spinner, Spinners, StopBehavior}; + /// + /// # fn foo() -> Result<(), String> { + /// fn frobnicate(should_work: bool) -> Result<(), String> { + /// if should_work { + /// Ok(()) + /// } else { + /// Err("Error!".to_string()) + /// } + /// } + /// + /// // Successful variant shows the message from [Spinner::stop_with] + /// // and not the one from [Spinner::on_drop] + /// { + /// let mut sp = Spinner::new(Spinners::Dots, "Frobincating...".into()) + /// .on_drop(StopBehavior::SymbolAndMessage( + /// "✗", + /// "Ouch, something went wrong with the Frobnicator!?".into())); + /// frobnicate(true)?; + /// sp.stop_with(&StopBehavior::SymbolAndMessage( + /// "✔", "Frobnication successful!".into())); + /// } + /// + /// // Failure variant in which the Spinner is dropped before having been explicitly + /// // stopped, the behavior passed into [Spinner::on_drop] is used. + /// { + /// let mut sp = Spinner::new(Spinners::Dots, "Frobincating...".into()) + /// .on_drop(StopBehavior::SymbolAndMessage( + /// "✗", + /// "Ouch, something went wrong with the Frobnicator!?".into())); + /// frobnicate(false)?; + /// sp.stop_with(&StopBehavior::SymbolAndMessage( + /// "✔", "Frobnication successful!".into())); + /// } + /// # Ok(()) + /// # } + /// ``` + pub fn on_drop(mut self, behavior: StopBehavior) -> Self { + let hdl = self.handle.take().unwrap(); + Self { + handle: Some(SpinnerHandle { + sender: hdl.sender, + join: hdl.join, + }), + on_drop: Some(behavior), + } + } + + /// Stops the spinner with a given `StopBehavior` + /// + /// Each StopBehavior matches one of the other `Spinner::stop*` methods. + pub fn stop_with(&mut self, stop_behavior: &StopBehavior) { + match stop_behavior { + StopBehavior::NewLine => self.stop_with_newline(), + StopBehavior::Message(msg) => self.stop_with_message(msg.to_owned()), + StopBehavior::Symbol(symbol) => self.stop_with_symbol(symbol), + StopBehavior::SymbolAndMessage(symbol, msg) => { + self.stop_and_persist(symbol, msg.to_owned()) + } + } } } + +pub enum StopBehavior { + NewLine, + Message(String), + Symbol(&'static str), + SymbolAndMessage(&'static str, String), +}