insta_cmd/
spawn.rs

1use std::collections::BTreeMap;
2use std::env;
3use std::ffi::OsStr;
4use std::io::Write;
5use std::mem;
6use std::ops::{Deref, DerefMut};
7use std::path::Path;
8use std::process::{Command, Output, Stdio};
9
10use serde::Serialize;
11
12#[derive(Serialize)]
13pub struct Info {
14    program: String,
15    args: Vec<String>,
16    #[serde(skip_serializing_if = "BTreeMap::is_empty")]
17    env: BTreeMap<String, String>,
18    #[serde(skip_serializing_if = "Option::is_none")]
19    stdin: Option<String>,
20}
21
22fn describe_program(cmd: &OsStr) -> String {
23    let filename = Path::new(cmd).file_name().unwrap();
24    let name = filename.to_string_lossy();
25    let mut name = &name as &str;
26    if !env::consts::EXE_SUFFIX.is_empty() {
27        name = name.strip_suffix(env::consts::EXE_SUFFIX).unwrap_or(name);
28    }
29    name.into()
30}
31
32impl Info {
33    fn from_std_command(cmd: &Command, stdin: Option<&[u8]>) -> Info {
34        Info {
35            program: describe_program(cmd.get_program()),
36            args: cmd
37                .get_args()
38                .map(|x| x.to_string_lossy().into_owned())
39                .collect(),
40            env: cmd
41                .get_envs()
42                .map(|(k, v)| {
43                    (
44                        k.to_string_lossy().into_owned(),
45                        v.unwrap_or(OsStr::new("")).to_string_lossy().into_owned(),
46                    )
47                })
48                .collect(),
49            stdin: stdin.as_ref().map(|x| String::from_utf8_lossy(x).into()),
50        }
51    }
52}
53
54/// Implemented by different types that can be spawned and snapshotted.
55pub trait Spawn {
56    #[doc(hidden)]
57    fn spawn_with_info(&mut self, stdin: Option<Vec<u8>>) -> (Info, Output);
58}
59
60/// Utility methods for spawning.
61pub trait SpawnExt {
62    /// This passes the given input to stdin before spawning the command.
63    fn pass_stdin(&mut self, stdin: impl Into<Vec<u8>>) -> SpawnWithStdin<'_>;
64}
65
66impl<T: Spawn> SpawnExt for T {
67    fn pass_stdin(&mut self, stdin: impl Into<Vec<u8>>) -> SpawnWithStdin<'_> {
68        SpawnWithStdin {
69            spawn: self,
70            stdin: stdin.into(),
71        }
72    }
73}
74
75pub struct SpawnWithStdin<'a> {
76    spawn: &'a mut dyn Spawn,
77    stdin: Vec<u8>,
78}
79
80impl<'a> Spawn for SpawnWithStdin<'a> {
81    fn spawn_with_info(&mut self, stdin: Option<Vec<u8>>) -> (Info, Output) {
82        self.spawn
83            .spawn_with_info(Some(stdin.unwrap_or(mem::take(&mut self.stdin))))
84    }
85}
86
87impl Spawn for Command {
88    fn spawn_with_info(&mut self, stdin: Option<Vec<u8>>) -> (Info, Output) {
89        let info = Info::from_std_command(self, stdin.as_deref());
90        let output = if let Some(stdin) = stdin {
91            self.stdin(Stdio::piped());
92            self.stdout(Stdio::piped());
93            self.stderr(Stdio::piped());
94            let mut child = self.spawn().unwrap();
95            let mut child_stdin = child.stdin.take().expect("Failed to open stdin");
96            std::thread::spawn(move || {
97                child_stdin
98                    .write_all(&stdin)
99                    .expect("Failed to write to stdin");
100            });
101            child.wait_with_output().unwrap()
102        } else {
103            self.output().unwrap()
104        };
105        (info, output)
106    }
107}
108
109impl<'a> Spawn for &'a mut Command {
110    fn spawn_with_info(&mut self, stdin: Option<Vec<u8>>) -> (Info, Output) {
111        <Command as Spawn>::spawn_with_info(self, stdin)
112    }
113}
114
115/// Like [`Command`] but sends some input to stdin.
116#[deprecated = "Use .pass_stdin(...) instead"]
117pub struct StdinCommand {
118    command: Command,
119    stdin: Vec<u8>,
120}
121
122#[allow(deprecated)]
123impl StdinCommand {
124    /// Creates a new command that also gets some input value fed to stdin.
125    pub fn new<S: AsRef<OsStr>, I: Into<Vec<u8>>>(program: S, stdin: I) -> StdinCommand {
126        let mut command = Command::new(program);
127        command.stdin(Stdio::piped());
128        command.stdout(Stdio::piped());
129        command.stderr(Stdio::piped());
130        StdinCommand {
131            command,
132            stdin: stdin.into(),
133        }
134    }
135}
136
137#[allow(deprecated)]
138impl Deref for StdinCommand {
139    type Target = Command;
140
141    fn deref(&self) -> &Self::Target {
142        &self.command
143    }
144}
145
146#[allow(deprecated)]
147impl DerefMut for StdinCommand {
148    fn deref_mut(&mut self) -> &mut Self::Target {
149        &mut self.command
150    }
151}
152
153#[allow(deprecated)]
154impl Spawn for StdinCommand {
155    fn spawn_with_info(&mut self, stdin: Option<Vec<u8>>) -> (Info, Output) {
156        Command::spawn_with_info(
157            &mut self.command,
158            Some(stdin.unwrap_or(mem::take(&mut self.stdin))),
159        )
160    }
161}
162
163impl<'a, T: AsRef<OsStr>> Spawn for &'a [T] {
164    fn spawn_with_info(&mut self, stdin: Option<Vec<u8>>) -> (Info, Output) {
165        let mut cmd = Command::new(self.first().expect("expected program name as first item"));
166        for arg in &self[1..] {
167            cmd.arg(arg);
168        }
169        cmd.spawn_with_info(stdin)
170    }
171}
172
173impl<T: AsRef<OsStr>, const N: usize> Spawn for [T; N] {
174    fn spawn_with_info(&mut self, stdin: Option<Vec<u8>>) -> (Info, Output) {
175        (&self[..]).spawn_with_info(stdin)
176    }
177}
178
179impl<T: AsRef<OsStr>> Spawn for Vec<T> {
180    fn spawn_with_info(&mut self, stdin: Option<Vec<u8>>) -> (Info, Output) {
181        (&self[..]).spawn_with_info(stdin)
182    }
183}