1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
//! Helper traits and types to make `std::process::Command` more ergonomic #![warn(missing_docs)] use std::process::{Command, Output}; use tracing::debug; use once_cell::sync::Lazy; /// Whether to debug-log command strings before they are run /// /// This is true if the `FLUVIO_CMD` environment variable is /// set to `true` (case insensitive). static SHOULD_LOG: Lazy<bool> = Lazy::new(|| { std::env::var("FLUVIO_CMD") .map(|it| it.eq_ignore_ascii_case("true")) .unwrap_or(false) }); /// `Ok(Output)` when a child process successfully runs and returns exit code `0`. /// /// All other circumstances are regarded as `Err(CommandError)`. This includes /// when there is an error invoking a child process, if a child process is /// terminated, or if the child process runs and returns a non-zero exit code. pub type CommandResult = Result<Output, CommandError>; /// An error type describing the kinds of failure a child process may have #[derive(thiserror::Error, Debug)] #[error("Failed to run \"{command}\"")] pub struct CommandError { /// The command that was attempting to run pub command: String, /// The kind of error that the command encountered pub source: CommandErrorKind, } /// Describes the particular kinds of errors that may occur while running a command #[derive(thiserror::Error, Debug)] pub enum CommandErrorKind { /// The child process was terminated, so did not exit successfully #[error("Child process was terminated and has no exit code")] Terminated, /// The child process completed with a non-zero exit code #[error("Child process completed with non-zero exit code {0}")] ExitError(i32), /// There was an error invoking the command #[error("An error occurred while invoking child process")] IoError(#[from] std::io::Error), } /// Adds useful extension methods to the `Command` type pub trait CommandExt { /// Inherit both `stdout` and `stderr` from this process. /// /// # Example /// /// ``` /// use std::process::Command; /// use fluvio_command::CommandExt; /// let _ = Command::new("echo") /// .arg("Hello world") /// .inherit() /// .status(); /// ``` fn inherit(&mut self) -> &mut Self; /// Return a stringified version of the Command. /// /// ``` /// use std::process::Command; /// use fluvio_command::CommandExt; /// let mut command = Command::new("echo"); /// command.arg("one").arg("two three"); /// let command_string: String = command.display(); /// assert_eq!(command_string, "echo one two three"); /// ``` fn display(&mut self) -> String; /// Returns a result signaling the outcome of executing this command. /// /// ``` /// use std::process::{Command, Output}; /// use fluvio_command::{CommandExt, CommandErrorKind}; /// /// // On success, we get the stdout and stderr output /// let output: Output = Command::new("true").result().unwrap(); /// /// let error = Command::new("false").result().unwrap_err(); /// assert!(matches!(error.source, CommandErrorKind::ExitError(1))); /// assert_eq!(error.command, "false"); /// /// let error = Command::new("foobar").result().unwrap_err(); /// assert!(matches!(error.source, CommandErrorKind::IoError(_))); /// assert_eq!(error.command, "foobar"); /// ``` fn result(&mut self) -> CommandResult; } impl CommandExt for Command { fn inherit(&mut self) -> &mut Self { use std::process::Stdio; self.stdout(Stdio::inherit()).stderr(Stdio::inherit()) } fn display(&mut self) -> String { format!("{:?}", self).replace("\"", "") } fn result(&mut self) -> CommandResult { if *SHOULD_LOG { debug!("Executing> {}", self.display()); } self.output() .map_err(|e| CommandError { command: self.display(), source: CommandErrorKind::IoError(e), }) .and_then(|output| match output.status.code() { Some(0i32) => Ok(output), None => Err(CommandError { command: self.display(), source: CommandErrorKind::Terminated, }), Some(code) => Err(CommandError { command: self.display(), source: CommandErrorKind::ExitError(code), }), }) } }