use haz_domain::name::{ProjectName, TaskName};
use haz_domain::project::Project;
use haz_domain::task::Task;
use haz_domain::workspace::Workspace;
use crate::engine::spec::QueryError;
#[derive(Debug, Clone, Copy)]
pub struct CandidateTask<'w> {
pub project_name: &'w ProjectName,
pub project: &'w Project,
pub task_name: &'w TaskName,
pub task: &'w Task,
}
pub fn collect_candidates<'w>(
workspace: &'w Workspace,
bearing_project: Option<&ProjectName>,
) -> Result<Vec<CandidateTask<'w>>, QueryError> {
match bearing_project {
Some(name) => {
let project = workspace.projects.get(name).ok_or_else(|| {
QueryError::BearingProjectNotInWorkspace {
name: name.to_string(),
}
})?;
Ok(project
.tasks
.iter()
.map(|(task_name, task)| CandidateTask {
project_name: &project.name,
project,
task_name,
task,
})
.collect())
}
None => Ok(workspace
.projects
.values()
.flat_map(|project| {
project
.tasks
.iter()
.map(move |(task_name, task)| CandidateTask {
project_name: &project.name,
project,
task_name,
task,
})
})
.collect()),
}
}
#[cfg(test)]
mod tests {
use std::collections::{BTreeMap, BTreeSet};
use std::path::PathBuf;
use std::str::FromStr;
use haz_domain::action::TaskAction;
use haz_domain::env::EnvSettings;
use haz_domain::name::ProjectName;
use haz_domain::path::{CanonicalPath, HazPath, ProjectRoot, WorkspaceRootPath};
use haz_domain::project::Project;
use haz_domain::settings::WorkspaceSettings;
use haz_domain::task::Task;
use haz_domain::workspace::Workspace;
use nonempty::NonEmpty;
use super::*;
fn argv(parts: &[&str]) -> NonEmpty<String> {
NonEmpty::from_vec(parts.iter().map(|s| (*s).to_owned()).collect()).unwrap()
}
fn bare_task(name: &str) -> Task {
Task {
name: TaskName::from_str(name).unwrap(),
action: TaskAction::Command(argv(&["true"])),
inputs: vec![],
outputs: vec![],
deps: vec![],
weak_deps: vec![],
mutex: None,
env: EnvSettings::default(),
}
}
fn project(name: &str, root: &str, task_names: &[&str]) -> Project {
let mut tasks = BTreeMap::new();
for &task_name in task_names {
tasks.insert(TaskName::from_str(task_name).unwrap(), bare_task(task_name));
}
Project {
name: ProjectName::from_str(name).unwrap(),
root: ProjectRoot::Nested(
CanonicalPath::from_absolute(&HazPath::parse(root).unwrap()).unwrap(),
),
tags: BTreeSet::new(),
tasks,
}
}
fn workspace(projects: Vec<Project>) -> Workspace {
let mut map = BTreeMap::new();
for project in projects {
map.insert(project.name.clone(), project);
}
Workspace {
root: WorkspaceRootPath::try_new(PathBuf::from("/abs/workspace")).unwrap(),
projects: map,
overlays: BTreeMap::new(),
settings: WorkspaceSettings::default(),
}
}
#[test]
fn qry_vocabulary_workspace_candidate_set_covers_every_task() {
let ws = workspace(vec![
project("lib", "/lib", &["build", "test"]),
project("web", "/web", &["bundle"]),
]);
let candidates = collect_candidates(&ws, None).unwrap();
let identities: Vec<(String, String)> = candidates
.iter()
.map(|c| (c.project_name.to_string(), c.task_name.to_string()))
.collect();
assert_eq!(
identities,
vec![
("lib".to_owned(), "build".to_owned()),
("lib".to_owned(), "test".to_owned()),
("web".to_owned(), "bundle".to_owned()),
],
);
}
#[test]
fn qry_vocabulary_bearing_project_candidate_set_restricts_to_that_project() {
let ws = workspace(vec![
project("lib", "/lib", &["build", "test"]),
project("web", "/web", &["bundle"]),
]);
let bearing = ProjectName::from_str("lib").unwrap();
let candidates = collect_candidates(&ws, Some(&bearing)).unwrap();
let identities: Vec<(String, String)> = candidates
.iter()
.map(|c| (c.project_name.to_string(), c.task_name.to_string()))
.collect();
assert_eq!(
identities,
vec![
("lib".to_owned(), "build".to_owned()),
("lib".to_owned(), "test".to_owned()),
],
);
}
#[test]
fn qry_engine_rejects_unknown_bearing_project() {
let ws = workspace(vec![project("lib", "/lib", &["build"])]);
let bearing = ProjectName::from_str("absent").unwrap();
let err = collect_candidates(&ws, Some(&bearing)).unwrap_err();
match err {
QueryError::BearingProjectNotInWorkspace { name } => assert_eq!(name, "absent"),
other => panic!("expected BearingProjectNotInWorkspace, got {other:?}"),
}
}
}