use std::path::Path;
use std::time::{Duration, Instant};
use serde_json::Value;
use super::config::Local;
use crate::cli_outbound::{Chunk, Cli, StreamPoll};
use crate::registry::mailbox::{Capture, Invocation};
const POLL: Duration = Duration::from_millis(20);
pub const TIMED_OUT: i32 = 124;
pub const NO_SUCH_TOOL: i32 = 127;
pub fn execute(set: &[Local], invocation: &Invocation, deadline: Duration) -> Capture {
let Some(local) = super::config::position(set, &invocation.tool).and_then(|at| set.get(at))
else {
return refused(
NO_SUCH_TOOL,
&format!(
"this machine no longer carries a tool called {:?}",
invocation.tool
),
);
};
match spawn(local, &invocation.input, deadline) {
Ok(capture) => capture,
Err(reason) => refused(NO_SUCH_TOOL, &reason),
}
}
fn spawn(local: &Local, input: &Value, deadline: Duration) -> Result<Capture, String> {
let (head, args) = local
.command
.split_first()
.ok_or("the command is an empty argv")?;
let args: Vec<&str> = args.iter().map(String::as_str).collect();
let mut stream = Cli::new(head)
.run_input(
local.cwd.as_deref().map(Path::new),
input.to_string().as_bytes(),
&args,
)
.map_err(|e| e.to_string())?;
let started = Instant::now();
let (mut out, mut err) = (Vec::new(), Vec::new());
loop {
match stream.try_next() {
StreamPoll::Ready(Chunk::Stdout(bytes)) => out.extend(bytes),
StreamPoll::Ready(Chunk::Stderr(bytes)) => err.extend(bytes),
StreamPoll::Ready(Chunk::Exited(info)) => {
return Ok(captured(&out, &err, info.shell_code()));
}
StreamPoll::Pending => {
if started.elapsed() >= deadline {
err.extend(
format!("\nyog tool-host: no answer within {deadline:?}; terminated\n")
.into_bytes(),
);
return Ok(captured(&out, &err, TIMED_OUT));
}
std::thread::sleep(POLL);
}
}
}
}
fn captured(out: &[u8], err: &[u8], exit_code: i32) -> Capture {
Capture {
stdout: String::from_utf8_lossy(out).into_owned(),
stderr: String::from_utf8_lossy(err).into_owned(),
exit_code,
}
}
fn refused(exit_code: i32, reason: &str) -> Capture {
Capture {
stdout: String::new(),
stderr: format!("yog tool-host: {reason}\n"),
exit_code,
}
}
#[cfg(test)]
mod tests;