summaryrefslogtreecommitdiff
path: root/src/cmd.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd.rs')
-rw-r--r--src/cmd.rs93
1 files changed, 93 insertions, 0 deletions
diff --git a/src/cmd.rs b/src/cmd.rs
new file mode 100644
index 0000000..6092f53
--- /dev/null
+++ b/src/cmd.rs
@@ -0,0 +1,93 @@
+use anyhow::{Context, Result};
+use std::{io::Read, path::Path, process::Command};
+
+/// Run a command with a description for a possible error and append the merged stdout and stderr.
+/// The boolean in the returned `Result` is true if the command's exit status is success.
+pub fn run_cmd(mut cmd: Command, description: &str, output: &mut Vec<u8>) -> Result<bool> {
+ let (mut reader, writer) = os_pipe::pipe()
+ .with_context(|| format!("Failed to create a pipe to run the command `{description}``"))?;
+
+ let writer_clone = writer.try_clone().with_context(|| {
+ format!("Failed to clone the pipe writer for the command `{description}`")
+ })?;
+
+ let mut handle = cmd
+ .stdout(writer_clone)
+ .stderr(writer)
+ .spawn()
+ .with_context(|| format!("Failed to run the command `{description}`"))?;
+
+ // Prevent pipe deadlock.
+ drop(cmd);
+
+ reader
+ .read_to_end(output)
+ .with_context(|| format!("Failed to read the output of the command `{description}`"))?;
+
+ output.push(b'\n');
+
+ handle
+ .wait()
+ .with_context(|| format!("Failed to wait on the command `{description}` to exit"))
+ .map(|status| status.success())
+}
+
+pub struct CargoCmd<'a> {
+ pub subcommand: &'a str,
+ pub args: &'a [&'a str],
+ pub bin_name: &'a str,
+ pub description: &'a str,
+ /// RUSTFLAGS="-A warnings"
+ pub hide_warnings: bool,
+ /// Added as `--target-dir` if `Self::dev` is true.
+ pub target_dir: &'a Path,
+ /// The output buffer to append the merged stdout and stderr.
+ pub output: &'a mut Vec<u8>,
+ /// true while developing Rustlings.
+ pub dev: bool,
+}
+
+impl<'a> CargoCmd<'a> {
+ /// Run `cargo SUBCOMMAND --bin EXERCISE_NAME … ARGS`.
+ pub fn run(&mut self) -> Result<bool> {
+ let mut cmd = Command::new("cargo");
+ cmd.arg(self.subcommand);
+
+ // A hack to make `cargo run` work when developing Rustlings.
+ if self.dev {
+ cmd.arg("--manifest-path")
+ .arg("dev/Cargo.toml")
+ .arg("--target-dir")
+ .arg(self.target_dir);
+ }
+
+ cmd.arg("--color")
+ .arg("always")
+ .arg("-q")
+ .arg("--bin")
+ .arg(self.bin_name)
+ .args(self.args);
+
+ if self.hide_warnings {
+ cmd.env("RUSTFLAGS", "-A warnings");
+ }
+
+ run_cmd(cmd, self.description, self.output)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_run_cmd() {
+ let mut cmd = Command::new("echo");
+ cmd.arg("Hello");
+
+ let mut output = Vec::with_capacity(8);
+ run_cmd(cmd, "echo …", &mut output).unwrap();
+
+ assert_eq!(output, b"Hello\n\n");
+ }
+}