use anyhow::{anyhow, Context, Result}; use std::{ collections::VecDeque, env, ffi::OsStr, fmt::Display, fs::{File, OpenOptions}, os::unix::process::CommandExt, path::PathBuf, process::{Child, Command, Stdio}, }; use crate::debug_eprintln; pub struct Args(Vec); impl From<&str> for Args { fn from(value: &str) -> Self { Self(Vec::from_iter(value.split_whitespace().map(String::from))) } } impl From<&String> for Args { fn from(value: &String) -> Self { Self(Vec::from_iter(value.split_whitespace().map(String::from))) } } impl From<&[String]> for Args { fn from(value: &[String]) -> Self { Self(value.to_vec()) } } fn get_compose_file() -> Result { let cf = env::var("COMPOSE_FILE")?; Ok(cf) } #[derive(Default)] pub struct CommandBuilder { args: Vec, } impl Display for CommandBuilder { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(&self.args.join(" ")) } } impl CommandBuilder { pub fn new(args: &str) -> Self { Self::default().args("docker") } pub fn docker() -> Self { Self::default().args("docker") } pub fn docker_compose() -> Self { let cf = get_compose_file().unwrap_or_else(|_| "docker/local/docker-compose.yaml".to_string()); Self::default().args("docker compose -f").args(&cf) } pub fn args(mut self, args: T) -> Self where Args: From, { self.args.extend(Args::from(args).0); self } pub fn build(self) -> Result { debug_eprintln!("running `{self}`"); let (first, rest) = self.args.split_first().context("empty args")?; let mut command = Command::new(first); command.args(rest); Ok(command) } pub fn exec_get_stdout(mut self) -> Result { Ok(String::from_utf8(self.build()?.output()?.stdout)?) } pub fn exec(mut self) -> Result<()> { self.build()?.spawn()?.wait()?; Ok(()) } pub fn spawn(mut self) -> Result<(Child)> { Ok(self.build()?.spawn()?) } pub fn exec_redirect_stdout(mut self, stdio: Stdio) -> Result<()> { self.build()?.stdout(stdio).spawn()?.wait()?; Ok(()) } }