vscode_generator/snippets/
snippet_builder.rs

1use crate::prelude::*;
2use super::Snippet;
3use std::{ time::SystemTime, fmt::Write };
4
5/// # The Snippet Builder
6/// 
7/// 🏗️ A builder pattern implementation for creating Snippet instances with elegant
8/// fluent interface. Think of it as your personal snippet butler.
9/// 
10/// ## Features
11/// 
12/// - 🎯 Fluent interface for natural snippet construction
13/// - 🔄 Automatic unique name generation
14/// - 📝 Rich body manipulation methods
15/// - ⚡ Validation before building
16/// 
17/// ## Usage
18/// ```rust
19/// let snippet = SnippetBuilder::new()
20///     .set_prefix("fn main")
21///     .add_line("fn main() {")
22///     .add_line("    println!(\"Hello, World!\");")
23///     .add_line("}")
24///     .build()
25///     .unwrap();
26/// ```
27/// 
28/// ## 🛠️ Advanced Usage
29/// ```rust
30/// let snippet = SnippetBuilder::new()
31///     .set_prefix("test")
32///     .set_body(vec![
33///         "#[test]",
34///         "fn test_${1}() {",
35///         "    ${0:// Test code}",
36///         "}"
37///     ])
38///     .set_description("Test function template")
39///     .set_scope("rust")
40///     .set_priority(10)
41///     .build()
42///     .unwrap();
43/// ```
44/// 
45/// ## 🔧 Body Manipulation
46/// ```rust
47/// let snippet = SnippetBuilder::new()
48///     .set_prefix("hello")
49///     .add_line("print!(\"Hello, world!\");")
50///     .map_body(|lines| {
51///         lines.insert(0, "// Hello world".to_owned());
52///     })
53///     .map_line(1, |line| {
54///         *line = line.replace("print!", "println!");
55///     })
56///     .build()
57///     .unwrap();
58/// ```
59/// 
60/// ## Methods
61/// 
62/// #### 🏷️ Core Methods:
63/// - `new()` - Creates new builder instance
64/// - `build()` - Constructs final Snippet
65/// - `validate()` - Checks builder state
66/// 
67/// #### 📝 Content Setting:
68/// - `set_name(name)` - Sets snippet name
69/// - `set_prefix(prefix)` - Sets trigger text
70/// - `set_description(desc)` - Sets description
71/// - `set_scope(scope)` - Sets language scope
72/// - `set_priority(prio)` - Sets suggestion priority
73/// 
74/// #### 📄 Body Manipulation:
75/// - `set_body(lines)` - Sets entire body content
76/// - `add_line(line)` - Adds single line
77/// - `add_lines(lines)` - Adds multiple lines
78/// - `set_line(n, line)` - Changes specific line
79/// - `map_body(fn)` - Transforms entire body
80/// - `map_line(n, fn)` - Transforms specific line
81/// 
82/// ## ⚠️ Validation Rules
83/// 
84/// Builder will fail if:
85/// - Name is empty
86/// - Prefix is empty
87/// - Body is empty
88/// - Line index is out of bounds
89/// 
90/// ## 🎯 Best Practices
91/// 
92/// - Always set meaningful prefix
93/// - Add description for clarity
94/// - Use scope for language-specific snippets
95/// - Consider priority for frequently used snippets
96/// 
97/// ## 🔄 Default Values
98/// 
99/// - `name`: Auto-generated unique ID
100/// - `prefix`: Empty string
101/// - `body`: Empty vector
102/// - Other fields: None
103#[derive(Debug, Clone)]
104pub struct SnippetBuilder {
105    name: String,
106    prefix: String,
107    body: Vec<String>,
108    description: Option<String>,
109    scope: Option<String>,
110    is_file_template: Option<bool>,
111    priority: Option<u32>,
112}
113
114impl SnippetBuilder {
115    /// Creates a new SnippetBuilder instance
116    pub fn new() -> Self {
117        Self::default()
118    }
119
120    /// Generates a an unique name for snippet
121    pub fn gen_name() -> String {
122        // get current timestamp in milliseconds:
123        let timestamp = SystemTime::now()
124            .duration_since(SystemTime::UNIX_EPOCH)
125            .unwrap_or_default()
126            .as_millis();
127
128        // generating 6 random characters:
129        let mut random_suffix = String::with_capacity(6);
130        for _ in 0..6 {
131            write!(&mut random_suffix, "{}", (b'a' + (fastrand::u8(0..26))) as char).unwrap();
132        }
133
134        format!("snippet_{}_{}", timestamp, random_suffix)
135    }
136
137    /// Validates the builder state
138    pub fn validate(&self) -> Result<()> {
139        if self.name.is_empty() {
140            return Err(Error::NameIsRequired);
141        }
142        if self.prefix.is_empty() {
143            return Err(Error::PrefixIsRequired);
144        }
145        if self.body.is_empty() {
146            return Err(Error::BodyIsEmpty);
147        }
148
149        Ok(())
150    }
151
152    /// Builds the Snippet instance
153    pub fn build(self) -> Result<Snippet> {
154        self.validate()?;
155
156        Ok(Snippet {
157            name: self.name,
158            prefix: self.prefix,
159            body: self.body,
160            description: self.description,
161            scope: self.scope,
162            is_file_template: self.is_file_template,
163            priority: self.priority,
164        })
165    }
166
167    /// Sets the name of the snippet
168    pub fn set_name<S: Into<String>>(mut self, name: S) -> Self {
169        self.name = name.into();
170        self
171    }
172
173    /// Sets the prefix (trigger) of the snippet
174    pub fn set_prefix<S: Into<String>>(mut self, prefix: S) -> Self {
175        self.prefix = prefix.into();
176        self
177    }
178
179    /// Sets the entire body of the snippet
180    pub fn set_body<S: Into<String>>(mut self, body: Vec<S>) -> Self {
181        self.body = body.into_iter().map(Into::into).collect();
182        self
183    }
184
185    /// Map snippet body using a transformation function that gets mutable reference
186    pub fn map_body<F>(mut self, mut f: F) -> Self 
187    where 
188        F: FnMut(&mut Vec<String>)
189    {
190        f(&mut self.body);
191        self
192    }
193
194    /// Adds a single line to the snippet body
195    pub fn add_line<S: Into<String>>(mut self, line: S) -> Self {
196        self.body.push(line.into());
197        self
198    }
199
200    /// Adds multiple lines to the snippet body
201    pub fn add_lines<S: Into<String>>(mut self, lines: impl IntoIterator<Item = S>) -> Self {
202        self.body.extend(lines.into_iter().map(Into::into));
203        self
204    }
205
206    /// Edits a specific line in the snippet body
207    pub fn set_line<S: Into<String>>(mut self, n: usize, line: S) -> Result<Self> {
208        if n >= self.body.len() {
209            return Err(Error::IndexOutOfBounds(n));
210        }
211        
212        self.body[n] = line.into();
213        Ok(self)
214    }
215
216    /// Map specific line using a transformation function
217    pub fn map_line<F>(mut self, n: usize, mut f: F) -> Result<Self>
218    where 
219        F: FnMut(&mut String)
220    {
221        if n >= self.body.len() {
222            return Err(Error::IndexOutOfBounds(n));
223        }
224
225        f(&mut self.body[n]);
226        Ok(self)
227    }
228
229    /// Sets the description of the snippet
230    pub fn set_description<S: Into<String>>(mut self, description: S) -> Self {
231        self.description = Some(description.into());
232        self
233    }
234
235    /// Sets the scope of the snippet
236    pub fn set_scope<S: Into<String>>(mut self, scope: S) -> Self {
237        self.scope = Some(scope.into());
238        self
239    }
240
241    /// Sets whether this snippet is a file template
242    pub fn set_is_file_template(mut self, is_template: bool) -> Self {
243        self.is_file_template = Some(is_template);
244        self
245    }
246
247    /// Sets the priority of the snippet
248    pub fn set_priority(mut self, priority: u32) -> Self {
249        self.priority = Some(priority);
250        self
251    }
252}
253
254impl Default for SnippetBuilder {
255    fn default() -> Self {
256        Self {
257            name: Self::gen_name(),
258            prefix: String::new(),
259            body: vec![],
260            description: None,
261            scope: None,
262            is_file_template: None,
263            priority: None,
264        }
265    }
266}