use std::collections::{BTreeMap, BTreeSet};
use semver::Version;
use crate::resolver::PackageRegistry;
#[must_use]
pub fn render_tree(
root_name: &str,
root_version: &str,
direct_deps: &BTreeMap<String, String>,
resolved: &BTreeMap<String, Version>,
registry: &PackageRegistry,
) -> String {
let mut output = String::new();
output.push_str(&format!("{root_name} v{root_version}\n"));
let dep_names: Vec<&String> = direct_deps.keys().collect();
let mut visited = BTreeSet::new();
for (i, name) in dep_names.iter().enumerate() {
let is_last = i == dep_names.len() - 1;
let prefix = if is_last { "└── " } else { "├── " };
let child_prefix = if is_last { " " } else { "│ " };
if let Some(version) = resolved.get(*name) {
output.push_str(&format!("{prefix}{name} v{version}\n"));
visited.insert(name.to_string());
render_subtree(
name,
version,
registry,
&mut output,
child_prefix,
&mut visited,
);
} else {
output.push_str(&format!("{prefix}{name} (unresolved)\n"));
}
}
output
}
fn render_subtree(
package: &str,
version: &Version,
registry: &PackageRegistry,
output: &mut String,
prefix: &str,
visited: &mut BTreeSet<String>,
) {
let deps = get_package_deps(registry, package, version);
let dep_names: Vec<&String> = deps.keys().collect();
for (i, name) in dep_names.iter().enumerate() {
let is_last = i == dep_names.len() - 1;
let connector = if is_last { "└── " } else { "├── " };
let child_prefix = format!("{}{}", prefix, if is_last { " " } else { "│ " });
let ver_str = format!(" v{}", deps[*name]);
let circular = if visited.contains(*name) { " (*)" } else { "" };
output.push_str(&format!("{prefix}{connector}{name}{ver_str}{circular}\n"));
if !visited.contains(*name) {
visited.insert(name.to_string());
if let Ok(v) = crate::version::parse_version(&deps[*name]) {
render_subtree(name, &v, registry, output, &child_prefix, visited);
}
}
}
}
fn get_package_deps(
_registry: &PackageRegistry,
_package: &str,
_version: &Version,
) -> BTreeMap<String, String> {
BTreeMap::new()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn render_simple_tree() {
let direct = BTreeMap::from([
("foo".to_string(), "^1.0".to_string()),
("bar".to_string(), "^2.0".to_string()),
]);
let resolved = BTreeMap::from([
("foo".to_string(), Version::new(1, 2, 0)),
("bar".to_string(), Version::new(2, 0, 1)),
]);
let registry = PackageRegistry::new();
let tree = render_tree("my-app", "0.1.0", &direct, &resolved, ®istry);
assert!(tree.contains("my-app v0.1.0"));
assert!(tree.contains("foo v1.2.0"));
assert!(tree.contains("bar v2.0.1"));
}
#[test]
fn render_empty_tree() {
let direct = BTreeMap::new();
let resolved = BTreeMap::new();
let registry = PackageRegistry::new();
let tree = render_tree("my-app", "1.0.0", &direct, &resolved, ®istry);
assert_eq!(tree, "my-app v1.0.0\n");
}
}