use crate::config::{Config, Settings};
use crate::task::workspace::{WorkspaceProject, WorkspaceProjectGraph};
use crate::ui::table::MiseTable;
use comfy_table::{Cell, Row};
use eyre::Result;
use serde::Serialize;
#[derive(Debug, usage_rs::Args)]
#[usage(
verbatim_doc_comment,
example(
r###"mise tasks graph"###,
help = r###"Inspect projects and their dependency edges"###
),
example(
r###"mise tasks graph --json"###,
help = r###"Emit the project graph as JSON"###
),
example(
r###"mise tasks graph --explain"###,
help = r###"Explain where inferred projects and task fields came from"###
)
)]
pub(super) struct TasksGraph {
#[usage(short = 'J', long, verbatim_doc_comment)]
json: bool,
#[usage(long, conflicts = "json", verbatim_doc_comment)]
explain: bool,
#[usage(long, alias = "no-headers", verbatim_doc_comment)]
no_header: bool,
}
#[derive(Serialize)]
struct ProjectGraphOutput<'a> {
projects: Vec<&'a WorkspaceProject>,
}
impl TasksGraph {
pub(super) async fn run(self) -> Result<()> {
Settings::get().ensure_experimental("workspace project graph")?;
let config = Config::get().await?;
let graph = config.workspace_project_graph()?;
if self.json {
self.display_json(&graph)
} else if self.explain {
self.display_explain(&graph)
} else {
self.display(&graph)
}
}
fn display(&self, graph: &WorkspaceProjectGraph) -> Result<()> {
let mut table = MiseTable::new(
self.no_header,
&["Project", "Root", "Dependencies", "Metadata"],
);
for project in graph.projects() {
let dependencies = project
.dependencies
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join(", ");
table.add_row(Row::from(vec![
Cell::new(&project.id),
Cell::new(project.root.display()),
Cell::new(if dependencies.is_empty() {
"-"
} else {
&dependencies
}),
Cell::new(serde_json::to_string(&project.metadata)?),
]));
}
table.print()
}
fn display_json(&self, graph: &WorkspaceProjectGraph) -> Result<()> {
let output = ProjectGraphOutput {
projects: graph.projects().collect(),
};
miseprintln!("{}", serde_json::to_string_pretty(&output)?);
Ok(())
}
fn display_explain(&self, graph: &WorkspaceProjectGraph) -> Result<()> {
for (index, project) in graph.projects().enumerate() {
if index > 0 {
miseprintln!();
}
miseprintln!("Project: {}", project.id);
print_provenance(" ", &project.provenance)?;
if !project.dependencies.is_empty() {
miseprintln!(" Dependencies:");
for dependency in &project.dependencies {
miseprint!(" {dependency}")?;
print_inline_provenance(project.dependency_provenance.get(dependency))?;
}
}
if !project.tasks.is_empty() {
miseprintln!(" Tasks:");
for (name, task) in &project.tasks {
miseprintln!(" {name}");
print_provenance(" ", &task.provenance)?;
let suggestions = &task.suggestions.provenance;
for input in &task.suggestions.inputs {
miseprint!(" Input: {input}")?;
print_inline_provenance(suggestions.inputs.as_ref())?;
}
if let Some(outputs) = &task.suggestions.outputs {
if outputs.is_empty() {
miseprint!(" Outputs: no files")?;
print_inline_provenance(suggestions.outputs.as_ref())?;
} else {
for output in outputs {
miseprint!(" Output: {output}")?;
print_inline_provenance(suggestions.outputs.as_ref())?;
}
}
}
if let Some(cache) = task.suggestions.cache {
miseprint!(
" Cache: {}",
if cache { "enabled" } else { "disabled" }
)?;
print_inline_provenance(suggestions.cache.as_ref())?;
}
if let Some(dependencies) = &task.suggestions.depends {
for dependency in dependencies {
miseprint!(" Task dependency: {dependency}")?;
print_inline_provenance(suggestions.depends.as_ref())?;
}
}
}
}
}
Ok(())
}
}
fn print_provenance(
indent: &str,
provenance: &crate::task::workspace::WorkspaceProvenance,
) -> Result<()> {
miseprintln!(
"{indent}Provider: {}",
provenance.provider.as_deref().unwrap_or("configuration")
);
if let Some(source) = &provenance.source {
miseprintln!("{indent}Source: {}", source.display());
}
Ok(())
}
fn print_inline_provenance(
provenance: Option<&crate::task::workspace::WorkspaceProvenance>,
) -> Result<()> {
let provider = provenance
.and_then(|provenance| provenance.provider.as_deref())
.unwrap_or("configuration");
if let Some(source) = provenance.and_then(|provenance| provenance.source.as_ref()) {
miseprintln!(" — {provider} ({})", source.display());
} else {
miseprintln!(" — {provider}");
}
Ok(())
}