1use std::path::PathBuf;
10use std::process::Command;
11
12pub mod git;
13pub mod tmux;
14
15#[derive(Debug)]
16pub enum Error {
17 CommandNotFound(String),
18 CommandFailed { cmd: String, detail: String },
19 Tmux(String),
20 Io(std::io::Error),
21}
22
23impl std::fmt::Display for Error {
24 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25 match self {
26 Self::CommandNotFound(s) => write!(f, "command not found: {s}"),
27 Self::CommandFailed { cmd, detail } => {
28 write!(f, "command failed: {cmd} — {detail}")
29 }
30 Self::Tmux(s) => write!(f, "tmux error: {s}"),
31 Self::Io(e) => write!(f, "io error: {e}"),
32 }
33 }
34}
35
36impl std::error::Error for Error {
37 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
38 match self {
39 Self::Io(e) => Some(e),
40 _ => None,
41 }
42 }
43}
44
45impl From<std::io::Error> for Error {
46 fn from(e: std::io::Error) -> Self {
47 Self::Io(e)
48 }
49}
50
51pub fn which(cmd: &str) -> Option<PathBuf> {
53 ::which::which(cmd).ok()
54}
55
56pub fn require(cmd: &str) -> Result<PathBuf, Error> {
58 ::which::which(cmd).map_err(|_| Error::CommandNotFound(cmd.to_string()))
59}
60
61pub fn run(program: &str, args: &[&str]) -> Result<String, Error> {
63 run_with_env(program, args, &[])
64}
65
66pub fn run_with_env(program: &str, args: &[&str], env: &[(&str, &str)]) -> Result<String, Error> {
68 require(program)?;
69
70 let mut command = Command::new(program);
71 command.args(args);
72 for (k, v) in env {
73 command.env(k, v);
74 }
75 let output = command.output()?;
76
77 if !output.status.success() {
78 let stderr = String::from_utf8_lossy(&output.stderr).into_owned();
79 return Err(Error::CommandFailed {
80 cmd: format!("{program} {}", args.first().unwrap_or(&"")),
81 detail: stderr,
82 });
83 }
84
85 Ok(String::from_utf8_lossy(&output.stdout).into_owned())
86}