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 manifest file as TOML.
25    let content = fs::read_to_string(manifest_path).ok()?;
26    let parsed: Value = content.parse().ok()?;
27
28    // Get the `[workspace]` table.
29    let workspace = parsed.get("workspace")?;
30    // Get the members array.
31    let members = workspace.get("members")?.as_array()?;
32
33    // The workspace root is the directory containing the workspace Cargo.toml.
34    let workspace_root = manifest_path.parent()?;
35
36    // For each member, construct the path: workspace_root / member / "Cargo.toml"
37    // and derive a member name from the member path.
38    let member_paths: Vec<(String, PathBuf)> = members
39        .iter()
40        .filter_map(|member| {
41            member.as_str().map(|s| {
42                // Construct the member's Cargo.toml path.
43                let member_manifest = workspace_root.join(s).join("Cargo.toml");
44                // Derive the member name from the last path component of s.
45                let member_name = Path::new(s)
46                    .file_name()
47                    .map(|os_str| os_str.to_string_lossy().into_owned())
48                    .unwrap_or_else(|| s.to_string());
49                (member_name, member_manifest)
50            })
51        })
52        .collect();
53
54    if member_paths.is_empty() {
55        None
56    } else {
57        Some(member_paths)
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64    use std::io::Write;
65    use tempfile::NamedTempFile;
66
67    #[test]
68    fn test_get_workspace_member_manifest_paths() {
69        let mut file = NamedTempFile::new().unwrap();
70        // Write a simple workspace manifest.
71        writeln!(file, "[workspace]").unwrap();
72        writeln!(
73            file,
74            "members = [\"cargo-e\", \"addendum/e_crate_version_checker\"]"
75        )
76        .unwrap();
77        let paths = get_workspace_member_manifest_paths(file.path());
78        assert!(paths.is_some());
79        let paths = paths.unwrap();
80        // Check that we got two entries.
81        assert_eq!(paths.len(), 2);
82        // For each entry, the path should end with "Cargo.toml".
83        for (_, path) in paths {
84            assert!(path.ends_with("Cargo.toml"));
85        }
86    }
87}