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