use crate::strategies::project_detector::{ProjectDetector, ProjectHint};
use colored::Colorize;
use std::path::Path;
use crate::utils::{preferred_pip_command, preferred_python_command};
pub use crate::strategies::project_detector::ProjectHint as ProjectType;
pub fn detect_project_types(path: &Path) -> Vec<ProjectType> {
ProjectDetector::detect_project_hints(path).unwrap_or_default()
}
pub fn suggest_commands(project_types: &[ProjectType]) -> Vec<String> {
let mut commands = Vec::new();
for project_type in project_types {
match project_type {
ProjectType::Docker => {
commands.push("docker build -t <image-name> .".to_string());
commands.push("docker run -p <port>:<port> <image-name>".to_string());
}
ProjectType::DockerCompose => {
commands.push("docker-compose up -d".to_string());
commands.push("docker-compose down".to_string());
}
ProjectType::Python => {
let pip = preferred_pip_command();
let python = preferred_python_command();
commands.push(format!("{pip} install -r requirements.txt"));
commands.push(format!("{python} main.py"));
}
ProjectType::NodeJs => {
commands.push("npm install".to_string());
commands.push("npm start".to_string());
}
ProjectType::Rust => {
commands.push("cargo build --release".to_string());
commands.push("cargo run".to_string());
}
ProjectType::Railway => {
commands.push("railway up".to_string());
commands.push("railway logs".to_string());
}
ProjectType::Go => {
commands.push("go build".to_string());
commands.push("go run .".to_string());
}
}
}
commands.dedup();
commands
}
pub fn display_project_types(project_types: &[ProjectType]) {
if project_types.is_empty() {
return;
}
let types_str = project_types
.iter()
.map(|project_type| {
format!(
"{} {}",
project_type_icon(*project_type),
project_type_name(*project_type)
)
})
.collect::<Vec<_>>()
.join(", ");
tracing::info!("{} {}", "Detected:".bright_blue(), types_str);
}
pub fn display_suggested_commands(project_types: &[ProjectType]) {
let commands = suggest_commands(project_types);
if commands.is_empty() {
return;
}
tracing::info!("{}", "Suggested commands:".bright_blue());
for cmd in commands.iter().take(3) {
tracing::info!(" {}", cmd.dimmed());
}
}
fn project_type_name(project_type: ProjectHint) -> &'static str {
match project_type {
ProjectType::Docker => "Docker",
ProjectType::DockerCompose => "Docker Compose",
ProjectType::Python => "Python",
ProjectType::NodeJs => "Node.js",
ProjectType::Rust => "Rust",
ProjectType::Railway => "Railway",
ProjectType::Go => "Go",
}
}
fn project_type_icon(project_type: ProjectHint) -> &'static str {
match project_type {
ProjectType::Docker => "🐳",
ProjectType::DockerCompose => "🐋",
ProjectType::Python => "🐍",
ProjectType::NodeJs => "📦",
ProjectType::Rust => "🦀",
ProjectType::Railway => "🚂",
ProjectType::Go => "🔷",
}
}
#[cfg(test)]
mod tests {
use super::{detect_project_types, suggest_commands, ProjectType};
use std::fs;
use std::sync::atomic::{AtomicU64, Ordering};
#[test]
fn detect_project_types_delegates_to_shared_build_hints() {
let project_root = temp_dir("xbp-cli-project-hints");
fs::create_dir_all(&project_root).expect("create temp dir");
fs::write(project_root.join("go.mod"), "module demo\n").expect("write go mod");
fs::write(project_root.join("Cargo.toml"), "[package]\nname='demo'\n")
.expect("write cargo toml");
let project_types = detect_project_types(&project_root);
assert!(project_types.contains(&ProjectType::Go));
assert!(project_types.contains(&ProjectType::Rust));
let _ = fs::remove_dir_all(project_root);
}
#[test]
fn suggest_commands_keeps_go_cli_guidance() {
let commands = suggest_commands(&[ProjectType::Go]);
assert_eq!(
commands,
vec!["go build".to_string(), "go run .".to_string()]
);
}
fn temp_dir(prefix: &str) -> std::path::PathBuf {
static COUNTER: AtomicU64 = AtomicU64::new(0);
let unique = COUNTER.fetch_add(1, Ordering::Relaxed);
std::env::temp_dir().join(format!("{prefix}-{unique}"))
}
}