query_shell/
lib.rs

1use std::str::FromStr;
2use sysinfo::{get_current_pid, ProcessExt, ProcessRefreshKind, RefreshKind, System, SystemExt};
3use thiserror::Error;
4
5/// A non-exhaustive list of errors when fetching the process name
6/// and resolving it into a shell.
7#[derive(Error, Debug, PartialEq, Clone)]
8#[non_exhaustive]
9pub enum Error {
10    #[error("The platform is not supported")]
11    UnsupportedPlatform,
12    #[error("Current process does not have a parent")]
13    NoParent,
14    #[error("Unknown shell")]
15    Unknown,
16    #[error("Unavailable with some su implementations")]
17    InSu,
18}
19
20/// Fetches the parent process's name in lowercase.
21///
22/// # Errors
23///
24/// Returns [`Error::UnsupportedPlatform`] if the call to [`sysinfo::get_current_pid`] fails.
25///
26/// Returns [`Error::NoParent`] if this process has no parent.
27pub fn get_shell_name() -> Result<String, Error> {
28    let sys =
29        System::new_with_specifics(RefreshKind::new().with_processes(ProcessRefreshKind::new()));
30    let process = sys
31        .process(get_current_pid().map_err(|_| Error::UnsupportedPlatform)?)
32        .expect("Process with current pid does not exist");
33    let parent = sys
34        .process(process.parent().ok_or(Error::NoParent)?)
35        .expect("Process with parent pid does not exist");
36    let shell = parent.name().trim().to_lowercase();
37    let shell = shell.strip_suffix(".exe").unwrap_or(&shell); // windows bad
38    let shell = shell.strip_prefix('-').unwrap_or(shell); // login shells
39    Ok(shell.to_owned())
40}
41pub fn get_shell() -> Result<Shell, Error> {
42    Shell::get()
43}
44
45/// The type of shell.
46#[derive(Debug, PartialEq, Eq, Clone, Copy)]
47#[non_exhaustive]
48pub enum Shell {
49    Bash,
50    Elvish,
51    Fish,
52    Ion,
53    Nushell,
54    Powershell,
55    Xonsh,
56    Zsh,
57}
58
59impl Shell {
60    /// Fetch the shell running this process.
61    ///
62    /// See [`get_shell_name`] for more info.
63    pub fn get() -> Result<Self, Error> {
64        match get_shell_name()?.as_str() {
65            "su" => Err(Error::InSu),
66            shell if shell.starts_with("python") => Ok(Self::Xonsh),
67            shell => Self::from_str(shell),
68        }
69    }
70    /// Get the string representation of this shell.
71    ///
72    /// All names are in lowercase.
73    ///
74    /// Unexpected values or ambiguities are sorted out below:
75    ///
76    /// - [`Shell::Nushell`] => `nu`
77    /// - [`Shell::Powershell`] => `powershell`
78    pub fn to_str(self) -> &'static str {
79        match self {
80            Shell::Bash => "bash",
81            Shell::Elvish => "elvish",
82            Shell::Fish => "fish",
83            Shell::Ion => "ion",
84            Shell::Nushell => "nu",
85            Shell::Powershell => "powershell",
86            Shell::Xonsh => "xonsh",
87            Shell::Zsh => "zsh",
88        }
89    }
90}
91
92impl FromStr for Shell {
93    type Err = Error;
94
95    fn from_str(s: &str) -> Result<Self, Self::Err> {
96        match s {
97            "bash" => Ok(Shell::Bash),
98            "elvish" => Ok(Shell::Elvish),
99            "fish" => Ok(Shell::Fish),
100            "ion" => Ok(Shell::Ion),
101            "nu" | "nushell" => Ok(Shell::Nushell),
102            "pwsh" | "powershell" => Ok(Shell::Powershell),
103            "xonsh" | "xon.sh" => Ok(Shell::Xonsh),
104            "zsh" => Ok(Shell::Zsh),
105            _ => Err(Error::Unknown),
106        }
107    }
108}