extern crate alloc;
use alloc::ffi::CString;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::ffi::{c_str::CStr, c_void};
use crate::libc;
pub(crate) fn to_ascii(mut num: usize, buf: &mut [u8]) -> usize {
if num == 0 {
buf[0] = b'0';
buf[1] = 0;
return 2;
}
let mut div = 1usize;
while num / div >= 10 {
div *= 10;
}
let mut i = 0usize;
while div != 0 {
buf[i] = b'0' + (num / div) as u8;
num %= div;
div /= 10;
i += 1;
}
buf[i] = b'\n';
i += 1;
buf[i] = 0;
i + 1
}
pub(crate) fn num_to_string(mut num: usize) -> String {
if num == 0 {
return "0".to_string();
}
let mut buf: [u8; 20] = [0; 20];
let mut div = 1usize;
while num / div >= 10 {
div *= 10;
}
let mut i = 0usize;
while div != 0 {
buf[i] = b'0' + (num / div) as u8;
num %= div;
div /= 10;
i += 1;
}
unsafe { String::from_utf8_unchecked(buf[..i].into()) }
}
pub(crate) fn float_to_string(mut f: f64, significant_digits: usize) -> String {
if f.is_nan() {
return "NaN".into();
}
if f.is_infinite() {
return if f < 0.0 { "-inf".into() } else { "inf".into() };
}
let mut result = String::new();
if f < 0.0 {
result.push('-');
f = -f;
}
let mut integer_part = f as u64;
let mut fraction_part = f - (integer_part as f64);
if integer_part == 0 {
result.push('0');
} else {
let mut buffer = [0u8; 20]; let mut idx = 0;
while integer_part > 0 {
buffer[idx] = (integer_part % 10) as u8 + b'0';
integer_part /= 10;
idx += 1;
}
while idx > 0 {
idx -= 1;
result.push(buffer[idx] as char);
}
}
if significant_digits > 0 {
result.push('.');
for _ in 0..significant_digits {
fraction_part *= 10.0;
let digit = fraction_part as u8; result.push((digit + b'0') as char);
fraction_part -= digit as f64;
}
}
result
}
#[allow(unused)]
pub(crate) fn print_hex(prefix: &CStr, v: &[u8]) {
let hex: alloc::string::String = v
.iter()
.map(|b| alloc::format!(" {:02x}", b))
.collect::<Vec<_>>()
.join("");
print_string(prefix, &hex);
}
#[allow(unused)]
pub(crate) fn print_string(prefix: &CStr, s: &str) {
let msg = CString::new(zclean(&mut s.to_string())).unwrap();
unsafe { libc::printf(c"%s%s\n".as_ptr(), prefix.as_ptr(), msg.as_ptr()) };
}
pub(crate) fn zclean(s: &mut str) -> &str {
for byte in unsafe { s.as_bytes_mut() } {
if *byte == 0 {
*byte = b'_';
}
}
s
}
pub(crate) fn slug(s: &str) -> String {
s.chars()
.map(|c| {
if c.is_alphanumeric() {
c.to_lowercase().next().unwrap_or('-')
} else {
'-'
}
})
.collect()
}
pub(crate) fn last_filename() -> String {
let id = tmux_pane_id();
let mut buf: [u8; 5] = [0; 5];
let buf_len = to_ascii(id, &mut buf[..]);
let mut out = String::with_capacity(16);
out.push_str("last-");
out.push_str(unsafe { str::from_utf8_unchecked(&buf[..buf_len - 2]) });
out.push_str(".json");
out
}
pub fn tmux_pane_id() -> usize {
let mut v = get_env(c"TMUX_PANE");
if v.is_empty() {
return 0;
}
let _ = v.drain(0..1);
v.parse::<usize>().ok().unwrap_or(0)
}
pub(crate) fn get_env(cs: &CStr) -> String {
let value_ptr = unsafe { libc::getenv(cs.as_ptr()) };
if value_ptr.is_null() {
return String::new();
}
let c_str = unsafe { CStr::from_ptr(value_ptr) };
c_str.to_string_lossy().into_owned()
}
pub(crate) fn ensure_dir_exists(dir: &str) {
let cs = CString::new(dir).unwrap();
if !path_exists(cs.as_ref()) {
unsafe { libc::mkdir(cs.as_ptr(), 0o755) };
}
}
pub(crate) fn path_exists(path: &CStr) -> bool {
unsafe { libc::access(path.as_ptr(), libc::F_OK) == 0 }
}
pub(crate) fn filename_read_to_string(filename: &str) -> Result<String, &'static str> {
let cs = CString::new(filename).unwrap();
let fd = unsafe { libc::open(cs.as_ptr(), libc::O_RDONLY) };
if fd < 0 {
return Err("NOT FOUND");
}
let mut content = Vec::new();
let mut buffer = [0u8; 4096];
loop {
let bytes_read =
unsafe { libc::read(fd, buffer.as_mut_ptr() as *mut c_void, buffer.len()) };
if bytes_read < 0 {
let _ = unsafe { libc::close(fd) };
return Err("READ ERROR");
}
if bytes_read == 0 {
break;
}
let bytes_read = bytes_read as usize; content.extend_from_slice(&buffer[..bytes_read]);
}
let out = String::from_utf8_lossy(&content);
Ok(out.into_owned().to_string())
}
#[cfg(test)]
mod tests {
use super::float_to_string;
#[test]
fn nan_and_infinity() {
assert_eq!(float_to_string(f64::NAN, 3), "NaN");
assert_eq!(float_to_string(f64::INFINITY, 3), "inf");
assert_eq!(float_to_string(f64::NEG_INFINITY, 3), "-inf");
}
#[test]
fn sign_and_integer_part() {
assert_eq!(float_to_string(-2.5, 1), "-2.5");
assert_eq!(float_to_string(0.0, 0), "0");
assert_eq!(float_to_string(-0.0, 2), "0.00"); assert_eq!(float_to_string(12345.0, 0), "12345");
}
#[test]
fn fractional_digits_truncate_not_round() {
assert_eq!(float_to_string(1.875, 2), "1.87");
}
#[test]
fn fractional_leading_zeros() {
assert_eq!(float_to_string(0.015625, 3), "0.015");
}
#[test]
fn no_decimal_point_when_zero_digits() {
assert_eq!(float_to_string(3.75, 0), "3");
}
}