use crate::runner::is_runner_available;
use crate::task_discovery;
use std::env;
pub fn execute(task_with_args: &str) -> Result<(), String> {
let mut parts = task_with_args.split_whitespace();
let task_name = parts
.next()
.ok_or_else(|| "No task name provided".to_string())?;
let args: Vec<&str> = parts.collect();
let current_dir =
env::current_dir().map_err(|e| format!("Failed to get current directory: {}", e))?;
let discovered = task_discovery::discover_tasks(¤t_dir);
let matching_tasks = task_discovery::get_matching_tasks(&discovered, task_name);
match matching_tasks.len() {
0 => Err(format!("dela: command or task not found: {}", task_name)),
1 => {
let task = matching_tasks[0];
if !is_runner_available(&task.runner) {
if task.runner == crate::types::TaskRunner::TravisCi {
return Err("Travis CI tasks cannot be executed locally - they are only available for discovery".to_string());
}
return Err(format!("Runner '{}' not found", task.runner.short_name()));
}
let mut command = task.runner.get_command(task);
if !args.is_empty() {
command.push(' ');
command.push_str(&args.join(" "));
}
println!("{}", command);
Ok(())
}
_ => {
let error_msg = task_discovery::format_ambiguous_task_error(task_name, &matching_tasks);
Err(error_msg)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::environment::{reset_to_real_environment, set_test_environment, TestEnvironment};
use crate::task_shadowing::{enable_mock, reset_mock};
use serial_test::serial;
use std::fs::{self, File};
use std::io::Write;
use tempfile::TempDir;
fn setup_test_env() -> (TempDir, TempDir) {
let project_dir = TempDir::new().expect("Failed to create temp directory");
let makefile_content = "
build: ## Building the project
\t@echo Building...
test: ## Running tests
\t@echo Testing...
";
let mut makefile =
File::create(project_dir.path().join("Makefile")).expect("Failed to create Makefile");
makefile
.write_all(makefile_content.as_bytes())
.expect("Failed to write Makefile");
let home_dir = TempDir::new().expect("Failed to create temp HOME directory");
env::set_var("HOME", home_dir.path());
fs::create_dir_all(home_dir.path().join(".dela"))
.expect("Failed to create .dela directory");
(project_dir, home_dir)
}
#[test]
#[serial]
fn test_get_command_single_task() {
let (project_dir, home_dir) = setup_test_env();
env::set_current_dir(&project_dir).expect("Failed to change directory");
reset_mock();
enable_mock();
let env = TestEnvironment::new().with_executable("make");
set_test_environment(env);
let result = execute("test");
assert!(result.is_ok(), "Should succeed for a single task");
reset_mock();
reset_to_real_environment();
drop(project_dir);
drop(home_dir);
}
#[test]
#[serial]
fn test_get_command_with_args() {
let (project_dir, home_dir) = setup_test_env();
env::set_current_dir(&project_dir).expect("Failed to change directory");
reset_mock();
enable_mock();
let env = TestEnvironment::new().with_executable("make");
set_test_environment(env);
let result = execute("test --verbose --coverage");
assert!(result.is_ok(), "Should succeed for task with arguments");
reset_mock();
reset_to_real_environment();
drop(project_dir);
drop(home_dir);
}
#[test]
#[serial]
fn test_get_command_no_task() {
let (project_dir, home_dir) = setup_test_env();
env::set_current_dir(&project_dir).expect("Failed to change directory");
let result = execute("nonexistent");
assert!(result.is_err(), "Should fail when no task found");
assert_eq!(
result.unwrap_err(),
"dela: command or task not found: nonexistent"
);
drop(project_dir);
drop(home_dir);
}
#[test]
#[serial]
fn test_get_command_missing_runner() {
let (project_dir, home_dir) = setup_test_env();
env::set_current_dir(&project_dir).expect("Failed to change directory");
reset_mock();
enable_mock();
let env = TestEnvironment::new();
set_test_environment(env);
let result = execute("test");
assert!(result.is_err(), "Should fail when runner is missing");
assert_eq!(result.unwrap_err(), "Runner 'make' not found");
reset_mock();
reset_to_real_environment();
drop(project_dir);
drop(home_dir);
}
#[test]
#[serial]
fn test_get_command_disambiguated_tasks() {
let (project_dir, home_dir) = setup_test_env();
env::set_current_dir(&project_dir).expect("Failed to change directory");
let package_json_content = r#"{
"name": "test-package",
"scripts": {
"test": "jest"
}
}"#;
File::create(project_dir.path().join("package.json"))
.unwrap()
.write_all(package_json_content.as_bytes())
.unwrap();
File::create(project_dir.path().join("package-lock.json"))
.unwrap()
.write_all(b"{}")
.unwrap();
reset_mock();
enable_mock();
let env = TestEnvironment::new()
.with_executable("make")
.with_executable("npm");
set_test_environment(env);
let result = execute("test");
assert!(result.is_err(), "Should fail with ambiguous task name");
assert!(
result
.unwrap_err()
.contains("Multiple tasks named 'test' found"),
"Error should mention multiple tasks"
);
let result = execute("test-m");
assert!(
result.is_ok(),
"Should succeed with disambiguated task name (make)"
);
let result = execute("test-n");
assert!(
result.is_ok(),
"Should succeed with disambiguated task name (npm)"
);
let result = execute("test-m --verbose");
assert!(
result.is_ok(),
"Should succeed with disambiguated task name and args"
);
reset_mock();
reset_to_real_environment();
drop(project_dir);
drop(home_dir);
}
}