use crate::core::NormalizedPath;
use std::path::Path;
use std::process::ExitCode;
pub(crate) fn absolute_path(path: &str) -> NormalizedPath {
let path = Path::new(path);
if path.is_absolute() {
path.into()
} else {
std::env::current_dir()
.unwrap_or_default()
.join(path)
.into()
}
}
pub(crate) fn exit_code_from_i32(code: i32) -> ExitCode {
let truncated = (code & 0xFF) as u8;
if code != 0 && truncated == 0 {
ExitCode::from(1)
} else {
ExitCode::from(truncated)
}
}
pub(crate) fn flag_truthy(value: Option<&str>) -> bool {
let Some(raw) = value else { return false };
let trimmed = raw.trim();
matches!(trimmed, "1")
|| trimmed.eq_ignore_ascii_case("true")
|| trimmed.eq_ignore_ascii_case("yes")
|| trimmed.eq_ignore_ascii_case("on")
}
pub(crate) fn env_flag_truthy(name: &str) -> bool {
flag_truthy(std::env::var(name).ok().as_deref())
}
pub(crate) fn resolve_endpoint(explicit: Option<&str>) -> String {
if let Some(ep) = explicit {
return ep.to_string();
}
if let Ok(ep) = std::env::var("ZCCACHE_ENDPOINT") {
return ep;
}
crate::ipc::default_endpoint()
}
#[cfg(unix)]
pub(crate) async fn connect(
endpoint: &str,
) -> Result<crate::ipc::IpcConnection, crate::ipc::IpcError> {
let mut conn = crate::ipc::connect_daemon(endpoint).await?;
conn.set_recv_timeout(crate::ipc::DEFAULT_CLIENT_RECV_TIMEOUT);
Ok(conn)
}
#[cfg(windows)]
pub(crate) async fn connect(
endpoint: &str,
) -> Result<crate::ipc::IpcClientConnection, crate::ipc::IpcError> {
let mut conn = crate::ipc::connect_daemon(endpoint).await?;
conn.set_recv_timeout(crate::ipc::DEFAULT_CLIENT_RECV_TIMEOUT);
Ok(conn)
}
const MAX_STDIN_BYTES: usize = 16 * 1024 * 1024;
pub(crate) fn slurp_stdin_if_piped() -> Vec<u8> {
use std::io::IsTerminal;
use std::io::Read;
let mut stdin = std::io::stdin();
if stdin.is_terminal() {
return Vec::new();
}
let mut buf = Vec::new();
let _ = stdin
.by_ref()
.take(MAX_STDIN_BYTES as u64)
.read_to_end(&mut buf);
buf
}
pub(crate) fn run_async(future: impl std::future::Future<Output = ExitCode>) -> ExitCode {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("failed to create tokio runtime")
.block_on(future)
}
pub(crate) fn format_uptime(secs: u64) -> String {
if secs < 60 {
format!("{secs}s")
} else if secs < 3600 {
format!("{}m {}s", secs / 60, secs % 60)
} else {
let h = secs / 3600;
let m = (secs % 3600) / 60;
format!("{h}h {m}m")
}
}
pub(crate) fn format_duration_ms(ms: u64) -> String {
if ms < 1000 {
format!("{ms}ms")
} else if ms < 60_000 {
format!("{:.1}s", ms as f64 / 1000.0)
} else {
let secs = ms / 1000;
format!("{}m {}s", secs / 60, secs % 60)
}
}
pub(crate) fn format_bytes(bytes: u64) -> String {
if bytes == 0 {
"0 B".to_string()
} else if bytes < 1024 {
format!("{bytes} B")
} else if bytes < 1024 * 1024 {
format!("{:.1} KB", bytes as f64 / 1024.0)
} else if bytes < 1024 * 1024 * 1024 {
format!("{:.1} MB", bytes as f64 / (1024.0 * 1024.0))
} else {
format!("{:.1} GB", bytes as f64 / (1024.0 * 1024.0 * 1024.0))
}
}
pub(crate) fn init_tracing() {
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("warn")),
)
.init();
}
pub(crate) const LOST_CONNECTION_MSG: &str = "zccache[err][R]: lost connection to daemon (no response). The daemon may have crashed or been killed mid-request — run `zccache status` to check, or `zccache stop` then retry to restart it.";
pub(crate) fn print_json_value(value: &serde_json::Value) {
match serde_json::to_string_pretty(value) {
Ok(s) => println!("{s}"),
Err(err) => {
eprintln!("zccache: failed to encode JSON output: {err}");
println!(r#"{{"status":"error","error":"failed to encode JSON output"}}"#);
}
}
}