1#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ]
3mod private
4{
5 use std::
8 {
9 fmt::Formatter,
10 path::{ Path, PathBuf },
11 process::{ Command, Stdio },
12 };
13 use std::collections::HashMap;
14 use std::ffi::OsString;
15 use duct::cmd;
16 use error_tools::
17 {
18 untyped::{ Error, Context, format_err },
19 };
21 use former::Former;
22 use iter_tools::iter::Itertools;
23
24 pub fn run( options : Run ) -> Result< Report, Report >
95 {
96 let bin_path : &Path = options.bin_path.as_ref();
97 let current_path : &Path = options.current_path.as_ref();
98
99 let mut report = Report
100 {
101 command : format!( "{} {}", bin_path.display(), options.args.iter().map( | a | a.to_string_lossy() ).join( " " ) ),
102 current_path : current_path.to_path_buf(),
103 .. Report::default()
104 };
105
106 let mut env: HashMap<String, String> = std::env::vars().collect();
107 env.extend( options.env_variable );
108
109 let output = if options.joining_streams
110 {
111 let output = cmd( bin_path.as_os_str(), &options.args )
112 .dir( current_path )
113 .full_env( env )
114 .stderr_to_stdout()
115 .stdout_capture()
116 .unchecked()
117 .run()
118 .map_err( | e |
119 {
120 report.error = Err( e.into() );
121 Err::< (), () >( () )
122 });
123
124 output
125 }
126 else
127 {
128 let child = Command::new( bin_path )
129 .args( &options.args )
130 .envs( env )
131 .stdout( Stdio::piped() )
132 .stderr( Stdio::piped() )
133 .current_dir( current_path )
134 .spawn()
135 .context( "failed to spawn process" )
136 .map_err( | e |
137 {
138 report.error = Err( e );
139 Err::< (), () >( () )
140 });
141
142 if report.error.is_err()
143 {
144 return Err( report );
145 }
146 let child = child.unwrap();
147
148 child
149 .wait_with_output()
150 .context( "failed to wait on child" )
151 .map_err( | e |
152 {
153 report.error = Err( e );
154 Err::< (), () >( () )
155 })
156 };
157
158 if report.error.is_err()
159 {
160 return Err( report );
161 }
162 let output = output.unwrap();
163
164 let out = String::from_utf8( output.stdout )
165 .context( "Found invalid UTF-8" )
166 .map_err( | e |
167 {
168 report.error = Err( e );
169 Err::< (), () >( () )
170 });
171
172 if out.is_err()
173 {
174 return Err( report );
175 }
176 let out = out.unwrap();
177
178 report.out = out;
179
180 let err = String::from_utf8( output.stderr )
181 .context( "Found invalid UTF-8" )
182 .map_err( | e |
183 {
184 report.error = Err( e );
185 Err::< (), () >( () )
186 });
187
188 if err.is_err()
189 {
190 return Err( report );
191 }
192 let err = err.unwrap();
193
194 report.err = err;
195
196 if output.status.success()
197 {
198 Ok( report )
199 }
200 else
201 {
202 report.error = Err( format_err!( "Process was finished with error code : {}", output.status ) );
203 Err( report )
204 }
205
206 }
207
208 #[ derive( Debug, Former ) ]
210 pub struct Run
212 {
213 bin_path : PathBuf,
214 current_path : PathBuf,
215 args : Vec< OsString >,
216 #[ former( default = false ) ]
217 joining_streams : bool,
218 env_variable : HashMap< String, String >,
219 }
220
221 impl RunFormer
222 {
223 pub fn run( self ) -> Result< Report, Report >
224 {
225 run( self.form() )
226 }
227
228 pub fn run_with_shell( self, exec_path : &str, ) -> Result< Report, Report >
240 {
241 let ( program, args ) =
242 if cfg!( target_os = "windows" )
243 {
244 ( "cmd", [ "/C", exec_path ] )
245 }
246 else
247 {
248 ( "sh", [ "-c", exec_path ] )
249 };
250 self
251 .args( args.into_iter().map( OsString::from ).collect::< Vec< _ > >() )
252 .bin_path( program )
253 .run()
254 }
255 }
256
257 #[ derive( Debug, ) ]
259 pub struct Report
260 {
261 pub command : String,
263 pub current_path : PathBuf,
265 pub out : String,
267 pub err : String,
269 pub error : Result< (), Error >
271 }
272
273 impl Clone for Report
274 {
275 fn clone( &self ) -> Self
276 {
277 Self
278 {
279 command : self.command.clone(),
280 current_path : self.current_path.clone(),
281 out : self.out.clone(),
282 err : self.err.clone(),
283 error : self.error.as_ref().map_err( | e | Error::msg( e.to_string() ) ).copied(),
284 }
286 }
287 }
288
289 impl Default for Report
290 {
291 fn default() -> Self
292 {
293 Report
294 {
295 command : String::default(),
296 current_path : PathBuf::new(),
297 out : String::default(),
298 err : String::default(),
299 error : Ok( () ),
300 }
301 }
302 }
303
304 impl core::fmt::Display for Report
305 {
306 fn fmt( &self, f : &mut Formatter< '_ > ) -> core::fmt::Result
307 {
308 f.write_fmt( format_args!( "> {}\n", self.command ) )?;
310 f.write_fmt( format_args!( " @ {}\n\n", self.current_path.display() ) )?;
311
312 if !self.out.trim().is_empty()
313 {
314 f.write_fmt( format_args!( " {}\n", self.out.replace( '\n', "\n " ) ) )?;
315 }
316 if !self.err.trim().is_empty()
317 {
318 f.write_fmt( format_args!( " {}\n", self.err.replace( '\n', "\n " ) ) )?;
319 }
320
321 Ok( () )
322 }
323 }
324
325}
326
327crate::mod_interface!
328{
329 own use run;
331 own use Run;
332 own use Report;
333}