auraline 0.16.0

utility that renders information into a string suitable to be embedded in the shell prompt.
use crate::{chunk::Chunk, options::Options};
use scopeguard::defer;
use smol_str::{format_smolstr, SmolStr};
use std::time::{Duration, SystemTime, UNIX_EPOCH};

const AURALINE_CMD_START: &str = "auraline_cmd_start";

pub async fn show(opts: &Options) -> Option<Chunk<SmolStr>> {
    if !opts.duration {
        return None;
    }
    let end_nanos = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .ok()?
        .as_nanos();
    let ppid = unsafe { libc::getppid() };
    let (cmd_start_time, start_time_str) =
        read_start_file(ppid).or_else(|| parent_pid(ppid).and_then(read_start_file))?;
    defer! {
        let _ = std::fs::remove_file(&cmd_start_time);
    };
    let start_nanos = start_time_str.trim().parse::<u128>().ok()?;
    if end_nanos > start_nanos {
        let duration = Duration::from_nanos((end_nanos - start_nanos) as u64);
        return Some(format_duration(duration));
    }

    None
}

fn format_duration(duration: Duration) -> Chunk<SmolStr> {
    let secs = duration.as_secs_f64();
    let icon = "󰄉";

    if secs < 0.000001 {
        return Chunk::new(icon, format_smolstr!("{}ns", duration.as_nanos()));
    }
    if secs < 0.001 {
        return Chunk::new(icon, format_smolstr!("{:.0}μs", secs * 1_000_000.0));
    }
    if secs < 1.0 {
        return Chunk::new(icon, format_smolstr!("{:.0}ms", secs * 1_000.0));
    }
    Chunk::new(icon, format_smolstr!("{:.2}s", secs))
}

fn read_start_file(pid: libc::pid_t) -> Option<(String, String)> {
    let path = format!("/tmp/{}.{}", AURALINE_CMD_START, pid);
    let content = std::fs::read_to_string(&path).ok()?;
    Some((path, content))
}

#[cfg(target_os = "macos")]
fn parent_pid(pid: libc::pid_t) -> Option<libc::pid_t> {
    unsafe {
        let mut info: libc::proc_bsdinfo = std::mem::zeroed();
        let size = std::mem::size_of::<libc::proc_bsdinfo>() as libc::c_int;
        let ret = libc::proc_pidinfo(
            pid,
            libc::PROC_PIDTBSDINFO,
            0,
            &mut info as *mut _ as *mut libc::c_void,
            size,
        );
        if ret == size && info.pbi_ppid > 1 {
            Some(info.pbi_ppid as libc::pid_t)
        } else {
            None
        }
    }
}

#[cfg(target_os = "linux")]
fn parent_pid(pid: libc::pid_t) -> Option<libc::pid_t> {
    let stat = std::fs::read_to_string(format!("/proc/{}/stat", pid)).ok()?;
    let after_comm = stat.rfind(')')?.checked_add(2)?;
    let ppid_str = stat.get(after_comm..)?.split_whitespace().nth(1)?;
    let ppid = ppid_str.parse::<libc::pid_t>().ok()?;
    (ppid > 1).then_some(ppid)
}

#[cfg(not(any(target_os = "macos", target_os = "linux")))]
fn parent_pid(_pid: libc::pid_t) -> Option<libc::pid_t> {
    None
}