use std::path::PathBuf;
fn add_to_user_path(new_path: &str) -> Result<(), Box<dyn std::error::Error>> {
#[cfg(target_os = "windows")]
add_win_path(new_path)?;
#[cfg(target_os = "macos")]
add_macos_path(new_path)?;
Ok(())
}
fn setup_project_dir() -> Result<PathBuf, Box<dyn std::error::Error>> {
let base_path = dirs::home_dir().expect("home directory not found!").join(".cmdlink");
std::fs::create_dir_all(&base_path).map_err(|e| format!("error creating project directory: {e}"))?;
let bins_dir = base_path.join("bins");
std::fs::create_dir_all(&bins_dir).map_err(|e| format!("error creating bins directory: {e}"))?;
add_to_user_path(&bins_dir.display().to_string())?;
Ok(base_path)
}
fn main() {
if let Err(err) = setup_project_dir() {
panic!("error setting up project directory: {}", err);
}
}
#[cfg(target_os = "windows")]
fn add_win_path(new_path: &str) -> Result<(), Box<dyn std::error::Error>> {
use std::{ffi::OsString, os::windows::ffi::OsStrExt};
use base64::{engine::general_purpose, Engine};
use windows_registry::CURRENT_USER;
let environment_key = CURRENT_USER.open("Environment")?;
let current_path = environment_key.get_string("Path").unwrap();
if current_path.split(';').any(|p| p == new_path) {
return Ok(());
}
let updated_path = if current_path.is_empty() {
new_path.to_string()
} else {
format!("{}{}", current_path, new_path)
};
let ps_command = format!("[Environment]::SetEnvironmentVariable('PATH', '{}', 'User')", updated_path);
let utf_16_bytes: Vec<u8> = OsString::from(ps_command)
.encode_wide()
.flat_map(|u| u.to_le_bytes())
.collect();
runas::Command::new("powershell.exe")
.args(&[
"-ExecutionPolicy",
"Bypass",
"-NoProfile",
"-EncodedCommand",
&general_purpose::STANDARD.encode(utf_16_bytes),
])
.gui(true)
.status()?;
Ok(())
}
#[cfg(target_family = "unix")]
fn add_macos_path(new_path: &str) -> Result<(), Box<dyn std::error::Error>> {
use std::{
fs::{self, File},
io::Write,
};
let profile_file = dirs::home_dir().expect("home directory not found!").join(".profile");
let mut file = if !profile_file.exists() {
File::create_new(&profile_file)?
} else {
File::options().append(true).create(true).open(&profile_file)?
};
let profile = fs::read_to_string(&profile_file)?;
if !profile.lines().any(|l| l.contains(new_path)) {
file.write_all(format!("export PATH=$PATH:{}\n", new_path).as_bytes())?;
}
Ok(())
}