Skip to main content

zoi_cli/cmd/
tree.rs

1use crate::pkg::install::resolver;
2use anyhow::{Result, anyhow};
3use colored::*;
4use std::collections::HashSet;
5
6pub fn run(package_names: &[String]) -> Result<()> {
7    if package_names.is_empty() {
8        println!("{}", "Please specify at least one package name.".yellow());
9        return Ok(());
10    }
11
12    println!("{} Resolving dependency tree...", "::".bold().blue());
13
14    let (graph, non_zoi_deps) =
15        resolver::resolve_dependency_graph(package_names, None, false, true, true, None, true)?;
16
17    if !non_zoi_deps.is_empty() {
18        println!(
19            "\n{} External dependencies (non-Zoi):",
20            "::".bold().yellow()
21        );
22        for dep in non_zoi_deps {
23            println!("  - {}", dep.dimmed());
24        }
25    }
26
27    println!("\n{} Dependency tree:", "::".bold().blue());
28
29    let mut visited = HashSet::new();
30    for source in package_names {
31        if let Some(children) = graph.adj.get("$root") {
32            for pkg_id in children {
33                let Some(node) = graph.nodes.get(pkg_id) else {
34                    continue;
35                };
36                if source.contains(&node.pkg.name) {
37                    print_node(&graph, pkg_id, "", true, &mut visited)?;
38                }
39            }
40        }
41    }
42
43    Ok(())
44}
45
46fn print_node(
47    graph: &resolver::DependencyGraph,
48    pkg_id: &str,
49    prefix: &str,
50    is_last: bool,
51    visited: &mut HashSet<String>,
52) -> Result<()> {
53    let node = graph
54        .nodes
55        .get(pkg_id)
56        .ok_or_else(|| anyhow!("Package not found in graph: {}", pkg_id))?;
57    let is_repeated = visited.contains(pkg_id);
58
59    let connector = if is_last { "└── " } else { "├── " };
60
61    let pkg_display = if let Some(sub) = &node.sub_package {
62        format!("{}:{}", node.pkg.name.cyan().bold(), sub.yellow())
63    } else {
64        node.pkg.name.cyan().bold().to_string()
65    };
66
67    let version_display = format!("v{}", node.version);
68    let repeated_mark = if is_repeated {
69        " (*)".dimmed()
70    } else {
71        "".normal()
72    };
73
74    println!(
75        "{}{}{}{} {}",
76        prefix, connector, pkg_display, repeated_mark, version_display
77    );
78
79    if is_repeated {
80        return Ok(());
81    }
82    visited.insert(pkg_id.to_string());
83
84    if let Some(children) = graph.adj.get(pkg_id) {
85        let child_count = children.len();
86        let mut sorted_children: Vec<_> = children.iter().collect();
87        sorted_children.sort();
88
89        for (i, child_id) in sorted_children.iter().enumerate() {
90            let new_prefix = format!("{}{}", prefix, if is_last { "    " } else { "│   " });
91            print_node(graph, child_id, &new_prefix, i == child_count - 1, visited)?;
92        }
93    }
94
95    Ok(())
96}