use crate::ZvError;
use color_eyre::{
Result,
eyre::{WrapErr, bail, eyre},
};
use semver::Version;
use std::{
borrow::Cow,
io,
path::{Path, PathBuf},
};
use yansi::Paint;
pub fn canonicalize<P: AsRef<Path>>(path: P) -> io::Result<PathBuf> {
dunce::canonicalize(path)
}
#[inline]
pub(crate) fn is_tty() -> bool {
yansi::is_enabled()
}
pub(crate) fn supports_interactive_prompts() -> bool {
if !is_tty() {
return false;
}
if std::env::var("CI").is_ok() {
return false;
}
if let Ok(term) = std::env::var("TERM")
&& term == "dumb"
{
return false;
}
if std::env::var("DEBIAN_FRONTEND").as_deref() == Ok("noninteractive") {
return false;
}
true
}
#[macro_export]
macro_rules! suggest {
($fmt:expr, cmd = $cmd:expr $(, $($args:tt)*)?) => {
println!(
"• {}",
format!($fmt, $crate::tools::format_cmd($cmd) $(, $($args)*)?)
);
};
($fmt:expr $(, $($args:tt)*)?) => {
println!("• {}", format!($fmt $(, $($args)*)?));
};
}
pub fn format_cmd(cmd: &str) -> String {
Paint::green(cmd).italic().to_string()
}
pub(crate) fn fetch_zv_dir() -> Result<(PathBuf, bool)> {
let zv_dir_env = match std::env::var("ZV_DIR") {
Ok(dir) if !dir.is_empty() => Some(dir),
Ok(_) => None,
Err(env_err) => match env_err {
std::env::VarError::NotPresent => None,
std::env::VarError::NotUnicode(ref str) => {
error(format!(
"Warning: ZV_DIR={str:?} is set but contains invalid Unicode."
));
return Err(eyre!(env_err));
}
},
};
let (zv_dir, using_env) = if let Some(zv_dir) = zv_dir_env {
(PathBuf::from(zv_dir), true )
} else {
(get_default_zv_dir()?, false )
};
match zv_dir.try_exists() {
Ok(true) => {
if !zv_dir.is_dir() {
error(format!(
"zv directory exists but is not a directory: {}. Please check ZV_DIR env var. Aborting...",
zv_dir.display()
));
bail!(eyre!("ZV_DIR exists but is not a directory"));
}
}
Ok(false) => {
if using_env {
std::fs::create_dir_all(&zv_dir)
.map_err(ZvError::Io)
.wrap_err_with(|| {
format!(
"Error creating ZV_DIR from env var ZV_DIR={}",
std::env::var("ZV_DIR").expect("Handled in fetch_zv_dir()")
)
})?;
} else {
std::fs::create_dir(&zv_dir)
.map_err(ZvError::Io)
.wrap_err_with(|| {
format!("Failed to create default .zv at {}", zv_dir.display())
})?;
}
}
Err(e) => {
error(format!(
"Failed to check zv directory at {:?}",
zv_dir.display(),
));
return Err(ZvError::Io(e).into());
}
};
let zv_dir = canonicalize(&zv_dir).map_err(ZvError::Io)?;
Ok((zv_dir, using_env))
}
pub(crate) fn get_default_zv_dir() -> Result<PathBuf> {
let shell = crate::shell::Shell::detect();
if let Some(home_dir) = shell.get_home_dir() {
Ok(home_dir.join(".zv"))
} else {
Err(eyre!(
"Unable to locate home directory.\
Please set `ZV_DIR` to use zv. If you think this is a bug please open an issue at <https://github.com/weezy20/zv/issues>"
))
}
}
#[inline]
pub fn warn(message: impl Into<Cow<'static, str>>) {
let msg = message.into();
eprintln!("{}: {}", "Warning".yellow().bold(), msg);
}
#[inline]
pub fn error(message: impl Into<Cow<'static, str>>) {
let msg = message.into();
eprintln!("{}: {}", "Error".red().bold(), msg);
}
pub fn calculate_file_hash(path: &Path) -> Result<u32> {
use crc32fast::Hasher;
use std::io::Read;
let mut file = std::fs::File::open(path)
.wrap_err_with(|| format!("Failed to open file for hashing: {}", path.display()))?;
let mut hasher = Hasher::new();
let mut buffer = [0; 8192];
loop {
let bytes_read = file
.read(&mut buffer)
.wrap_err_with(|| format!("Failed to read file for hashing: {}", path.display()))?;
if bytes_read == 0 {
break;
}
hasher.update(&buffer[..bytes_read]);
}
Ok(hasher.finalize())
}
pub fn files_have_same_hash(path1: &Path, path2: &Path) -> Result<bool> {
if !path1.exists() || !path2.exists() {
return Ok(false);
}
Ok(calculate_file_hash(path1)? == calculate_file_hash(path2)?)
}
pub fn sanitize_build_zig_zon_name(name: Option<&str>, zig_version: &Version) -> Option<String> {
if *zig_version < Version::new(0, 12, 0) {
return None; }
let default_name = "app";
let raw = name.unwrap_or(default_name).trim();
let mut sanitized = raw
.chars()
.map(|c| match c {
'a'..='z' | 'A'..='Z' | '0'..='9' | '_' => c,
'-' | ' ' | '.' => '_',
_ => '_', })
.collect::<String>()
.to_lowercase();
if let Some(first_char) = sanitized.chars().next()
&& first_char.is_ascii_digit()
{
sanitized = format!("_{}", sanitized);
}
Some(if *zig_version >= Version::new(0, 13, 0) {
format!(".{sanitized}") } else {
format!("\"{sanitized}\"") })
}