mecha10-templates 0.8.2

Mecha10 project templates — code generation, init, and template services
Documentation
//! Shared helper for registering a generated crate as a Cargo workspace member.

use crate::paths;
use anyhow::{Context, Result};
use std::path::Path;

/// Add `member_path` (e.g. `"nodes/my-node"` or `"drivers/my-driver"`) to the project's
/// `[workspace] members` array in its root `Cargo.toml`, creating the `[workspace]`/`members`
/// sections if they don't already exist. A no-op if the member is already present.
pub async fn add_workspace_member(project_root: &Path, member_path: &str) -> Result<()> {
    let cargo_path = project_root.join(paths::rust::CARGO_TOML);

    let content = tokio::fs::read_to_string(&cargo_path)
        .await
        .context("Failed to read Cargo.toml")?;

    let mut doc: toml::Value = toml::from_str(&content).context("Failed to parse Cargo.toml")?;
    let member_path = member_path.to_string();

    if let Some(workspace) = doc.get_mut("workspace") {
        if let Some(members) = workspace.get_mut("members") {
            if let Some(arr) = members.as_array_mut() {
                let exists = arr.iter().any(|m| m.as_str() == Some(&member_path));
                if !exists {
                    arr.push(toml::Value::String(member_path));
                }
            }
        } else {
            workspace.as_table_mut().unwrap().insert(
                "members".to_string(),
                toml::Value::Array(vec![toml::Value::String(member_path)]),
            );
        }
    } else {
        let mut workspace_table = toml::map::Map::new();
        workspace_table.insert(
            "members".to_string(),
            toml::Value::Array(vec![toml::Value::String(member_path)]),
        );
        doc.as_table_mut()
            .unwrap()
            .insert("workspace".to_string(), toml::Value::Table(workspace_table));
    }

    let updated_content = toml::to_string_pretty(&doc)?;
    tokio::fs::write(&cargo_path, updated_content).await?;

    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::TempDir;

    #[tokio::test]
    async fn add_workspace_member_creates_workspace_section_when_absent() {
        let temp = TempDir::new().unwrap();
        tokio::fs::write(temp.path().join("Cargo.toml"), "[package]\nname = \"proj\"\n")
            .await
            .unwrap();

        add_workspace_member(temp.path(), "drivers/my-driver").await.unwrap();

        let content = tokio::fs::read_to_string(temp.path().join("Cargo.toml")).await.unwrap();
        let doc: toml::Value = toml::from_str(&content).unwrap();
        let members = doc["workspace"]["members"].as_array().unwrap();
        assert!(members.iter().any(|m| m.as_str() == Some("drivers/my-driver")));
    }

    #[tokio::test]
    async fn add_workspace_member_is_idempotent() {
        let temp = TempDir::new().unwrap();
        tokio::fs::write(
            temp.path().join("Cargo.toml"),
            "[workspace]\nmembers = [\"drivers/my-driver\"]\n",
        )
        .await
        .unwrap();

        add_workspace_member(temp.path(), "drivers/my-driver").await.unwrap();

        let content = tokio::fs::read_to_string(temp.path().join("Cargo.toml")).await.unwrap();
        let doc: toml::Value = toml::from_str(&content).unwrap();
        let members = doc["workspace"]["members"].as_array().unwrap();
        assert_eq!(
            members
                .iter()
                .filter(|m| m.as_str() == Some("drivers/my-driver"))
                .count(),
            1
        );
    }
}