use clap_complete::engine::{ArgValueCandidates, CompletionCandidate};
use cuengine::ModuleEvalOptions;
use cuenv_core::ModuleEvaluation;
use cuenv_core::cue::discovery::compute_relative_path;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use crate::commands::env_file::{discover_env_cue_directories, find_cue_module_root};
use crate::commands::task::list_builder::prepare_task_index;
fn complete_tasks() -> Vec<CompletionCandidate> {
let tasks = get_available_tasks(".", "cuenv");
tasks
.into_iter()
.map(|(name, description)| {
let mut candidate = CompletionCandidate::new(name);
if let Some(desc) = description {
candidate = candidate.help(Some(desc.into()));
}
candidate
})
.collect()
}
fn get_available_tasks(path: &str, package: &str) -> Vec<(String, Option<String>)> {
let dir_path = Path::new(path);
let Some(module_root) = find_cue_module_root(dir_path) else {
return Vec::new();
};
let env_cue_dirs = discover_env_cue_directories(&module_root, package);
if env_cue_dirs.is_empty() {
return Vec::new();
}
let mut all_instances = HashMap::new();
let mut all_projects = Vec::new();
for dir in env_cue_dirs {
let dir_rel_path = compute_relative_path(&dir, &module_root);
let options = ModuleEvalOptions {
recursive: false,
target_dir: Some(dir.to_string_lossy().to_string()),
..Default::default()
};
let Ok(raw) = cuengine::evaluate_module(&module_root, package, Some(&options)) else {
continue;
};
for (path_str, value) in raw.instances {
let rel_path = if path_str == "." {
dir_rel_path.clone()
} else {
path_str
};
all_instances.insert(rel_path.clone(), value);
}
for project_path in raw.projects {
let rel_project_path = if project_path == "." {
dir_rel_path.clone()
} else {
project_path
};
if !all_projects.contains(&rel_project_path) {
all_projects.push(rel_project_path);
}
}
}
if all_instances.is_empty() {
return Vec::new();
}
let module = ModuleEvaluation::from_raw(module_root.clone(), all_instances, all_projects, None);
let Ok(target_path) = dir_path.canonicalize() else {
return Vec::new();
};
let relative_path = compute_relative_path(&target_path, &module_root);
let Some(instance) = module.get(&PathBuf::from(&relative_path)) else {
return Vec::new();
};
let Ok(mut manifest) = instance.deserialize::<cuenv_core::manifest::Project>() else {
return Vec::new();
};
let task_index = prepare_task_index(&mut manifest, &target_path).or_else(|_| {
cuenv_core::tasks::TaskIndex::build(&manifest.tasks)
});
let Ok(task_index) = task_index else {
return Vec::new();
};
task_index
.list()
.iter()
.map(|indexed| {
let description = match &indexed.node {
cuenv_core::tasks::TaskNode::Task(task) => task.description.clone(),
cuenv_core::tasks::TaskNode::Group(g) => g.description.clone(),
cuenv_core::tasks::TaskNode::Sequence(_) => None,
};
(indexed.name.clone(), description)
})
.collect()
}
#[allow(dead_code)] fn complete_task_params(task_name: &str) -> Vec<CompletionCandidate> {
let Some(params) = get_task_params(".", "cuenv", task_name) else {
return Vec::new();
};
params
.into_iter()
.map(|(flag, description)| {
let mut candidate = CompletionCandidate::new(flag);
if let Some(desc) = description {
candidate = candidate.help(Some(desc.into()));
}
candidate
})
.collect()
}
#[allow(dead_code)] fn get_task_params(
path: &str,
package: &str,
task_name: &str,
) -> Option<Vec<(String, Option<String>)>> {
let dir_path = Path::new(path);
let module_root = find_cue_module_root(dir_path)?;
let env_cue_dirs = discover_env_cue_directories(&module_root, package);
if env_cue_dirs.is_empty() {
return None;
}
let mut all_instances = HashMap::new();
let mut all_projects = Vec::new();
for dir in env_cue_dirs {
let dir_rel_path = compute_relative_path(&dir, &module_root);
let options = ModuleEvalOptions {
recursive: false,
target_dir: Some(dir.to_string_lossy().to_string()),
..Default::default()
};
let Ok(raw) = cuengine::evaluate_module(&module_root, package, Some(&options)) else {
continue;
};
for (path_str, value) in raw.instances {
let rel_path = if path_str == "." {
dir_rel_path.clone()
} else {
path_str
};
all_instances.insert(rel_path.clone(), value);
}
for project_path in raw.projects {
let rel_project_path = if project_path == "." {
dir_rel_path.clone()
} else {
project_path
};
if !all_projects.contains(&rel_project_path) {
all_projects.push(rel_project_path);
}
}
}
if all_instances.is_empty() {
return None;
}
let module = ModuleEvaluation::from_raw(module_root.clone(), all_instances, all_projects, None);
let target_path = dir_path.canonicalize().ok()?;
let relative_path = compute_relative_path(&target_path, &module_root);
let instance = module.get(&PathBuf::from(&relative_path))?;
let mut manifest: cuenv_core::manifest::Project = instance.deserialize().ok()?;
let task_index = prepare_task_index(&mut manifest, &target_path)
.or_else(|_| cuenv_core::tasks::TaskIndex::build(&manifest.tasks))
.ok()?;
let task_entry = task_index.resolve(task_name).ok()?;
let cuenv_core::tasks::TaskNode::Task(task) = &task_entry.node else {
return None;
};
let params = task.params.as_ref()?;
let completions: Vec<_> = params
.named
.iter()
.flat_map(|(name, param_def)| {
let main_flag = (format!("--{name}"), param_def.description.clone());
let short_flag = param_def
.short
.as_ref()
.map(|short| (format!("-{short}"), param_def.description.clone()));
std::iter::once(main_flag).chain(short_flag)
})
.collect();
Some(completions)
}
pub fn task_completer() -> ArgValueCandidates {
ArgValueCandidates::new(complete_tasks)
}
#[cfg(test)]
mod tests {
use super::*;
use std::env;
use std::fs;
#[test]
fn test_complete_tasks() {
let results = complete_tasks();
assert!(results.is_empty() || !results.is_empty());
}
#[test]
fn test_get_available_tasks_no_config() {
let tasks = get_available_tasks("/nonexistent", "cuenv");
assert!(tasks.is_empty());
}
#[test]
fn test_find_cue_module_root_nonexistent() {
let result = find_cue_module_root(Path::new("/nonexistent/path/that/does/not/exist"));
assert!(result.is_none());
}
#[test]
fn test_find_cue_module_root_no_cue_mod() {
let temp = env::temp_dir();
let result = find_cue_module_root(&temp);
let _ = result;
}
#[test]
fn test_find_cue_module_root_with_cue_mod() {
let temp = tempfile::tempdir().unwrap();
let cue_mod = temp.path().join("cue.mod");
fs::create_dir(&cue_mod).unwrap();
let result = find_cue_module_root(temp.path());
assert!(result.is_some());
assert_eq!(result.unwrap(), temp.path().canonicalize().unwrap());
}
#[test]
fn test_find_cue_module_root_in_subdirectory() {
let temp = tempfile::tempdir().unwrap();
let cue_mod = temp.path().join("cue.mod");
fs::create_dir(&cue_mod).unwrap();
let subdir = temp.path().join("foo").join("bar");
fs::create_dir_all(&subdir).unwrap();
let result = find_cue_module_root(&subdir);
assert!(result.is_some());
assert_eq!(result.unwrap(), temp.path().canonicalize().unwrap());
}
#[test]
fn test_get_available_tasks_empty_path() {
let tasks = get_available_tasks("", "cuenv");
let _ = tasks;
}
#[test]
fn test_get_available_tasks_invalid_package() {
let tasks = get_available_tasks(".", "nonexistent_package_name");
assert!(tasks.is_empty());
}
#[test]
fn test_complete_task_params_no_task() {
let params = complete_task_params("nonexistent_task");
assert!(params.is_empty());
}
#[test]
fn test_get_task_params_nonexistent_path() {
let result = get_task_params("/nonexistent", "cuenv", "test");
assert!(result.is_none());
}
#[test]
fn test_get_task_params_invalid_package() {
let result = get_task_params(".", "invalid_package", "test");
assert!(result.is_none());
}
#[test]
fn test_task_completer_returns_candidates() {
let completer = task_completer();
let _ = completer;
}
#[test]
fn test_complete_tasks_produces_candidates() {
let candidates = complete_tasks();
for candidate in &candidates {
let _ = format!("{candidate:?}");
}
}
}