use std::io::Write;
use std::process::{Command, Stdio};
use std::sync::OnceLock;
static COLOR: OnceLock<bool> = OnceLock::new();
pub fn init_color() {
let enabled = match std::env::var("LOOOP_COLOR") {
Ok(v) if v == "1" => true,
Ok(v) if v == "0" => false,
_ => is_stdout_tty() && std::env::var_os("NO_COLOR").is_none(),
};
let _ = COLOR.set(enabled);
unsafe { std::env::set_var("LOOOP_COLOR", if enabled { "1" } else { "0" }) };
}
fn color_on() -> bool {
*COLOR.get().unwrap_or(&false)
}
#[cfg(unix)]
fn is_stdout_tty() -> bool {
unsafe { libc_isatty(1) }
}
#[cfg(not(unix))]
fn is_stdout_tty() -> bool {
false
}
#[cfg(unix)]
unsafe fn libc_isatty(fd: i32) -> bool {
unsafe extern "C" {
fn isatty(fd: i32) -> i32;
}
unsafe { isatty(fd) == 1 }
}
macro_rules! code {
($name:ident, $seq:expr) => {
pub fn $name() -> &'static str {
if color_on() { $seq } else { "" }
}
};
}
code!(rst, "\x1b[0m");
code!(dim, "\x1b[2m");
code!(b, "\x1b[1m");
code!(cyan, "\x1b[36m");
code!(grn, "\x1b[32m");
code!(red, "\x1b[31m");
code!(yel, "\x1b[33m");
pub fn log(msg: &str) {
println!("{}[{}]{} {}", dim(), hms(), rst(), msg);
}
pub fn hms() -> String {
chrono::Local::now().format("%H:%M:%S").to_string()
}
pub fn date_fmt(fmt: &str) -> String {
Command::new("date")
.arg(format!("+{fmt}"))
.output()
.ok()
.filter(|o| o.status.success())
.map(|o| String::from_utf8_lossy(&o.stdout).trim_end().to_string())
.unwrap_or_default()
}
pub fn content_hash(input: &[u8]) -> String {
let tool = if on_path("shasum") {
"shasum"
} else if on_path("sha1sum") {
"sha1sum"
} else {
"cksum"
};
let mut child = match Command::new(tool)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()
{
Ok(c) => c,
Err(_) => return String::new(),
};
if let Some(mut si) = child.stdin.take() {
let _ = si.write_all(input);
}
let out = match child.wait_with_output() {
Ok(o) => o,
Err(_) => return String::new(),
};
String::from_utf8_lossy(&out.stdout)
.split_whitespace()
.next()
.unwrap_or("")
.to_string()
}
pub fn on_path(cmd: &str) -> bool {
let Some(path) = std::env::var_os("PATH") else {
return false;
};
std::env::split_paths(&path).any(|dir| {
let p = dir.join(cmd);
p.is_file() && is_executable(&p)
})
}
#[cfg(unix)]
fn is_executable(p: &std::path::Path) -> bool {
use std::os::unix::fs::PermissionsExt;
std::fs::metadata(p)
.map(|m| m.permissions().mode() & 0o111 != 0)
.unwrap_or(false)
}
#[cfg(not(unix))]
fn is_executable(_p: &std::path::Path) -> bool {
true
}
pub fn pid_alive(pid: &str) -> bool {
if pid.is_empty() {
return false;
}
Command::new("kill")
.args(["-0", pid])
.stderr(Stdio::null())
.status()
.map(|s| s.success())
.unwrap_or(false)
}