use std::collections::HashSet;
use anyhow::{Result, bail};
use crate::resolver::{ResolutionOverrides, ResolveError};
use crate::types::{ProjectContext, TaskSource};
pub(super) fn parse_qualified_task(input: &str) -> (Option<TaskSource>, &str) {
if let Some(colon) = input.find(':') {
let prefix = &input[..colon];
if let Some(source) = TaskSource::from_label(prefix) {
return (Some(source), &input[colon + 1..]);
}
}
(None, input)
}
pub(super) fn detect_reversed_qualifier(input: &str) -> Option<(TaskSource, &str)> {
let colon = input.rfind(':')?;
let suffix = &input[colon + 1..];
let source = TaskSource::from_label(suffix)?;
Some((source, &input[..colon]))
}
pub(crate) fn precheck_task(
ctx: &ProjectContext,
overrides: &ResolutionOverrides,
task: &str,
) -> Result<()> {
let (qualifier, task_name) = parse_qualified_task(task);
let found: Vec<_> = ctx.tasks.iter().filter(|t| t.name == task_name).collect();
let restricted: Vec<_> = if qualifier.is_some() {
found.clone()
} else if let Some(allowed) = allowed_runner_sources(overrides) {
found
.iter()
.copied()
.filter(|t| allowed.contains(&t.source))
.collect()
} else {
found.clone()
};
if !restricted.is_empty() {
if let Some(source) = qualifier
&& !restricted.iter().any(|t| t.source == source)
{
bail!("task {task_name:?} not found in {}", source.label());
}
return Ok(());
}
if let Some(source) = qualifier {
bail!("task {task_name:?} not found in {}", source.label());
}
if let Some((src, task_part)) = detect_reversed_qualifier(task) {
let src_label = src.label();
bail!(
"unknown qualifier in {task:?}: source {src_label:?} must come first.\n\
hint: did you mean \"{src_label}:{task_part}\"?",
);
}
if let Some(reason) = runner_constraint_error(overrides, &found) {
return Err(reason.into());
}
Ok(())
}
pub(crate) fn allowed_runner_sources(
overrides: &ResolutionOverrides,
) -> Option<HashSet<TaskSource>> {
if let Some(ovr) = overrides.runner.as_ref() {
return Some(ovr.runner.task_source().into_iter().collect());
}
if !overrides.prefer_runners.is_empty() {
let set: HashSet<_> = overrides
.prefer_runners
.iter()
.filter_map(|r| r.task_source())
.collect();
return Some(set);
}
None
}
pub(crate) fn runner_constraint_error(
overrides: &ResolutionOverrides,
found: &[&crate::types::Task],
) -> Option<ResolveError> {
if let Some(ovr) = overrides.runner.as_ref() {
let label = ovr.runner.label();
if ovr.runner.task_source().is_none() {
return Some(ResolveError::InvalidOverride {
value: label.to_string(),
reason: "no task source is registered for this runner; cannot restrict candidates",
});
}
let reason = if found.is_empty() {
"no task with that name exists in the project"
} else {
"no candidate task is registered under this runner's source"
};
return Some(ResolveError::InvalidOverride {
value: label.to_string(),
reason,
});
}
if !overrides.prefer_runners.is_empty() {
let names = overrides
.prefer_runners
.iter()
.map(|r| r.label())
.collect::<Vec<_>>()
.join(", ");
return Some(ResolveError::InvalidOverride {
value: format!("[{names}]"),
reason: "[task_runner].prefer matched no candidate task source",
});
}
None
}