sweet_fs/process/
command.rs

1use anyhow::Result;
2use std::process::Child;
3use std::process::Command;
4use std::process::Output;
5use std::process::Stdio;
6
7
8pub struct CommandExt;
9
10impl CommandExt {
11	/// Create a command from a vector
12	pub fn from_vec<T: AsRef<str>>(cmd: &Vec<T>) -> Command {
13		let mut command = Command::new(cmd[0].as_ref());
14		for arg in cmd.iter().skip(1) {
15			command.arg(arg.as_ref());
16		}
17		command
18	}
19
20
21	/// Create a command from a string, splitting on whitespace
22	pub fn from_whitespace(cmd: &str) -> Command {
23		let cmd = cmd.split_whitespace();
24		let mut command = Command::new(cmd.clone().next().unwrap());
25		for arg in cmd.skip(1) {
26			command.arg(arg);
27		}
28		command
29	}
30
31	/// Turn an exit status into a Result
32	pub fn unwrap_status(mut cmd: Command) -> Result<()> {
33		let status = cmd.status()?;
34		if !status.success() {
35			Err(anyhow::anyhow!("Command failed: {:?}", status))?;
36		}
37		Ok(())
38	}
39	/// Turn a non-empty output into a Result
40	pub fn unwrap_output_empty(mut cmd: Command) -> Result<()> {
41		let output = cmd.output()?;
42		if output.stdout.is_empty() && output.stderr.is_empty() {
43			Ok(())
44		} else {
45			anyhow::bail!(
46				"Expected empty output, received: \nStdout: {}\nStderr: {}",
47				String::from_utf8_lossy(&output.stdout),
48				String::from_utf8_lossy(&output.stderr)
49			)
50		}
51	}
52
53
54	/// Run a command and pipe the output to stdio. Returns error only if execution failed, not if it returns error
55	pub fn spawn_command(args: &Vec<&str>) -> Result<Child> {
56		println!("{}", args.join(" "));
57		let child = Self::get_command(args)
58			// .stdout(Stdio::piped())
59			.spawn()?;
60		Ok(child)
61	}
62
63	pub fn spawn_command_blocking(args: &Vec<&str>) -> Result<()> {
64		let _ = Self::get_command(args)
65			.stdout(Stdio::inherit())
66			.stderr(Stdio::inherit())
67			.output()?;
68		Ok(())
69	}
70
71	pub fn spawn_command_hold_stdio(args: &Vec<&str>) -> Result<CommandOutput> {
72		let out = Self::get_command(args).output()?;
73		Ok(out.into())
74	}
75
76	fn get_command(args: &Vec<&str>) -> Command {
77		let mut cmd = Command::new(args[0]);
78		cmd.args(args[1..].iter());
79		cmd
80	}
81
82	pub fn spawn_command_with_shell_blocking(args: &Vec<&str>) -> Result<()> {
83		let _ = Self::get_command_with_shell(args)
84			.stdout(Stdio::inherit())
85			.stderr(Stdio::inherit())
86			.output()?;
87		Ok(())
88	}
89
90	fn get_command_with_shell(args: &Vec<&str>) -> Command {
91		let is_windows = cfg!(target_os = "windows");
92		let (cmd, arg) = if is_windows {
93			// ("cmd", "\\C")
94			("powershell", "-Command")
95		} else {
96			("sh", "-c")
97		};
98		let mut cmd = Command::new(cmd);
99		cmd.arg(arg);
100		cmd.args(args);
101		cmd
102	}
103}
104
105pub struct CommandOutput {
106	pub success: bool,
107	pub stdout: String,
108	pub stderr: String,
109}
110
111impl From<Output> for CommandOutput {
112	fn from(output: Output) -> Self {
113		let stdout = String::from_utf8_lossy(&output.stdout).to_string();
114		let stderr = String::from_utf8_lossy(&output.stderr).to_string();
115		CommandOutput {
116			success: output.status.success(),
117			stdout,
118			stderr,
119		}
120	}
121}