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::cli::Env;
use crate::syscall;
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 fn num_to_string<T>(num: T) -> String
where
T: TryInto<i128> + Copy,
{
let Ok(mut num) = num.try_into() else {
panic!("num_to_string only supports values that fit in i128");
};
if num == 0 {
return "0".to_string();
}
let negative = num < 0;
if negative {
num = -num;
}
let mut buf: [u8; 40] = [0; 40];
let mut div = 1i128;
let mut i = 0usize;
if negative {
buf[i] = b'-';
i += 1;
}
while num / div >= 10 {
div *= 10;
}
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 fn print_string(prefix: &CStr, s: &str) {
let msg = CString::new(zclean(&mut s.to_string())).unwrap();
let _ = syscall::write(1, prefix.as_ptr().cast::<c_void>(), prefix.count_bytes());
let _ = syscall::write(1, msg.as_ptr().cast::<c_void>(), msg.count_bytes());
let _ = syscall::write(1, c"\n".as_ptr().cast::<c_void>(), c"\n".count_bytes());
}
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 {
let mut out = String::with_capacity(16);
out.extend(s.chars().map(|c| {
if c.is_alphanumeric() {
c.to_lowercase().next().unwrap_or('-')
} else {
'-'
}
}));
out
}
pub(crate) fn last_filename(env: &Env) -> String {
let mut buf: [u8; 3] = [0; 3];
let buf_len = tmux_pane_id(env.TMUX_PANE.unwrap_or_default(), &mut buf);
let mut out = String::with_capacity(16);
out.push_str("last-");
out.push_str(unsafe { str::from_utf8_unchecked(&buf[..buf_len]) });
out.push_str(".json");
out
}
pub fn tmux_pane_id(tmux_pane_var: &str, buf: &mut [u8]) -> usize {
let v = tmux_pane_var;
if v.is_empty() {
buf[0] = b'0';
return 1;
}
let id_len = v.len() - 1;
buf[..id_len].copy_from_slice(&v.as_bytes()[1..]);
id_len
}
pub(crate) fn ensure_dir_exists(dir: &str) {
let cs = CString::new(dir).unwrap();
if !path_exists(cs.as_ref()) {
syscall::mkdir(cs.as_ptr(), 0o755);
}
}
pub(crate) fn path_exists(path: &CStr) -> bool {
syscall::access(path.as_ptr(), syscall::F_OK) == 0
}
pub(crate) fn filename_read_to_string(filename: &str) -> Result<String, &'static str> {
let cs = CString::new(filename).unwrap();
let fd = match syscall::open(cs.as_ptr(), syscall::O_RDONLY, 0) {
Ok(fd) => fd,
Err(v) if v == "Permission denied" => {
return Err(v);
}
Err(_) => {
return Err("NOT FOUND");
}
};
let mut content = Vec::new();
let mut buffer = [0u8; 4096];
loop {
let bytes_read = syscall::read(fd, buffer.as_mut_ptr() as *mut c_void, buffer.len());
if bytes_read < 0 {
let _ = syscall::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, num_to_string};
#[test]
fn num_to_string_handles_sign() {
assert_eq!(num_to_string(-42), "-42");
assert_eq!(num_to_string(42usize), "42");
assert_eq!(num_to_string(0), "0");
}
#[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");
}
}