cargo_lock/dependency/
tree.rs1use super::{
9 graph::{EdgeDirection, Graph, NodeIndex, Nodes},
10 Dependency,
11};
12use crate::{error::Error, lockfile::Lockfile, Map};
13use std::{collections::BTreeSet as Set, io};
14
15#[derive(Clone, Debug)]
17pub struct Tree {
18 graph: Graph,
20
21 nodes: Nodes,
23}
24
25impl Tree {
26 pub fn new(lockfile: &Lockfile) -> Result<Self, Error> {
28 let mut graph = Graph::new();
29 let mut nodes = Map::new();
30
31 for package in &lockfile.packages {
33 let node_index = graph.add_node(package.clone());
34 nodes.insert(Dependency::from(package), node_index);
35 }
36
37 for package in &lockfile.packages {
39 let parent_index = nodes[&Dependency::from(package)];
40
41 for dependency in &package.dependencies {
42 if let Some(node_index) = nodes.get(dependency) {
43 graph.add_edge(parent_index, *node_index, dependency.clone());
44 } else {
45 return Err(Error::Resolution(format!(
46 "failed to find dependency: {dependency}"
47 )));
48 }
49 }
50 }
51
52 Ok(Tree { graph, nodes })
53 }
54
55 pub fn render(
58 &self,
59 w: &mut impl io::Write,
60 node_index: NodeIndex,
61 direction: EdgeDirection,
62 exact: bool,
63 ) -> io::Result<()> {
64 self.render_with_symbols(w, node_index, direction, &Symbols::default(), exact)
65 }
66
67 pub fn render_with_symbols(
70 &self,
71 w: &mut impl io::Write,
72 node_index: NodeIndex,
73 direction: EdgeDirection,
74 symbols: &Symbols,
75 exact: bool,
76 ) -> io::Result<()> {
77 Presenter::new(&self.graph, symbols).print_node(w, node_index, direction, exact)
78 }
79
80 pub fn roots(&self) -> Vec<NodeIndex> {
83 self.graph.externals(EdgeDirection::Incoming).collect()
84 }
85
86 pub fn graph(&self) -> &Graph {
88 &self.graph
89 }
90
91 pub fn nodes(&self) -> &Nodes {
93 &self.nodes
94 }
95}
96
97pub struct Symbols {
99 down: &'static str,
100 tee: &'static str,
101 ell: &'static str,
102 right: &'static str,
103}
104
105impl Default for Symbols {
106 fn default() -> Symbols {
107 Self {
108 down: "│",
109 tee: "├",
110 ell: "└",
111 right: "─",
112 }
113 }
114}
115
116struct Presenter<'g, 's> {
118 graph: &'g Graph,
120
121 symbols: &'s Symbols,
123
124 levels_continue: Vec<bool>,
126
127 visited: Set<NodeIndex>,
129}
130
131impl<'g, 's> Presenter<'g, 's> {
132 fn new(graph: &'g Graph, symbols: &'s Symbols) -> Self {
134 Self {
135 graph,
136 symbols,
137 levels_continue: vec![],
138 visited: Set::new(),
139 }
140 }
141
142 fn print_node(
144 &mut self,
145 w: &mut impl io::Write,
146 node_index: NodeIndex,
147 direction: EdgeDirection,
148 exact: bool,
149 ) -> io::Result<()> {
150 let package = &self.graph[node_index];
151 let new = self.visited.insert(node_index);
152
153 if let Some((&last_continues, rest)) = self.levels_continue.split_last() {
154 for &continues in rest {
155 let c = if continues { self.symbols.down } else { " " };
156 write!(w, "{c} ")?;
157 }
158
159 let c = if last_continues {
160 self.symbols.tee
161 } else {
162 self.symbols.ell
163 };
164
165 write!(w, "{0}{1}{1} ", c, self.symbols.right)?;
166 }
167
168 if exact {
169 let spec = if let Some(checksum) = &package.checksum {
170 format!("checksum:{checksum}")
171 } else if let Some(src) = &package.source {
172 src.to_string()
173 } else {
174 "inexact".to_string()
175 };
176 writeln!(w, "{} {} {}", &package.name, &package.version, spec)?;
177 } else {
178 writeln!(w, "{} {}", &package.name, &package.version)?;
179 }
180
181 if !new {
182 return Ok(());
183 }
184
185 use petgraph::visit::EdgeRef;
186 let dependencies = self
187 .graph
188 .edges_directed(node_index, direction)
189 .map(|edge| match direction {
190 EdgeDirection::Incoming => edge.source(),
191 EdgeDirection::Outgoing => edge.target(),
192 })
193 .collect::<Vec<_>>();
194
195 for (i, dependency) in dependencies.iter().enumerate() {
196 self.levels_continue.push(i < (dependencies.len() - 1));
197 self.print_node(w, *dependency, direction, exact)?;
198 self.levels_continue.pop();
199 }
200
201 Ok(())
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208
209 #[test]
210 fn compute_tree_v3() {
211 let lockfile = Lockfile::load("tests/examples/Cargo.lock.v3").unwrap();
213 Tree::new(&lockfile).unwrap();
214 }
215
216 #[test]
217 fn compute_roots_v3() {
218 let lockfile = Lockfile::load("tests/examples/Cargo.lock.v3").unwrap();
219 let tree = Tree::new(&lockfile).unwrap();
220 let roots = tree.roots();
221 assert_eq!(roots.len(), 1);
222
223 let root_package = &tree.graph[roots[0]];
224 assert_eq!(root_package.name.as_str(), "cargo-lock");
225 }
226
227 #[test]
228 fn compute_tree_git_ref() {
229 let lockfile = Lockfile::load("tests/examples/Cargo.lock.git-ref").unwrap();
230 Tree::new(&lockfile).unwrap();
231 }
232}