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}