mecha10-cli 0.1.47

Mecha10 CLI tool
Documentation
#![allow(dead_code)]

//! Template service for code generation
//!
//! This service provides template rendering for generating code files.
//! It wraps the TemplateEngine and provides higher-level operations.

use crate::framework::{TemplateEngine, TemplateVars};
use anyhow::Result;
use std::collections::HashMap;
use std::path::{Path, PathBuf};

/// Template service for code generation
///
/// # Examples
///
/// ```rust,ignore
/// use mecha10_cli::services::TemplateService;
/// use std::collections::HashMap;
///
/// # async fn example() -> anyhow::Result<()> {
/// // Create service with default templates
/// let template_service = TemplateService::new();
///
/// // Render a template
/// let mut vars = HashMap::new();
/// vars.insert("name", "camera_driver");
/// let rendered = template_service.render("drivers/sensor.rs.template", &vars)?;
///
/// // Render to file
/// template_service.render_to_file(
///     "drivers/sensor.rs.template",
///     "output/camera_driver.rs",
///     &vars
/// ).await?;
/// # Ok(())
/// # }
/// ```
pub struct TemplateService {
    engine: TemplateEngine,
}

impl TemplateService {
    /// Create a new template service with default templates
    pub fn new() -> Self {
        Self {
            engine: TemplateEngine::default(),
        }
    }

    /// Create a template service with a custom template directory
    ///
    /// # Arguments
    ///
    /// * `template_dir` - Path to directory containing templates
    pub fn with_template_dir(template_dir: impl Into<PathBuf>) -> Self {
        Self {
            engine: TemplateEngine::new(template_dir),
        }
    }

    /// Render a template with variables
    ///
    /// # Arguments
    ///
    /// * `template_path` - Path to template file relative to template directory
    /// * `variables` - HashMap of variable names to values
    ///
    /// # Errors
    ///
    /// Returns an error if:
    /// - The template file doesn't exist
    /// - The template file cannot be read
    /// - Variable substitution fails
    pub fn render(&self, template_path: &str, variables: &HashMap<&str, &str>) -> Result<String> {
        self.engine.render(template_path, variables)
    }

    /// Render a template and write it to a file
    ///
    /// # Arguments
    ///
    /// * `template_path` - Path to template file relative to template directory
    /// * `output_path` - Destination file path
    /// * `variables` - HashMap of variable names to values
    ///
    /// # Errors
    ///
    /// Returns an error if:
    /// - The template file doesn't exist
    /// - The template file cannot be read
    /// - The output file cannot be written
    pub async fn render_to_file(
        &self,
        template_path: &str,
        output_path: impl AsRef<Path>,
        variables: &HashMap<&str, &str>,
    ) -> Result<()> {
        self.engine.render_to_file(template_path, output_path, variables).await
    }

    /// List available templates in a category
    ///
    /// # Arguments
    ///
    /// * `category` - Template category (e.g., "drivers", "nodes", "types")
    ///
    /// # Returns
    ///
    /// Vector of template names without the .template extension
    pub fn list_templates(&self, category: &str) -> Result<Vec<String>> {
        self.engine.list_templates(category)
    }

    /// Check if a template exists
    ///
    /// # Arguments
    ///
    /// * `template_path` - Path to template file relative to template directory
    pub fn template_exists(&self, template_path: &str) -> bool {
        self.engine.template_exists(template_path)
    }

    /// Create a template variables builder
    ///
    /// Helper method to create a TemplateVars instance for building
    /// variable mappings with name transformations.
    ///
    /// # Examples
    ///
    /// ```rust,ignore
    /// let service = TemplateService::new();
    /// let mut vars = service.create_vars();
    /// vars.add_name("camera_driver");
    /// // Now vars contains: name, PascalName, snake_name, UPPER_NAME
    /// ```
    pub fn create_vars(&self) -> TemplateVars {
        TemplateVars::new()
    }

    /// Generate a node file from template
    ///
    /// Convenience method for generating node files with standard naming.
    ///
    /// # Arguments
    ///
    /// * `node_name` - Name of the node (snake_case)
    /// * `node_type` - Type of node ("sensor", "actuator", "controller", etc.)
    /// * `output_dir` - Directory where the node will be generated
    pub async fn generate_node(&self, node_name: &str, node_type: &str, output_dir: impl AsRef<Path>) -> Result<()> {
        let mut vars = self.create_vars();
        vars.add_name(node_name);

        let template_path = format!("nodes/{}.rs.template", node_type);
        if !self.template_exists(&template_path) {
            return Err(anyhow::anyhow!(
                "Template not found: {}. Available node types: sensor, actuator, controller",
                template_path
            ));
        }

        let output_path = output_dir.as_ref().join(node_name).join("src").join("lib.rs");

        // Create parent directories
        if let Some(parent) = output_path.parent() {
            tokio::fs::create_dir_all(parent).await?;
        }

        self.render_to_file(&template_path, output_path, &vars.to_hashmap())
            .await
    }

    /// Generate a driver file from template
    ///
    /// Convenience method for generating driver files with standard naming.
    ///
    /// # Arguments
    ///
    /// * `driver_name` - Name of the driver (snake_case)
    /// * `driver_type` - Type of driver ("camera", "motor", "sensor", etc.)
    /// * `output_dir` - Directory where the driver will be generated
    pub async fn generate_driver(
        &self,
        driver_name: &str,
        driver_type: &str,
        output_dir: impl AsRef<Path>,
    ) -> Result<()> {
        let mut vars = self.create_vars();
        vars.add_name(driver_name);

        let template_path = format!("drivers/{}.rs.template", driver_type);
        if !self.template_exists(&template_path) {
            return Err(anyhow::anyhow!(
                "Template not found: {}. Available driver types: camera, motor, sensor, imu",
                template_path
            ));
        }

        let output_path = output_dir.as_ref().join(driver_name).join("src").join("lib.rs");

        // Create parent directories
        if let Some(parent) = output_path.parent() {
            tokio::fs::create_dir_all(parent).await?;
        }

        self.render_to_file(&template_path, output_path, &vars.to_hashmap())
            .await
    }

    /// Generate a custom type file from template
    ///
    /// Convenience method for generating type files with standard naming.
    ///
    /// # Arguments
    ///
    /// * `type_name` - Name of the type (PascalCase or snake_case)
    /// * `output_dir` - Directory where the type will be generated
    pub async fn generate_type(&self, type_name: &str, output_dir: impl AsRef<Path>) -> Result<()> {
        let mut vars = self.create_vars();
        vars.add_name(type_name);

        let template_path = "types/custom.rs.template";
        if !self.template_exists(template_path) {
            return Err(anyhow::anyhow!("Template not found: {}", template_path));
        }

        let output_path = output_dir.as_ref().join(format!("{}.rs", type_name));

        // Create parent directories
        if let Some(parent) = output_path.parent() {
            tokio::fs::create_dir_all(parent).await?;
        }

        self.render_to_file(template_path, output_path, &vars.to_hashmap())
            .await
    }

    /// Generate a Cargo.toml file for a package
    ///
    /// # Arguments
    ///
    /// * `package_name` - Name of the package
    /// * `package_type` - Type of package ("node", "driver", "type")
    /// * `output_dir` - Directory where Cargo.toml will be generated
    pub async fn generate_cargo_toml(
        &self,
        package_name: &str,
        package_type: &str,
        output_dir: impl AsRef<Path>,
    ) -> Result<()> {
        let mut vars = self.create_vars();
        vars.add_name(package_name);

        let template_path = format!("cargo/{}.toml.template", package_type);
        if !self.template_exists(&template_path) {
            return Err(anyhow::anyhow!(
                "Template not found: {}. Available types: node, driver, type",
                template_path
            ));
        }

        let output_path = output_dir.as_ref().join("Cargo.toml");
        self.render_to_file(&template_path, output_path, &vars.to_hashmap())
            .await
    }

    /// Render a string template directly (not from file)
    ///
    /// # Arguments
    ///
    /// * `template_content` - Template content as string
    /// * `variables` - HashMap of variable names to values
    pub fn render_string(&self, template_content: &str, variables: &HashMap<&str, &str>) -> Result<String> {
        let mut result = template_content.to_string();
        for (key, value) in variables {
            let placeholder = format!("{{{{{}}}}}", key);
            result = result.replace(&placeholder, value);
        }
        Ok(result)
    }
}

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