use std::fmt;
use std::path::PathBuf;
use only_semantic::{DocumentAst, TaskAst};
use crate::dag::expand_execution_order;
use crate::resolve::{
build_execution_nodes, build_task_index, document_echo, document_label, document_preview,
document_shell, merge_parameter_inputs, resolve_root_task, resolve_root_task_in_document,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Invocation<'a> {
Task {
target: &'a str,
args: Vec<&'a str>,
overrides: Vec<(&'a str, &'a str)>,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExecutionNode {
pub stage: usize,
pub name: String,
pub commands: Vec<String>,
pub params: Vec<PlanParam>,
pub shell: Option<String>,
pub shell_fallback: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PlanParam {
pub name: String,
pub default_value: Option<String>,
pub value: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ExecutionPlan {
pub nodes: Vec<ExecutionNode>,
pub echo: bool,
pub label: bool,
pub preview: bool,
pub shell: Option<String>,
pub working_dir: PathBuf,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PlanError {
UnknownTask(String),
HelperTask(String),
TaskUnavailable(String),
MissingRequiredParameter(String),
UnknownParameter {
task: String,
name: String,
},
DuplicateOverride(String),
CyclicDependency(String),
TooManyArguments {
task: String,
expected: usize,
got: usize,
},
}
impl fmt::Display for PlanError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UnknownTask(task) => write!(f, "task '{task}' is not defined"),
Self::HelperTask(task) => {
write!(f, "helper task '{task}' cannot be invoked directly")
}
Self::TaskUnavailable(task) => {
write!(f, "task '{task}' is not available for this environment")
}
Self::MissingRequiredParameter(name) => {
write!(f, "missing required parameter '{{{{{name}}}}}'")
}
Self::UnknownParameter { task, name } => {
write!(f, "unknown parameter '{name}' for task '{task}'")
}
Self::DuplicateOverride(name) => write!(f, "duplicate parameter override '{name}'"),
Self::CyclicDependency(path) => write!(f, "cyclic dependency detected: {path}"),
Self::TooManyArguments {
task,
expected,
got,
} => write!(
f,
"too many arguments for task '{task}'; expected at most {expected}, got {got}"
),
}
}
}
impl std::error::Error for PlanError {}
pub fn build_execution_plan(document: &DocumentAst, invocation: Invocation<'_>) -> ExecutionPlan {
let working_dir = std::env::current_dir().unwrap_or_default();
try_build_execution_plan_in_dir(document, invocation, working_dir).unwrap_or_default()
}
pub fn try_build_execution_plan(
document: &DocumentAst,
invocation: Invocation<'_>,
) -> Result<ExecutionPlan, PlanError> {
let working_dir = std::env::current_dir().unwrap_or_default();
try_build_execution_plan_in_dir(document, invocation, working_dir)
}
pub fn try_build_execution_plan_in_dir(
document: &DocumentAst,
invocation: Invocation<'_>,
working_dir: PathBuf,
) -> Result<ExecutionPlan, PlanError> {
let Invocation::Task {
target,
args,
overrides,
} = invocation;
let tasks = build_task_index(document);
let root = resolve_root_task(&tasks, target)?;
let overrides = merge_parameter_inputs(args, overrides, root)?;
let ordered = expand_execution_order(root, &overrides, &tasks)?;
Ok(ExecutionPlan {
nodes: build_execution_nodes(ordered),
echo: document_echo(document),
label: document_label(document),
preview: document_preview(document),
shell: document_shell(document),
working_dir,
})
}
pub fn select_root_task_variant<'a>(
document: &'a DocumentAst,
target: &str,
) -> Result<&'a TaskAst, PlanError> {
resolve_root_task_in_document(document, target)
}