tron/builder.rs
1//! Builder patterns for fluent template construction.
2//!
3//! This module provides builder patterns for [`TronTemplate`] and [`TronRef`]
4//! that allow for more ergonomic and fluent template creation with method chaining.
5//!
6//! # Examples
7//!
8//! Using the template builder:
9//!
10//! ```
11//! use tron::TronTemplateBuilder;
12//! use std::collections::HashMap;
13//!
14//! let mut values = HashMap::new();
15//! values.insert("name", "greet");
16//! values.insert("body", "println!(\"Hello, World!\");");
17//!
18//! let template = TronTemplateBuilder::new()
19//! .content("fn @[name]@() {\n @[body]@\n}")
20//! .set("name", "greet")
21//! .set("body", "println!(\"Hello, World!\");")
22//! .build()
23//! .unwrap();
24//!
25//! let result = template.render().unwrap();
26//! ```
27//!
28//! Using the template reference builder:
29//!
30//! ```
31//! use tron::{TronTemplateBuilder, TronRefBuilder};
32//!
33//! let template_ref = TronRefBuilder::new()
34//! .content("fn main() {\n @[body]@\n}")
35//! .dependency("serde = \"1.0\"")
36//! .dependency("tokio = \"1.0\"")
37//! .set("body", "println!(\"Hello from builder!\");")
38//! .build()
39//! .unwrap();
40//! ```
41
42use std::collections::HashMap;
43use std::path::Path;
44use crate::error::{Result, TronError};
45use crate::template::TronTemplate;
46use crate::template_ref::TronRef;
47
48/// Builder for creating [`TronTemplate`] instances with a fluent API.
49///
50/// This builder allows you to construct templates step by step, setting
51/// content, placeholder values, and other configuration options.
52///
53/// # Examples
54///
55/// Basic template building:
56///
57/// ```
58/// use tron::TronTemplateBuilder;
59///
60/// let template = TronTemplateBuilder::new()
61/// .content("Hello @[name]@!")
62/// .set("name", "World")
63/// .build()
64/// .unwrap();
65///
66/// assert_eq!(template.render().unwrap(), "Hello World!");
67/// ```
68///
69/// Building from file:
70///
71/// ```no_run
72/// use tron::TronTemplateBuilder;
73///
74/// let template = TronTemplateBuilder::new()
75/// .from_file("template.tpl")
76/// .set("name", "World")
77/// .build()
78/// .unwrap();
79/// ```
80#[derive(Debug, Default)]
81pub struct TronTemplateBuilder {
82 content: Option<String>,
83 file_path: Option<String>,
84 placeholder_values: HashMap<String, String>,
85}
86
87impl TronTemplateBuilder {
88 /// Create a new template builder.
89 ///
90 /// # Examples
91 ///
92 /// ```
93 /// use tron::TronTemplateBuilder;
94 ///
95 /// let builder = TronTemplateBuilder::new();
96 /// ```
97 pub fn new() -> Self {
98 Self::default()
99 }
100
101 /// Set the template content directly.
102 ///
103 /// # Examples
104 ///
105 /// ```
106 /// use tron::TronTemplateBuilder;
107 ///
108 /// let template = TronTemplateBuilder::new()
109 /// .content("Hello @[name]@!")
110 /// .build()
111 /// .unwrap();
112 /// ```
113 pub fn content<S: Into<String>>(mut self, content: S) -> Self {
114 self.content = Some(content.into());
115 self
116 }
117
118 /// Load template content from a file.
119 ///
120 /// This will override any previously set content.
121 ///
122 /// # Examples
123 ///
124 /// ```no_run
125 /// use tron::TronTemplateBuilder;
126 ///
127 /// let template = TronTemplateBuilder::new()
128 /// .from_file("templates/function.tpl")
129 /// .build()
130 /// .unwrap();
131 /// ```
132 pub fn from_file<P: AsRef<Path>>(mut self, path: P) -> Self {
133 self.file_path = Some(path.as_ref().to_string_lossy().to_string());
134 self
135 }
136
137 /// Set a placeholder value.
138 ///
139 /// # Examples
140 ///
141 /// ```
142 /// use tron::TronTemplateBuilder;
143 ///
144 /// let template = TronTemplateBuilder::new()
145 /// .content("Hello @[name]@!")
146 /// .set("name", "World")
147 /// .build()
148 /// .unwrap();
149 /// ```
150 pub fn set<K, V>(mut self, placeholder: K, value: V) -> Self
151 where
152 K: Into<String>,
153 V: Into<String>,
154 {
155 self.placeholder_values.insert(placeholder.into(), value.into());
156 self
157 }
158
159 /// Set multiple placeholder values at once.
160 ///
161 /// # Examples
162 ///
163 /// ```
164 /// use tron::TronTemplateBuilder;
165 /// use std::collections::HashMap;
166 ///
167 /// let mut values = HashMap::new();
168 /// values.insert("greeting", "Hello");
169 /// values.insert("name", "World");
170 ///
171 /// let template = TronTemplateBuilder::new()
172 /// .content("@[greeting]@ @[name]@!")
173 /// .set_many(values)
174 /// .build()
175 /// .unwrap();
176 /// ```
177 pub fn set_many<K, V, I>(mut self, values: I) -> Self
178 where
179 K: Into<String>,
180 V: Into<String>,
181 I: IntoIterator<Item = (K, V)>,
182 {
183 for (key, value) in values {
184 self.placeholder_values.insert(key.into(), value.into());
185 }
186 self
187 }
188
189 /// Build the final [`TronTemplate`].
190 ///
191 /// # Examples
192 ///
193 /// ```
194 /// use tron::TronTemplateBuilder;
195 ///
196 /// let template = TronTemplateBuilder::new()
197 /// .content("Hello @[name]@!")
198 /// .set("name", "World")
199 /// .build()
200 /// .unwrap();
201 /// ```
202 ///
203 /// # Errors
204 ///
205 /// Returns an error if:
206 /// - Neither content nor file path was provided
207 /// - The file path is invalid or the file cannot be read
208 /// - The template syntax is invalid
209 /// - Any placeholder values reference non-existent placeholders
210 pub fn build(self) -> Result<TronTemplate> {
211 let mut template = match (self.content, self.file_path) {
212 (Some(content), None) => TronTemplate::new(&content)?,
213 (None, Some(file_path)) => TronTemplate::from_file(&file_path)?,
214 (Some(content), Some(_)) => {
215 // Content takes precedence over file path
216 TronTemplate::new(&content)?
217 },
218 (None, None) => {
219 return Err(TronError::InvalidSyntax(
220 "Must provide either content or file path".to_string()
221 ));
222 }
223 };
224
225 // Set all placeholder values
226 for (key, value) in self.placeholder_values {
227 template.set(&key, &value)?;
228 }
229
230 Ok(template)
231 }
232}
233
234/// Builder for creating [`TronRef`] instances with a fluent API.
235///
236/// This builder allows you to construct template references step by step,
237/// setting content, dependencies, placeholder values, and other configuration options.
238///
239/// # Examples
240///
241/// Basic template reference building:
242///
243/// ```
244/// use tron::TronRefBuilder;
245///
246/// let template_ref = TronRefBuilder::new()
247/// .content("fn @[name]@() { @[body]@ }")
248/// .dependency("serde = \"1.0\"")
249/// .set("name", "example")
250/// .set("body", "println!(\"Hello!\");")
251/// .build()
252/// .unwrap();
253/// ```
254#[derive(Debug, Default)]
255pub struct TronRefBuilder {
256 template_builder: TronTemplateBuilder,
257 dependencies: Vec<String>,
258}
259
260impl TronRefBuilder {
261 /// Create a new template reference builder.
262 ///
263 /// # Examples
264 ///
265 /// ```
266 /// use tron::TronRefBuilder;
267 ///
268 /// let builder = TronRefBuilder::new();
269 /// ```
270 pub fn new() -> Self {
271 Self::default()
272 }
273
274 /// Set the template content directly.
275 ///
276 /// # Examples
277 ///
278 /// ```
279 /// use tron::TronRefBuilder;
280 ///
281 /// let template_ref = TronRefBuilder::new()
282 /// .content("Hello @[name]@!")
283 /// .build()
284 /// .unwrap();
285 /// ```
286 pub fn content<S: Into<String>>(mut self, content: S) -> Self {
287 self.template_builder = self.template_builder.content(content);
288 self
289 }
290
291 /// Load template content from a file.
292 ///
293 /// # Examples
294 ///
295 /// ```no_run
296 /// use tron::TronRefBuilder;
297 ///
298 /// let template_ref = TronRefBuilder::new()
299 /// .from_file("templates/function.tpl")
300 /// .build()
301 /// .unwrap();
302 /// ```
303 pub fn from_file<P: AsRef<Path>>(mut self, path: P) -> Self {
304 self.template_builder = self.template_builder.from_file(path);
305 self
306 }
307
308 /// Set a placeholder value.
309 ///
310 /// # Examples
311 ///
312 /// ```
313 /// use tron::TronRefBuilder;
314 ///
315 /// let template_ref = TronRefBuilder::new()
316 /// .content("Hello @[name]@!")
317 /// .set("name", "World")
318 /// .build()
319 /// .unwrap();
320 /// ```
321 pub fn set<K, V>(mut self, placeholder: K, value: V) -> Self
322 where
323 K: Into<String>,
324 V: Into<String>,
325 {
326 self.template_builder = self.template_builder.set(placeholder, value);
327 self
328 }
329
330 /// Set multiple placeholder values at once.
331 ///
332 /// # Examples
333 ///
334 /// ```
335 /// use tron::TronRefBuilder;
336 /// use std::collections::HashMap;
337 ///
338 /// let mut values = HashMap::new();
339 /// values.insert("greeting", "Hello");
340 /// values.insert("name", "World");
341 ///
342 /// let template_ref = TronRefBuilder::new()
343 /// .content("@[greeting]@ @[name]@!")
344 /// .set_many(values)
345 /// .build()
346 /// .unwrap();
347 /// ```
348 pub fn set_many<K, V, I>(mut self, values: I) -> Self
349 where
350 K: Into<String>,
351 V: Into<String>,
352 I: IntoIterator<Item = (K, V)>,
353 {
354 self.template_builder = self.template_builder.set_many(values);
355 self
356 }
357
358 /// Add a dependency that will be included in rust-script execution.
359 ///
360 /// # Examples
361 ///
362 /// ```
363 /// use tron::TronRefBuilder;
364 ///
365 /// let template_ref = TronRefBuilder::new()
366 /// .content("fn main() {}")
367 /// .dependency("serde = \"1.0\"")
368 /// .dependency("tokio = \"1.0\"")
369 /// .build()
370 /// .unwrap();
371 /// ```
372 pub fn dependency<S: Into<String>>(mut self, dependency: S) -> Self {
373 self.dependencies.push(dependency.into());
374 self
375 }
376
377 /// Add multiple dependencies at once.
378 ///
379 /// # Examples
380 ///
381 /// ```
382 /// use tron::TronRefBuilder;
383 ///
384 /// let template_ref = TronRefBuilder::new()
385 /// .content("fn main() {}")
386 /// .dependencies(&["serde = \"1.0\"", "tokio = \"1.0\""])
387 /// .build()
388 /// .unwrap();
389 /// ```
390 pub fn dependencies<I, S>(mut self, dependencies: I) -> Self
391 where
392 I: IntoIterator<Item = S>,
393 S: AsRef<str>,
394 {
395 for dep in dependencies {
396 self.dependencies.push(dep.as_ref().to_string());
397 }
398 self
399 }
400
401 /// Build the final [`TronRef`].
402 ///
403 /// # Examples
404 ///
405 /// ```
406 /// use tron::TronRefBuilder;
407 ///
408 /// let template_ref = TronRefBuilder::new()
409 /// .content("Hello @[name]@!")
410 /// .dependency("serde = \"1.0\"")
411 /// .set("name", "World")
412 /// .build()
413 /// .unwrap();
414 /// ```
415 ///
416 /// # Errors
417 ///
418 /// Returns an error if the underlying template cannot be built.
419 pub fn build(self) -> Result<TronRef> {
420 let template = self.template_builder.build()?;
421 let mut template_ref = TronRef::new(template);
422
423 for dependency in self.dependencies {
424 template_ref.add_dependency(&dependency);
425 }
426
427 Ok(template_ref)
428 }
429}
430
431#[cfg(test)]
432mod tests {
433 use super::*;
434
435 #[test]
436 fn test_template_builder_basic() -> Result<()> {
437 let template = TronTemplateBuilder::new()
438 .content("Hello @[name]@!")
439 .set("name", "World")
440 .build()?;
441
442 assert_eq!(template.render()?, "Hello World!");
443 Ok(())
444 }
445
446 #[test]
447 fn test_template_builder_multiple_placeholders() -> Result<()> {
448 let template = TronTemplateBuilder::new()
449 .content("@[greeting]@ @[name]@!")
450 .set("greeting", "Hello")
451 .set("name", "World")
452 .build()?;
453
454 assert_eq!(template.render()?, "Hello World!");
455 Ok(())
456 }
457
458 #[test]
459 fn test_template_builder_set_many() -> Result<()> {
460 let mut values = HashMap::new();
461 values.insert("greeting", "Hello");
462 values.insert("name", "World");
463
464 let template = TronTemplateBuilder::new()
465 .content("@[greeting]@ @[name]@!")
466 .set_many(values)
467 .build()?;
468
469 assert_eq!(template.render()?, "Hello World!");
470 Ok(())
471 }
472
473 #[test]
474 fn test_template_builder_no_content_error() {
475 let result = TronTemplateBuilder::new().build();
476 assert!(matches!(result, Err(TronError::InvalidSyntax(_))));
477 }
478
479 #[test]
480 fn test_template_builder_invalid_placeholder() {
481 let result = TronTemplateBuilder::new()
482 .content("Hello @[name]@!")
483 .set("nonexistent", "value")
484 .build();
485 assert!(matches!(result, Err(TronError::MissingPlaceholder(_))));
486 }
487
488 #[test]
489 fn test_template_ref_builder_basic() -> Result<()> {
490 let template_ref = TronRefBuilder::new()
491 .content("Hello @[name]@!")
492 .set("name", "World")
493 .build()?;
494
495 assert_eq!(template_ref.render()?, "Hello World!");
496 Ok(())
497 }
498
499 #[test]
500 fn test_template_ref_builder_with_dependencies() -> Result<()> {
501 let template_ref = TronRefBuilder::new()
502 .content("fn main() {}")
503 .dependency("serde = \"1.0\"")
504 .dependency("tokio = \"1.0\"")
505 .build()?;
506
507 assert_eq!(template_ref.dependencies().len(), 2);
508 assert!(template_ref.dependencies().contains(&"serde = \"1.0\"".to_string()));
509 assert!(template_ref.dependencies().contains(&"tokio = \"1.0\"".to_string()));
510 Ok(())
511 }
512
513 #[test]
514 fn test_template_ref_builder_dependencies_method() -> Result<()> {
515 let template_ref = TronRefBuilder::new()
516 .content("fn main() {}")
517 .dependencies(&["serde = \"1.0\"", "tokio = \"1.0\""])
518 .build()?;
519
520 assert_eq!(template_ref.dependencies().len(), 2);
521 Ok(())
522 }
523
524 #[test]
525 fn test_template_ref_builder_complex() -> Result<()> {
526 let mut values = HashMap::new();
527 values.insert("name", "greet");
528 values.insert("body", "println!(\"Hello!\");");
529
530 let template_ref = TronRefBuilder::new()
531 .content("fn @[name]@() {\n @[body]@\n}")
532 .dependencies(&["serde = \"1.0\"", "tokio = \"1.0\""])
533 .set_many(values)
534 .build()?;
535
536 let result = template_ref.render()?;
537 assert!(result.contains("fn greet()"));
538 assert!(result.contains("println!(\"Hello!\");"));
539 assert_eq!(template_ref.dependencies().len(), 2);
540 Ok(())
541 }
542}