diff --git a/src/cli.rs b/src/cli.rs index 993301dc..a6946b71 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -59,7 +59,11 @@ pub enum Commands { /// Show status of the foc-devnet system Status, /// Show version information - Version, + Version { + /// Force plain output without tracing prefixes, even when stdout is a terminal + #[arg(long)] + notty: bool, + }, } /// Build subcommands diff --git a/src/main.rs b/src/main.rs index 1a076fb6..f24a4188 100644 --- a/src/main.rs +++ b/src/main.rs @@ -49,7 +49,7 @@ fn main() -> Result<(), Box> { main_app::command_handlers::handle_build(build_command) } Commands::Status => main_app::command_handlers::handle_status(), - Commands::Version => main_app::version::handle_version(), + Commands::Version { notty } => main_app::version::handle_version(notty), }; // Handle the result diff --git a/src/main_app/version.rs b/src/main_app/version.rs index a0c6a576..0e53c054 100644 --- a/src/main_app/version.rs +++ b/src/main_app/version.rs @@ -4,10 +4,26 @@ use foc_devnet::config::{Config, Location}; use foc_devnet::version_info::VersionInfo; +use std::io::IsTerminal; use tracing::info; -/// Execute the version command -pub fn handle_version() -> Result<(), Box> { +/// Emit a line either as a tracing INFO event or a plain println. +macro_rules! emit { + ($plain:expr, $fmt:literal $(, $arg:expr)*) => { + if $plain { + println!($fmt $(, $arg)*); + } else { + info!($fmt $(, $arg)*); + } + }; +} + +/// Execute the version command. +/// +/// Plain output (no tracing prefixes) is used when stdout is not a terminal, +/// or when `notty` is `true` (explicit override). +pub fn handle_version(notty: bool) -> Result<(), Box> { + let plain = notty || !std::io::stdout().is_terminal(); // Version information is read-only, no poison protection needed let version_info = VersionInfo::from_env(); let dirty_suffix = if version_info.dirty.is_empty() { @@ -16,59 +32,71 @@ pub fn handle_version() -> Result<(), Box> { "-dirty" }; - info!("foc-devnet {}", version_info.version); - info!("Commit: {}{}", version_info.commit, dirty_suffix); - info!("Branch: {}", version_info.branch); + emit!(plain, "foc-devnet {}", version_info.version); + emit!(plain, "Commit: {}{}", version_info.commit, dirty_suffix); + emit!(plain, "Branch: {}", version_info.branch); - // Calculate relative time - let now = chrono::Utc::now().timestamp(); - let diff_seconds = now - version_info.build_timestamp; - - let relative_time = if diff_seconds < 60 { - format!("({} seconds ago)", diff_seconds) - } else if diff_seconds < 3600 { - format!("({} minutes ago)", diff_seconds / 60) - } else if diff_seconds < 86400 { - format!("({} hours ago)", diff_seconds / 3600) - } else { - format!("({} days ago)", diff_seconds / 86400) - }; + let relative_time = + format_relative_time(chrono::Utc::now().timestamp() - version_info.build_timestamp); - info!( + emit!( + plain, "Built (UTC): {} {}", - version_info.build_time_utc, relative_time + version_info.build_time_utc, + relative_time ); - info!("Built (Local): {}", version_info.build_time_local); + emit!(plain, "Built (Local): {}", version_info.build_time_local); - // Print default configuration values let default_config = Config::default(); - info!(""); - print_location_info("default:code:lotus", &default_config.lotus); - print_location_info("default:code:curio", &default_config.curio); + emit!(plain, ""); + print_location_info(plain, "default:code:lotus", &default_config.lotus); + print_location_info(plain, "default:code:curio", &default_config.curio); print_location_info( + plain, "default:code:filecoin-services", &default_config.filecoin_services, ); - print_location_info("default:code:multicall3", &default_config.multicall3); - info!("default:yugabyte: {}", default_config.yugabyte_download_url); + print_location_info(plain, "default:code:multicall3", &default_config.multicall3); + emit!( + plain, + "default:yugabyte: {}", + default_config.yugabyte_download_url + ); Ok(()) } -/// Print location information in a formatted way -fn print_location_info(label: &str, location: &Location) { +const SECS_PER_MIN: i64 = 60; +const SECS_PER_HOUR: i64 = 3_600; +const SECS_PER_DAY: i64 = 86_400; + +/// Format a duration in seconds as a human-readable relative time string. +fn format_relative_time(diff_seconds: i64) -> String { + if diff_seconds < SECS_PER_MIN { + format!("({} seconds ago)", diff_seconds) + } else if diff_seconds < SECS_PER_HOUR { + format!("({} minutes ago)", diff_seconds / SECS_PER_MIN) + } else if diff_seconds < SECS_PER_DAY { + format!("({} hours ago)", diff_seconds / SECS_PER_HOUR) + } else { + format!("({} days ago)", diff_seconds / SECS_PER_DAY) + } +} + +/// Print location information in a formatted way. +fn print_location_info(plain: bool, label: &str, location: &Location) { match location { Location::LocalSource { dir } => { - info!("{}: local source at {}", label, dir); + emit!(plain, "{}: local source at {}", label, dir); } Location::GitCommit { url, commit } => { - info!("{}: {}, commit {}", label, url, commit); + emit!(plain, "{}: {}, commit {}", label, url, commit); } Location::GitTag { url, tag } => { - info!("{}: {}, tag {}", label, url, tag); + emit!(plain, "{}: {}, tag {}", label, url, tag); } Location::GitBranch { url, branch } => { - info!("{}: {}, branch {}", label, url, branch); + emit!(plain, "{}: {}, branch {}", label, url, branch); } } }