bawawa/
program.rs

1use crate::{Command, ErrorKind, Result, ResultExt as _};
2use std::{ffi, fmt};
3
4/// a program, pre-checked and known to exist in the environment $PATH
5#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
6pub struct Program(String);
7
8impl Program {
9    /// create a new program without checking if the program
10    /// actually exists and if we have permission to execute
11    pub(super) fn new_unchecked(program: String) -> Self {
12        Program(program)
13    }
14
15    /// create a new `Program` from the given string.
16    ///
17    /// This function will check the program actually exists before
18    /// returning the newly constructed program.
19    ///
20    /// This will allow to pre-check all the necessary objects before
21    /// utilising the program to the different commands.
22    ///
23    /// # Error
24    ///
25    /// the function will fail if the program cannot be found or cannot
26    /// be executed. The following program will return an error of kind
27    /// [`ErrorKind`]::InvalidProgramName:
28    ///
29    /// ```
30    /// # use bawawa::{Program, ErrorKind};
31    /// let error = Program::new("unknown-program").unwrap_err();
32    ///
33    /// match error.kind() {
34    ///   ErrorKind::InvalidProgramName(_) => (),
35    /// #   _ => panic!("wrong error, {:?}", error)
36    ///   // ...
37    /// }
38    /// ```
39    ///
40    /// [`ErrorKind`]: ./enum.ErrorKind.html
41    ///
42    pub fn new<P: AsRef<str>>(program: P) -> Result<Self> {
43        let program = Program::new_unchecked(program.as_ref().to_owned());
44        let mut cmd = Command::new(program.clone());
45        cmd.arguments(&["--help"]);
46        let child = cmd
47            .spawn()
48            .chain_err(|| ErrorKind::InvalidProgramName(program.clone()))?;
49
50        // the process has started successfully
51        // we drop the `child` so it is then killed
52        // see: https://docs.rs/tokio-process/0.2.4/tokio_process/struct.Child.html
53        std::mem::drop(child);
54
55        Ok(program)
56    }
57}
58
59impl AsRef<str> for Program {
60    fn as_ref(&self) -> &str {
61        self.0.as_str()
62    }
63}
64
65impl AsRef<ffi::OsStr> for Program {
66    fn as_ref(&self) -> &ffi::OsStr {
67        self.0.as_ref()
68    }
69}
70
71impl fmt::Display for Program {
72    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
73        self.0.fmt(f)
74    }
75}
76
77#[cfg(test)]
78mod test {
79    use super::*;
80
81    #[cfg(unix)]
82    #[test]
83    fn program_exists() {
84        use crate::error_chain::ChainedError as _;
85
86        const PROGRAM_NAME: &str = "sh";
87
88        if let Err(error) = Program::new(PROGRAM_NAME.to_owned()) {
89            eprintln!("{}", error.display_chain().to_string());
90            panic!("The program does not seem to exist, we are expected it to");
91        }
92    }
93
94    #[test]
95    fn program_does_not_exists() {
96        use crate::error_chain::ChainedError as _;
97
98        const PROGRAM_NAME: &str = "the-impossible-program-that-does-not-exist";
99
100        let error = Program::new(PROGRAM_NAME.to_owned()).expect_err("program should not exist");
101
102        match error.kind() {
103            ErrorKind::InvalidProgramName(program) => assert_eq!(program.0.as_str(), PROGRAM_NAME),
104            _ => panic!("unexpected error: {}", error.display_chain().to_string()),
105        }
106    }
107}