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
use std::collections::HashMap;
use std::ffi::{OsStr, OsString};
use std::io::Result;
use std::path::PathBuf;
use std::process::{Command, ExitStatus};

//-- Cmd -------------------------------------------------------------------------------------------

/// Clone-able variant of [`std::process::Command`] with some build specific helper methods
///
/// [`std::process::Command`]: (https://doc.rust-lang.org/std/process/struct.Command.html)
///
/// Cloning allows configuring commands with some common arguments and then invoking it with
/// additional arguments/env vars at different places inside the script.
///
/// Method of this type are mirroring those of std::process::Command with addition of [`run`] and
/// [`run_result`] commands for easier use inside of build sript.
///
/// [`run`]: #method.run
/// [`run_result`]: #method.run_result
///
#[derive(Clone, Debug)]
pub struct Cmd {
    program: OsString,
    args: Vec<OsString>,
    envs: HashMap<OsString, OsString>,
    work: Option<PathBuf>,
}

impl Cmd {

    /// Constructs a new Cmd for launching the executable at path `program`
    pub fn new<S: AsRef<OsStr>>(program: S) -> Self {
        Self {
            program: program.as_ref().to_owned(),
            args: vec![],
            envs: HashMap::new(),
            work: None,
        }
    }

    /// Adds an argument to the list of execution arguments
    pub fn arg<S: AsRef<OsStr>>(mut self, arg: S) -> Self {
        self.args.push(arg.as_ref().to_owned());
        self
    }

    /// Adds multiple arguments to the list of execution arguments
    pub fn args<I, S>(mut self, args: I) -> Self
        where
            I: IntoIterator<Item = S>,
            S: AsRef<OsStr>,
    {
        self.args.extend(args.into_iter().map(|e| e.as_ref().to_owned()));
        self
    }

    /// Sets an environment variable
    pub fn env<K: AsRef<OsStr>, V: AsRef<OsStr>>(mut self, env: K, val: V) -> Self {
        self.envs.insert(env.as_ref().to_owned(), val.as_ref().to_owned());
        self
    }

    /// Run the command and exit the build with informative panic message.
    ///
    /// This is convienece method for calling [`std::process::Command::status()`] method on command
    /// instance retrieved by [`command`] method
    ///
    /// [`command`]: #method.command
    /// [`std::process::Command::status()`]:
    /// https://doc.rust-lang.org/std/process/struct.Command.html#method.status
    pub fn run(&self) {
        self.run_result().expect(format!("Command executon '{:?} {:?} {:?}' failed",
            self.program, self.args, self.envs).as_str()
        );
    }

    /// Run the command and return it's status.
    ///
    /// This is convienece method for calling [`std::process::Command::status()`] method on command
    /// instance retrieved by [`command`] method
    ///
    /// [`command`]: #method.command
    /// [`std::process::Command::status()`]:
    /// https://doc.rust-lang.org/std/process/struct.Command.html#method.status
    pub fn run_result(&self) -> Result<ExitStatus> {
        self.command().status()
    }

    /// Build the `std::process::Command` with args and environment variables set up by methods on
    /// this Cmd instance.
    pub fn command(&self) -> Command {
        let mut command = Command::new(&self.program);
        command.args(&self.args);
        command.envs(&self.envs);

        if let Some(work_dir) = &self.work {
            command.current_dir(work_dir);
        }

        command
    }
}