use std::process;
use clap::{Arg, ArgMatches, Command};
use pwrzv::{
MetricDetail, PwrzvError, check_platform, get_platform_name, get_power_reserve_level_direct,
get_power_reserve_level_with_details_direct,
};
use std::collections::HashMap;
use std::time::Duration;
use tokio::time::sleep;
const VERSION: &str = env!("CARGO_PKG_VERSION");
#[tokio::main]
async fn main() {
let matches = build_cli().get_matches();
if let Err(e) = run(matches).await {
eprintln!("Error: {e}");
process::exit(1);
}
}
fn build_cli() -> Command {
Command::new("pwrzv")
.version(VERSION)
.about("A cross-platform system power reserve monitoring tool inspired by Rolls-Royce cars")
.long_about(
"pwrzv monitors system resources and provides power reserve level assessment, \
inspired by the Power Reserve gauge from Rolls-Royce cars.\
\n\nSupported platforms: Linux, macOS\
\n\nBy default, pwrzv runs continuously and updates output every 3 seconds.\
\nUse --once for single-shot output.",
)
.arg(
Arg::new("detailed")
.short('d')
.long("detailed")
.value_name("FORMAT")
.help("Show detailed component scores with optional format (text, json, yaml)")
.value_parser(["text", "json", "yaml"])
.num_args(0..=1)
.default_missing_value("text"),
)
.arg(
Arg::new("interval")
.short('t')
.long("interval")
.value_name("SECONDS")
.help("Set output refresh interval in seconds (default: 3)")
.value_parser(clap::value_parser!(u64)),
)
.arg(
Arg::new("once")
.long("once")
.help("Show output once and exit")
.action(clap::ArgAction::SetTrue),
)
}
async fn run(matches: ArgMatches) -> Result<(), PwrzvError> {
if let Err(e) = check_platform() {
eprintln!("❌ Platform check failed: {e}");
eprintln!("💡 Currently only Linux and macOS are supported");
process::exit(1);
}
println!("✅ Platform check passed for: {}", get_platform_name());
if matches.get_flag("once") {
if let Some(format) = matches.get_one::<String>("detailed") {
let (level, details) = get_power_reserve_level_with_details_direct().await?;
output_detailed_result(format, level, &details)?;
} else {
let level = get_power_reserve_level_direct().await?;
println!("{level:.2}");
}
return Ok(());
}
let output_interval = matches.get_one::<u64>("interval").copied().unwrap_or(3);
eprintln!("🔄 Starting continuous monitoring (interval: {output_interval}s)");
eprintln!("💡 Press Ctrl+C to stop");
eprintln!();
loop {
let now = chrono::Local::now().format("%Y-%m-%d %H:%M:%S");
if matches.get_one::<String>("detailed").is_some() {
print!("\x1b[2J\x1b[H"); println!("{now}"); }
if let Some(format) = matches.get_one::<String>("detailed") {
match get_power_reserve_level_with_details_direct().await {
Ok((level, details)) => {
output_detailed_result(format, level, &details)?;
}
Err(e) => {
eprintln!("{now} ❌ Failed to collect metrics: {e}");
}
}
} else {
match get_power_reserve_level_direct().await {
Ok(level) => {
println!("{now} Power Reserve: {level:.2}");
}
Err(e) => {
eprintln!("{now} ❌ Failed to collect metrics: {e}");
}
}
}
sleep(Duration::from_secs(output_interval)).await;
}
}
fn output_detailed_result(
format: &str,
level: f32,
details: &HashMap<String, MetricDetail>,
) -> Result<(), PwrzvError> {
match format {
"json" => {
let json_output = serde_json::json!({
"platform": get_platform_name(),
"timestamp": chrono::Utc::now().to_rfc3339(),
"power_reserve_level": level,
"level_description": format_level_description(level),
"metrics": details,
"total_metrics": details.len()
});
println!("{}", serde_json::to_string_pretty(&json_output).unwrap());
}
"yaml" => {
let yaml_data = serde_json::json!({
"platform": get_platform_name(),
"timestamp": chrono::Utc::now().to_rfc3339(),
"power_reserve_level": level,
"level_description": format_level_description(level),
"metrics": details,
"total_metrics": details.len()
});
println!("{}", serde_yaml::to_string(&yaml_data).unwrap());
}
_ => {
println!("═══════════════════════════════════════════════════════════");
println!("🔋 Power Reserve Analysis");
println!("═══════════════════════════════════════════════════════════");
println!();
println!(
"📊 Overall Power Reserve: {:.2} {}",
level,
format_level_emoji(level)
);
println!(" Status: {}", format_level_description(level));
println!();
if !details.is_empty() {
print_metrics_section(details);
}
println!("───────────────────────────────────────────────────────────");
println!("💡 Interpretation:");
println!(" • Scores range from 1.0 (Critical) to 5.0 (Abundant)");
println!(" • Overall level is determined by the lowest component score");
println!(" • Higher precision allows for more accurate assessment");
if level < 2.0 {
println!(" ⚠️ Consider optimizing system resources");
}
}
}
Ok(())
}
fn print_metrics_section(details: &HashMap<String, MetricDetail>) {
println!("📈 Component Metrics:");
let mut sorted_metrics: Vec<_> = details.iter().collect();
sorted_metrics.sort_by(|a, b| a.1.score.partial_cmp(&b.1.score).unwrap());
for (key, detail) in sorted_metrics {
let status_emoji = format_level_emoji(detail.score);
println!(
" {key:<30}: value={:.3} score={:.3} {status_emoji}",
detail.value, detail.score
);
}
println!();
}
fn format_level_description(level: f32) -> &'static str {
if level >= 4.0 {
"Abundant - Excellent performance"
} else if level >= 3.0 {
"High - Good performance"
} else if level >= 2.0 {
"Medium - Normal performance"
} else if level >= 1.0 {
"Low - Degraded performance"
} else {
"Critical - Poor performance"
}
}
fn format_level_emoji(level: f32) -> &'static str {
if level >= 4.0 {
"🌟"
} else if level >= 3.0 {
"👌"
} else if level >= 2.0 {
"⚠️"
} else if level >= 1.0 {
"🔶"
} else {
"🚨"
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cli_parsing() {
let app = build_cli();
let matches = app.try_get_matches_from(vec!["pwrzv"]).unwrap();
assert!(matches.get_one::<String>("detailed").is_none());
}
#[test]
fn test_cli_with_args() {
let app = build_cli();
let matches = app
.try_get_matches_from(vec!["pwrzv", "--detailed", "json"])
.unwrap();
assert_eq!(matches.get_one::<String>("detailed").unwrap(), "json");
let app = build_cli();
let matches = app
.try_get_matches_from(vec!["pwrzv", "--detailed"])
.unwrap();
assert_eq!(matches.get_one::<String>("detailed").unwrap(), "text");
}
#[test]
fn test_cli_interval_parsing() {
let app = build_cli();
let matches = app
.try_get_matches_from(vec!["pwrzv", "--interval", "10"])
.unwrap();
assert_eq!(matches.get_one::<u64>("interval").unwrap(), &10);
}
#[test]
fn test_cli_once_flag() {
let app = build_cli();
let matches = app.try_get_matches_from(vec!["pwrzv", "--once"]).unwrap();
assert!(matches.get_flag("once"));
}
}