what_the_path/shell.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
use std::env;
use std::path::{Path, PathBuf};
use std::process::Command;
use crate::dirs::get_home_dir;
#[derive(Debug)]
/// Represents different types of Unix shells supported by this library.
///
/// This enum provides variants for common Unix shells (POSIX, Zsh, Bash, Fish)
/// along with their specific configuration handling.
///
/// # Examples
///
/// ```
/// use what_the_path::Shell;
///
/// // Detect current shell
/// if let Some(shell) = Shell::detect() {
/// match shell {
/// Shell::Zsh(_) => println!("Using Zsh"),
/// Shell::Bash(_) => println!("Using Bash"),
/// Shell::Fish(_) => println!("Using Fish"),
/// Shell::POSIX(_) => println!("Using POSIX shell"),
/// }
/// }
/// ```
///
/// # Variants
///
/// * `POSIX` - Default POSIX-compliant shell (like sh)
/// * `Zsh` - Z shell
/// * `Bash` - Bourne Again Shell
/// * `Fish` - Friendly Interactive Shell
///
pub enum Shell {
POSIX(POSIX),
Zsh(Zsh),
Bash(Bash),
Fish(Fish),
}
impl Shell {
/// Detects the current shell by examining the `SHELL` environment variable.
///
/// This function attempts to identify the shell type based on the `SHELL` environment variable.
/// It will return `None` on Windows systems as the `SHELL` variable is not typically used.
///
/// # Returns
/// - `Some(Shell)` containing the detected shell type if:
/// - Running on a non-Windows system
/// - The `SHELL` environment variable exists and contains a recognized shell name
/// - `None` if:
/// - Running on Windows
/// - The `SHELL` environment variable does not exist
///
/// # Shell Detection
/// The following shells are recognized (in order):
/// - Zsh
/// - Bash
/// - Fish
/// - Any other shell is assumed to be POSIX-compliant
pub fn detect_by_shell_var() -> Option<Shell> {
if cfg!(windows) {
return None;
}
match env::var("SHELL").ok()?.as_str() {
shell if shell.contains("zsh") => Some(Shell::Zsh(Zsh)),
shell if shell.contains("bash") => Some(Shell::Bash(Bash)),
shell if shell.contains("fish") => Some(Shell::Fish(Fish)),
_ => Some(Shell::POSIX(POSIX)),
}
}
}
#[derive(Debug)]
pub struct POSIX;
impl POSIX {
pub fn does_exist() -> bool {
true
}
pub fn get_rcfiles() -> Option<Vec<PathBuf>> {
let dir = get_home_dir()?;
Some(vec![dir.join(".profile")])
}
}
#[derive(Debug)]
pub struct Zsh;
impl Zsh {
pub fn does_exist() -> bool {
matches!(env::var("SHELL"), Ok(v) if v.contains("zsh"))
|| Command::new("zsh").output().is_ok()
}
pub fn get_rcfiles() -> Option<Vec<PathBuf>> {
let output = std::process::Command::new("zsh")
.args(["-c", "echo -n $ZDOTDIR"])
.output()
.ok()?;
if output.stdout.is_empty() {
return None;
}
// give location
let location = PathBuf::from(String::from_utf8(output.stdout).ok()?.trim());
Some(vec![location.join(".zshenv")])
}
}
#[derive(Debug)]
pub struct Bash;
impl Bash {
pub fn does_exist() -> bool {
matches!(env::var("SHELL"), Ok(v) if v.contains("bash"))
|| Command::new("bash").output().is_ok()
}
pub fn get_rcfiles() -> Option<Vec<PathBuf>> {
let dir = get_home_dir()?;
let rcfiles = [".bash_profile", ".bash_login", ".bashrc"]
.iter()
.map(|rc| dir.join(rc))
.collect();
Some(rcfiles)
}
}
#[derive(Debug)]
pub struct Fish;
impl Fish {
pub fn does_exist() -> bool {
matches!(env::var("SHELL"), Ok(v) if v.contains("fish"))
|| Command::new("fish").output().is_ok()
}
/// Returns the configuration directory path for Fish shell
///
/// This function attempts to locate the Fish shell's configuration directory
/// by joining the user's home directory with the Fish config path.
///
/// # Returns
/// - `Some(Vec<PathBuf>)` containing the path to Fish's conf.d directory
/// - `None` if the home directory cannot be determined
///
/// # Important
/// Note that this function returns a directory path (`conf.d`), not individual
/// file paths. You'll need to enumerate the directory contents to access
/// specific configuration files.
///
/// # Example
/// ```
/// if let Some(paths) = get_rcfiles() {
/// // paths[0] points to ~/.config/fish/conf.d directory
/// // not to specific .fish files
/// }
/// ```
pub fn get_rcfiles() -> Option<Vec<PathBuf>> {
let mut paths = vec![];
if let Some(path) = env::var("XDG_CONFIG_HOME").ok() {
paths.push(PathBuf::from(path).join(".config/fish/conf.d"));
};
if let Some(path) = get_home_dir() {
paths.push(path.join(".config/fish/conf.d"));
}
Some(paths)
}
}
pub fn does_path_exist(path: impl AsRef<Path>) -> bool {
matches!(env::var("PATH"), Ok(paths) if paths.contains(path.as_ref().to_str().unwrap()))
}
pub fn append_to_rcfile(rcfile: PathBuf, line: &str) -> std::io::Result<()> {
use std::fs::OpenOptions;
use std::io::Write;
let mut file = OpenOptions::new().append(true).open(rcfile)?;
writeln!(file, "{}", line)
}
pub fn remove_from_rcfile(rcfile: PathBuf, line: &str) -> std::io::Result<()> {
let line_bytes = line.as_bytes();
let file = std::fs::read_to_string(&rcfile)?;
let file_bytes = file.as_bytes();
if let Some(idx) = file_bytes
.windows(line_bytes.len())
.position(|w| w == line_bytes)
{
let mut new_bytes = file_bytes[..idx].to_vec();
new_bytes.extend(&file_bytes[idx + line_bytes.len()..]);
let content = String::from_utf8(new_bytes).unwrap();
std::fs::write(&rcfile, content)?;
}
Ok(())
}