diff --git a/wasm-rpc-stubgen/src/commands/app.rs b/wasm-rpc-stubgen/src/commands/app.rs index 595cab53..743c9537 100644 --- a/wasm-rpc-stubgen/src/commands/app.rs +++ b/wasm-rpc-stubgen/src/commands/app.rs @@ -2,8 +2,8 @@ use crate::cargo::regenerate_cargo_package_component; use crate::fs; use crate::fs::PathExtra; use crate::log::{ - log_action, log_skipping_up_to_date, log_validated_action_result, log_warn_action, LogColorize, - LogIndent, + log_action, log_skipping_up_to_date, log_validated_action_result, log_warn_action, + set_log_output, LogColorize, LogIndent, Output, }; use crate::model::app::{ includes_from_yaml_file, Application, ComponentName, ComponentPropertiesExtensions, @@ -27,6 +27,7 @@ use itertools::Itertools; use std::cell::OnceCell; use std::cmp::Ordering; use std::collections::{BTreeSet, HashMap}; +use std::fmt::Write; use std::marker::PhantomData; use std::path::{Path, PathBuf}; use std::process::Command; @@ -39,6 +40,7 @@ pub struct Config { pub profile: Option, pub offline: bool, pub extensions: PhantomData, + pub log_output: Output, } #[derive(Debug, Clone)] @@ -57,7 +59,10 @@ pub struct ApplicationContext { impl ApplicationContext { pub fn new(config: Config) -> anyhow::Result> { + set_log_output(config.log_output); + let ctx = to_anyhow( + config.log_output, "Failed to create application context, see problems above", load_app_validated(&config).and_then(|application| { ResolvedWitApplication::new(&application, config.profile.as_ref()).map(|wit| { @@ -252,6 +257,7 @@ impl ApplicationContext { fn update_wit_context(&mut self) -> anyhow::Result<()> { to_anyhow( + self.config.log_output, "Failed to update application wit context, see problems above", ResolvedWitApplication::new(&self.application, self.profile()).map(|wit| { self.wit = wit; @@ -486,6 +492,7 @@ pub async fn build(config: Config) -> a pub fn clean(config: Config) -> anyhow::Result<()> { let app = to_anyhow( + config.log_output, "Failed to load application manifest(s), see problems above", load_app_validated(&config), )?; @@ -579,6 +586,7 @@ pub fn available_custom_commands( config: Config, ) -> anyhow::Result> { let app = to_anyhow( + config.log_output, "Failed to load application manifest(s), see problems above", load_app_validated(&config), )?; @@ -781,36 +789,78 @@ fn find_main_source() -> Option { last_source } -fn to_anyhow(message: &str, result: ValidatedResult) -> anyhow::Result { - fn print_warns(warns: Vec) { +fn to_anyhow( + log_output: Output, + message: &str, + result: ValidatedResult, +) -> anyhow::Result { + fn format_warns(warns: Vec) -> String { let label = "Warning".yellow(); - for warn in warns { - eprintln!("{}: {}", label, warn); - } + warns + .into_iter() + .map(|warn| format!("{}: {}", label, warn)) + .join("\n") } - fn print_errors(errors: Vec) { + fn format_errors(errors: Vec) -> String { let label = "Error".red(); - for error in errors { - eprintln!("{}: {}", label, error); - } + errors + .into_iter() + .map(|error| format!("{}: {}", label, error)) + .join("\n") } match result { ValidatedResult::Ok(value) => Ok(value), ValidatedResult::OkWithWarns(components, warns) => { - println!(); - print_warns(warns); - println!(); + match log_output { + Output::Stdout => { + println!("\n{}\n", format_warns(warns)); + } + Output::Stderr => { + eprintln!("\n{}\n", format_warns(warns)); + } + Output::None => { + // NOP + } + } + Ok(components) } ValidatedResult::WarnsAndErrors(warns, errors) => { - println!(); - print_warns(warns); - print_errors(errors); - println!(); + let message = match log_output { + Output::Stdout => { + println!("\n"); + println!("{}", format_warns(warns)); + println!("{}", format_errors(errors)); + println!("\n"); + + message.to_string() + } + Output::Stderr => { + eprintln!("\n"); + eprintln!("{}", format_warns(warns)); + eprintln!("{}", format_errors(errors)); + eprintln!("\n"); + + message.to_string() + } + Output::None => { + fn with_new_line_if_not_empty(mut str: String) -> String { + if !str.is_empty() { + str.write_char('\n').unwrap() + } + str + } + + let warns = with_new_line_if_not_empty(format_warns(warns)); + let errors = with_new_line_if_not_empty(format_errors(errors)); + + format!("\n{}{}\n{}", warns, errors, message) + } + }; - Err(anyhow!(message.to_string())) + Err(anyhow!(message)) } } } diff --git a/wasm-rpc-stubgen/src/lib.rs b/wasm-rpc-stubgen/src/lib.rs index b6c3ec5c..57541ecc 100644 --- a/wasm-rpc-stubgen/src/lib.rs +++ b/wasm-rpc-stubgen/src/lib.rs @@ -27,6 +27,7 @@ pub mod wit_encode; pub mod wit_generate; pub mod wit_resolve; +use crate::log::Output; use crate::model::app::{ComponentPropertiesExtensions, ComponentPropertiesExtensionsAny}; use crate::stub::{StubConfig, StubDefinition}; use crate::wit_generate::UpdateCargoToml; @@ -309,6 +310,7 @@ pub async fn run_app_command( profile: args.profile.map(|profile| profile.into()), offline: args.offline, extensions: PhantomData::, + log_output: Output::Stdout, }) .await } @@ -318,6 +320,7 @@ pub async fn run_app_command( profile: None, offline: false, extensions: PhantomData::, + log_output: Output::Stdout, }), App::CustomCommand(_args) => { // TODO: parse app manifest / profile args diff --git a/wasm-rpc-stubgen/src/log.rs b/wasm-rpc-stubgen/src/log.rs index eff0d723..879deb04 100644 --- a/wasm-rpc-stubgen/src/log.rs +++ b/wasm-rpc-stubgen/src/log.rs @@ -6,9 +6,17 @@ use std::sync::{LazyLock, RwLock}; static LOG_STATE: LazyLock> = LazyLock::new(RwLock::default); +#[derive(Debug, Clone, Copy)] +pub enum Output { + Stdout, + Stderr, + None, +} + struct LogState { indent_count: usize, indent_prefix: String, + output: Output, } impl LogState { @@ -16,6 +24,7 @@ impl LogState { Self { indent_count: 0, indent_prefix: "".to_string(), + output: Output::Stdout, } } @@ -32,6 +41,10 @@ impl LogState { fn regen_indent_prefix(&mut self) { self.indent_prefix = " ".repeat(self.indent_count); } + + fn set_output(&mut self, output: Output) { + self.output = output; + } } impl Default for LogState { @@ -61,22 +74,68 @@ impl Drop for LogIndent { } } +pub struct LogOutput { + prev_output: Output, +} + +impl LogOutput { + pub fn new(output: Output) -> Self { + let prev_output = LOG_STATE.read().unwrap().output; + LOG_STATE.write().unwrap().set_output(output); + Self { prev_output } + } +} + +impl Drop for LogOutput { + fn drop(&mut self) { + LOG_STATE.write().unwrap().set_output(self.prev_output); + } +} + +pub fn set_log_output(output: Output) { + LOG_STATE.write().unwrap().set_output(output); +} + pub fn log_action>(action: &str, subject: T) { - println!( + let state = LOG_STATE.read().unwrap(); + let message = format!( "{}{} {}", - LOG_STATE.read().unwrap().indent_prefix, + state.indent_prefix, action.log_color_action(), subject.as_ref() - ) + ); + + match state.output { + Output::Stdout => { + println!("{}", message); + } + Output::Stderr => { + eprintln!("{}", message); + } + Output::None => { + // NOP + } + } } pub fn log_warn_action>(action: &str, subject: T) { - println!( + let state = LOG_STATE.read().unwrap(); + let message = format!( "{}{} {}", LOG_STATE.read().unwrap().indent_prefix, action.log_color_warn(), subject.as_ref(), - ) + ); + + match state.output { + Output::Stdout => { + println!("{}", message) + } + Output::Stderr => { + eprintln!("{}", message) + } + Output::None => {} + } } pub fn log_skipping_up_to_date>(subject: T) {