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
use std::env;
use std::path::{Path, PathBuf};
use std::process::Command;

use crate::dirs::get_home_dir;

#[derive(Debug)]
pub enum Shell {
    POSIX(POSIX),
    Zsh(Zsh),
    Bash(Bash),
    Fish(Fish),
}

pub fn get_shell_by_env_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(())
}