From 85f36165c3e8cae21c32f2cf7ad1034efcffa716 Mon Sep 17 00:00:00 2001 From: maciektr Date: Tue, 4 Jun 2024 14:26:00 +0200 Subject: [PATCH] Print Cargo logs with SpinnerBar::println --- scarb/src/bin/scarb/commands/commands.rs | 4 +- scarb/src/bin/scarb/commands/run.rs | 4 +- .../compiler/plugin/proc_macro/compilation.rs | 4 +- scarb/tests/build_cairo_plugin.rs | 21 +------ utils/scarb-ui/src/components/machine.rs | 2 +- utils/scarb-ui/src/components/spinner.rs | 29 ++++++++-- utils/scarb-ui/src/components/status.rs | 2 +- utils/scarb-ui/src/components/typed.rs | 2 +- utils/scarb-ui/src/components/value.rs | 2 +- utils/scarb-ui/src/lib.rs | 55 ++++++++++++++++++- utils/scarb-ui/src/message.rs | 8 +-- utils/scarb-ui/src/widget.rs | 24 +++++++- 12 files changed, 117 insertions(+), 40 deletions(-) diff --git a/scarb/src/bin/scarb/commands/commands.rs b/scarb/src/bin/scarb/commands/commands.rs index 0a6e194e5..33361d5fd 100644 --- a/scarb/src/bin/scarb/commands/commands.rs +++ b/scarb/src/bin/scarb/commands/commands.rs @@ -34,9 +34,9 @@ struct CommandsList { } impl Message for CommandsList { - fn text(self) -> String { + fn text(&self) -> String { let mut text = String::from("Installed Commands:\n"); - for (name, info) in self.commands { + for (name, info) in self.commands.iter() { text.push_str(&format!("{:<22}: {}\n", name, info)); } text diff --git a/scarb/src/bin/scarb/commands/run.rs b/scarb/src/bin/scarb/commands/run.rs index e089f55d5..3c5017a06 100644 --- a/scarb/src/bin/scarb/commands/run.rs +++ b/scarb/src/bin/scarb/commands/run.rs @@ -93,14 +93,14 @@ struct ScriptsList { } impl Message for ScriptsList { - fn text(self) -> String { + fn text(&self) -> String { let mut text = String::new(); write!(text, "Scripts available via `scarb run`",).unwrap(); if !self.single_package { write!(text, " for package `{}`", self.package).unwrap(); } writeln!(text, ":",).unwrap(); - for (name, definition) in self.scripts { + for (name, definition) in self.scripts.iter() { writeln!(text, "{:<22}: {}", name, definition).unwrap(); } text diff --git a/scarb/src/compiler/plugin/proc_macro/compilation.rs b/scarb/src/compiler/plugin/proc_macro/compilation.rs index 9c4c6c060..774bc1253 100644 --- a/scarb/src/compiler/plugin/proc_macro/compilation.rs +++ b/scarb/src/compiler/plugin/proc_macro/compilation.rs @@ -163,8 +163,8 @@ impl PipedText { } impl Message for PipedText { - fn text(self) -> String { - self.0 + fn text(&self) -> String { + self.0.to_string() } fn structured(self, ser: S) -> Result { diff --git a/scarb/tests/build_cairo_plugin.rs b/scarb/tests/build_cairo_plugin.rs index ed58def42..a66e34ce4 100644 --- a/scarb/tests/build_cairo_plugin.rs +++ b/scarb/tests/build_cairo_plugin.rs @@ -142,14 +142,8 @@ fn compile_cairo_plugin() { let stdout = String::from_utf8_lossy(&output.stdout).to_string(); assert!(stdout.contains("Compiling some v1.0.0")); let lines = stdout.lines().map(ToString::to_string).collect::>(); - let (last, lines) = lines.split_last().unwrap(); - assert_matches(r#"[..] Finished release target(s) in [..]"#, last); let (last, _lines) = lines.split_last().unwrap(); - // Line from Cargo output - assert_matches( - r#"[..]Finished `release` profile [optimized] target(s) in[..]"#, - last, - ); + assert_matches(r#"[..] Finished release target(s) in [..]"#, last); } #[test] @@ -171,14 +165,8 @@ fn check_cairo_plugin() { let stdout = String::from_utf8_lossy(&output.stdout).to_string(); assert!(stdout.contains("Checking some v1.0.0")); let lines = stdout.lines().map(ToString::to_string).collect::>(); - let (last, lines) = lines.split_last().unwrap(); - assert_matches(r#"[..] Finished checking release target(s) in [..]"#, last); let (last, _lines) = lines.split_last().unwrap(); - // Line from Cargo output - assert_matches( - r#"[..]Finished `release` profile [optimized] target(s) in[..]"#, - last, - ); + assert_matches(r#"[..] Finished checking release target(s) in [..]"#, last); } #[test] @@ -225,14 +213,11 @@ fn can_use_json_output() { r#"{"status":"checking","message":"some v1.0.0 ([..]Scarb.toml)"}"#, first, ); - let (last, lines) = lines.split_last().unwrap(); + let (last, _lines) = lines.split_last().unwrap(); assert_matches( r#"{"status":"finished","message":"checking release target(s) in [..]"}"#, last, ); - // Line from Cargo. - let (last, _lines) = lines.split_last().unwrap(); - assert_matches(r#"{"reason":"build-finished","success":true}"#, last); } #[test] diff --git a/utils/scarb-ui/src/components/machine.rs b/utils/scarb-ui/src/components/machine.rs index fba6151ab..a3974ab51 100644 --- a/utils/scarb-ui/src/components/machine.rs +++ b/utils/scarb-ui/src/components/machine.rs @@ -12,7 +12,7 @@ impl Message for MachineMessage where T: Serialize, { - fn text(self) -> String { + fn text(&self) -> String { serde_json::to_string_pretty(&self.0).expect("MachineData must serialize without panics") } diff --git a/utils/scarb-ui/src/components/spinner.rs b/utils/scarb-ui/src/components/spinner.rs index 06025e0b2..af84cff92 100644 --- a/utils/scarb-ui/src/components/spinner.rs +++ b/utils/scarb-ui/src/components/spinner.rs @@ -1,8 +1,9 @@ +use std::sync::{Arc, Weak}; use std::time::Duration; use indicatif::{ProgressBar, ProgressStyle}; -use crate::Widget; +use crate::{PrintOverrider, PrintOverriderState, Widget, WidgetHandle}; /// Spinner widget informing about an ongoing process. pub struct Spinner { @@ -24,7 +25,7 @@ impl Spinner { /// Finishes the associated [`Spinner`] when dropped. pub struct SpinnerHandle { - pb: ProgressBar, + pb: Arc, } impl Drop for SpinnerHandle { @@ -33,13 +34,31 @@ impl Drop for SpinnerHandle { } } +impl WidgetHandle for SpinnerHandle { + fn print_driver(&self) -> Option> { + struct Overrider(Weak); + impl PrintOverrider for Overrider { + fn println(&self, message: &str) -> PrintOverriderState { + let Some(pb) = self.0.upgrade() else { + return PrintOverriderState::Inactive; + }; + pb.println(message); + PrintOverriderState::Active + } + } + Some(Box::new(Overrider(Arc::downgrade(&self.pb)))) + } +} + impl Widget for Spinner { type Handle = SpinnerHandle; fn text(self) -> Self::Handle { - let pb = ProgressBar::new_spinner() - .with_style(Spinner::default_style()) - .with_message(self.message); + let pb = Arc::new( + ProgressBar::new_spinner() + .with_style(Spinner::default_style()) + .with_message(self.message), + ); pb.enable_steady_tick(Duration::from_millis(120)); SpinnerHandle { pb } } diff --git a/utils/scarb-ui/src/components/status.rs b/utils/scarb-ui/src/components/status.rs index ca245b247..2b14bc32d 100644 --- a/utils/scarb-ui/src/components/status.rs +++ b/utils/scarb-ui/src/components/status.rs @@ -36,7 +36,7 @@ impl<'a> Status<'a> { } impl<'a> Message for Status<'a> { - fn text(self) -> String { + fn text(&self) -> String { format!( "{} {}", Style::from_dotted_str(self.color).bold().apply_to(pad_str( diff --git a/utils/scarb-ui/src/components/typed.rs b/utils/scarb-ui/src/components/typed.rs index 5c7caeada..2611c6ba9 100644 --- a/utils/scarb-ui/src/components/typed.rs +++ b/utils/scarb-ui/src/components/typed.rs @@ -65,7 +65,7 @@ impl<'a> TypedMessage<'a> { } impl<'a> Message for TypedMessage<'a> { - fn text(self) -> String { + fn text(&self) -> String { if self.skip_type_for_text { self.message.to_string() } else { diff --git a/utils/scarb-ui/src/components/value.rs b/utils/scarb-ui/src/components/value.rs index 542efd5c6..848319b05 100644 --- a/utils/scarb-ui/src/components/value.rs +++ b/utils/scarb-ui/src/components/value.rs @@ -29,7 +29,7 @@ impl<'a, T> Message for ValueMessage<'a, T> where T: Display + Serialize, { - fn text(self) -> String { + fn text(&self) -> String { self.value.to_string() } diff --git a/utils/scarb-ui/src/lib.rs b/utils/scarb-ui/src/lib.rs index c9a863f8c..520d7442d 100644 --- a/utils/scarb-ui/src/lib.rs +++ b/utils/scarb-ui/src/lib.rs @@ -26,6 +26,8 @@ pub use indicatif::{ BinaryBytes, DecimalBytes, FormattedDuration, HumanBytes, HumanCount, HumanDuration, HumanFloatCount, }; +use std::fmt::Debug; +use std::sync::{Arc, RwLock}; pub use message::*; pub use verbosity::*; @@ -53,10 +55,28 @@ pub enum OutputFormat { /// colour, etc. /// /// All human-oriented messaging (basically all writes to `stdout`) must go through this object. -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct Ui { verbosity: Verbosity, output_format: OutputFormat, + state: Arc>, +} + +impl Debug for Ui { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Ui") + .field("verbosity", &self.verbosity) + .field("output_format", &self.output_format) + .finish() + } +} + +/// An encapsulation of the UI state. +/// +/// This can be used by `Ui` to store stateful information. +#[derive(Default)] +pub struct State { + active_print_overrider: Option>, } impl Ui { @@ -65,6 +85,9 @@ impl Ui { Self { verbosity, output_format, + state: Arc::new(RwLock::new(State { + active_print_overrider: None, + })), } } @@ -103,6 +126,13 @@ impl Ui { pub fn widget(&self, widget: T) -> Option { if self.output_format == OutputFormat::Text && self.verbosity >= Verbosity::Normal { let handle = widget.text(); + // Store the handle in state for further interaction. + if let Some(driver) = handle.print_driver() { + self.state + .write() + .expect("failed to acquire write lock") + .active_print_overrider = Some(driver); + } Some(handle) } else { None @@ -149,7 +179,28 @@ impl Ui { fn do_print(&self, message: T) { match self.output_format { - OutputFormat::Text => message.print_text(), + OutputFormat::Text => { + let read_lock = self.state.read().expect("failed to acquire read lock"); + if let Some(widget) = read_lock.active_print_overrider.as_ref() { + match widget.println(message.text().as_str()) { + PrintOverriderState::Active => { + // Print has succeeded. + } + PrintOverriderState::Inactive => { + // The driver is inactive. Print has failed. + message.print_text(); + // Clear widget from state. + drop(read_lock); + self.state + .write() + .expect("failed to acquire write lock") + .active_print_overrider = None; + } + } + } else { + message.print_text() + } + } OutputFormat::Json => message.print_json(), } } diff --git a/utils/scarb-ui/src/message.rs b/utils/scarb-ui/src/message.rs index d3d66b76a..5bfa1b2cc 100644 --- a/utils/scarb-ui/src/message.rs +++ b/utils/scarb-ui/src/message.rs @@ -16,7 +16,7 @@ pub trait Message { /// Return textual representation of this message. /// /// Default implementation returns empty string, making [`Ui`] skip printing this message. - fn text(self) -> String + fn text(&self) -> String where Self: Sized, { @@ -75,13 +75,13 @@ pub trait Message { } impl Message for &str { - fn text(self) -> String { + fn text(&self) -> String { self.to_string() } } impl Message for String { - fn text(self) -> String { - self + fn text(&self) -> String { + self.to_string() } } diff --git a/utils/scarb-ui/src/widget.rs b/utils/scarb-ui/src/widget.rs index 95055d8c8..cf6954f08 100644 --- a/utils/scarb-ui/src/widget.rs +++ b/utils/scarb-ui/src/widget.rs @@ -2,8 +2,30 @@ pub trait Widget { /// Allows for live interaction with the widget, and its drop is called when the widget should /// be cleared. - type Handle; + type Handle: WidgetHandle; /// Display the widget on the standard output, and return a handle for further interaction. fn text(self) -> Self::Handle; } + +/// A handle for a widget that allows for interaction with it. +pub trait WidgetHandle { + /// Get the print driver for the widget. + fn print_driver(&self) -> Option> { + None + } +} + +/// The state of the print overrider handle. +pub enum PrintOverriderState { + /// The print overrider is active and can be used. + Active, + /// The print overrider is no longer active and cannot be used. + Inactive, +} + +/// A trait for providing the print functionality of a widget. +pub trait PrintOverrider: Sync + Send { + /// Print the message to the widget controlled output. + fn println(&self, message: &str) -> PrintOverriderState; +}