mecha10-templates 0.8.2

Mecha10 project templates — code generation, init, and template services
Documentation
//! Rust project template generation
//!
//! Handles generation of Rust-specific files (Cargo.toml, src/main.rs, build.rs, rustfmt.toml).

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

pub struct RustTemplates;

impl RustTemplates {
    /// Create a new RustTemplates instance
    pub fn new() -> Self {
        Self
    }

    /// Create Cargo.toml for the project, reading the template from the freshly-fetched
    /// `mecha10-assets` extraction at `assets_root`.
    pub async fn create_cargo_toml(&self, path: &Path, project_name: &str, assets_root: &Path) -> Result<()> {
        let template_path = assets_root.join("project/Cargo.toml.prod.template");
        let template = tokio::fs::read_to_string(&template_path)
            .await
            .with_context(|| format!("Failed to read {:?} from downloaded mecha10-assets", template_path))?;
        let content = template.replace("{{project_name}}", project_name);

        tokio::fs::write(path.join(paths::rust::CARGO_TOML), content).await?;
        Ok(())
    }

    /// Create src/main.rs for the project, reading the template from the freshly-fetched
    /// `mecha10-assets` extraction at `assets_root`.
    pub async fn create_main_rs(&self, path: &Path, project_name: &str, assets_root: &Path) -> Result<()> {
        let template_path = assets_root.join("project/main.rs.template");
        let template = tokio::fs::read_to_string(&template_path)
            .await
            .with_context(|| format!("Failed to read {:?} from downloaded mecha10-assets", template_path))?;
        let content = template.replace("{{project_name}}", project_name);

        let src_dir = path.join(paths::project::SRC_DIR);
        tokio::fs::create_dir_all(&src_dir).await?;
        tokio::fs::write(path.join(paths::rust::MAIN_RS), content).await?;
        Ok(())
    }

    /// Create build.rs for the project, reading the template from the freshly-fetched
    /// `mecha10-assets` extraction at `assets_root`.
    pub async fn create_build_rs(&self, path: &Path, assets_root: &Path) -> Result<()> {
        let template_path = assets_root.join("project/build.rs.template");
        let content = tokio::fs::read_to_string(&template_path)
            .await
            .with_context(|| format!("Failed to read {:?} from downloaded mecha10-assets", template_path))?;

        tokio::fs::write(path.join(paths::rust::BUILD_RS), content).await?;
        Ok(())
    }

    /// Create rustfmt.toml for the project, reading the template from the freshly-fetched
    /// `mecha10-assets` extraction at `assets_root`.
    pub async fn create_rustfmt_toml(&self, path: &Path, assets_root: &Path) -> Result<()> {
        let template_path = assets_root.join("project/rustfmt.toml.template");
        let content = tokio::fs::read_to_string(&template_path)
            .await
            .with_context(|| format!("Failed to read {:?} from downloaded mecha10-assets", template_path))?;

        tokio::fs::write(path.join(paths::rust::RUSTFMT_TOML), content).await?;
        Ok(())
    }
}

impl Default for RustTemplates {
    fn default() -> Self {
        Self::new()
    }
}

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

    /// Stage a fake extracted `mecha10-assets` root containing a single `project/<name>`
    /// template file, mirroring the real `mecha10-assets` layout.
    fn stage_assets_root_with(name: &str, content: &str) -> tempfile::TempDir {
        let assets_root = tempfile::tempdir().unwrap();
        let project_dir = assets_root.path().join("project");
        std::fs::create_dir_all(&project_dir).unwrap();
        std::fs::write(project_dir.join(name), content).unwrap();
        assets_root
    }

    #[tokio::test]
    async fn create_build_rs_reads_from_mecha10_assets_and_writes_project_file() {
        let assets_root = stage_assets_root_with("build.rs.template", "fn main() {}\n");
        let dest = tempfile::tempdir().unwrap();

        RustTemplates::new()
            .create_build_rs(dest.path(), assets_root.path())
            .await
            .unwrap();

        let content = tokio::fs::read_to_string(dest.path().join("build.rs")).await.unwrap();
        assert_eq!(content, "fn main() {}\n");
    }

    #[tokio::test]
    async fn create_main_rs_replaces_project_name_placeholder() {
        let assets_root = stage_assets_root_with("main.rs.template", "// {{project_name}}\n");
        let dest = tempfile::tempdir().unwrap();

        RustTemplates::new()
            .create_main_rs(dest.path(), "my-robot", assets_root.path())
            .await
            .unwrap();

        let content = tokio::fs::read_to_string(dest.path().join("src/main.rs"))
            .await
            .unwrap();
        assert_eq!(content, "// my-robot\n");
    }

    #[tokio::test]
    async fn create_rustfmt_toml_reads_from_mecha10_assets_and_writes_project_file() {
        let assets_root = stage_assets_root_with("rustfmt.toml.template", "edition = \"2021\"\n");
        let dest = tempfile::tempdir().unwrap();

        RustTemplates::new()
            .create_rustfmt_toml(dest.path(), assets_root.path())
            .await
            .unwrap();

        let content = tokio::fs::read_to_string(dest.path().join("rustfmt.toml"))
            .await
            .unwrap();
        assert_eq!(content, "edition = \"2021\"\n");
    }
}