commandext/
lib.rs

1// Copyright (c) 2016 vergen developers
2//
3// Licensed under the Apache License, Version 2.0
4// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT
5// license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. All files in the project carrying such notice may not be copied,
7// modified, or distributed except according to those terms.
8
9//! Defines the `CommandExt` type.
10//!
11//! `CommandExt` wraps `Command` and enhances it.
12//!
13//! A `CommandExt` is built similar to a `Command`. Supply the command name, add
14//! args, set enviroment variables, etc. The `exec` function takes a closure
15//! that executes the command and returns the given result `T`.
16//!
17//! # Examples
18//!
19//! ```rust
20//! use commandext::{CommandExt,to_procout};
21//!
22//! let mut cmd = CommandExt::new("echo");
23//! cmd.env("DEBUG", "true");
24//! cmd.arg("test");
25//!
26//! let output = cmd.exec(to_procout());
27//! ```
28//!
29//! ```rust
30//! // Execute "echo 'Testing Spawn'" via spawn and verify the exit code was 0.
31//! // As a side effect, you would see 'Testing Spawn' on stdout.
32//! use commandext::{CommandExt,to_res};
33//!
34//! let res = CommandExt::new("echo").arg("Testing Spawn").exec(to_res());
35//!
36//! assert_eq!(Ok(0), res);
37//! ```
38//!
39//! ```rust
40//! // Exeute "echo test" via output and verify the output is indeed "test\n".
41//! // In this case there is nothing on stdout.  The output is consumed here.
42//! extern crate sodium_sys;
43//! extern crate commandext;
44//!
45//! use commandext::{CommandExt,to_procout};
46//! use sodium_sys::crypto::utils::secmem;
47//!
48//! fn main() {
49//!     let cmd = CommandExt::new("echo").arg("test").exec(to_procout());
50//!     let output = cmd.unwrap();
51//!
52//!     assert_eq!(secmem::memcmp(&[116, 101, 115, 116, 10], &output.stdout[..]), 0);
53//! }
54//! ```
55#![cfg_attr(feature="clippy", feature(plugin))]
56#![cfg_attr(feature="clippy", plugin(clippy))]
57#![cfg_attr(feature="clippy", deny(clippy, clippy_pedantic))]
58#![deny(missing_docs)]
59#[cfg(test)]
60extern crate sodium_sys;
61
62#[cfg(windows)]
63use std::borrow::BorrowMut;
64use std::cell::RefCell;
65use std::io;
66use std::path::Path;
67use std::process::{Child, Command, Output, Stdio};
68
69/// Extends `std::io::process::Command`.
70#[cfg(unix)]
71pub struct CommandExt {
72    /// The command to execute.
73    cmd: RefCell<Command>,
74    /// If true, show a header containg the command to be executed.
75    header: bool,
76}
77
78/// Extends `std::io::process::Command`.
79#[cfg(windows)]
80pub struct CommandExt {
81    /// The command to execute.
82    cmd: RefCell<Command>,
83    /// The sh -c args string.
84    shargs: String,
85    /// If true, show a header containg the command to be executed.
86    header: bool,
87}
88
89/// Surround a message with 80 # character lines.
90///
91/// <pre>
92/// ################################################################################
93///   msg
94/// ################################################################################
95/// </pre>
96///
97pub fn header(msg: &str) {
98    println!("{:#<80}", "#");
99    println!("{}", msg);
100    println!("{:#<80}", "#");
101}
102
103#[cfg(unix)]
104fn build_command(cmd: &str) -> Command {
105    Command::new(cmd)
106}
107
108#[cfg(windows)]
109fn build_command(_: &str) -> Command {
110    let mut new_cmd = Command::new("sh");
111    new_cmd.arg("-c");
112    new_cmd
113}
114
115/// Generate a closure that returns a Output from the given command.
116pub fn to_procout() -> fn(cmd: &mut Command) -> io::Result<Output> {
117    fn blah(cmd: &mut Command) -> io::Result<Output> {
118        cmd.output()
119    };
120    blah
121}
122
123/// Generate a closure that returns a Child from the given command.
124pub fn to_proc() -> fn(cmd: &mut Command) -> io::Result<Child> {
125    fn blah(cmd: &mut Command) -> io::Result<Child> {
126        cmd.spawn()
127    };
128    blah
129}
130
131/// Generate a closure that returns a Result with the exit code of the process.
132///
133/// # Return Values
134/// * `Ok(0)` - success
135/// * `Err(x)` - failure
136pub fn to_res() -> fn(cmd: &mut Command) -> Result<i32, i32> {
137    fn blah(cmd: &mut Command) -> Result<i32, i32> {
138        cmd.stdout(Stdio::inherit());
139        cmd.stderr(Stdio::inherit());
140
141        match cmd.spawn() {
142            Ok(mut p) => {
143                match p.wait() {
144                    Ok(status) => {
145                        match status.code() {
146                            Some(code) => {
147                                if code == 0 {
148                                    Ok(code)
149                                } else {
150                                    Err(code)
151                                }
152                            }
153                            None => Err(1),
154                        }
155                    }
156                    Err(_) => Err(1),
157                }
158            }
159            Err(e) => panic!("Failed to execute: {}", e),
160        }
161    };
162    blah
163}
164
165impl CommandExt {
166    /// Create a new CommandExt.
167    ///
168    /// # Arguments
169    ///
170    /// * cmd - The command to use, i.e. "echo".
171    /// * exec - The Executable to use when executing the command.
172    #[cfg(unix)]
173    pub fn new(cmd: &str) -> CommandExt {
174        CommandExt {
175            cmd: RefCell::new(build_command(cmd)),
176            header: false,
177        }
178    }
179
180    /// Create a new CommandExt.
181    ///
182    /// # Arguments
183    /// * cmd - The command to use, i.e. "echo".
184    /// * exec - The Executable to use when executing the command.
185    ///
186    /// # Note
187    /// On Windows, the cmd is built as "sh -c", and the cmd is added
188    /// to the shargs string.  When the command is exeuted, this results
189    /// in "sh -c 'shargs'".
190    #[cfg(windows)]
191    pub fn new(cmd: &str) -> CommandExt {
192        CommandExt {
193            cmd: RefCell::new(build_command(cmd)),
194            shargs: cmd.to_string(),
195            header: false,
196        }
197    }
198
199    /// Set the working directory for the command.
200    ///
201    /// # Arguments
202    /// * `wd` - The working directory for the command execution.
203    pub fn wd(&mut self, wd: &Path) -> &mut CommandExt {
204        self.cmd.borrow_mut().current_dir(wd);
205        self
206    }
207
208    /// Set the header boolean.
209    ///
210    /// # Arguments
211    /// * show_header - true, a header showing what will be executed is
212    /// printed on stdout. Otherwise, no header is printed.
213    pub fn header(&mut self, show_header: bool) -> &mut CommandExt {
214        self.header = show_header;
215        self
216    }
217
218    /// Add an argument to the command.
219    ///
220    /// # Arguments
221    /// * arg - The argument to add to the command.
222    #[cfg(unix)]
223    pub fn arg(&mut self, arg: &str) -> &mut CommandExt {
224        self.cmd.borrow_mut().arg(arg);
225        self
226    }
227
228    /// Add an argument to the command.
229    ///
230    /// # Arguments
231    /// * arg - The argument to add to the command.
232    ///
233    /// # Note
234    /// On Windows, the argument is appended to the shargs value.
235    #[cfg(windows)]
236    pub fn arg(&mut self, arg: &str) -> &mut CommandExt {
237        self.shargs.push_str(" ");
238        self.shargs.push_str(arg);
239        self
240    }
241
242    /// Add arguments to the command.
243    ///
244    /// # Arguments
245    /// * `args` - A vector of argments to add to the command.
246    #[cfg(unix)]
247    pub fn args(&mut self, args: &[&str]) -> &mut CommandExt {
248        self.cmd.borrow_mut().args(args);
249        self
250    }
251
252    /// Add arguments to the command.
253    ///
254    /// # Arguments
255    /// * `args` - A vector of argments to add to the command.
256    ///
257    /// # Note
258    /// On Windows, the arguments are appended to the shargs value.
259    #[cfg(windows)]
260    pub fn args(&mut self, args: &[&str]) -> &mut CommandExt {
261        for arg in args.iter() {
262            self.shargs.push_str(" ");
263            self.shargs.push_str(*arg);
264        }
265        self
266    }
267
268    /// Set the command environment.
269    ///
270    /// # Arguments
271    /// * `key` - The key for the variable.
272    /// * `value` - The value for the variable.
273    pub fn env(&mut self, key: &str, val: &str) -> &mut CommandExt {
274        self.cmd.borrow_mut().env(key, val);
275        self
276    }
277
278    /// Execute the Command, returning result 'T'
279    ///
280    /// # Arguments
281    /// * `execfn` - A closure taking a Command, executes it via output or spawn
282    /// and returns result type `T`.
283    #[cfg(unix)]
284    pub fn exec<T>(&mut self, execfn: fn(cmd: &mut Command) -> T) -> T {
285        let mut cmd = self.cmd.borrow_mut();
286        if self.header {
287            header(&format!("  Executing '{:?}'", cmd)[..]);
288        }
289        execfn(&mut cmd)
290    }
291
292    /// Execute the Command, returning result 'T'
293    ///
294    /// # Arguments
295    /// * `execfn` - A closure taking a Command, executes it via output or spawn
296    /// and returns result type `T`.
297    #[cfg(windows)]
298    pub fn exec<T>(&mut self, execfn: fn(cmd: &mut Command) -> T) -> T {
299        let ref shargs = self.shargs;
300        let mut new_cmd = self.cmd.borrow_mut();
301        new_cmd.arg(shargs);
302
303        if self.header {
304            header(&format!("  Executing '{:?}'", new_cmd)[..]);
305        }
306        execfn(&mut new_cmd)
307    }
308}
309
310#[cfg(test)]
311/// Tests
312mod test {
313    use sodium_sys::crypto::utils::secmem;
314    use super::{to_procout, to_res};
315    use super::CommandExt;
316
317    #[test]
318    fn test_command_ext() {
319        let cmd = CommandExt::new("echo").arg("test").exec(to_procout());
320        let output = match cmd {
321            Ok(o) => o,
322            Err(e) => panic!("{:?}", e),
323        };
324
325        if cfg!(unix) {
326            assert_eq!(secmem::memcmp(&[116, 101, 115, 116, 10], &output.stdout[..]),
327                       0);
328        } else if cfg!(windows) {
329            assert_eq!(secmem::memcmp(&[116, 101, 115, 116, 10], &output.stdout[..]),
330                       0);
331        }
332        assert!(output.stderr.is_empty());
333        assert!(output.status.success());
334    }
335
336    #[test]
337    fn test_output_env() {
338        let cmd = CommandExt::new("env").env("TST", "1").exec(to_procout());
339        let output = match cmd {
340            Ok(o) => o,
341            Err(e) => panic!("{:?}", e),
342        };
343
344        assert!(output.stderr.is_empty());
345        assert!(output.status.success());
346    }
347
348    #[test]
349    fn test_spawn() {
350        let res = CommandExt::new("echo").arg("Testing Spawn").exec(to_res());
351        assert_eq!(Ok(0), res);
352    }
353
354    #[test]
355    fn test_spawn_header() {
356        let mut cmd = CommandExt::new("echo");
357        cmd.arg("Testing Spawn");
358        cmd.header(true);
359        let res = cmd.exec(to_res());
360        assert_eq!(Ok(0), res);
361    }
362
363    #[test]
364    fn test_spawn_env() {
365        let cmd = CommandExt::new("env").env("TST", "1").exec(to_res());
366        assert_eq!(Ok(0), cmd);
367    }
368}