gdenv_lib/
command_runner.rs1use anyhow::Result;
4use anyhow::{Context, bail};
5use std::fmt::{Display, Formatter};
6use std::path::PathBuf;
7
8#[derive(Clone, Debug)]
9pub struct Command {
10 pub executable: PathBuf,
11 pub working_dir: PathBuf,
12 pub args: Vec<String>,
13 pub failure_message: Option<String>,
14}
15
16#[derive(Default, Clone, Debug)]
17pub struct CommandChain {
18 commands: Vec<Command>,
19}
20
21impl Command {
22 pub fn execute(&self) -> Result<()> {
23 let mut command = std::process::Command::new(&self.executable);
24 command.current_dir(&self.working_dir).args(&self.args);
25
26 if !self.working_dir.exists() {
27 bail!(
28 "Can't execute command: {:?}\n Reason: Working directory does not exist: {:?}",
29 command,
30 self.working_dir
31 );
32 }
33
34 let status = command
35 .spawn()
36 .with_context(|| format!("Failed to spawn process: {:?}", command))?
37 .wait()
38 .with_context(|| format!("Failed to wait for process: {:?}", command))?;
39
40 if !status.success() {
41 let message = self
42 .failure_message
43 .as_ref()
44 .map(|m| format!("\n{}", m))
45 .unwrap_or_default();
46 bail!(
47 "Process exited with code {}\nCommand: {}{}",
48 status,
49 self,
50 message
51 )
52 } else {
53 Ok(())
54 }
55 }
56}
57
58impl CommandChain {
59 pub fn new() -> Self {
60 Self::default()
61 }
62
63 pub fn append(&mut self, command: impl Into<Command>) -> &mut Self {
65 self.commands.push(command.into());
66 self
67 }
68
69 pub fn prepend(&mut self, command: impl Into<Command>) -> &mut Self {
71 self.commands.insert(0, command.into());
72 self
73 }
74
75 pub fn execute(&self) -> Result<()> {
77 for command in self.commands.iter() {
78 command.execute()?;
79 }
80 Ok(())
81 }
82
83 pub fn commands(&self) -> &[Command] {
84 &self.commands
85 }
86}
87
88impl Display for Command {
89 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
90 write!(
91 f,
92 r#"cd "{}" && {}"#,
93 self.working_dir.display(),
94 self.executable.display()
95 )?;
96 for arg in &self.args {
97 write!(f, " {}", arg)?;
98 }
99 Ok(())
100 }
101}
102
103impl Display for CommandChain {
104 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
105 let mut first = true;
106 for command in &self.commands {
107 if !first {
108 write!(f, " && ")?;
109 }
110 write!(f, "{}", command)?;
111 first = false;
112 }
113 Ok(())
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120
121 #[test]
122 fn test_command_chain_display() {
123 let chain = CommandChain {
124 commands: vec![
125 Command {
126 executable: PathBuf::from("echo"),
127 working_dir: PathBuf::from("/home/user"),
128 args: vec![String::from("hello")],
129 failure_message: None,
130 },
131 Command {
132 executable: PathBuf::from("cat"),
133 working_dir: PathBuf::from("/home/user"),
134 args: vec![String::from("world")],
135 failure_message: None,
136 },
137 ],
138 };
139 assert_eq!(
140 format!("{}", chain),
141 r#"cd "/home/user" && echo hello && cd "/home/user" && cat world"#
142 );
143 }
144}