1use std::collections::{BTreeMap, BTreeSet};
4
5use semver::Version;
6
7use crate::resolver::PackageRegistry;
8
9#[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 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 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
83fn get_package_deps(
87 _registry: &PackageRegistry,
88 _package: &str,
89 _version: &Version,
90) -> BTreeMap<String, String> {
91 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, ®istry);
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, ®istry);
127 assert_eq!(tree, "my-app v1.0.0\n");
128 }
129}