use std::{collections::HashSet, sync::Arc};
use crate::config::Config;
use crate::task::{Deps, GetMatchingExt, Task, build_task_ref_map};
use crate::ui::style::{self};
use crate::ui::tree::{print_tree, print_tree_compact};
use console::style;
use eyre::{Result, eyre};
use itertools::Itertools;
use petgraph::dot::Dot;
#[derive(Debug, usage_rs::Args)]
#[usage(
verbatim_doc_comment,
example(
r###"mise tasks deps"###,
help = r###"Show dependencies for all tasks"###
),
example(
r###"mise tasks deps lint test check"###,
help = r###"Show dependencies for the "lint", "test" and "check" tasks"###
),
example(
r###"mise tasks deps --dot"###,
help = r###"Show dependencies in DOT format"###
),
example(
r###"mise tasks deps --compact"###,
help = r###"Collapse repeated dependencies"###
),
unknown_flags = "error"
)]
pub(super) struct TasksDeps {
#[usage(verbatim_doc_comment)]
pub tasks: Option<Vec<String>>,
#[usage(long, conflicts = "dot", verbatim_doc_comment)]
pub compact: bool,
#[usage(long, verbatim_doc_comment)]
pub dot: bool,
#[usage(long, verbatim_doc_comment)]
pub hidden: bool,
}
impl TasksDeps {
pub(super) async fn run(self) -> Result<()> {
let config = Config::get().await?;
let tasks = if self.tasks.is_none() {
self.get_all_tasks(&config).await?
} else {
self.get_task_lists(&config).await?
};
if self.dot {
self.print_deps_dot(&config, tasks).await?;
} else {
self.print_deps_tree(&config, tasks).await?;
}
Ok(())
}
async fn get_all_tasks(&self, config: &Arc<Config>) -> Result<Vec<Task>> {
let ctx = crate::task::TaskLoadContext::all();
Ok(config
.tasks_with_context(Some(&ctx))
.await?
.values()
.filter(|t| self.hidden || !t.hide)
.cloned()
.collect())
}
async fn get_task_lists(&self, config: &Arc<Config>) -> Result<Vec<Task>> {
let task_names: Vec<String> = self
.tasks
.as_ref()
.unwrap_or(&vec![])
.iter()
.map(|t| crate::task::expand_colon_task_syntax(t, config))
.collect::<Result<Vec<_>>>()?;
let monorepo_patterns: Vec<&str> = task_names
.iter()
.filter(|t| t.starts_with("//"))
.map(|s| s.as_str())
.collect();
let monorepo_tasks = if !monorepo_patterns.is_empty() {
let ctx = crate::task::TaskLoadContext::from_patterns(monorepo_patterns.into_iter());
Some(config.tasks_with_context(Some(&ctx)).await?)
} else {
None
};
let has_regular = task_names.iter().any(|t| !t.starts_with("//"));
let regular_tasks = if has_regular {
Some(config.tasks().await?)
} else {
None
};
let monorepo_ref_map = monorepo_tasks
.as_ref()
.map(|t| build_task_ref_map(t.iter()));
let regular_ref_map = regular_tasks.as_ref().map(|t| build_task_ref_map(t.iter()));
let mut tasks = vec![];
for task_name in &task_names {
let (all_tasks, ref_map) = if task_name.starts_with("//") {
(
monorepo_tasks.as_ref().unwrap(),
monorepo_ref_map.as_ref().unwrap(),
)
} else {
(
regular_tasks.as_ref().unwrap(),
regular_ref_map.as_ref().unwrap(),
)
};
let matching = ref_map.get_matching(task_name).ok();
let task = matching.and_then(|m| m.first().cloned().cloned());
match task {
Some(task) => {
tasks.push(task.clone());
}
None => {
return Err(self.err_no_task(task_name, all_tasks));
}
}
}
Ok(tasks)
}
async fn print_deps_tree(&self, config: &Arc<Config>, tasks: Vec<Task>) -> Result<()> {
let deps = Deps::new(config, tasks.clone()).await?;
let start_indexes = deps.graph.node_indices().filter(|&idx| {
let task = &deps.graph[idx];
tasks.iter().any(|t| t.name == task.name)
});
let mut seen = HashSet::new();
for idx in start_indexes {
if self.compact {
seen.remove(&idx);
print_tree_compact(&(&deps.graph, idx), |item| item.1, &mut seen)?;
} else {
print_tree(&(&deps.graph, idx))?;
}
}
Ok(())
}
async fn print_deps_dot(&self, config: &Arc<Config>, tasks: Vec<Task>) -> Result<()> {
let deps = Deps::new(config, tasks).await?;
miseprintln!(
"{:?}",
Dot::with_attr_getters(
&deps.graph,
&[
petgraph::dot::Config::NodeNoLabel,
petgraph::dot::Config::EdgeNoLabel
],
&|_, _| String::new(),
&|_, nr| format!("label = \"{}\"", nr.1.graph_display_name()),
),
);
Ok(())
}
fn err_no_task(
&self,
t: &str,
all_tasks: &std::collections::BTreeMap<String, Task>,
) -> eyre::Report {
let task_names = all_tasks
.values()
.map(|v| v.display_name.clone())
.map(style::ecyan)
.join(", ");
let t = style(&t).yellow().for_stderr();
eyre!("no tasks named `{t}` found. Available tasks: {task_names}")
}
}