Skip to main content

bare_script/
shell.rs

1//! Shell integration helpers.
2//!
3//! This module provides helper functions for common shell operations.
4
5use crate::error::ScriptResult;
6use crate::sync::CommandBuilder;
7
8/// Finds the full path to an executable.
9///
10/// # Errors
11///
12/// Returns an error if the executable is not found.
13pub fn which(name: &str) -> ScriptResult<Option<std::path::PathBuf>> {
14    #[cfg(windows)]
15    {
16        let result = CommandBuilder::new("where")
17            .arg(name)
18            .capture_output()
19            .execute()?;
20
21        if result.success() {
22            let path = result
23                .stdout_str()
24                .lines()
25                .next()
26                .map(std::path::PathBuf::from);
27            Ok(path)
28        } else {
29            Ok(None)
30        }
31    }
32
33    #[cfg(not(windows))]
34    {
35        let result = CommandBuilder::new("which")
36            .arg(name)
37            .capture_output()
38            .execute()?;
39
40        if result.success() {
41            let path = result
42                .stdout_str()
43                .trim()
44                .lines()
45                .next()
46                .map(std::path::PathBuf::from);
47            Ok(path)
48        } else {
49            Ok(None)
50        }
51    }
52}
53
54/// Checks if an executable exists in PATH.
55///
56/// # Example
57///
58/// ```rust
59/// use bare_script::shell::command_exists;
60///
61/// assert!(command_exists("ls").unwrap_or(false) || command_exists("dir").unwrap_or(false));
62/// ```
63pub fn command_exists(name: &str) -> ScriptResult<bool> {
64    Ok(which(name)?.is_some())
65}
66
67/// Gets the current platform's shell executable name.
68///
69/// Returns "cmd" on Windows, "sh" on Unix.
70pub fn default_shell() -> &'static str {
71    #[cfg(windows)]
72    {
73        "cmd"
74    }
75
76    #[cfg(not(windows))]
77    {
78        "sh"
79    }
80}
81
82/// Gets the shell flag for executing a command string.
83///
84/// Returns "/C" on Windows, "-c" on Unix.
85pub fn shell_exec_flag() -> &'static str {
86    #[cfg(windows)]
87    {
88        "/C"
89    }
90
91    #[cfg(not(windows))]
92    {
93        "-c"
94    }
95}
96
97/// Executes a shell command string.
98///
99/// # Errors
100///
101/// Returns an error if the shell command fails.
102pub fn eval(command: &str) -> ScriptResult<crate::output::Output> {
103    CommandBuilder::new(default_shell())
104        .arg(shell_exec_flag())
105        .arg(command)
106        .capture_output()
107        .execute()
108}
109
110/// Gets environment variable value safely.
111///
112/// Returns None if the variable is not set.
113pub fn get_env(var: &str) -> Option<std::ffi::OsString> {
114    std::env::var(var).ok().map(std::ffi::OsString::from)
115}
116
117/// Checks if running on Windows.
118pub fn is_windows() -> bool {
119    cfg!(windows)
120}
121
122/// Checks if running on a Unix-like system.
123pub fn is_unix() -> bool {
124    cfg!(unix)
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130
131    #[test]
132    fn test_default_shell() {
133        let shell = default_shell();
134        assert!(!shell.is_empty());
135    }
136
137    #[test]
138    fn test_shell_exec_flag() {
139        let flag = shell_exec_flag();
140        assert!(!flag.is_empty());
141    }
142
143    #[test]
144    fn test_platform_checks() {
145        let is_win = is_windows();
146        let is_nix = is_unix();
147        assert!(is_win || is_nix);
148        assert!(!(is_win && is_nix));
149    }
150}