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}