1use colored::*;
2use shells::sh;
3use std::env;
4use std::error::Error;
5use std::fmt;
6
7#[derive(Debug, Clone)]
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 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<'_> {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 write!(
41 f,
42 "{}: '{}'\nsh_exec Exit code: {}\nsh_exec Error ID: {}\n",
43 "Command failed".red(),
44 self.command,
45 self.exit_code,
46 self.error_id.green(),
47 )?;
48
49 if !self.stdout.is_empty() {
50 write!(f, "Standard output:\n{}\n", self.stdout.green())?;
51 }
52
53 if !self.stderr.is_empty() {
54 write!(f, "Standard error:\n{}\n", self.stderr.magenta())?;
55 }
56
57 Ok(())
58 }
59}
60
61impl Error for ShellError<'_> {}
62
63pub fn execute_command(cmd: &str, error_id: &'static str) -> Result<String, ShellError<'static>> {
65 let command = cmd.to_string();
66 let (code, stdout, stderr) = sh!("{}", cmd);
67
68 if code == 0 {
70 Ok(stdout)
71 } else {
72 let error = ShellError::new(command, code, stderr, stdout, error_id);
73 Err(error)
74 }
75}
76
77pub fn get_env(env: &str, error_id: &'static str) -> Result<String, ShellError<'static>> {
80 match env::var(env) {
82 Err(e) => Err(ShellError {
83 command: format!("shell-exec: get_env({env})"),
84 exit_code: 0,
85 stderr: format!("Environment variable '{env}' is not defined: {e:#?}."),
86 stdout: "".to_string(),
87 error_id,
88 }),
89 Ok(value) => Ok(value),
90 }
91}
92
93pub fn main_run(run: fn() -> Result<(), Box<dyn Error>>) {
97 if let Err(e) = run() {
98 eprintln!("Version: {}", env!("CARGO_PKG_VERSION"));
99 eprintln!("Name: {}", env!("CARGO_PKG_NAME"));
100 eprintln!("Authors: {}", env!("CARGO_PKG_AUTHORS"));
101
102 eprintln!("Description: {}", env!("CARGO_PKG_DESCRIPTION"));
104 eprintln!("Homepage: {}", env!("CARGO_PKG_HOMEPAGE"));
105 eprintln!("Repository: {}", env!("CARGO_PKG_REPOSITORY"));
106 eprintln!("{e}")
107 }
108}
109
110#[macro_export]
117macro_rules! trap_panics_and_errors {
118 ($error_id:literal , $main:expr) => {
119 use std::process;
120 use std::error::Error;
121 use colored::*;
122 use log::*;
123 match std::panic::catch_unwind(|| {
124 match $main() {
125 Err(e) => {
126 error!("{}: {}", "trap_panics_and_errors".red(), $error_id.green());
127 error!(" Version: {}", env!("CARGO_PKG_VERSION"));
128 error!(" Name: {}", env!("CARGO_PKG_NAME"));
129 error!(" Authors: {}", env!("CARGO_PKG_AUTHORS"));
130
131 error!(" Description: {}", env!("CARGO_PKG_DESCRIPTION"));
133 error!(" Homepage: {}", env!("CARGO_PKG_HOMEPAGE"));
134 error!(" Repository: {}", env!("CARGO_PKG_REPOSITORY"));
135 error!(" Error: {e}");
136 process::exit(1)
138 }
139 Ok(result) => result,
140 }
141 }) {
142 Ok(result) => result,
143 Err(e) => {
144 eprintln!(
145 "Error id: {}, 31963-28837-7387. Error {}: {e:#?}!", $error_id,
146 "Application panicked".red()
147 );
148 std::process::exit(101);
149 }
150 }
151 };
152}
153
154#[macro_export]
158macro_rules! exec {
159 ($error_id:literal , $verbose:expr , $($cmd:tt )* ) => {{
160 use colored::Colorize;
161 let formatted_str = &format!($( $cmd )*);
162 if $verbose { eprintln!("{}", format!("exec!({},{})", $error_id, formatted_str ).magenta()) }
163 execute_command(formatted_str, $error_id)
164 }};
165}
166
167#[macro_export]
174macro_rules! s {
175 ($error_id:literal , $($cmd:tt )* ) => {{
176 use colored::Colorize;
177 use log::{debug, info, error};
178 let formatted_str = &format!($( $cmd )*);
179 info!("{}", format!("s!({},{})", $error_id, formatted_str ).magenta());
180 let output = execute_command(formatted_str, $error_id);
181 match output.clone() {
183 std::result::Result::Ok(output) => debug!("{}", output),
184 Err(e) => error!("{}", e),
185 }
186 output
187 }};
188}
189
190
191#[macro_export]
194macro_rules! e {
195 ($($cmd:tt )* ) => {{
196 let formatted_str = &format!($( $cmd )*);
197 let output = execute_command(formatted_str, "no-error-id-specified");
198 match output.clone() {
200 std::result::Result::Ok(output) => output,
201 Err(e) => panic!("Error executing command {formatted_str}: {}", e),
202 }
203 }};
204}
205
206#[macro_export]
213macro_rules! a {
214 ($error_id:literal , $duration:tt, $($cmd:tt )* ) => {{
215 use std::{thread, time};
216 use colored::Colorize;
217 use log::{debug, info, error};
218 let handle = thread::spawn( || {
219 let formatted_str = &format!($( $cmd )*);
220 info!("{}", format!("s!({},{})", $error_id, formatted_str ).magenta());
221 let output = execute_command(formatted_str, $error_id);
222 match output.clone() {
224 std::result::Result::Ok(output) => debug!("{}", output),
225 std::result::Result::Err(e) => error!("{}", e),
226 }
227 output
228 });
229 let ten_millis = time::Duration::from_millis(10);
230 let now = time::Instant::now();
231
232 loop {
233 if handle.is_finished() {
234 break;
235 }
236 thread::sleep(ten_millis);
237 if now.elapsed() >= $duration {
238 break;
239 }
240 }
241 if handle.is_finished() {
242 match handle.join() {
243 std::result::Result::Ok(result) => {
244 std::result::Result::Ok(result?)
245 }
246 std::result::Result::Err(e) => {
247 let cmd = format!($( $cmd )*);
248 let error = ShellError::new(cmd, -2, String::from("Join Failed"), String::from("Joining of command failed"), $error_id);
249 std::result::Result::Err(error)
250 }
251 }
252 } else {
253 let cmd = format!($( $cmd )*);
254 let error = ShellError::new(cmd, -1, String::from("Timeout"), String::from("Command timed out"), $error_id);
255 std::result::Result::Err(error)
256 }
257
258 }};
259}
260
261use std::io;
262use std::io::Write;
263
264pub fn read_prompt(prompt: &str) -> String {
265 print!("{prompt}");
266 io::stdout().flush().unwrap();
269 let mut buffer = String::new();
274 io::stdin().read_line(&mut buffer).expect("Failed to read from stdin");
275 buffer.trim().to_string()
278}
279
280
281#[cfg(test)]
282mod tests {
283 use super::*;
284
285 #[test]
286 fn test_successful_command() {
287 let output = execute_command("echo Hello World", "8923-2323-2323").unwrap();
288 assert_eq!(output.trim(), "Hello World");
289 }
290
291 #[test]
292 fn test_successful_fmt() {
293 let output = exec!("8923-2323-2323", false, "echo Hello World").unwrap();
294 assert_eq!(output.trim(), "Hello World");
295 }
296
297 #[test]
298 fn test_successful_fmt2() {
299 let output = exec!("21236-28986-4446", true, "echo {}", "Hello World",).unwrap();
300 assert_eq!(output.trim(), "Hello World");
301 }
302
303 #[test]
304 fn test_failing_command() {
305 let result = execute_command("nonexistent_command", "8923-2323-3289");
306 assert!(result.is_err());
307 let error = result.unwrap_err();
308 assert_eq!(error.exit_code, 127);
309 assert!(!error.stderr.is_empty());
310 }
311}