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}