Skip to content

Commit 96f8391

Browse files
committed
feat: add stdout and stderr capture as experimental features
1 parent f024ff9 commit 96f8391

File tree

3 files changed

+70
-29
lines changed

3 files changed

+70
-29
lines changed

src/files/config.rs

+13-4
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ use std::str::FromStr;
66
use crate::files::variables::AppConfig;
77
use crate::serialization::{parse_json, save_json};
88

9-
#[derive(Serialize, Deserialize, Debug)]
9+
#[derive(Serialize, Deserialize, Debug, Default)]
1010
pub struct Root {
1111
pub logs: Logs,
12+
pub experimental: Experimental,
1213
}
1314

1415
#[derive(Serialize, Deserialize, Debug)]
@@ -36,8 +37,16 @@ pub enum Strategy {
3637
Append,
3738
}
3839

39-
impl Logs {
40-
pub fn new() -> Self {
40+
#[derive(Serialize, Deserialize, Debug, Default)]
41+
pub struct Experimental {
42+
#[serde(default)]
43+
pub capture_stdout: bool,
44+
#[serde(default)]
45+
pub capture_stderr: bool,
46+
}
47+
48+
impl Default for Logs {
49+
fn default() -> Self {
4150
Self {
4251
enabled: false,
4352
level: Level::Info,
@@ -90,7 +99,7 @@ pub fn get_config() -> Result<Root, Box<dyn Error>> {
9099
if config.config_path().exists() {
91100
parse_json(&config.config_path())
92101
} else {
93-
let root = Root { logs: Logs::new() };
102+
let root = Root::default();
94103
save_json(&root, &config.config_path())?;
95104
Ok(root)
96105
}

src/logging.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use std::path::MAIN_SEPARATOR;
1010
use std::sync::{Arc, Mutex};
1111

1212
pub fn setup_logger() -> Result<(), Box<dyn Error>> {
13-
let logs_config = get_config().map_or_else(|_| Logs::new(), |config| config.logs);
13+
let logs_config = get_config().map_or_else(|_| Logs::default(), |config| config.logs);
1414

1515
let config = AppConfig::new();
1616
let log_file_path = config.logs_path();

src/runner.rs

+56-24
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
use crate::files::config::get_config;
12
use crate::packages::Package;
23
use log::{debug, error, info, warn};
34
use std::io::{stdin, BufRead, BufReader, IsTerminal, Read, Write};
45
use std::path::Path;
56
use std::process::{Command, Stdio};
7+
use std::thread;
68

79
pub fn run(package: &Package, binary: Option<String>, params: &Vec<String>) -> bool {
810
let interactive = !stdin().is_terminal();
@@ -19,10 +21,10 @@ pub fn run(package: &Package, binary: Option<String>, params: &Vec<String>) -> b
1921
args.push("-i".to_string());
2022
}
2123

22-
add_volumes(&package, &mut args);
23-
add_current_directory(&package, &mut args);
24-
add_environment_variables(&package, &mut args);
25-
add_binary_entrypoint(&package, &binary, &mut args);
24+
add_volumes(package, &mut args);
25+
add_current_directory(package, &mut args);
26+
add_environment_variables(package, &mut args);
27+
add_binary_entrypoint(package, &binary, &mut args);
2628

2729
args.push(format!(
2830
"{}:{}",
@@ -77,13 +79,30 @@ fn add_binary_entrypoint(package: &Package, binary: &Option<String>, args: &mut
7779
}
7880
}
7981

82+
fn get_stdio(config: &crate::files::config::Root) -> (Stdio, Stdio) {
83+
let stdout = if config.experimental.capture_stdout {
84+
Stdio::piped()
85+
} else {
86+
Stdio::inherit()
87+
};
88+
let stderr = if config.experimental.capture_stderr {
89+
Stdio::piped()
90+
} else {
91+
Stdio::inherit()
92+
};
93+
(stdout, stderr)
94+
}
95+
8096
fn run_command_with_args(command: &str, args: &[String], stdin_buffer: Option<Vec<u8>>) -> bool {
8197
debug!("Running command: {} {:?}", command, args);
8298

99+
let config = get_config().unwrap_or_default();
100+
let (stdout, stderr) = get_stdio(&config);
101+
83102
let mut child = Command::new(command)
84103
.args(args)
85-
.stdout(Stdio::piped())
86-
.stderr(Stdio::piped())
104+
.stdout(stdout)
105+
.stderr(stderr)
87106
.stdin(Stdio::piped())
88107
.spawn()
89108
.expect("Failed to spawn command");
@@ -96,39 +115,52 @@ fn run_command_with_args(command: &str, args: &[String], stdin_buffer: Option<Ve
96115
}
97116

98117
let stdout_thread = spawn_log_thread(
99-
BufReader::new(child.stdout.take().expect("Failed to open stdout")),
118+
child.stdout.take(),
100119
|line| info!("{}", line),
120+
config.experimental.capture_stdout,
101121
);
102122
let stderr_thread = spawn_log_thread(
103-
BufReader::new(child.stderr.take().expect("Failed to open stderr")),
123+
child.stderr.take(),
104124
|line| error!("{}", line),
125+
config.experimental.capture_stderr,
105126
);
106127

107-
let status = child.wait();
108-
let _ = stdout_thread.join();
109-
let _ = stderr_thread.join();
128+
let status = child.wait().expect("Failed to wait on child process");
110129

111-
match status {
112-
Ok(status) => status.success(),
113-
Err(e) => {
114-
error!("Command failed to complete: {}", e);
115-
false
116-
}
130+
if let Some(thread) = stdout_thread {
131+
let _ = thread.join();
117132
}
133+
134+
if let Some(thread) = stderr_thread {
135+
let _ = thread.join();
136+
}
137+
138+
status.success()
118139
}
119140

120-
fn spawn_log_thread<R: BufRead + Send + 'static>(
121-
reader: R,
141+
fn spawn_log_thread<R: Read + Send + 'static>(
142+
reader: Option<R>,
122143
log_fn: impl Fn(&str) + Send + 'static,
123-
) -> std::thread::JoinHandle<()> {
124-
std::thread::spawn(move || {
125-
for line in reader.lines() {
144+
capture: bool,
145+
) -> Option<thread::JoinHandle<()>> {
146+
if !capture {
147+
return None;
148+
}
149+
let reader = reader.expect("Failed to open reader");
150+
Some(thread::spawn(move || {
151+
let reader = BufReader::new(reader);
152+
for line in reader.split(b'\n') {
126153
match line {
127-
Ok(line) => log_fn(&line),
154+
Ok(line) => match std::str::from_utf8(&line) {
155+
Ok(line) => log_fn(line),
156+
Err(_) => error!(
157+
"Failed to read line from output: stream did not contain valid UTF-8"
158+
),
159+
},
128160
Err(e) => error!("Failed to read line from output: {}", e),
129161
}
130162
}
131-
})
163+
}))
132164
}
133165

134166
pub fn pull(package: &Package) -> bool {

0 commit comments

Comments
 (0)