mecha10_cli/services/
template.rs

1#![allow(dead_code)]
2
3//! Template service for code generation
4//!
5//! This service provides template rendering for generating code files.
6//! It wraps the TemplateEngine and provides higher-level operations.
7
8use crate::framework::{TemplateEngine, TemplateVars};
9use anyhow::Result;
10use std::collections::HashMap;
11use std::path::{Path, PathBuf};
12
13/// Template service for code generation
14///
15/// # Examples
16///
17/// ```rust,ignore
18/// use mecha10_cli::services::TemplateService;
19/// use std::collections::HashMap;
20///
21/// # async fn example() -> anyhow::Result<()> {
22/// // Create service with default templates
23/// let template_service = TemplateService::new();
24///
25/// // Render a template
26/// let mut vars = HashMap::new();
27/// vars.insert("name", "camera_driver");
28/// let rendered = template_service.render("drivers/sensor.rs.template", &vars)?;
29///
30/// // Render to file
31/// template_service.render_to_file(
32///     "drivers/sensor.rs.template",
33///     "output/camera_driver.rs",
34///     &vars
35/// ).await?;
36/// # Ok(())
37/// # }
38/// ```
39pub struct TemplateService {
40    engine: TemplateEngine,
41}
42
43impl TemplateService {
44    /// Create a new template service with default templates
45    pub fn new() -> Self {
46        Self {
47            engine: TemplateEngine::default(),
48        }
49    }
50
51    /// Create a template service with a custom template directory
52    ///
53    /// # Arguments
54    ///
55    /// * `template_dir` - Path to directory containing templates
56    pub fn with_template_dir(template_dir: impl Into<PathBuf>) -> Self {
57        Self {
58            engine: TemplateEngine::new(template_dir),
59        }
60    }
61
62    /// Render a template with variables
63    ///
64    /// # Arguments
65    ///
66    /// * `template_path` - Path to template file relative to template directory
67    /// * `variables` - HashMap of variable names to values
68    ///
69    /// # Errors
70    ///
71    /// Returns an error if:
72    /// - The template file doesn't exist
73    /// - The template file cannot be read
74    /// - Variable substitution fails
75    pub fn render(&self, template_path: &str, variables: &HashMap<&str, &str>) -> Result<String> {
76        self.engine.render(template_path, variables)
77    }
78
79    /// Render a template and write it to a file
80    ///
81    /// # Arguments
82    ///
83    /// * `template_path` - Path to template file relative to template directory
84    /// * `output_path` - Destination file path
85    /// * `variables` - HashMap of variable names to values
86    ///
87    /// # Errors
88    ///
89    /// Returns an error if:
90    /// - The template file doesn't exist
91    /// - The template file cannot be read
92    /// - The output file cannot be written
93    pub async fn render_to_file(
94        &self,
95        template_path: &str,
96        output_path: impl AsRef<Path>,
97        variables: &HashMap<&str, &str>,
98    ) -> Result<()> {
99        self.engine.render_to_file(template_path, output_path, variables).await
100    }
101
102    /// List available templates in a category
103    ///
104    /// # Arguments
105    ///
106    /// * `category` - Template category (e.g., "drivers", "nodes", "types")
107    ///
108    /// # Returns
109    ///
110    /// Vector of template names without the .template extension
111    pub fn list_templates(&self, category: &str) -> Result<Vec<String>> {
112        self.engine.list_templates(category)
113    }
114
115    /// Check if a template exists
116    ///
117    /// # Arguments
118    ///
119    /// * `template_path` - Path to template file relative to template directory
120    pub fn template_exists(&self, template_path: &str) -> bool {
121        self.engine.template_exists(template_path)
122    }
123
124    /// Create a template variables builder
125    ///
126    /// Helper method to create a TemplateVars instance for building
127    /// variable mappings with name transformations.
128    ///
129    /// # Examples
130    ///
131    /// ```rust,ignore
132    /// let service = TemplateService::new();
133    /// let mut vars = service.create_vars();
134    /// vars.add_name("camera_driver");
135    /// // Now vars contains: name, PascalName, snake_name, UPPER_NAME
136    /// ```
137    pub fn create_vars(&self) -> TemplateVars {
138        TemplateVars::new()
139    }
140
141    /// Generate a node file from template
142    ///
143    /// Convenience method for generating node files with standard naming.
144    ///
145    /// # Arguments
146    ///
147    /// * `node_name` - Name of the node (snake_case)
148    /// * `node_type` - Type of node ("sensor", "actuator", "controller", etc.)
149    /// * `output_dir` - Directory where the node will be generated
150    pub async fn generate_node(&self, node_name: &str, node_type: &str, output_dir: impl AsRef<Path>) -> Result<()> {
151        let mut vars = self.create_vars();
152        vars.add_name(node_name);
153
154        let template_path = format!("nodes/{}.rs.template", node_type);
155        if !self.template_exists(&template_path) {
156            return Err(anyhow::anyhow!(
157                "Template not found: {}. Available node types: sensor, actuator, controller",
158                template_path
159            ));
160        }
161
162        let output_path = output_dir.as_ref().join(node_name).join("src").join("lib.rs");
163
164        // Create parent directories
165        if let Some(parent) = output_path.parent() {
166            tokio::fs::create_dir_all(parent).await?;
167        }
168
169        self.render_to_file(&template_path, output_path, &vars.to_hashmap())
170            .await
171    }
172
173    /// Generate a driver file from template
174    ///
175    /// Convenience method for generating driver files with standard naming.
176    ///
177    /// # Arguments
178    ///
179    /// * `driver_name` - Name of the driver (snake_case)
180    /// * `driver_type` - Type of driver ("camera", "motor", "sensor", etc.)
181    /// * `output_dir` - Directory where the driver will be generated
182    pub async fn generate_driver(
183        &self,
184        driver_name: &str,
185        driver_type: &str,
186        output_dir: impl AsRef<Path>,
187    ) -> Result<()> {
188        let mut vars = self.create_vars();
189        vars.add_name(driver_name);
190
191        let template_path = format!("drivers/{}.rs.template", driver_type);
192        if !self.template_exists(&template_path) {
193            return Err(anyhow::anyhow!(
194                "Template not found: {}. Available driver types: camera, motor, sensor, imu",
195                template_path
196            ));
197        }
198
199        let output_path = output_dir.as_ref().join(driver_name).join("src").join("lib.rs");
200
201        // Create parent directories
202        if let Some(parent) = output_path.parent() {
203            tokio::fs::create_dir_all(parent).await?;
204        }
205
206        self.render_to_file(&template_path, output_path, &vars.to_hashmap())
207            .await
208    }
209
210    /// Generate a custom type file from template
211    ///
212    /// Convenience method for generating type files with standard naming.
213    ///
214    /// # Arguments
215    ///
216    /// * `type_name` - Name of the type (PascalCase or snake_case)
217    /// * `output_dir` - Directory where the type will be generated
218    pub async fn generate_type(&self, type_name: &str, output_dir: impl AsRef<Path>) -> Result<()> {
219        let mut vars = self.create_vars();
220        vars.add_name(type_name);
221
222        let template_path = "types/custom.rs.template";
223        if !self.template_exists(template_path) {
224            return Err(anyhow::anyhow!("Template not found: {}", template_path));
225        }
226
227        let output_path = output_dir.as_ref().join(format!("{}.rs", type_name));
228
229        // Create parent directories
230        if let Some(parent) = output_path.parent() {
231            tokio::fs::create_dir_all(parent).await?;
232        }
233
234        self.render_to_file(template_path, output_path, &vars.to_hashmap())
235            .await
236    }
237
238    /// Generate a Cargo.toml file for a package
239    ///
240    /// # Arguments
241    ///
242    /// * `package_name` - Name of the package
243    /// * `package_type` - Type of package ("node", "driver", "type")
244    /// * `output_dir` - Directory where Cargo.toml will be generated
245    pub async fn generate_cargo_toml(
246        &self,
247        package_name: &str,
248        package_type: &str,
249        output_dir: impl AsRef<Path>,
250    ) -> Result<()> {
251        let mut vars = self.create_vars();
252        vars.add_name(package_name);
253
254        let template_path = format!("cargo/{}.toml.template", package_type);
255        if !self.template_exists(&template_path) {
256            return Err(anyhow::anyhow!(
257                "Template not found: {}. Available types: node, driver, type",
258                template_path
259            ));
260        }
261
262        let output_path = output_dir.as_ref().join("Cargo.toml");
263        self.render_to_file(&template_path, output_path, &vars.to_hashmap())
264            .await
265    }
266
267    /// Render a string template directly (not from file)
268    ///
269    /// # Arguments
270    ///
271    /// * `template_content` - Template content as string
272    /// * `variables` - HashMap of variable names to values
273    pub fn render_string(&self, template_content: &str, variables: &HashMap<&str, &str>) -> Result<String> {
274        let mut result = template_content.to_string();
275        for (key, value) in variables {
276            let placeholder = format!("{{{{{}}}}}", key);
277            result = result.replace(&placeholder, value);
278        }
279        Ok(result)
280    }
281}
282
283impl Default for TemplateService {
284    fn default() -> Self {
285        Self::new()
286    }
287}