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}