use std::path::{Path, PathBuf};
use std::process::Command;
use crate::platform::BIN_NAME;
use crate::ui::Ui;
use super::binary_in;
pub fn check_path(dir: &Path, ui: &Ui) {
if on_path(dir) {
return;
}
ui.blank();
ui.warn(format!("Note: {} is not on your PATH.", dir.display()));
if cfg!(windows) {
ui.warn("To add it, run:");
ui.warn("");
ui.warn(" $path = [Environment]::GetEnvironmentVariable('Path', 'User')");
ui.warn(format!(
" [Environment]::SetEnvironmentVariable('Path', \"$path;{}\", 'User')",
dir.display()
));
ui.warn("");
ui.warn("Then restart your terminal.");
return;
}
let shell = std::env::var("SHELL").unwrap_or_default();
let dir = dir.display();
if shell.ends_with("fish") {
ui.warn("Add it by appending this to ~/.config/fish/config.fish:");
ui.warn(format!(" fish_add_path {dir}"));
} else {
let profile = if shell.ends_with("zsh") {
"~/.zshrc"
} else {
"~/.bashrc"
};
ui.warn(format!("Add it by appending this to {profile}:"));
ui.warn(format!(" export PATH=\"{dir}:$PATH\""));
}
ui.warn("Then restart your shell.");
}
pub fn check_shadowing(dir: &Path, ui: &Ui) {
let installed = binary_in(dir);
let Some(resolved) = resolve_on_path(BIN_NAME) else {
return;
};
if same_file(&resolved, &installed) {
return;
}
let version = Command::new(&resolved)
.arg("--version")
.output()
.ok()
.filter(|out| out.status.success())
.map(|out| String::from_utf8_lossy(&out.stdout).trim().to_string())
.filter(|text| !text.is_empty())
.unwrap_or_else(|| "unknown version".to_string());
ui.blank();
ui.warn("Warning: 'accent' currently resolves to a different binary:");
ui.warn(format!(" {} ({version})", resolved.display()));
ui.warn(format!(
"which shadows the up-to-date binary at {}.",
installed.display()
));
if cfg!(windows) {
ui.warn(format!(
"Remove the shadowing binary or move {} earlier in your PATH,",
dir.display()
));
ui.warn("then start a new terminal.");
} else {
ui.warn(format!(
"Remove the shadowing binary or move {} earlier in your PATH,",
dir.display()
));
ui.warn("then run 'hash -r' (bash/zsh) or restart your shell.");
}
}
pub fn on_path(dir: &Path) -> bool {
std::env::var_os("PATH")
.map(|path| std::env::split_paths(&path).any(|entry| entry == dir))
.unwrap_or(false)
}
pub fn resolve_on_path(name: &str) -> Option<PathBuf> {
let path = std::env::var_os("PATH")?;
std::env::split_paths(&path)
.map(|dir| dir.join(name))
.find(|candidate| is_executable(candidate))
}
fn is_executable(path: &Path) -> bool {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::metadata(path)
.map(|meta| meta.is_file() && meta.permissions().mode() & 0o111 != 0)
.unwrap_or(false)
}
#[cfg(not(unix))]
{
path.is_file()
}
}
fn same_file(a: &Path, b: &Path) -> bool {
match (a.canonicalize(), b.canonicalize()) {
(Ok(a), Ok(b)) => a == b,
_ => a == b,
}
}