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
54pub trait Spawn {
56 #[doc(hidden)]
57 fn spawn_with_info(&mut self, stdin: Option<Vec<u8>>) -> (Info, Output);
58}
59
60pub trait SpawnExt {
62 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#[deprecated = "Use .pass_stdin(...) instead"]
117pub struct StdinCommand {
118 command: Command,
119 stdin: Vec<u8>,
120}
121
122#[allow(deprecated)]
123impl StdinCommand {
124 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}