ricecoder_generation/templates/
parser.rs1use crate::models::Placeholder;
7use crate::templates::error::TemplateError;
8use std::collections::HashSet;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum TemplateElement {
13 Text(String),
15 Placeholder(String),
17 Conditional {
19 condition: String,
21 content: Vec<TemplateElement>,
23 },
24 Loop {
26 variable: String,
28 content: Vec<TemplateElement>,
30 },
31 Include(String),
33}
34
35#[derive(Debug, Clone)]
37pub struct ParsedTemplate {
38 pub elements: Vec<TemplateElement>,
40 pub placeholders: Vec<Placeholder>,
42 pub placeholder_names: HashSet<String>,
44}
45
46pub struct TemplateParser;
48
49impl TemplateParser {
50 pub fn parse(content: &str) -> Result<ParsedTemplate, TemplateError> {
58 let mut parser = Parser::new(content);
59 parser.parse()
60 }
61
62 pub fn extract_placeholders(content: &str) -> Result<Vec<String>, TemplateError> {
70 let parsed = Self::parse(content)?;
71 Ok(parsed.placeholder_names.into_iter().collect())
72 }
73
74 pub fn has_conditionals(content: &str) -> Result<bool, TemplateError> {
76 Ok(content.contains("{{#if") && content.contains("{{/if}}"))
77 }
78
79 pub fn has_loops(content: &str) -> Result<bool, TemplateError> {
81 Ok(content.contains("{{#each") && content.contains("{{/each}}"))
82 }
83
84 pub fn has_includes(content: &str) -> Result<bool, TemplateError> {
86 Ok(content.contains("{{>"))
87 }
88}
89
90struct Parser {
92 content: String,
93 position: usize,
94 line: usize,
95 placeholder_names: HashSet<String>,
96}
97
98impl Parser {
99 fn new(content: &str) -> Self {
100 Self {
101 content: content.to_string(),
102 position: 0,
103 line: 1,
104 placeholder_names: HashSet::new(),
105 }
106 }
107
108 fn parse(&mut self) -> Result<ParsedTemplate, TemplateError> {
109 let elements = self.parse_elements()?;
110 let placeholders = self.extract_placeholder_definitions();
111
112 Ok(ParsedTemplate {
113 elements,
114 placeholders,
115 placeholder_names: self.placeholder_names.clone(),
116 })
117 }
118
119 fn parse_elements(&mut self) -> Result<Vec<TemplateElement>, TemplateError> {
120 let mut elements = Vec::new();
121
122 while self.position < self.content.len() {
123 if self.peek_char() == Some('{') && self.peek_ahead(1) == Some('{') {
124 let element = self.parse_template_syntax()?;
126 elements.push(element);
127 } else {
128 let text = self.parse_text();
130 if !text.is_empty() {
131 elements.push(TemplateElement::Text(text));
132 }
133 }
134 }
135
136 Ok(elements)
137 }
138
139 fn parse_template_syntax(&mut self) -> Result<TemplateElement, TemplateError> {
140 self.consume_char()?; self.consume_char()?; match self.peek_char() {
144 Some('#') => self.parse_block(),
145 Some('>') => self.parse_include(),
146 Some(_) => self.parse_placeholder(),
147 None => Err(TemplateError::InvalidSyntax {
148 line: self.line,
149 message: "Unexpected end of template".to_string(),
150 }),
151 }
152 }
153
154 fn parse_block(&mut self) -> Result<TemplateElement, TemplateError> {
155 self.consume_char()?; let block_type = self.read_until_whitespace_or_char('}')?;
158
159 match block_type.as_str() {
160 "if" => self.parse_conditional(),
161 "each" => self.parse_loop(),
162 _ => Err(TemplateError::InvalidSyntax {
163 line: self.line,
164 message: format!("Unknown block type: {}", block_type),
165 }),
166 }
167 }
168
169 fn parse_conditional(&mut self) -> Result<TemplateElement, TemplateError> {
170 self.skip_whitespace();
171 let condition = self.read_until_string("}}")?;
172 self.consume_string("}}")?;
173
174 let content = self.parse_until_end_block("if")?;
175
176 Ok(TemplateElement::Conditional { condition, content })
177 }
178
179 fn parse_loop(&mut self) -> Result<TemplateElement, TemplateError> {
180 self.skip_whitespace();
181 let variable = self.read_until_whitespace_or_char('}')?;
182 self.skip_whitespace();
183 self.consume_string("}}")?;
184
185 let content = self.parse_until_end_block("each")?;
186
187 Ok(TemplateElement::Loop { variable, content })
188 }
189
190 fn parse_include(&mut self) -> Result<TemplateElement, TemplateError> {
191 self.consume_char()?; self.skip_whitespace();
193
194 let partial_name = self.read_until_string("}}")?;
195 self.consume_string("}}")?;
196
197 Ok(TemplateElement::Include(partial_name))
198 }
199
200 fn parse_placeholder(&mut self) -> Result<TemplateElement, TemplateError> {
201 let placeholder_name = self.read_until_string("}}")?;
202 self.consume_string("}}")?;
203
204 let base_name = self.extract_base_name(&placeholder_name);
206 self.placeholder_names.insert(base_name);
207
208 Ok(TemplateElement::Placeholder(placeholder_name))
209 }
210
211 fn parse_text(&mut self) -> String {
212 let mut text = String::new();
213
214 while let Some(ch) = self.peek_char() {
215 if ch == '{' && self.peek_ahead(1) == Some('{') {
216 break;
217 }
218
219 if ch == '\n' {
220 self.line += 1;
221 }
222
223 text.push(ch);
224 self.position += 1;
225 }
226
227 text
228 }
229
230 fn parse_until_end_block(
231 &mut self,
232 block_type: &str,
233 ) -> Result<Vec<TemplateElement>, TemplateError> {
234 let mut elements = Vec::new();
235 let end_marker = format!("{{{{/{}}}}}", block_type);
236
237 while self.position < self.content.len() {
238 if self.content[self.position..].starts_with(&end_marker) {
239 self.position += end_marker.len();
240 return Ok(elements);
241 }
242
243 if self.peek_char() == Some('{') && self.peek_ahead(1) == Some('{') {
244 let element = self.parse_template_syntax()?;
245 elements.push(element);
246 } else {
247 let text = self.parse_text();
248 if !text.is_empty() {
249 elements.push(TemplateElement::Text(text));
250 }
251 }
252 }
253
254 Err(TemplateError::InvalidSyntax {
255 line: self.line,
256 message: format!("Unclosed {{{{#{}}}}}", block_type),
257 })
258 }
259
260 fn extract_base_name(&self, placeholder: &str) -> String {
261 let placeholder = placeholder.trim();
269
270 if let Some(pos) = placeholder.find('_') {
272 return placeholder[..pos].to_lowercase();
273 }
274
275 if let Some(pos) = placeholder.find('-') {
277 return placeholder[..pos].to_lowercase();
278 }
279
280 if placeholder
282 .chars()
283 .all(|c| c.is_uppercase() || !c.is_alphabetic())
284 {
285 return placeholder.to_lowercase();
286 }
287
288 let mut base = String::new();
291 let mut chars = placeholder.chars().peekable();
292
293 if let Some(first) = chars.next() {
295 base.push(first.to_lowercase().next().unwrap_or(first));
296 }
297
298 while let Some(&ch) = chars.peek() {
300 if ch.is_uppercase() {
301 break;
302 }
303 base.push(ch);
304 chars.next();
305 }
306
307 if base.is_empty() {
308 placeholder.to_lowercase()
309 } else {
310 base
311 }
312 }
313
314 fn peek_char(&self) -> Option<char> {
315 self.content.chars().nth(self.position)
316 }
317
318 fn peek_ahead(&self, offset: usize) -> Option<char> {
319 self.content.chars().nth(self.position + offset)
320 }
321
322 fn consume_char(&mut self) -> Result<char, TemplateError> {
323 match self.peek_char() {
324 Some(ch) => {
325 self.position += 1;
326 if ch == '\n' {
327 self.line += 1;
328 }
329 Ok(ch)
330 }
331 None => Err(TemplateError::InvalidSyntax {
332 line: self.line,
333 message: "Unexpected end of template".to_string(),
334 }),
335 }
336 }
337
338 fn consume_string(&mut self, s: &str) -> Result<(), TemplateError> {
339 for ch in s.chars() {
340 if self.consume_char()? != ch {
341 return Err(TemplateError::InvalidSyntax {
342 line: self.line,
343 message: format!("Expected '{}'", s),
344 });
345 }
346 }
347 Ok(())
348 }
349
350 fn read_until_string(&mut self, delimiter: &str) -> Result<String, TemplateError> {
351 let mut result = String::new();
352
353 while self.position < self.content.len() {
354 if self.content[self.position..].starts_with(delimiter) {
355 return Ok(result);
356 }
357
358 result.push(self.consume_char()?);
359 }
360
361 Err(TemplateError::InvalidSyntax {
362 line: self.line,
363 message: format!("Unterminated template element, expected '{}'", delimiter),
364 })
365 }
366
367 fn read_until_whitespace_or_char(&mut self, ch: char) -> Result<String, TemplateError> {
368 let mut result = String::new();
369
370 while let Some(c) = self.peek_char() {
371 if c.is_whitespace() || c == ch {
372 return Ok(result);
373 }
374 result.push(self.consume_char()?);
375 }
376
377 Ok(result)
378 }
379
380 fn skip_whitespace(&mut self) {
381 while let Some(ch) = self.peek_char() {
382 if ch.is_whitespace() {
383 if ch == '\n' {
384 self.line += 1;
385 }
386 self.position += 1;
387 } else {
388 break;
389 }
390 }
391 }
392
393 fn extract_placeholder_definitions(&self) -> Vec<Placeholder> {
394 self.placeholder_names
395 .iter()
396 .map(|name| Placeholder {
397 name: name.clone(),
398 description: format!("Placeholder: {}", name),
399 default: None,
400 required: true,
401 })
402 .collect()
403 }
404}
405
406#[cfg(test)]
407mod tests {
408 use super::*;
409
410 #[test]
411 fn test_parse_simple_placeholder() {
412 let content = "Hello {{name}}";
413 let result = TemplateParser::parse(content).unwrap();
414 assert_eq!(result.placeholder_names.len(), 1);
415 assert!(result.placeholder_names.contains("name"));
416 }
417
418 #[test]
419 fn test_parse_multiple_placeholders() {
420 let content = "{{Name}} is a {{type}}";
421 let result = TemplateParser::parse(content).unwrap();
422 assert_eq!(result.placeholder_names.len(), 2);
423 assert!(result.placeholder_names.contains("name"));
424 assert!(result.placeholder_names.contains("type"));
425 }
426
427 #[test]
428 fn test_parse_case_variations() {
429 let content = "{{Name}} {{name}} {{NAME}} {{name_snake}} {{name-kebab}} {{nameCamel}}";
430 let result = TemplateParser::parse(content).unwrap();
431 assert_eq!(result.placeholder_names.len(), 1);
432 assert!(result.placeholder_names.contains("name"));
433 }
434
435 #[test]
436 fn test_parse_conditional() {
437 let content = "{{#if condition}}content{{/if}}";
438 let result = TemplateParser::parse(content).unwrap();
439 assert!(!result.elements.is_empty());
440 }
441
442 #[test]
443 fn test_parse_loop() {
444 let content = "{{#each items}}{{name}}{{/each}}";
445 let result = TemplateParser::parse(content).unwrap();
446 assert!(result.placeholder_names.contains("name"));
447 }
448
449 #[test]
450 fn test_extract_placeholders() {
451 let content = "{{Name}} and {{description}}";
452 let placeholders = TemplateParser::extract_placeholders(content).unwrap();
453 assert_eq!(placeholders.len(), 2);
454 }
455
456 #[test]
457 fn test_has_conditionals() {
458 let content = "{{#if test}}yes{{/if}}";
459 assert!(TemplateParser::has_conditionals(content).unwrap());
460 }
461
462 #[test]
463 fn test_has_loops() {
464 let content = "{{#each items}}{{name}}{{/each}}";
465 assert!(TemplateParser::has_loops(content).unwrap());
466 }
467
468 #[test]
469 fn test_has_includes() {
470 let content = "{{> partial}}";
471 assert!(TemplateParser::has_includes(content).unwrap());
472 }
473
474 #[test]
475 fn test_unclosed_placeholder_error() {
476 let content = "Hello {{name";
477 let result = TemplateParser::parse(content);
478 assert!(result.is_err());
479 }
480
481 #[test]
482 fn test_unclosed_conditional_error() {
483 let content = "{{#if test}}content";
484 let result = TemplateParser::parse(content);
485 assert!(result.is_err());
486 }
487}