1use teaql_tool_core::{Result, TeaQLToolError};
2use std::process::Command;
3use wait_timeout::ChildExt;
4use std::time::Duration;
5
6#[derive(Debug, Clone)]
7pub struct CmdTool;
8
9impl CmdTool {
10 pub fn new() -> Self { Self }
11
12 pub fn run_with_timeout(&self, cmd_line: &str, timeout_secs: u64) -> Result<(String, String, i32)> {
13 let parts: Vec<&str> = cmd_line.split_whitespace().collect();
14 if parts.is_empty() {
15 return Err(TeaQLToolError::ExecutionError("Empty command".into()));
16 }
17
18 let mut cmd = Command::new(parts[0]);
19 cmd.args(&parts[1..]);
20 cmd.stdout(std::process::Stdio::piped());
21 cmd.stderr(std::process::Stdio::piped());
22
23 let mut child = cmd.spawn().map_err(|e| TeaQLToolError::ExecutionError(e.to_string()))?;
24 let timeout = Duration::from_secs(timeout_secs);
25
26 match child.wait_timeout(timeout).map_err(|e| TeaQLToolError::ExecutionError(e.to_string()))? {
27 Some(status) => {
28 let out = child.wait_with_output().map_err(|e| TeaQLToolError::ExecutionError(e.to_string()))?;
29 let stdout = String::from_utf8_lossy(&out.stdout).to_string();
30 let stderr = String::from_utf8_lossy(&out.stderr).to_string();
31 Ok((stdout, stderr, status.code().unwrap_or(-1)))
32 }
33 None => {
34 let _ = child.kill();
35 let _ = child.wait();
36 Err(TeaQLToolError::ExecutionError("Command timed out".into()))
37 }
38 }
39 }
40}
41
42impl Default for CmdTool {
43 fn default() -> Self { Self::new() }
44}