workspacer_tree/
tree.rs

1// ---------------- [ File: workspacer-tree/src/tree.rs ]
2crate::ix!();
3
4#[derive(Debug)]
5pub struct WorkspaceDependencyTree {
6    // The top-level crates with no incoming edges (roots).
7    // Not public; we’ll provide only internal constructor & printing.
8    roots: Vec<WorkspaceTreeNode>,
9}
10
11#[derive(Debug)]
12pub struct WorkspaceTreeNode {
13    crate_name:   String,
14    crate_version: Option<SemverVersion>,
15    crate_path:   PathBuf,
16    children:     Vec<WorkspaceTreeNode>,
17}
18
19impl WorkspaceDependencyTree {
20    /// Internal constructor for the final tree.
21    pub fn new(roots: Vec<WorkspaceTreeNode>) -> Self {
22        Self { roots }
23    }
24
25    /// Helper for checking whether we’re empty (no crates).
26    pub fn is_empty(&self) -> bool {
27        self.roots.is_empty()
28    }
29
30    /// Render the entire forest as a string.  
31    /// `show_version`, `show_path` => whether to display version/path in output.
32    pub fn render(
33        &self,
34        show_version: bool,
35        show_path: bool,
36    ) -> String {
37        if self.is_empty() {
38            return "(no crates in workspace)".to_string();
39        }
40
41        let mut out = String::new();
42        for (i, root) in self.roots.iter().enumerate() {
43            // Indentation starts at level=0:
44            root.render_recursive(0, show_version, show_path, &mut out);
45            if i + 1 < self.roots.len() {
46                out.push('\n'); // blank line between top-level roots
47            }
48        }
49        out
50    }
51}
52
53impl WorkspaceTreeNode {
54    /// Internal constructor for an individual node.
55    pub fn new(
56        crate_name: String,
57        crate_version: Option<SemverVersion>,
58        crate_path: PathBuf,
59    ) -> Self {
60        Self {
61            crate_name,
62            crate_version,
63            crate_path,
64            children: Vec::new(),
65        }
66    }
67
68    /// Recursively push child nodes
69    pub fn add_child(&mut self, child: WorkspaceTreeNode) {
70        self.children.push(child);
71    }
72
73    /// Recursively renders `self` into the output string with indentation.
74    /// `level` controls how many spaces to indent. 
75    pub fn render_recursive(
76        &self,
77        level: usize,
78        show_version: bool,
79        show_path: bool,
80        out: &mut String,
81    ) {
82        // Indent with 2 spaces * level
83        let indent = "  ".repeat(level);
84
85        // e.g. "my_crate (v1.2.3)  [/some/path]"
86        let mut line = format!("{}{}", indent, self.crate_name);
87
88        if show_version {
89            if let Some(ref v) = self.crate_version {
90                line.push_str(&format!(" (v{})", v));
91            }
92        }
93
94        if show_path {
95            line.push_str(&format!("  [{}]", self.crate_path.display()));
96        }
97
98        out.push_str(&line);
99        out.push('\n');
100
101        // Recurse on children
102        for child in &self.children {
103            child.render_recursive(level + 1, show_version, show_path, out);
104        }
105    }
106}
107
108#[async_trait]
109pub trait WorkspaceTreeBuilder {
110    /// Build the workspace dependency tree up to `levels` levels deep,
111    /// optionally toggling “verbose,” or ignoring it as desired.
112    async fn build_workspace_tree(
113        &self,
114        levels: usize,
115        verbose: bool,
116    ) -> Result<WorkspaceDependencyTree, WorkspaceError>;
117}
118
119// We need to ensure P, H meet the strict bounds that your Workspace<P,H> requires:
120//   P: From<PathBuf> + AsRef<Path> + Send + Sync + 'static
121//   H: CrateHandleInterface<P> + Send + Sync + 'static
122#[async_trait]
123impl<P,H> WorkspaceTreeBuilder for Workspace<P,H>
124where
125    P: From<PathBuf> + AsRef<Path> + Send + Sync + 'static,
126    H: CrateHandleInterface<P> + Send + Sync + 'static,
127{
128    async fn build_workspace_tree(
129        &self,
130        levels: usize,
131        verbose: bool,
132    ) -> Result<WorkspaceDependencyTree, WorkspaceError>
133    {
134        // 1) We gather all crates in the workspace:
135        let all_crates = self.crates(); 
136        //  ^ typically returns e.g. &Vec<Arc<AsyncMutex<H>>>, or an iterator
137
138        // Build a petgraph with (node=crate name), (edge=dependency).
139        let mut graph = Graph::<String, ()>::new();
140        let mut name_to_idx = BTreeMap::new();
141
142        // For storing info about each crate => version + path + internal deps
143        // We'll store Option<SemverVersion> for the crate_version, plus the PathBuf
144        let mut crate_info: Vec<(String, Option<SemverVersion>, PathBuf, Vec<String>)> = Vec::new();
145
146        // Lock each crate, gather metadata
147        for arc_h in all_crates {
148            let guard = arc_h.lock().await;
149
150            let crate_name = guard.name().to_string();
151
152            // Suppose `guard.version()?` yields an Option<SemverVersion> or a SemverVersion?
153            // If it yields `SemverVersion`, wrap it in Some(...). If it can be None, store that.
154            let crate_version = Some(guard.version()?); 
155
156            // Suppose guard.as_ref() yields a P that is basically a Path or something?
157            // We'll get a PathBuf from it:
158            let crate_path: PathBuf = guard.as_ref().to_path_buf();
159
160            // We also gather “internal_deps,” i.e. other crates in the workspace.
161            let internal_deps: Vec<String> = guard.internal_dependencies().await?;
162
163            // Add node to the graph
164            let node_idx = graph.add_node(crate_name.clone());
165            name_to_idx.insert(crate_name.clone(), node_idx);
166
167            // Put in crate_info for later lookup
168            crate_info.push((crate_name, crate_version, crate_path, internal_deps));
169        }
170
171        // 2) Add edges
172        for (crate_name, _v, _path, deps) in &crate_info {
173            let src_idx = name_to_idx[crate_name];
174            for dep_name in deps {
175                if let Some(&dst_idx) = name_to_idx.get(dep_name) {
176                    graph.add_edge(src_idx, dst_idx, ());
177                }
178            }
179        }
180
181        // 3) Identify “roots” => nodes with no incoming edges
182        let mut indeg = BTreeMap::new();
183        for n in graph.node_indices() {
184            indeg.insert(n, 0usize);
185        }
186        for e in graph.edge_indices() {
187            let (s, d) = graph.edge_endpoints(e).unwrap();
188            // increment the in-degree of d
189            *indeg.get_mut(&d).unwrap() += 1;
190        }
191
192        let roots: Vec<_> = indeg
193            .iter()
194            .filter_map(|(node_idx, count)| {
195                if *count == 0 {
196                    Some(*node_idx)
197                } else {
198                    None
199                }
200            })
201            .collect();
202
203        // 4) Build the “WorkspaceDependencyTree”
204        let mut out_roots = Vec::new();
205
206        for root_idx in roots {
207            let crate_name = graph[root_idx].clone(); // The node’s name
208            let (version, path) = crate_info_for_name(&crate_info, &crate_name);
209            
210            let mut root_node = WorkspaceTreeNode::new(
211                crate_name,
212                version,
213                path,
214            );
215            
216            // Recursively gather children (if levels>0)
217            if levels > 0 {
218                build_children_rec(
219                    &graph,
220                    &crate_info,
221                    root_idx,
222                    1,
223                    levels,
224                    &mut root_node
225                );
226            }
227
228            out_roots.push(root_node);
229        }
230
231        let tree = WorkspaceDependencyTree::new(out_roots);
232        Ok(tree)
233    }
234}
235
236/// Utility: find the version/path for the given crate_name in our crate_info list.
237fn crate_info_for_name(
238    crate_info: &[(String, Option<SemverVersion>, PathBuf, Vec<String>)],
239    name: &str,
240) -> (Option<SemverVersion>, PathBuf) 
241{
242    for (cn, cv, p, _) in crate_info {
243        if cn == name {
244            return (cv.clone(), p.clone());
245        }
246    }
247    // fallback if not found
248    (None, PathBuf::new())
249}
250
251/// Recursively build child nodes up to `max_levels`.
252fn build_children_rec(
253    graph: &Graph<String, ()>,
254    crate_info: &[(String, Option<SemverVersion>, PathBuf, Vec<String>)],
255    node_idx: petgraph::graph::NodeIndex,
256    current_level: usize,
257    max_levels: usize,
258    parent_node: &mut WorkspaceTreeNode,
259) {
260    if current_level >= max_levels {
261        return; // do not go deeper
262    }
263
264    // For each outgoing edge => build child
265    for edge in graph.edges_directed(node_idx, petgraph::Direction::Outgoing) {
266        let child_idx = edge.target();
267        let crate_name = &graph[child_idx];
268
269        let (version, path) = crate_info_for_name(crate_info, crate_name);
270
271        let mut child_node = WorkspaceTreeNode::new(
272            crate_name.to_string(),
273            version,
274            path,
275        );
276
277        // Recurse
278        build_children_rec(
279            graph, crate_info,
280            child_idx,
281            current_level + 1,
282            max_levels,
283            &mut child_node,
284        );
285
286        parent_node.add_child(child_node);
287    }
288}