use std::path::Path;
use std::process::{Command, Stdio};
use std::time::{Duration, Instant};
pub trait Machine {
fn installed(&self, program: &str) -> bool;
fn docker_running(&self) -> bool;
fn os(&self) -> String;
fn run(&self, program: &str, args: &[&str], dir: &Path) -> Result<(), String>;
}
pub struct System;
impl Machine for System {
fn installed(&self, program: &str) -> bool {
Command::new(program)
.arg("--version")
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.is_ok_and(|status| status.success())
}
fn docker_running(&self) -> bool {
let Ok(mut child) = Command::new("docker")
.arg("info")
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
else {
return false;
};
let started = Instant::now();
while started.elapsed() < Duration::from_secs(5) {
match child.try_wait() {
Ok(Some(status)) => return status.success(),
Ok(None) => std::thread::sleep(Duration::from_millis(50)),
Err(_) => return false,
}
}
let _ = child.kill();
false
}
fn os(&self) -> String {
match std::env::consts::OS {
"macos" => "macOS".into(),
"windows" => "Windows".into(),
"linux" => linux_distribution().unwrap_or_else(|| "Linux".into()),
other => other.into(),
}
}
fn run(&self, program: &str, args: &[&str], dir: &Path) -> Result<(), String> {
let output = Command::new(program)
.args(args)
.current_dir(dir)
.output()
.map_err(|error| error.to_string())?;
match output.status.success() {
true => Ok(()),
false => Err(String::from_utf8_lossy(&output.stderr).trim().to_string()),
}
}
}
fn linux_distribution() -> Option<String> {
let release = std::fs::read_to_string("/etc/os-release").ok()?;
release.lines().find_map(|line| {
let value = line.strip_prefix("PRETTY_NAME=")?;
Some(value.trim_matches('"').to_string())
})
}
pub struct Tool {
pub program: &'static str,
pub name: &'static str,
pub why: &'static str,
}
pub const GIT: Tool = Tool {
program: "git",
name: "git",
why: "to version the project, so that every change can be reviewed and undone",
};
pub const DOCKER: Tool = Tool {
program: "docker",
name: "Docker",
why: "to run the app, its database and its front end in dev without installing them",
};
impl Tool {
pub fn install_prompt(&self, os: &str) -> String {
format!(
"I am on {os} and I need {name} for a Balise project, {why}. \
Explain how to install it on my system, step by step, and how to check that it works.\n",
name = self.name,
why = self.why
)
}
pub fn learn_prompt(&self) -> String {
format!(
"I am starting a Balise project, which uses {name} {why}. \
Give me a short tutorial: what {name} is, why Balise uses it, and the few commands worth knowing.\n",
name = self.name,
why = self.why
)
}
}