1use crate::error::{Error, Result};
5use std::collections::HashMap;
6use std::path::PathBuf;
7use std::process::{ExitStatus, Stdio};
8use tempfile::NamedTempFile;
9use tokio::fs;
10use tokio::io::{AsyncBufReadExt, BufReader};
11use tokio::process::Command;
12use colored::Colorize;
13
14pub async fn execute_script(
15 command_line: &str,
16 wd: PathBuf,
17 env: &mut HashMap<String, String>,
18) -> Result<(ExitStatus, String, String)> {
19 let env_file = NamedTempFile::new()?;
20 let env_file_path = env_file.path().to_owned();
21 let shell_command = format!(
22 ". {} && env -0 > {}",
23 command_line,
24 env_file_path.display()
25 );
26
27 let mut child = Command::new("sh")
28 .arg("-c")
29 .arg(shell_command)
30 .stdout(Stdio::piped())
31 .stderr(Stdio::piped())
32 .current_dir(wd)
33 .envs(env.clone())
34 .spawn()?;
35
36 let stdout = child
37 .stdout
38 .take()
39 .ok_or(Error::Other("unable to capture script stdout".to_string()))?;
40 let stderr = child
41 .stderr
42 .take()
43 .ok_or(Error::Other("unable to capture script stderr".to_string()))?;
44
45 let stdout_future: tokio::task::JoinHandle<std::result::Result<_, futures::io::Error>> =
46 tokio::spawn(async move {
47 let mut buf = BufReader::new(stdout);
48 let mut result: Vec<String> = Vec::new();
49 loop {
50 let mut s = String::new();
51 let size = buf.read_line(&mut s).await?;
52 if size == 0 {
53 break Ok(result);
54 }
55 log::info!("{}", s.strip_suffix("\n").or(Some(&s)).unwrap().dimmed());
56 result.push(s);
57 }
58 });
59 let stderr_future: tokio::task::JoinHandle<std::result::Result<_, futures::io::Error>> =
60 tokio::spawn(async move {
61 let mut buf = BufReader::new(stderr);
62 let mut result: Vec<String> = Vec::new();
63 loop {
64 let mut s = String::new();
65 let size = buf.read_line(&mut s).await?;
66 if size == 0 {
67 break Ok(result);
68 }
69 log::info!("{}", s.strip_suffix("\n").or(Some(&s)).unwrap().dimmed());
70 result.push(s);
71 }
72 });
73
74 let status = child.wait().await?;
75 let stdout_result = stdout_future.await??;
76 let stderr_result = stderr_future.await??;
77
78 let env_contents = fs::read_to_string(env_file_path).await?;
79 for line in env_contents.split('\0') {
80 log::trace!("captured env: {line}");
81 if let Some(eq_pos) = line.find('=') {
82 let var_name = &line[..eq_pos];
83 if var_name.starts_with("BLACKJACK_") {
84 let value = &line[eq_pos + 1..];
85 let value = value.trim_matches('\'');
86 env.insert(var_name.to_string(), value.to_string());
87 }
88 }
89 }
90 log::trace!("exit code: {status}");
91
92 Ok((status, stdout_result.join("\n"), stderr_result.join("\n")))
93}