sh_exec/
lib.rs

1use colored::*;
2use shells::sh;
3use std::env;
4use std::error::Error;
5use std::fmt;
6
7/// Custom error type for shell command execution
8#[derive(Debug)]
9pub struct ShellError<'a> {
10    command: String,
11    exit_code: i32,
12    stderr: String,
13    stdout: String,
14    error_id: &'a str,
15}
16
17impl ShellError<'_> {
18    /// Creates a new ShellError instance
19    pub fn new(
20        command: String,
21        exit_code: i32,
22        stderr: String,
23        stdout: String,
24        error_id: &'static str,
25    ) -> Self {
26        ShellError {
27            command,
28            exit_code,
29            stderr,
30            stdout,
31            error_id,
32        }
33    }
34}
35
36impl fmt::Display for ShellError<'_> {
37    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38        write!(
39            f,
40            "{}: {}\nExit code: {}\nError ID: {}\n",
41            "Command failed".red(),
42            self.command,
43            self.exit_code,
44            self.error_id.green(),
45        )?;
46
47        if !self.stdout.is_empty() {
48            write!(f, "Standard output:\n{}\n", self.stdout.green())?;
49        }
50
51        if !self.stderr.is_empty() {
52            write!(f, "Standard error:\n{}\n", self.stderr.magenta())?;
53        }
54
55        Ok(())
56    }
57}
58
59impl Error for ShellError<'_> {}
60
61/// Executes a shell command and returns a Result containing the command's output
62pub fn execute_command(cmd: &str, error_id: &'static str) -> Result<String, ShellError<'static>> {
63    let command = cmd.to_string();
64    let (code, stdout, stderr) = sh!("{}", cmd);
65
66    // Check exit code
67    if code == 0 {
68        Ok(stdout)
69    } else {
70        let error = ShellError::new(command, code, stderr, stdout, error_id);
71        Err(error)
72    }
73}
74
75pub fn get_env(env: &str, error_id: &'static str) -> Result<String, ShellError<'static>> {
76    // Get VERSION from environment
77    match env::var(env) {
78        Err(e) => Err(ShellError {
79            command: format!("shell-exec: get_env({env})"),
80            exit_code: 0,
81            stderr: format!("Environment variable '{env}' is not defined: {e:#?}."),
82            stdout: "".to_string(),
83            error_id,
84        }),
85        Ok(value) => Ok(value),
86    }
87}
88
89pub fn main_run(run: fn() -> Result<(), Box<dyn Error>>) {
90    if let Err(e) = run() {
91        eprintln!("Version: {}", env!("CARGO_PKG_VERSION"));
92        eprintln!("Name: {}", env!("CARGO_PKG_NAME"));
93        eprintln!("Authors: {}", env!("CARGO_PKG_AUTHORS"));
94
95        // Optional fields
96        eprintln!("Description: {}", env!("CARGO_PKG_DESCRIPTION"));
97        eprintln!("Homepage: {}", env!("CARGO_PKG_HOMEPAGE"));
98        eprintln!("Repository: {}", env!("CARGO_PKG_REPOSITORY"));
99        eprintln!("{e}")
100    }
101}
102
103/// trap_panics_and_errors traps panics that might be issued when calling a given function
104/// It will print a nice error message in case a panic is trapped.
105/// This macro also traps errors, prints the error and exists the program with error code 1
106///
107/// NOTE
108///   the Err type returned by the given function must return an Err that implements the Display trait.
109#[macro_export]
110macro_rules! trap_panics_and_errors {
111    ($error_id:literal , $main:expr) => {
112        use std::process;
113        use std::error::Error;
114        use colored::*;
115        match std::panic::catch_unwind(|| {
116            match $main() {
117                Err(e) => {
118                    eprintln!("{}: {}", "trap_panics_and_errors".red(), $error_id.green());
119                    eprintln!("  Version: {}", env!("CARGO_PKG_VERSION"));
120                    eprintln!("  Name: {}", env!("CARGO_PKG_NAME"));
121                    eprintln!("  Authors: {}", env!("CARGO_PKG_AUTHORS"));
122
123                    // Optional fields
124                    eprintln!("  Description: {}", env!("CARGO_PKG_DESCRIPTION"));
125                    eprintln!("  Homepage: {}", env!("CARGO_PKG_HOMEPAGE"));
126                    eprintln!("  Repository: {}", env!("CARGO_PKG_REPOSITORY"));
127                    eprintln!("  Error: {e}");
128                    // Exit with error (non-zero)
129                    process::exit(1)
130                }
131                Ok(result) => result,
132            }
133        }) {
134            Ok(result) => result,
135            Err(e) => {
136                eprintln!(
137                    "Error id: {}, 31963-28837-7387. Error {}: {e:#?}!", $error_id,
138                    "Application panicked".red()
139                );
140                std::process::exit(101);
141            }
142        }
143    };
144}
145
146#[macro_export]
147macro_rules! exec {
148    ($error_id:literal , $verbose:expr , $($cmd:tt )* ) => {{
149        let formatted_str = &format!($( $cmd )*);
150        if $verbose { eprintln!("{}", format!("exec!({},{})", $error_id, formatted_str ).magenta()) }
151        execute_command(formatted_str, $error_id)
152    }};
153}
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158
159    #[test]
160    fn test_successful_command() {
161        let output = execute_command("echo Hello World", "8923-2323-2323").unwrap();
162        assert_eq!(output.trim(), "Hello World");
163    }
164
165    #[test]
166    fn test_successful_fmt() {
167        let output = exec!("8923-2323-2323", false, "echo Hello World").unwrap();
168        assert_eq!(output.trim(), "Hello World");
169    }
170
171    #[test]
172    fn test_successful_fmt2() {
173        let output = exec!("21236-28986-4446", true, "echo {}", "Hello World",).unwrap();
174        assert_eq!(output.trim(), "Hello World");
175    }
176
177    #[test]
178    fn test_failing_command() {
179        let result = execute_command("nonexistent_command", "8923-2323-3289");
180        assert!(result.is_err());
181        let error = result.unwrap_err();
182        assert_eq!(error.exit_code, 127);
183        assert!(!error.stderr.is_empty());
184    }
185}