cargo_e/
e_workspace.rs

1use std::fs;
2use std::path::{Path, PathBuf};
3use toml::Value;
4
5/// Returns true if the Cargo.toml at `manifest_path` is a workspace manifest,
6/// i.e. if it contains a `[workspace]` section.
7pub fn is_workspace_manifest(manifest_path: &Path) -> bool {
8    if let Ok(content) = fs::read_to_string(manifest_path) {
9        // Look for a line starting with "[workspace]"
10        for line in content.lines() {
11            if line.trim_start().starts_with("[workspace]") {
12                return true;
13            }
14        }
15    }
16    false
17}
18
19/// Parses the workspace manifest at `manifest_path` and returns a vector of tuples.
20/// Each tuple contains the member's name (derived from its path) and the absolute path
21/// to that member's Cargo.toml file.
22/// Returns None if no workspace members are found.
23pub fn get_workspace_member_manifest_paths(manifest_path: &Path) -> Option<Vec<(String, PathBuf)>> {
24    // Read and parse the workspace manifest.
25    let content = fs::read_to_string(manifest_path).ok()?;
26    let parsed: Value = content.parse().ok()?;
27
28    // Get the `[workspace]` table and its "members" array.
29    let workspace = parsed.get("workspace")?;
30    let members = workspace.get("members")?.as_array()?;
31
32    // The workspace root is the directory containing the workspace Cargo.toml.
33    let workspace_root = manifest_path.parent()?;
34
35    let mut member_paths = Vec::new();
36
37    for member in members {
38        if let Some(s) = member.as_str() {
39            if s.ends_with("/*") {
40                // Strip the trailing "/*" and use that as a base directory.
41                let base = workspace_root.join(s.trim_end_matches("/*"));
42                // Scan the base directory for subdirectories that contain a Cargo.toml.
43                if let Ok(entries) = fs::read_dir(&base) {
44                    for entry in entries.flatten() {
45                        let path = entry.path();
46                        if path.is_dir() {
47                            let cargo_toml = path.join("Cargo.toml");
48                            if cargo_toml.exists() {
49                                // Use the directory's name as the member name.
50                                if let Some(member_name) =
51                                    path.file_name().and_then(|os| os.to_str())
52                                {
53                                    member_paths.push((
54                                        format!(
55                                            "{}/{}",
56                                            s.trim_end_matches("/*"),
57                                            member_name.to_string()
58                                        ),
59                                        cargo_toml,
60                                    ));
61                                }
62                            }
63                        }
64                    }
65                }
66            } else {
67                // Use the declared member path directly.
68                let member_path = workspace_root.join(s);
69                let member_manifest = member_path.join("Cargo.toml");
70                if member_manifest.exists() {
71                    let mut member_name = Path::new(s)
72                        .file_name()
73                        .and_then(|os| os.to_str())
74                        .unwrap_or(s)
75                        .to_string();
76                    if member_name.eq("src-tauri") {
77                        // Special case for src-tauri, use the parent directory name.
78                        member_name = member_path
79                            .parent()
80                            .and_then(|p| p.file_name())
81                            .and_then(|os| os.to_str())
82                            .unwrap_or(s)
83                            .to_string();
84                    }
85                    member_paths.push((member_name, member_manifest));
86                }
87            }
88        }
89    }
90
91    if member_paths.is_empty() {
92        None
93    } else {
94        Some(member_paths)
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101    use std::fs;
102    use tempfile::TempDir;
103
104    #[test]
105    fn test_workspace_member_manifest_paths_found() {
106        // Create a temporary directory to serve as the workspace root.
107        let temp_dir = TempDir::new().unwrap();
108
109        // Create a workspace manifest (Cargo.toml) in the workspace root.
110        let workspace_manifest_path = temp_dir.path().join("Cargo.toml");
111        let workspace_manifest_content = r#"
112[workspace]
113members = ["cargo-e", "addendum/e_crate_version_checker"]
114        "#;
115        fs::write(&workspace_manifest_path, workspace_manifest_content).unwrap();
116
117        // Create a dummy member directory "cargo-e" with its own Cargo.toml.
118        let cargo_e_dir = temp_dir.path().join("cargo-e");
119        fs::create_dir_all(&cargo_e_dir).unwrap();
120        fs::write(cargo_e_dir.join("Cargo.toml"), "dummy content").unwrap();
121
122        // Create a dummy member directory "addendum/e_crate_version_checker" with its own Cargo.toml.
123        let e_crate_dir = temp_dir
124            .path()
125            .join("addendum")
126            .join("e_crate_version_checker");
127        fs::create_dir_all(&e_crate_dir).unwrap();
128        fs::write(e_crate_dir.join("Cargo.toml"), "dummy content").unwrap();
129
130        // Adjusted the test to trim leading and trailing whitespace from the workspace manifest content.
131        let workspace_manifest_content = r#"[workspace]\nmembers = [\n    \"cargo-e\",\n    \"addendum/e_crate_version_checker\"\n]\n".trim();
132        fs::write(&workspace_manifest_path, workspace_manifest_content).unwrap();
133
134        // Call the function under test.
135        let result = get_workspace_member_manifest_paths(&workspace_manifest_path);
136        assert!(result.is_some());
137        let members = result.unwrap();
138        assert_eq!(members.len(), 2);
139
140        // Verify that each returned path ends with "Cargo.toml".
141        for (_, path) in members {
142            assert!(path.ends_with("Cargo.toml"));
143        }
144    }
145
146    #[test]
147    fn test_workspace_member_manifest_paths_not_found() {
148        // Create a temporary directory to serve as the workspace root.
149        let temp_dir = TempDir::new().unwrap();
150
151        // Create a workspace manifest (Cargo.toml) in the workspace root.
152        let workspace_manifest_path = temp_dir.path().join("Cargo.toml");
153        let workspace_manifest_content = r#"
154[workspace]
155members = ["cargo-e", "addendum/e_crate_version_checker"]
156        "#;
157        fs::write(&workspace_manifest_path, workspace_manifest_content).unwrap();
158
159        // Do NOT create the dummy member directories or Cargo.toml files.
160
161        // Call the function and assert that it returns None.
162        let result = get_workspace_member_manifest_paths(&workspace_manifest_path);
163        assert!(result.is_none());
164    }
165}