use std::path::PathBuf;
use anyhow::Result;
use clap::{Args, ValueEnum};
use cabin_core::DependencyKind;
use crate::cli::{
ConfigSelectionArgs, ResolveFormat, WorkspaceSelectionArgs, build_selection_request,
build_workspace_selection, compute_feature_resolution, lockfile_path_for,
read_optional_lockfile, resolve_invocation_manifest,
};
#[derive(Debug, Clone, Copy, ValueEnum)]
#[clap(rename_all = "kebab-case")]
pub(crate) enum TreeKindFilter {
All,
Normal,
}
impl TreeKindFilter {
fn to_filter(self) -> Option<DependencyKind> {
match self {
Self::All => None,
Self::Normal => Some(DependencyKind::Normal),
}
}
}
#[derive(Debug, Args)]
pub(crate) struct TreeArgs {
#[arg(long, value_name = "PATH")]
pub manifest_path: Option<PathBuf>,
#[arg(long, value_name = "FORMAT", default_value = "human")]
pub format: ResolveFormat,
#[arg(long, value_name = "KIND", default_value = "all")]
pub kind: TreeKindFilter,
#[command(flatten)]
pub workspace_selection: WorkspaceSelectionArgs,
#[command(flatten)]
pub selection: ConfigSelectionArgs,
#[arg(long)]
pub no_patches: bool,
}
pub(crate) fn tree(args: &TreeArgs) -> Result<()> {
let manifest_path = resolve_invocation_manifest(args.manifest_path.as_deref())?;
let tree_selection = build_workspace_selection(&args.workspace_selection);
let (prepared_ports, initial_graph) = crate::cli::port::prepare_ports_and_load_initial_graph(
&manifest_path,
None,
true,
false,
false,
&tree_selection,
args.no_patches,
)?;
let port_sources: Vec<cabin_workspace::PortPackageSource> = prepared_ports
.iter()
.map(crate::cli::port::workspace_source)
.collect();
let effective_config = crate::cli::config::load_effective_config(&initial_graph)?;
let active_patches =
crate::cli::patch::load_active_patches(&initial_graph, &effective_config, args.no_patches)?;
let patched_sources = active_patches.workspace_sources();
let graph = crate::cli::patch::reload_for_patches(
&manifest_path,
initial_graph,
&patched_sources,
&port_sources,
)?;
let lockfile_path = lockfile_path_for(&manifest_path);
let lockfile = read_optional_lockfile(&lockfile_path)?;
let request = build_selection_request(
&args.selection.features,
args.selection.all_features,
args.selection.no_default_features,
);
let resolved_selection = cabin_workspace::resolve_package_selection(&graph, &tree_selection)?;
let _feature_resolution = compute_feature_resolution(
&graph,
&resolved_selection,
&request,
&std::collections::BTreeSet::new(),
)?;
let inputs = cabin_explain::TreeInputs {
graph: &graph,
roots: &resolved_selection.packages,
lockfile: lockfile.as_ref(),
active_patches: Some(&active_patches),
kind_filter: args.kind.to_filter(),
};
let forest = cabin_explain::build_tree(&inputs);
match args.format {
ResolveFormat::Human => {
let rendered = cabin_explain::render_tree_human(&forest);
print!("{rendered}");
}
ResolveFormat::Json => {
let value = cabin_explain::render_tree_json(&forest);
crate::print_pretty_json(&value, "failed to serialize tree as JSON")?;
}
}
Ok(())
}