diff --git a/Cargo.lock b/Cargo.lock index 4d5dec2..b32df35 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -82,6 +82,7 @@ dependencies = [ "flate2", "git2", "glob-match", + "indicatif", "inquire", "kdl", "reqwest", @@ -211,6 +212,19 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "console" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys 0.52.0", +] + [[package]] name = "core-foundation" version = "0.9.3" @@ -310,6 +324,12 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "encoding_rs" version = "0.8.31" @@ -634,6 +654,19 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "indicatif" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" +dependencies = [ + "console", + "instant", + "number_prefix", + "portable-atomic", + "unicode-width", +] + [[package]] name = "inquire" version = "0.7.0" @@ -905,6 +938,12 @@ dependencies = [ "libc", ] +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + [[package]] name = "object" version = "0.32.1" @@ -1018,6 +1057,12 @@ version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" +[[package]] +name = "portable-atomic" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" + [[package]] name = "ppv-lite86" version = "0.2.16" diff --git a/Cargo.toml b/Cargo.toml index 65e771b..12842ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ dirs = "5.0.1" flate2 = { version = "1.0.28" } git2 = { version = "0.18.1", features = ["vendored-libgit2"] } glob-match = { version = "0.2.1" } +indicatif = "0.17.8" inquire = { version = "0.7.0", features = ["editor"] } kdl = { version = "4.6.0" } reqwest = { version = "0.11.22", features = ["json"] } diff --git a/src/actions/actions.rs b/src/actions/actions.rs index cc5a6d8..83879c7 100644 --- a/src/actions/actions.rs +++ b/src/actions/actions.rs @@ -1,10 +1,13 @@ use std::path::{Path, PathBuf}; +use std::process; +use crossterm::style::Stylize; use run_script::ScriptOptions; use unindent::Unindent; use crate::actions::{State, Value}; use crate::manifest::actions::*; +use crate::spinner::Spinner; impl Copy { pub async fn execute(&self) -> anyhow::Result<()> { @@ -52,6 +55,7 @@ impl Run { P: Into + AsRef, { let mut command = self.command.clone(); + let spinner = Spinner::new(); if let Some(injects) = &self.injects { for inject in injects { @@ -62,13 +66,44 @@ impl Run { } } + let name = self + .name + .clone() + .or_else(|| { + let lines = command.trim().lines().count(); + + if lines > 1 { + Some(command.trim().lines().next().unwrap().to_string() + "...") + } else { + Some(command.clone()) + } + }) + .unwrap(); + let options = ScriptOptions { working_directory: Some(root.into()), ..ScriptOptions::new() }; - // NOTE: This will exit the main process in case of error. - let (output, _) = run_script::run_script_or_exit!(command, options); + spinner.set_message(format!("{}", name.clone().grey())); + + // Actually run the script. + let (code, output, err) = run_script::run_script!(command, options)?; + let has_failed = code > 0; + + // Re-format depending on the exit code. + let name = if has_failed { name.red() } else { name.green() }; + + // Stopping before printing output/errors, otherwise the spinner message won't be cleared. + spinner.stop_with_message(format!("{name}\n",)); + + if has_failed { + if !err.is_empty() { + eprintln!("{err}"); + } + + process::exit(1); + } Ok(println!("{}", output.trim())) } diff --git a/src/actions/executor.rs b/src/actions/executor.rs index 0fab769..78e1ae6 100644 --- a/src/actions/executor.rs +++ b/src/actions/executor.rs @@ -73,8 +73,8 @@ impl Executor { let mut state = State::new(); for ActionSuite { name, actions, .. } in suites { - let symbol = "⦿".blue().bold(); - let title = "Running suite".blue(); + let symbol = "+".blue().bold(); + let title = "Suite".blue(); let name = name.clone().green(); println!("{symbol} {title}: {name}\n"); diff --git a/src/lib.rs b/src/lib.rs index 0d1c7d9..fd3dc81 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,4 +6,5 @@ pub mod fs; pub mod manifest; pub mod path; pub mod repository; +pub mod spinner; pub mod unpacker; diff --git a/src/spinner.rs b/src/spinner.rs new file mode 100644 index 0000000..83408af --- /dev/null +++ b/src/spinner.rs @@ -0,0 +1,53 @@ +use std::time::Duration; + +use indicatif::{ProgressBar, ProgressStyle}; + +/// Small wrapper around the `indicatif` spinner. +pub struct Spinner { + spinner: ProgressBar, +} + +impl Spinner { + /// Creates a new spinner. + pub fn new() -> Self { + let style = ProgressStyle::default_spinner().tick_chars("⠋⠙⠚⠒⠂⠂⠒⠲⠴⠦⠖⠒⠐⠐⠒⠓⠋·"); + let spinner = ProgressBar::new_spinner(); + + spinner.set_style(style); + spinner.enable_steady_tick(Duration::from_millis(80)); + + Self { spinner } + } + + /// Sets the message of the spinner. + pub fn set_message(&self, message: S) + where + S: Into + AsRef, + { + self.spinner.set_message(message.into()); + } + + /// Stops the spinner. + pub fn stop(&self) { + self.spinner.finish(); + } + + /// Stops the spinner with the message. + pub fn stop_with_message(&self, message: S) + where + S: Into + AsRef, + { + self.spinner.finish_with_message(message.into()); + } + + /// Stops the spinner and clears the message. + pub fn stop_with_clear(&self) { + self.spinner.finish_and_clear(); + } +} + +impl Default for Spinner { + fn default() -> Self { + Self::new() + } +}