1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
//! Cargo metadata integration for dependency tree visualization.
use crate::tree::Tree;
impl Tree {
/// Builds a dependency tree from cargo metadata.
///
/// Requires the `cargo-metadata` feature.
///
/// # Examples
///
/// ```no_run
/// use treelog::Tree;
///
/// let tree = Tree::from_cargo_metadata(".").unwrap();
/// ```
#[cfg(feature = "arbitrary-cargo")]
pub fn from_cargo_metadata<P: AsRef<std::path::Path>>(
manifest_path: P,
) -> Result<Self, cargo_metadata::Error> {
let metadata = cargo_metadata::MetadataCommand::new()
.manifest_path(manifest_path.as_ref())
.exec()?;
// Build a map of package names to packages (for dependency lookup)
let packages_by_name: std::collections::HashMap<_, _> = metadata
.packages
.iter()
.map(|pkg| (pkg.name.as_str(), pkg))
.collect();
// Find root package (the one we're building)
let root_package =
metadata
.root_package()
.ok_or_else(|| cargo_metadata::Error::CargoMetadata {
stderr: "No root package found".to_string(),
})?;
Ok(Self::from_cargo_package(
root_package,
&packages_by_name,
&mut std::collections::HashSet::new(),
))
}
#[cfg(feature = "arbitrary-cargo")]
#[allow(clippy::only_used_in_recursion)]
fn from_cargo_package(
package: &cargo_metadata::Package,
packages_by_name: &std::collections::HashMap<&str, &cargo_metadata::Package>,
visited: &mut std::collections::HashSet<cargo_metadata::PackageId>,
) -> Self {
// Handle cycles
if visited.contains(&package.id) {
return Tree::new_leaf(format!("{} (cycle)", package.name));
}
visited.insert(package.id.clone());
let label = format!("{} {}", package.name, package.version);
// Get dependencies - look up by name
let children: Vec<Tree> = package
.dependencies
.iter()
.filter_map(|dep| {
// Find package by name
packages_by_name
.get(dep.name.as_str())
.map(|dep_pkg| Self::from_cargo_package(dep_pkg, packages_by_name, visited))
})
.collect();
visited.remove(&package.id);
if children.is_empty() {
Tree::new_leaf(label)
} else {
Tree::Node(label, children)
}
}
/// Builds a dependency tree for a specific package from cargo metadata.
///
/// Requires the `cargo-metadata` feature.
///
/// # Examples
///
/// ```no_run
/// use treelog::Tree;
///
/// let tree = Tree::from_cargo_package_deps("treelog", ".").unwrap();
/// ```
#[cfg(feature = "arbitrary-cargo")]
pub fn from_cargo_package_deps<P: AsRef<std::path::Path>>(
package_name: &str,
manifest_path: P,
) -> Result<Self, cargo_metadata::Error> {
let metadata = cargo_metadata::MetadataCommand::new()
.manifest_path(manifest_path.as_ref())
.exec()?;
let packages_by_name: std::collections::HashMap<_, _> = metadata
.packages
.iter()
.map(|pkg| (pkg.name.as_str(), pkg))
.collect();
let package = metadata
.packages
.iter()
.find(|pkg| pkg.name == package_name)
.ok_or_else(|| cargo_metadata::Error::CargoMetadata {
stderr: format!("Package '{}' not found", package_name),
})?;
Ok(Self::from_cargo_package(
package,
&packages_by_name,
&mut std::collections::HashSet::new(),
))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "arbitrary-cargo")]
#[test]
fn test_cargo_metadata_parsing() {
// This test requires a valid Cargo.toml in the project root
// We'll just check that the function exists and can be called
// In a real scenario, this would need a test fixture
let result = Tree::from_cargo_metadata("Cargo.toml");
// This might fail if not run from project root, which is fine for a test
if let Ok(tree) = result {
assert!(tree.is_node());
}
}
}