diff --git a/benches/benchmark.rs b/benches/benchmark.rs index 8d62082..da3250a 100644 --- a/benches/benchmark.rs +++ b/benches/benchmark.rs @@ -1,8 +1,8 @@ use criterion::{Criterion, criterion_group, criterion_main}; use microfetch_lib::{ UtsName, - colors::print_dots, desktop::get_desktop_info, + dots::print_dots, release::{get_os_pretty_name, get_system_info}, system::{ get_memory_usage, diff --git a/src/colors.rs b/src/colors.rs index 7c65944..0a7af23 100644 --- a/src/colors.rs +++ b/src/colors.rs @@ -1,5 +1,22 @@ use std::sync::LazyLock; +// All this because concat!() doesn't accept const parameters +// See https://github.com/rust-lang/rust/issues/31383 +#[macro_export] +macro_rules! RESET {() => {"\x1b[0m"}} +#[macro_export] +macro_rules! BLUE {() => {"\x1b[34m"}} +#[macro_export] +macro_rules! CYAN {() => {"\x1b[34m"}} +#[macro_export] +macro_rules! GREEN {() => {"\x1b[32m"}} +#[macro_export] +macro_rules! YELLOW {() => {"\x1b[33m"}} +#[macro_export] +macro_rules! RED {() => {"\x1b[31m"}} +#[macro_export] +macro_rules! MAGENTA {() => {"\x1b[35m"}} + pub struct Colors { pub reset: &'static str, pub blue: &'static str, @@ -7,7 +24,6 @@ pub struct Colors { pub green: &'static str, pub yellow: &'static str, pub red: &'static str, - pub magenta: &'static str, } impl Colors { @@ -20,62 +36,26 @@ impl Colors { green: "", yellow: "", red: "", - magenta: "", } } else { Self { - reset: "\x1b[0m", - blue: "\x1b[34m", - cyan: "\x1b[36m", - green: "\x1b[32m", - yellow: "\x1b[33m", - red: "\x1b[31m", - magenta: "\x1b[35m", + reset: RESET!(), + blue: BLUE!(), + cyan: CYAN!(), + green: GREEN!(), + yellow: YELLOW!(), + red: RED!(), } } } } -pub static COLORS: LazyLock = LazyLock::new(|| { +pub static IS_NO_COLOR: LazyLock = LazyLock::new(|| { + // Check for NO_COLOR once at startup const NO_COLOR: *const libc::c_char = c"NO_COLOR".as_ptr(); - let is_no_color = unsafe { !libc::getenv(NO_COLOR).is_null() }; - Colors::new(is_no_color) + unsafe { !libc::getenv(NO_COLOR).is_null() } }); -#[must_use] -#[cfg_attr(feature = "hotpath", hotpath::measure)] -pub fn print_dots() -> String { - // 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 -} +pub static COLORS: LazyLock = LazyLock::new(|| { + Colors::new(*IS_NO_COLOR) +}); diff --git a/src/desktop.rs b/src/desktop.rs index 501e967..09caf3b 100644 --- a/src/desktop.rs +++ b/src/desktop.rs @@ -1,4 +1,6 @@ -use std::{ffi::CStr, fmt::Write}; +use std::ffi::CStr; + +use crate::unknown; #[must_use] #[cfg_attr(feature = "hotpath", hotpath::measure)] @@ -7,9 +9,9 @@ pub fn get_desktop_info() -> String { let desktop_str = unsafe { let ptr = libc::getenv(c"XDG_CURRENT_DESKTOP".as_ptr()); if ptr.is_null() { - "Unknown" + unknown() } else { - let s = CStr::from_ptr(ptr).to_str().unwrap_or("Unknown"); + let s = CStr::from_ptr(ptr).to_str().unwrap_or_else(|_| unknown()); s.strip_prefix("none+").unwrap_or(s) } }; @@ -17,10 +19,10 @@ pub fn get_desktop_info() -> String { let backend_str = unsafe { let ptr = libc::getenv(c"XDG_SESSION_TYPE".as_ptr()); if ptr.is_null() { - "Unknown" + unknown() } else { - let s = CStr::from_ptr(ptr).to_str().unwrap_or("Unknown"); - if s.is_empty() { "Unknown" } else { s } + let s = CStr::from_ptr(ptr).to_str().unwrap_or_else(|_| unknown()); + if s.is_empty() { unknown() } else { s } } }; @@ -29,11 +31,12 @@ pub fn get_desktop_info() -> String { let mut result = String::with_capacity(desktop_str.len() + backend_str.len() + 3); result.push_str(desktop_str); - result.push_str(" ("); + result.push(' '); + result.push('('); // Capitalize first character of backend if let Some(first_char) = backend_str.chars().next() { - let _ = write!(result, "{}", first_char.to_ascii_uppercase()); + result.push(first_char.to_ascii_uppercase()); result.push_str(&backend_str[first_char.len_utf8()..]); } diff --git a/src/dots.rs b/src/dots.rs new file mode 100644 index 0000000..3ed3e0a --- /dev/null +++ b/src/dots.rs @@ -0,0 +1,33 @@ +use crate::{colors::IS_NO_COLOR, BLUE, CYAN, GREEN, MAGENTA, RED, RESET, YELLOW}; + +macro_rules! GLYPH {() => {""}} + +macro_rules! GAP {() => {" "}} + +const NO_COLORS_STR: &str = concat!( + GLYPH!(), GAP!(), + GLYPH!(), GAP!(), + GLYPH!(), GAP!(), + GLYPH!(), GAP!(), + GLYPH!(), GAP!(), + GLYPH!(), +); + +const COLORS_STR: &str = concat!( + BLUE!(), GLYPH!(), GAP!(), + CYAN!(), GLYPH!(), GAP!(), + GREEN!(), GLYPH!(), GAP!(), + YELLOW!(), GLYPH!(), GAP!(), + RED!(), GLYPH!(), GAP!(), + MAGENTA!(), GLYPH!(), RESET!(), +); + +#[must_use] +#[cfg_attr(feature = "hotpath", hotpath::measure)] +pub fn print_dots() -> &'static str { + if *IS_NO_COLOR { + NO_COLORS_STR + } else { + COLORS_STR + } +} diff --git a/src/lib.rs b/src/lib.rs index 1e0f9f3..71e4e3d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,22 @@ pub mod colors; pub mod desktop; +pub mod dots; pub mod release; pub mod syscall; pub mod system; pub mod uptime; -use std::mem::MaybeUninit; +use std::{io, mem::MaybeUninit}; + +#[inline] +#[cold] +pub const fn unknown() -> &'static str { "Unknown" } + +#[inline] +#[cold] +pub fn last_os_error() -> io::Result { + Err(io::Error::last_os_error()) +} /// Wrapper for `libc::utsname` with safe accessor methods pub struct UtsName(libc::utsname); @@ -19,7 +30,7 @@ impl UtsName { pub fn uname() -> Result { let mut uts = MaybeUninit::uninit(); if unsafe { libc::uname(uts.as_mut_ptr()) } != 0 { - return Err(std::io::Error::last_os_error()); + return last_os_error(); } Ok(Self(unsafe { uts.assume_init() })) } diff --git a/src/main.rs b/src/main.rs index e29d6d1..edbe14a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ mod colors; mod desktop; +mod dots; mod release; mod syscall; mod system; @@ -7,11 +8,11 @@ mod uptime; use std::io::{self, Cursor, Write}; -pub use microfetch_lib::UtsName; +pub use microfetch_lib::{UtsName, last_os_error, unknown}; use crate::{ - colors::print_dots, desktop::get_desktop_info, + dots::print_dots, release::{get_os_pretty_name, get_system_info}, system::{ get_memory_usage, @@ -37,7 +38,7 @@ fn main() -> Result<(), Box> { uptime: get_current()?, memory_usage: get_memory_usage()?, storage: get_root_disk_usage()?, - colors: print_dots(), + dots: print_dots(), }; print_system_info(&fields)?; } @@ -57,7 +58,7 @@ struct Fields { desktop: String, memory_usage: String, storage: String, - colors: String, + dots: &'static str, } #[cfg_attr(feature = "hotpath", hotpath::measure)] @@ -75,7 +76,7 @@ fn print_system_info( desktop, memory_usage, storage, - colors, + dots, } = fields; let cyan = COLORS.cyan; @@ -96,7 +97,7 @@ fn print_system_info( {blue} ▟█▛{cyan}▗█▖ {cyan}▟█▛ {cyan} {blue}Desktop{reset}  {desktop} {blue} ▝█▛ {cyan}██▖{blue}▗▄▄▄▄▄▄▄▄▄▄▄ {cyan}󰍛 {blue}Memory{reset}  {memory_usage} {blue} ▝ {cyan}▟█▜█▖{blue}▀▀▀▀▀██▛▀▀▘ {cyan}󱥎 {blue}Storage (/){reset}  {storage} - {cyan} ▟█▘ ▜█▖ {blue}▝█▛ {cyan} {blue}Colors{reset}  {colors}\n\n" + {cyan} ▟█▘ ▜█▖ {blue}▝█▛ {cyan} {blue}Colors{reset}  {dots}\n\n" )?; let len = cursor.position() as usize; diff --git a/src/release.rs b/src/release.rs index 8d3232e..5cf79ee 100644 --- a/src/release.rs +++ b/src/release.rs @@ -1,19 +1,25 @@ -use std::{fmt::Write as _, io}; +use std::io; -use crate::{UtsName, syscall::read_file_fast}; +use crate::{syscall::read_file_fast, unknown, UtsName}; #[must_use] #[cfg_attr(feature = "hotpath", hotpath::measure)] pub fn get_system_info(utsname: &UtsName) -> String { - 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"); + let sysname = utsname.sysname().to_str().unwrap_or_else(|_| unknown()); + let release = utsname.release().to_str().unwrap_or_else(|_| unknown()); + let machine = utsname.machine().to_str().unwrap_or_else(|_| 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.push_str(sysname); + result.push(' '); + result.push_str(release); + result.push(' '); + result.push('('); + result.push_str(machine); + result.push(')'); result } @@ -65,5 +71,5 @@ pub fn get_os_pretty_name() -> Result { offset += line_end + 1; } - Ok("Unknown".to_owned()) + Ok(unknown().to_owned()) } diff --git a/src/syscall.rs b/src/syscall.rs index 0c8634b..1afda3a 100644 --- a/src/syscall.rs +++ b/src/syscall.rs @@ -10,6 +10,8 @@ use std::io; +use crate::last_os_error; + /// Direct syscall to open a file /// /// # Returns @@ -169,6 +171,12 @@ pub unsafe fn sys_close(fd: i32) -> i32 { } } +#[inline] +#[cold] +fn path_too_long() -> io::Result { + Err(io::Error::new(io::ErrorKind::InvalidInput, "Path too long")) +} + /// Read entire file using direct syscalls. This avoids libc overhead and can be /// significantly faster for small files. /// @@ -182,7 +190,7 @@ pub fn read_file_fast(path: &str, buffer: &mut [u8]) -> io::Result { // Use stack-allocated buffer for null-terminated path (max 256 bytes) let path_bytes = path.as_bytes(); if path_bytes.len() >= 256 { - return Err(io::Error::new(io::ErrorKind::InvalidInput, "Path too long")); + return path_too_long(); } let mut path_buf = [0u8; 256]; @@ -192,14 +200,14 @@ pub fn read_file_fast(path: &str, buffer: &mut [u8]) -> io::Result { unsafe { let fd = sys_open(path_buf.as_ptr(), O_RDONLY); if fd < 0 { - return Err(io::Error::last_os_error()); + return last_os_error(); } let bytes_read = sys_read(fd, buffer.as_mut_ptr(), buffer.len()); let _ = sys_close(fd); if bytes_read < 0 { - return Err(io::Error::last_os_error()); + return last_os_error(); } #[allow(clippy::cast_sign_loss)] diff --git a/src/system.rs b/src/system.rs index ba8fe79..a152210 100644 --- a/src/system.rs +++ b/src/system.rs @@ -1,6 +1,14 @@ use std::{ffi::CStr, fmt::Write as _, io, mem::MaybeUninit}; -use crate::{UtsName, colors::COLORS, syscall::read_file_fast}; +use crate::{colors::COLORS, last_os_error, syscall::read_file_fast, UtsName}; + +#[inline] +#[cold] +const fn unknown_user() -> &'static str { "unknown_user" } + +#[inline] +#[cold] +const fn unknown_host() -> &'static str { "unknown_host" } #[must_use] #[cfg_attr(feature = "hotpath", hotpath::measure)] @@ -8,12 +16,12 @@ pub fn get_username_and_hostname(utsname: &UtsName) -> String { let username = unsafe { let ptr = libc::getenv(c"USER".as_ptr()); if ptr.is_null() { - "unknown_user" + unknown_user() } else { - CStr::from_ptr(ptr).to_str().unwrap_or("unknown_user") + CStr::from_ptr(ptr).to_str().unwrap_or_else(|_| unknown_user()) } }; - let hostname = utsname.nodename().to_str().unwrap_or("unknown_host"); + let hostname = utsname.nodename().to_str().unwrap_or_else(|_| unknown_host()); let capacity = COLORS.yellow.len() + username.len() @@ -35,13 +43,17 @@ pub fn get_username_and_hostname(utsname: &UtsName) -> String { result } +#[inline] +#[cold] +const fn unknown_shell() -> &'static str { "unknown_shell" } + #[must_use] #[cfg_attr(feature = "hotpath", hotpath::measure)] pub fn get_shell() -> String { unsafe { let ptr = libc::getenv(c"SHELL".as_ptr()); if ptr.is_null() { - return "unknown_shell".into(); + return unknown_shell().into(); } let bytes = CStr::from_ptr(ptr).to_bytes(); @@ -63,7 +75,7 @@ pub fn get_root_disk_usage() -> Result { let path = b"/\0"; if unsafe { libc::statvfs(path.as_ptr().cast(), vfs.as_mut_ptr()) } != 0 { - return Err(io::Error::last_os_error()); + return last_os_error(); } let vfs = unsafe { vfs.assume_init() }; diff --git a/src/uptime.rs b/src/uptime.rs index 095af7d..6a2cdfb 100644 --- a/src/uptime.rs +++ b/src/uptime.rs @@ -1,5 +1,7 @@ use std::{io, mem::MaybeUninit}; +use crate::last_os_error; + /// Faster integer to string conversion without the formatting overhead. #[inline] fn itoa(mut n: u64, buf: &mut [u8]) -> &str { @@ -73,7 +75,7 @@ pub fn get_current() -> Result { let uptime_seconds = { let mut info = MaybeUninit::uninit(); if unsafe { sys_sysinfo(info.as_mut_ptr()) } != 0 { - return Err(io::Error::last_os_error()); + return last_os_error(); } #[allow(clippy::cast_sign_loss)] unsafe {