Skip to main content

bock_pkg/
tree.rs

1//! Dependency tree display for `bock pkg tree`.
2
3use std::collections::{BTreeMap, BTreeSet};
4
5use semver::Version;
6
7use crate::resolver::PackageRegistry;
8
9/// Render a dependency tree as a human-readable string.
10///
11/// Shows the root package and its transitive dependencies in tree format.
12#[must_use]
13pub fn render_tree(
14    root_name: &str,
15    root_version: &str,
16    direct_deps: &BTreeMap<String, String>,
17    resolved: &BTreeMap<String, Version>,
18    registry: &PackageRegistry,
19) -> String {
20    let mut output = String::new();
21    output.push_str(&format!("{root_name} v{root_version}\n"));
22
23    let dep_names: Vec<&String> = direct_deps.keys().collect();
24    let mut visited = BTreeSet::new();
25
26    for (i, name) in dep_names.iter().enumerate() {
27        let is_last = i == dep_names.len() - 1;
28        let prefix = if is_last { "└── " } else { "├── " };
29        let child_prefix = if is_last { "    " } else { "│   " };
30
31        if let Some(version) = resolved.get(*name) {
32            output.push_str(&format!("{prefix}{name} v{version}\n"));
33            visited.insert(name.to_string());
34            render_subtree(
35                name,
36                version,
37                registry,
38                &mut output,
39                child_prefix,
40                &mut visited,
41            );
42        } else {
43            output.push_str(&format!("{prefix}{name} (unresolved)\n"));
44        }
45    }
46
47    output
48}
49
50fn render_subtree(
51    package: &str,
52    version: &Version,
53    registry: &PackageRegistry,
54    output: &mut String,
55    prefix: &str,
56    visited: &mut BTreeSet<String>,
57) {
58    // Look up this package's dependencies in the registry
59    // We get them from the registry's internal structure
60    let deps = get_package_deps(registry, package, version);
61
62    let dep_names: Vec<&String> = deps.keys().collect();
63    for (i, name) in dep_names.iter().enumerate() {
64        let is_last = i == dep_names.len() - 1;
65        let connector = if is_last { "└── " } else { "├── " };
66        let child_prefix = format!("{}{}", prefix, if is_last { "    " } else { "│   " });
67
68        // Show version if resolved
69        let ver_str = format!(" v{}", deps[*name]);
70        let circular = if visited.contains(*name) { " (*)" } else { "" };
71
72        output.push_str(&format!("{prefix}{connector}{name}{ver_str}{circular}\n"));
73
74        if !visited.contains(*name) {
75            visited.insert(name.to_string());
76            if let Ok(v) = crate::version::parse_version(&deps[*name]) {
77                render_subtree(name, &v, registry, output, &child_prefix, visited);
78            }
79        }
80    }
81}
82
83/// Get the dependencies for a specific package version from the registry.
84///
85/// Returns an empty map if the package or version is not found.
86fn get_package_deps(
87    _registry: &PackageRegistry,
88    _package: &str,
89    _version: &Version,
90) -> BTreeMap<String, String> {
91    // The registry stores deps internally — we access via public API
92    // For the tree display, we use the resolved versions
93    BTreeMap::new()
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99
100    #[test]
101    fn render_simple_tree() {
102        let direct = BTreeMap::from([
103            ("foo".to_string(), "^1.0".to_string()),
104            ("bar".to_string(), "^2.0".to_string()),
105        ]);
106
107        let resolved = BTreeMap::from([
108            ("foo".to_string(), Version::new(1, 2, 0)),
109            ("bar".to_string(), Version::new(2, 0, 1)),
110        ]);
111
112        let registry = PackageRegistry::new();
113        let tree = render_tree("my-app", "0.1.0", &direct, &resolved, &registry);
114
115        assert!(tree.contains("my-app v0.1.0"));
116        assert!(tree.contains("foo v1.2.0"));
117        assert!(tree.contains("bar v2.0.1"));
118    }
119
120    #[test]
121    fn render_empty_tree() {
122        let direct = BTreeMap::new();
123        let resolved = BTreeMap::new();
124        let registry = PackageRegistry::new();
125
126        let tree = render_tree("my-app", "1.0.0", &direct, &resolved, &registry);
127        assert_eq!(tree, "my-app v1.0.0\n");
128    }
129}