command_builder/
lib.rs

1#[cfg(test)]
2mod tests {
3    use super::*;
4    // TODO: Write tests
5    #[test]
6    fn pipe() {
7        let command = Single::new("echo")
8            .a("foo\nbar\nbaz")
9            .pipe(Single::new("grep").a("bar"));
10        assert_eq!(command.run().unwrap().stdout(), "bar\n");
11    }
12}
13
14use std::collections::{HashMap, HashSet};
15use std::process::Stdio;
16use std::{fmt, io, io::Write, process};
17
18/// The output of a command after it has been run. Contains both stdout and stderr along with the exit code.
19#[derive(Clone)]
20pub struct Output {
21    stderr: String,
22    stdout: String,
23    exit_code: i32,
24}
25
26impl Output {
27    /// Test if the process finished successfully. The process is considered successful if the exit code is 0.
28    pub fn success(&self) -> bool {
29        self.code() == 0
30    }
31    /// Returns the exit code for the process.
32    pub fn code(&self) -> i32 {
33        self.exit_code
34    }
35    /// A view into standard out.
36    pub fn stdout(&self) -> &str {
37        self.stdout.as_ref()
38    }
39    /// A view into standard error.
40    pub fn stderr(&self) -> &str {
41        self.stderr.as_ref()
42    }
43}
44
45pub trait Command: Sized + std::fmt::Debug + Clone {
46    /// Equivalent to &&, as in "command 1" && "command 2".
47    fn and<C: Command>(self, other: C) -> And<Self, C> {
48        And {
49            first: self,
50            second: other,
51        }
52    }
53
54    /// Equivalent to ||, as in "command 1" || "command 2".
55    fn or<C: Command>(self, other: C) -> Or<Self, C> {
56        Or {
57            first: self,
58            second: other,
59        }
60    }
61
62    /// Equivalent to ;, as in "command 1"; "command 2".
63    fn then<C: Command>(self, other: C) -> Then<Self, C> {
64        Then {
65            first: self,
66            second: other,
67        }
68    }
69
70    /// Equivalent to |, as in "pipe 1" | "into 2".
71    fn pipe<C: Command>(self, other: C) -> Pipe<Self, C> {
72        Pipe {
73            first: self,
74            second: other,
75        }
76    }
77
78    /// Sets the env in the environment the command is run in.
79    fn env(self, key: &str, value: &str) -> Env<Self> {
80        Env {
81            key: key.to_owned(),
82            value: value.to_owned(),
83            on: self,
84        }
85    }
86
87    /// Clears the environment for non-explicitly set variables.
88    fn clear_envs(self) -> ClearEnv<Self> {
89        ClearEnv { on: self }
90    }
91
92    /// Removes a variable from the enviroment in which the command is run.
93    fn without_env(self, key: &str) -> ExceptEnv<Self> {
94        ExceptEnv {
95            key: key.to_owned(),
96            on: self,
97        }
98    }
99
100    /// Takes an iterable of Strings for keys to remove.
101    fn without_envs<I: IntoIterator<Item = String>>(self, envs: I) -> ExceptEnvs<Self, I> {
102        ExceptEnvs {
103            on: self,
104            keys: envs,
105        }
106    }
107
108    fn with_dir<P: AsRef<std::path::Path>>(self, dir: P) -> Dir<Self> {
109        Dir {
110            on: self,
111            path: dir.as_ref().to_owned(),
112        }
113    }
114
115    /// Runs the command.
116    fn run(&self) -> io::Result<Output> {
117        self.run_internal(None, false, HashMap::new(), HashSet::new(), None)
118    }
119
120    /// Pipes `input` into the following command.
121    fn with_input(self, input: &str) -> Input<Self> {
122        Input {
123            input: input.to_owned(),
124            on: self,
125        }
126    }
127
128    /// The command used to define all others.
129    /// input: the string to be piped into the next command run.
130    /// clear_env: if the global enviromental variables should be cleared.
131    /// env: what enviromental variables to set, supercedes clear_env.
132    fn run_internal(
133        &self,
134        input: Option<&str>,
135        clear_env: bool,
136        env: HashMap<String, String>,
137        del_env: HashSet<String>,
138        path: Option<std::path::PathBuf>,
139    ) -> io::Result<Output>;
140}
141
142/// Sets the directory of the command.
143#[derive(Clone)]
144pub struct Dir<C>
145where
146    C: Command,
147{
148    on: C,
149    path: std::path::PathBuf,
150}
151
152impl<C: Command> fmt::Debug for Dir<C> {
153    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
154        write!(f, "cd {:?}; {:?}", self.path, self.on)
155    }
156}
157
158impl<C: Command> Command for Dir<C> {
159    fn run_internal(
160        &self,
161        input: std::option::Option<&str>,
162        clear_env: bool,
163        envs: std::collections::HashMap<std::string::String, std::string::String>,
164        del_envs: std::collections::HashSet<std::string::String>,
165        _: Option<std::path::PathBuf>,
166    ) -> std::result::Result<Output, std::io::Error> {
167        self.on
168            .run_internal(input, clear_env, envs, del_envs, Some(self.path.clone()))
169    }
170}
171
172/// Removes multable keys from the enviroment.
173#[derive(Clone)]
174pub struct ExceptEnvs<C, I>
175where
176    C: Command,
177    I: IntoIterator<Item = String>,
178{
179    on: C,
180    keys: I,
181}
182
183/// Contains input to be piped into a command.
184#[derive(Clone)]
185pub struct Input<F>
186where
187    F: Command,
188{
189    input: String,
190    on: F,
191}
192
193impl<F: Command> std::fmt::Debug for Input<F> {
194    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
195        write!(f, "{:?} < \"{}\"", self.on, self.input)
196    }
197}
198
199impl<F: Command> Command for Input<F> {
200    fn run_internal(
201        &self,
202        input: std::option::Option<&str>,
203        clear_env: bool,
204        env: std::collections::HashMap<std::string::String, std::string::String>,
205        del_env: HashSet<String>,
206        path: Option<std::path::PathBuf>,
207    ) -> std::result::Result<Output, std::io::Error> {
208        let input_string = match input.as_ref() {
209            Some(prev) => prev.to_string() + &self.input,
210            None => self.input.to_owned(),
211        };
212        self.on
213            .run_internal(Some(&input_string), clear_env, env, del_env, path)
214    }
215}
216impl<F: Command> fmt::Debug for ClearEnv<F> {
217    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
218        write!(f, "CLEAR_ENV \"{:?}\"", self.on)
219    }
220}
221impl<F: Command> Command for ClearEnv<F> {
222    fn run_internal(
223        &self,
224        input: std::option::Option<&str>,
225        _: bool,
226        env: std::collections::HashMap<std::string::String, std::string::String>,
227        del_env: HashSet<String>,
228        path: Option<std::path::PathBuf>,
229    ) -> std::result::Result<Output, std::io::Error> {
230        self.on.run_internal(input, true, env, del_env, path)
231    }
232}
233
234/// Indicates that the environment should be cleared.
235#[derive(Clone)]
236pub struct ClearEnv<F>
237where
238    F: Command,
239{
240    on: F,
241}
242
243/// Unsets a key from the enviroment.
244#[derive(Clone)]
245pub struct ExceptEnv<F>
246where
247    F: Command,
248{
249    key: String,
250    on: F,
251}
252impl<F: Command> fmt::Debug for ExceptEnv<F> {
253    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
254        write!(f, "unset {} {:?}", self.key, self.on)
255    }
256}
257impl<F: Command> Command for ExceptEnv<F> {
258    fn run_internal(
259        &self,
260        input: Option<&str>,
261        clear_env: bool,
262        env: std::collections::HashMap<std::string::String, std::string::String>,
263        mut del_env: HashSet<String>,
264        path: Option<std::path::PathBuf>,
265    ) -> std::result::Result<Output, std::io::Error> {
266        del_env.insert(self.key.clone());
267        self.on.run_internal(input, clear_env, env, del_env, path)
268    }
269}
270
271/// Adds a key-value to the calling environment.
272#[derive(Clone)]
273pub struct Env<F>
274where
275    F: Command,
276{
277    key: String,
278    value: String,
279    on: F,
280}
281impl<F: Command> fmt::Debug for Env<F> {
282    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
283        write!(f, "{}={} {:?}", self.key, self.value, self.on)
284    }
285}
286impl<F: Command> Command for Env<F> {
287    fn run_internal(
288        &self,
289        input: Option<&str>,
290        clear_env: bool,
291        mut env: std::collections::HashMap<std::string::String, std::string::String>,
292        del_env: HashSet<String>,
293        path: Option<std::path::PathBuf>,
294    ) -> std::result::Result<Output, std::io::Error> {
295        env.insert(self.key.clone(), self.value.clone());
296        self.on.run_internal(input, clear_env, env, del_env, path)
297    }
298}
299
300/// Allows piping of one command's standard out into another.
301#[derive(Clone)]
302pub struct Pipe<F, S>
303where
304    F: Command,
305    S: Command,
306{
307    first: F,
308    second: S,
309}
310
311impl<F: Command, S: Command> fmt::Debug for Pipe<F, S> {
312    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
313        write!(f, "{:?} | {:?}", self.first, self.second)
314    }
315}
316impl<F: Command, S: Command> Command for Pipe<F, S> {
317    fn run_internal(
318        &self,
319        input: std::option::Option<&str>,
320        clear_env: bool,
321        env: std::collections::HashMap<std::string::String, std::string::String>,
322        del_env: HashSet<String>,
323        path: Option<std::path::PathBuf>,
324    ) -> std::result::Result<Output, std::io::Error> {
325        let first = self.first.run_internal(
326            input,
327            clear_env,
328            env.clone(),
329            del_env.clone(),
330            path.clone(),
331        )?;
332        self.second
333            .run_internal(Some(&first.stdout), clear_env, env, del_env, path)
334    }
335}
336
337impl<F: Command, S: Command> fmt::Debug for Then<F, S> {
338    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
339        write!(f, "{:?}; {:?}", self.first, self.second)
340    }
341}
342impl<F: Command, S: Command> Command for Then<F, S> {
343    fn run_internal(
344        &self,
345        input: std::option::Option<&str>,
346        clear_env: bool,
347        env: std::collections::HashMap<std::string::String, std::string::String>,
348        del_env: HashSet<String>,
349        path: Option<std::path::PathBuf>,
350    ) -> std::result::Result<Output, std::io::Error> {
351        self.first
352            .run_internal(input, clear_env, env.clone(), del_env.clone(), path.clone())?;
353        self.second
354            .run_internal(None, clear_env, env, del_env, path)
355    }
356}
357
358/// Executes one command, then another.
359#[derive(Clone)]
360pub struct Then<F, S>
361where
362    F: Command,
363    S: Command,
364{
365    first: F,
366    second: S,
367}
368
369impl<F: Command, S: Command> fmt::Debug for And<F, S> {
370    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
371        write!(f, "{:?} && {:?}", self.first, self.second)
372    }
373}
374impl<F: Command, S: Command> Command for And<F, S> {
375    fn run_internal(
376        &self,
377        input: std::option::Option<&str>,
378        clear_env: bool,
379        env: std::collections::HashMap<std::string::String, std::string::String>,
380        del_env: HashSet<String>,
381        path: Option<std::path::PathBuf>,
382    ) -> std::result::Result<Output, std::io::Error> {
383        let first = self.first.run_internal(
384            input,
385            clear_env,
386            env.clone(),
387            del_env.clone(),
388            path.clone(),
389        )?;
390        if first.success() {
391            self.second
392                .run_internal(None, clear_env, env, del_env, path)
393        } else {
394            Ok(first)
395        }
396    }
397}
398
399/// Executes one command and if successful returns the result of the other.
400#[derive(Clone)]
401pub struct And<F, S>
402where
403    F: Command,
404    S: Command,
405{
406    first: F,
407    second: S,
408}
409
410impl<F: Command, S: Command> fmt::Debug for Or<F, S> {
411    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
412        write!(f, "{:?} || {:?}", self.first, self.second)
413    }
414}
415impl<F: Command, S: Command> Command for Or<F, S> {
416    fn run_internal(
417        &self,
418        input: Option<&str>,
419        clear_env: bool,
420        env: HashMap<String, String>,
421        del_env: HashSet<String>,
422        path: Option<std::path::PathBuf>,
423    ) -> io::Result<Output> {
424        let first = self.first.run_internal(
425            input,
426            clear_env,
427            env.clone(),
428            del_env.clone(),
429            path.clone(),
430        )?;
431        if !first.success() {
432            self.second
433                .run_internal(None, clear_env, env, del_env, path)
434        } else {
435            Ok(first)
436        }
437    }
438}
439
440/// Executes one command and if unsuccessful returns the result of the other.
441#[derive(Clone)]
442pub struct Or<F, S>
443where
444    F: Command,
445    S: Command,
446{
447    first: F,
448    second: S,
449}
450
451/// Holds a single command to be run.
452#[derive(Clone, PartialEq, Eq)]
453pub struct Single(Vec<String>);
454impl fmt::Debug for Single {
455    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
456        write!(
457            f,
458            "{}{}",
459            self.0.first().unwrap(),
460            self.0
461                .iter()
462                .skip(1)
463                .fold(String::new(), |old: String, next: &String| old + " " + next)
464        )
465    }
466}
467impl Command for Single {
468    fn run_internal(
469        &self,
470        input: Option<&str>,
471        do_clear_env: bool,
472        env: HashMap<String, String>,
473        del_env: HashSet<String>,
474        path: Option<std::path::PathBuf>,
475    ) -> Result<Output, std::io::Error> {
476        let f = self.0.first().unwrap();
477        let mut out = envs_remove(
478            clear_env(
479                with_path(
480                    process::Command::new(f)
481                        .args(self.0.iter().skip(1))
482                        .stderr(Stdio::piped())
483                        .stdin(Stdio::piped())
484                        .stdout(Stdio::piped()),
485                    path,
486                ),
487                do_clear_env,
488            )
489            .envs(env.iter()),
490            del_env.iter(),
491        )
492        .spawn()?;
493        if let Some(input) = input {
494            write!(
495                match out.stdin.as_mut() {
496                    Some(i) => i,
497                    None => return Err(io::Error::from(io::ErrorKind::BrokenPipe)),
498                },
499                "{}",
500                input
501            )?;
502        }
503        let output = out.wait_with_output()?;
504        Ok(Output {
505            stderr: String::from_utf8_lossy(&output.stderr).to_owned().into(),
506            stdout: String::from_utf8_lossy(&output.stdout).into(),
507            exit_code: output.status.code().unwrap_or(1),
508        })
509    }
510}
511
512impl Single {
513    /// Creates a new Command which can be run in the shell.
514    pub fn new(command: &str) -> Self {
515        Self(vec![command.to_owned()])
516    }
517
518    /// Adds an argument to the command.
519    pub fn arg<S>(mut self, argument: S) -> Self
520    where
521        S: AsRef<str>,
522    {
523        self.0.push(argument.as_ref().to_owned());
524        self
525    }
526
527    /// Adds multiple arguments.
528    pub fn args<I, S>(self, arguments: I) -> Self
529    where
530        I: IntoIterator<Item = S>,
531        S: AsRef<str>,
532    {
533        arguments.into_iter().fold(self, |this, arg| this.arg(arg))
534    }
535}
536
537fn envs_remove<I, K>(command: &mut process::Command, keys: I) -> &mut process::Command
538where
539    I: IntoIterator<Item = K>,
540    K: AsRef<std::ffi::OsStr>,
541{
542    let mut iter = keys.into_iter();
543    match iter.next() {
544        Some(k) => envs_remove(command.env_remove(k), iter),
545        None => command,
546    }
547}
548
549fn clear_env(command: &mut process::Command, clear: bool) -> &mut process::Command {
550    match clear {
551        true => command.env_clear(),
552        false => command,
553    }
554}
555
556fn with_path(
557    command: &mut process::Command,
558    path: Option<std::path::PathBuf>,
559) -> &mut process::Command {
560    match path {
561        Some(p) => command.current_dir(p),
562        None => command,
563    }
564}