use crate::installers::common::utils::{print_error, print_step, print_success};
use std::path::PathBuf;
use std::process::Command;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum BuildMode {
Debug,
#[default]
Release,
}
impl std::fmt::Display for BuildMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BuildMode::Debug => write!(f, "debug"),
BuildMode::Release => write!(f, "release"),
}
}
}
#[derive(Debug, Clone)]
pub struct LaunchResult {
pub success: bool,
pub binary_path: Option<String>,
pub errors: Vec<String>,
}
impl LaunchResult {
pub fn success(binary_path: String) -> Self {
Self {
success: true,
binary_path: Some(binary_path),
errors: Vec::new(),
}
}
pub fn failure(error: String) -> Self {
Self {
success: false,
binary_path: None,
errors: vec![error],
}
}
}
pub fn launch_tui(build_mode: BuildMode, extra_args: &[String]) -> LaunchResult {
let project_root = detect_project_root();
let rusty_stack_dir = project_root.join("rusty-stack");
if !rusty_stack_dir.exists() {
let msg = format!(
"Rust project directory not found: {}",
rusty_stack_dir.display()
);
print_error(&msg);
return LaunchResult::failure(msg);
}
print_step(&format!("Build mode: {build_mode}"));
let (exec_path, build_args) = match build_mode {
BuildMode::Debug => ("target/debug/rusty-stack", vec!["build"]),
BuildMode::Release => ("target/release/rusty-stack", vec!["build", "--release"]),
};
print_step(&format!("Building with cargo {}...", build_mode));
let output = Command::new("cargo")
.args(&build_args)
.current_dir(&rusty_stack_dir)
.stdout(std::process::Stdio::inherit())
.stderr(std::process::Stdio::inherit())
.output();
match output {
Ok(output) if output.status.success() => {
let binary_path = rusty_stack_dir.join(exec_path);
if !binary_path.exists() {
let msg = format!("Binary not found after build: {}", binary_path.display());
print_error(&msg);
return LaunchResult::failure(msg);
}
print_success(&format!("Built successfully: {}", binary_path.display()));
print_step(&format!("Launching {}", exec_path));
let _ = extra_args; LaunchResult::success(binary_path.to_string_lossy().to_string())
}
Ok(output) => {
let stderr = String::from_utf8_lossy(&output.stderr);
let msg = format!("Build failed: {stderr}");
print_error(&msg);
LaunchResult::failure(msg)
}
Err(e) => {
let msg = format!("Failed to run cargo: {e}");
print_error(&msg);
LaunchResult::failure(msg)
}
}
}
fn detect_project_root() -> PathBuf {
let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
if cwd.join("rusty-stack").exists() {
return cwd;
}
if let Some(parent) = cwd.parent() {
if parent.join("rusty-stack").exists() {
return parent.to_path_buf();
}
if let Some(grandparent) = parent.parent() {
if grandparent.join("rusty-stack").exists() {
return grandparent.to_path_buf();
}
}
}
cwd
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_build_mode_default_is_release() {
assert_eq!(BuildMode::default(), BuildMode::Release);
}
#[test]
fn test_build_mode_display() {
assert_eq!(format!("{}", BuildMode::Debug), "debug");
assert_eq!(format!("{}", BuildMode::Release), "release");
}
#[test]
fn test_launch_result_success() {
let result = LaunchResult::success("/path/to/rusty-stack".to_string());
assert!(result.success);
assert_eq!(result.binary_path, Some("/path/to/rusty-stack".to_string()));
assert!(result.errors.is_empty());
}
#[test]
fn test_launch_result_failure() {
let result = LaunchResult::failure("Build error".to_string());
assert!(!result.success);
assert!(result.binary_path.is_none());
assert_eq!(result.errors.len(), 1);
}
#[test]
fn test_detect_project_root_no_panic() {
let root = detect_project_root();
assert!(!root.as_os_str().is_empty());
}
#[test]
fn test_launch_tui_returns_result() {
let result = launch_tui(BuildMode::Release, &[]);
let _ = result.success;
}
}