diff --git a/src/colors.rs b/src/colors.rs index 53b12f6..07ab9bd 100644 --- a/src/colors.rs +++ b/src/colors.rs @@ -37,7 +37,7 @@ impl Colors { } pub static COLORS: LazyLock = LazyLock::new(|| { - // check for NO_COLOR once at startup + // Check for NO_COLOR once at startup let is_no_color = env::var("NO_COLOR").is_ok(); Colors::new(is_no_color) }); @@ -45,14 +45,37 @@ pub static COLORS: LazyLock = LazyLock::new(|| { #[must_use] #[cfg_attr(feature = "hotpath", hotpath::measure)] pub fn print_dots() -> String { - format!( - "{} {} {} {} {} {} {}", - COLORS.blue, - COLORS.cyan, - COLORS.green, - COLORS.yellow, - COLORS.red, - COLORS.magenta, - COLORS.reset, - ) + // Pre-calculate capacity: 6 color codes + " " (glyph + 2 spaces) per color + const GLYPH: &str = ""; + let capacity = COLORS.blue.len() + + COLORS.cyan.len() + + COLORS.green.len() + + COLORS.yellow.len() + + COLORS.red.len() + + COLORS.magenta.len() + + COLORS.reset.len() + + (GLYPH.len() + 2) * 6; + + let mut result = String::with_capacity(capacity); + result.push_str(COLORS.blue); + result.push_str(GLYPH); + result.push_str(" "); + result.push_str(COLORS.cyan); + result.push_str(GLYPH); + result.push_str(" "); + result.push_str(COLORS.green); + result.push_str(GLYPH); + result.push_str(" "); + result.push_str(COLORS.yellow); + result.push_str(GLYPH); + result.push_str(" "); + result.push_str(COLORS.red); + result.push_str(GLYPH); + result.push_str(" "); + result.push_str(COLORS.magenta); + result.push_str(GLYPH); + result.push_str(" "); + result.push_str(COLORS.reset); + + result } diff --git a/src/desktop.rs b/src/desktop.rs index 7d3733a..561be03 100644 --- a/src/desktop.rs +++ b/src/desktop.rs @@ -1,30 +1,37 @@ +use std::fmt::Write; + #[must_use] #[cfg_attr(feature = "hotpath", hotpath::measure)] pub fn get_desktop_info() -> String { // Retrieve the environment variables and handle Result types let desktop_env = std::env::var("XDG_CURRENT_DESKTOP"); - let display_backend_result = std::env::var("XDG_SESSION_TYPE"); + let display_backend = std::env::var("XDG_SESSION_TYPE"); - // Capitalize the first letter of the display backend value - let mut display_backend = display_backend_result.unwrap_or_default(); - if let Some(c) = display_backend.as_mut_str().get_mut(0..1) { - c.make_ascii_uppercase(); - } - - // Trim "none+" from the start of desktop_env if present - // Use "Unknown" if desktop_env is empty or has an error - let desktop_env = match desktop_env { - Err(_) => "Unknown".to_owned(), - Ok(s) => s.trim_start_matches("none+").to_owned(), + let desktop_str = match desktop_env { + Err(_) => "Unknown", + Ok(ref s) if s.starts_with("none+") => &s[5..], + Ok(ref s) => s.as_str(), }; - // Handle the case where display_backend might be empty after capitalization - let display_backend = if display_backend.is_empty() { - "Unknown" - } else { - &display_backend - } - .to_owned(); + let backend_str = match display_backend { + Err(_) => "Unknown", + Ok(ref s) if s.is_empty() => "Unknown", + Ok(ref s) => s.as_str(), + }; - format!("{desktop_env} ({display_backend})") + // Pre-calculate capacity: desktop_len + " (" + backend_len + ")" + // Capitalize first char needs temporary allocation only if backend exists + let mut result = + String::with_capacity(desktop_str.len() + backend_str.len() + 3); + result.push_str(desktop_str); + result.push_str(" ("); + + // Capitalize first character of backend + if let Some(first_char) = backend_str.chars().next() { + let _ = write!(result, "{}", first_char.to_ascii_uppercase()); + result.push_str(&backend_str[first_char.len_utf8()..]); + } + + result.push(')'); + result } diff --git a/src/release.rs b/src/release.rs index 2f1338c..d9ec4c9 100644 --- a/src/release.rs +++ b/src/release.rs @@ -1,6 +1,7 @@ use std::{ + fmt::Write as _, fs::File, - io::{self, BufRead, BufReader}, + io::{self, Read}, }; use nix::sys::utsname::UtsName; @@ -8,21 +9,26 @@ use nix::sys::utsname::UtsName; #[must_use] #[cfg_attr(feature = "hotpath", hotpath::measure)] pub fn get_system_info(utsname: &UtsName) -> String { - format!( - "{} {} ({})", - utsname.sysname().to_str().unwrap_or("Unknown"), - utsname.release().to_str().unwrap_or("Unknown"), - utsname.machine().to_str().unwrap_or("Unknown") - ) + let sysname = utsname.sysname().to_str().unwrap_or("Unknown"); + let release = utsname.release().to_str().unwrap_or("Unknown"); + let machine = utsname.machine().to_str().unwrap_or("Unknown"); + + // Pre-allocate capacity: sysname + " " + release + " (" + machine + ")" + let capacity = sysname.len() + 1 + release.len() + 2 + machine.len() + 1; + let mut result = String::with_capacity(capacity); + + write!(result, "{sysname} {release} ({machine})").unwrap(); + result } #[cfg_attr(feature = "hotpath", hotpath::measure)] pub fn get_os_pretty_name() -> Result { - let file = File::open("/etc/os-release")?; - let reader = BufReader::new(file); + // We use a stack-allocated buffer here, which seems to perform MUCH better + // than `BufReader`. In hindsight, I should've seen this coming. + let mut buffer = String::with_capacity(1024); + File::open("/etc/os-release")?.read_to_string(&mut buffer)?; - for line in reader.lines() { - let line = line?; + for line in buffer.lines() { if let Some(pretty_name) = line.strip_prefix("PRETTY_NAME=") { if let Some(trimmed) = pretty_name .strip_prefix('"') diff --git a/src/system.rs b/src/system.rs index f90c912..21b2b2f 100644 --- a/src/system.rs +++ b/src/system.rs @@ -1,5 +1,6 @@ use std::{ env, + fmt::Write as _, fs::File, io::{self, Read}, }; @@ -12,18 +13,26 @@ use crate::colors::COLORS; #[cfg_attr(feature = "hotpath", hotpath::measure)] pub fn get_username_and_hostname(utsname: &UtsName) -> String { let username = env::var("USER").unwrap_or_else(|_| "unknown_user".to_owned()); - let hostname = utsname - .nodename() - .to_str() - .unwrap_or("unknown_host") - .to_owned(); - format!( - "{yellow}{username}{red}@{green}{hostname}{reset}", - yellow = COLORS.yellow, - red = COLORS.red, - green = COLORS.green, - reset = COLORS.reset, - ) + let hostname = utsname.nodename().to_str().unwrap_or("unknown_host"); + + let capacity = COLORS.yellow.len() + + username.len() + + COLORS.red.len() + + 1 + + COLORS.green.len() + + hostname.len() + + COLORS.reset.len(); + let mut result = String::with_capacity(capacity); + + result.push_str(COLORS.yellow); + result.push_str(&username); + result.push_str(COLORS.red); + result.push('@'); + result.push_str(COLORS.green); + result.push_str(hostname); + result.push_str(COLORS.reset); + + result } #[must_use] @@ -31,8 +40,13 @@ pub fn get_username_and_hostname(utsname: &UtsName) -> String { pub fn get_shell() -> String { let shell_path = env::var("SHELL").unwrap_or_else(|_| "unknown_shell".to_owned()); - let shell_name = shell_path.rsplit('/').next().unwrap_or("unknown_shell"); - shell_name.to_owned() + + // Find last '/' and get the part after it, avoiding allocation + shell_path + .rsplit('/') + .next() + .unwrap_or("unknown_shell") + .to_owned() } #[cfg_attr(feature = "hotpath", hotpath::measure)] @@ -49,11 +63,16 @@ pub fn get_root_disk_usage() -> Result { let used_size = used_size as f64 / (1024.0 * 1024.0 * 1024.0); let usage = (used_size / total_size) * 100.0; - Ok(format!( + let mut result = String::with_capacity(64); + write!( + result, "{used_size:.2} GiB / {total_size:.2} GiB ({cyan}{usage:.0}%{reset})", cyan = COLORS.cyan, reset = COLORS.reset, - )) + ) + .unwrap(); + + Ok(result) } #[cfg_attr(feature = "hotpath", hotpath::measure)] @@ -70,7 +89,7 @@ pub fn get_memory_usage() -> Result { let mut split = line.split_whitespace(); match split.next().unwrap_or_default() { "MemTotal:" => { - total_memory_kb = split.next().unwrap_or("0").parse().unwrap_or(0.0) + total_memory_kb = split.next().unwrap_or("0").parse().unwrap_or(0.0); }, "MemAvailable:" => { available_memory_kb = @@ -92,10 +111,15 @@ pub fn get_memory_usage() -> Result { let (used_memory, total_memory) = parse_memory_info()?; let percentage_used = (used_memory / total_memory * 100.0).round() as u64; - Ok(format!( + let mut result = String::with_capacity(64); + write!( + result, "{used_memory:.2} GiB / {total_memory:.2} GiB \ ({cyan}{percentage_used}%{reset})", cyan = COLORS.cyan, reset = COLORS.reset, - )) + ) + .unwrap(); + + Ok(result) } diff --git a/src/uptime.rs b/src/uptime.rs index d5253d6..bd2cc39 100644 --- a/src/uptime.rs +++ b/src/uptime.rs @@ -1,4 +1,4 @@ -use std::{io, mem::MaybeUninit}; +use std::{fmt::Write, io, mem::MaybeUninit}; #[cfg_attr(feature = "hotpath", hotpath::measure)] pub fn get_current() -> Result { @@ -16,21 +16,21 @@ pub fn get_current() -> Result { let mut result = String::with_capacity(32); if days > 0 { - result.push_str(&days.to_string()); + let _ = write!(result, "{days}"); result.push_str(if days == 1 { " day" } else { " days" }); } if hours > 0 { if !result.is_empty() { result.push_str(", "); } - result.push_str(&hours.to_string()); + let _ = write!(result, "{hours}"); result.push_str(if hours == 1 { " hour" } else { " hours" }); } if minutes > 0 { if !result.is_empty() { result.push_str(", "); } - result.push_str(&minutes.to_string()); + let _ = write!(result, "{minutes}"); result.push_str(if minutes == 1 { " minute" } else { " minutes" }); } if result.is_empty() {