Skip to main content

embeddenator_workspace/
workspace.rs

1//! Workspace scanning and repository discovery.
2
3use anyhow::{Context, Result};
4use std::path::{Path, PathBuf};
5use walkdir::WalkDir;
6
7use crate::cargo::CargoManifest;
8
9/// Scans the workspace for Cargo.toml files.
10#[derive(Debug)]
11pub struct WorkspaceScanner {
12    root: PathBuf,
13}
14
15impl WorkspaceScanner {
16    /// Create a new workspace scanner.
17    pub fn new(root: impl AsRef<Path>) -> Self {
18        Self {
19            root: root.as_ref().to_path_buf(),
20        }
21    }
22
23    /// Find all Cargo.toml files in the workspace, excluding target/ and .git/ directories.
24    pub fn find_manifests(&self) -> Result<Vec<CargoManifest>> {
25        let mut manifests = Vec::new();
26
27        for entry in WalkDir::new(&self.root)
28            .follow_links(false)
29            .into_iter()
30            .filter_entry(|e| {
31                let name = e.file_name().to_string_lossy();
32                // Skip target, .git, and other build directories
33                !matches!(name.as_ref(), "target" | ".git" | "node_modules" | ".cargo")
34            })
35        {
36            let entry = entry.context("Failed to read directory entry")?;
37
38            if entry.file_type().is_file() && entry.file_name() == "Cargo.toml" {
39                match CargoManifest::load(entry.path()) {
40                    Ok(manifest) => manifests.push(manifest),
41                    Err(e) => {
42                        eprintln!("Warning: Failed to parse {}: {}", entry.path().display(), e);
43                    }
44                }
45            }
46        }
47
48        Ok(manifests)
49    }
50
51    /// Find all embeddenator-* package manifests (excluding nested crates).
52    pub fn find_embeddenator_packages(&self) -> Result<Vec<CargoManifest>> {
53        let all_manifests = self.find_manifests()?;
54
55        // Filter for top-level embeddenator packages
56        // Exclude: embeddenator/crates/*, embeddenator/embeddenator-core/crates/*
57        let mut packages: Vec<CargoManifest> = all_manifests
58            .into_iter()
59            .filter(|m| {
60                let path_str = m.path.to_string_lossy();
61                m.package_name.starts_with("embeddenator")
62                    && !path_str.contains("/crates/")
63                    && !path_str.contains("/target/")
64            })
65            .collect();
66
67        packages.sort_by(|a, b| a.package_name.cmp(&b.package_name));
68        Ok(packages)
69    }
70}