1use std::fmt;
6use std::path::PathBuf;
7use std::process::Command;
8
9pub mod git;
10pub mod tmux;
11
12#[derive(Debug)]
14pub enum Error {
15 CommandNotFound(String),
16 CommandFailed { cmd: String, detail: String },
17 Tmux(String),
18 Io(std::io::Error),
19}
20
21impl fmt::Display for Error {
22 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23 match self {
24 Error::CommandNotFound(cmd) => write!(f, "command not found: {cmd}"),
25 Error::CommandFailed { cmd, detail } => write!(f, "command failed: {cmd} — {detail}"),
26 Error::Tmux(msg) => write!(f, "tmux error: {msg}"),
27 Error::Io(err) => write!(f, "io error: {err}"),
28 }
29 }
30}
31
32impl std::error::Error for Error {
33 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
34 match self {
35 Error::Io(err) => Some(err),
36 _ => None,
37 }
38 }
39}
40
41impl From<std::io::Error> for Error {
42 fn from(err: std::io::Error) -> Self {
43 Error::Io(err)
44 }
45}
46
47pub fn which(cmd: &str) -> Option<PathBuf> {
49 ::which::which(cmd).ok()
50}
51
52pub fn require(cmd: &str) -> Result<PathBuf, Error> {
54 ::which::which(cmd).map_err(|_| Error::CommandNotFound(cmd.to_string()))
55}
56
57pub fn run(program: &str, args: &[&str]) -> Result<String, Error> {
59 run_with_env(program, args, &[])
60}
61
62pub fn run_with_env(program: &str, args: &[&str], env: &[(&str, &str)]) -> Result<String, Error> {
64 require(program)?;
65
66 let mut command = Command::new(program);
67 command.args(args);
68 for (k, v) in env {
69 command.env(k, v);
70 }
71 let output = command.output()?;
72
73 if !output.status.success() {
74 let stderr = String::from_utf8_lossy(&output.stderr).to_string();
75 return Err(Error::CommandFailed {
76 cmd: format!("{program} {}", args.first().unwrap_or(&"")),
77 detail: stderr,
78 });
79 }
80
81 Ok(String::from_utf8_lossy(&output.stdout).to_string())
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87
88 #[test]
89 fn test_which_exists() {
90 assert!(which("ls").is_some());
91 }
92
93 #[test]
94 fn test_which_not_exists() {
95 assert!(which("nonexistent_command_12345").is_none());
96 }
97
98 #[test]
99 fn test_require_exists() {
100 assert!(require("ls").is_ok());
101 }
102
103 #[test]
104 fn test_require_not_exists() {
105 let result = require("nonexistent_command_12345");
106 assert!(result.is_err());
107 assert!(matches!(result, Err(Error::CommandNotFound(_))));
108 }
109
110 #[test]
111 fn test_run() {
112 let output = run("echo", &["hello"]).unwrap();
113 assert_eq!(output.trim(), "hello");
114 }
115
116 #[test]
117 fn test_run_not_found() {
118 let result = run("nonexistent_command_12345", &[]);
119 assert!(matches!(result, Err(Error::CommandNotFound(_))));
120 }
121
122 #[test]
123 fn test_has_session_nonexistent() {
124 assert!(!tmux::has_session("dkdc_test_nonexistent_12345"));
125 }
126}