1use crate::templates::error::TemplateError;
4use std::collections::{HashMap, HashSet};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum CaseTransform {
9 PascalCase,
11 CamelCase,
13 SnakeCase,
15 KebabCase,
17 UpperCase,
19 LowerCase,
21}
22
23impl CaseTransform {
24 pub fn apply(&self, input: &str) -> String {
26 use heck::{ToKebabCase, ToLowerCamelCase, ToPascalCase, ToSnakeCase};
27
28 match self {
29 CaseTransform::PascalCase => input.to_pascal_case(),
30 CaseTransform::CamelCase => input.to_lower_camel_case(),
31 CaseTransform::SnakeCase => input.to_snake_case(),
32 CaseTransform::KebabCase => input.to_kebab_case(),
33 CaseTransform::UpperCase => input.to_uppercase(),
34 CaseTransform::LowerCase => input.to_lowercase(),
35 }
36 }
37}
38
39#[derive(Debug, Clone)]
41pub struct Placeholder {
42 pub name: String,
44 pub case_transform: CaseTransform,
46 pub required: bool,
48 pub default: Option<String>,
50}
51
52impl Placeholder {
53 pub fn new(name: String, case_transform: CaseTransform) -> Self {
55 Self {
56 name,
57 case_transform,
58 required: true,
59 default: None,
60 }
61 }
62
63 pub fn with_required(mut self, required: bool) -> Self {
65 self.required = required;
66 self
67 }
68
69 pub fn with_default(mut self, default: String) -> Self {
71 self.default = Some(default);
72 self.required = false;
73 self
74 }
75}
76
77pub struct PlaceholderResolver {
79 values: HashMap<String, String>,
81 required: HashSet<String>,
83 max_depth: usize,
85}
86
87impl PlaceholderResolver {
88 pub fn new() -> Self {
90 Self {
91 values: HashMap::new(),
92 required: HashSet::new(),
93 max_depth: 10,
94 }
95 }
96
97 pub fn with_max_depth(mut self, max_depth: usize) -> Self {
99 self.max_depth = max_depth;
100 self
101 }
102
103 pub fn add_value(&mut self, name: impl Into<String>, value: impl Into<String>) {
105 self.values.insert(name.into(), value.into());
106 }
107
108 pub fn add_values(&mut self, values: HashMap<String, String>) {
110 self.values.extend(values);
111 }
112
113 pub fn require(&mut self, name: impl Into<String>) {
115 self.required.insert(name.into());
116 }
117
118 pub fn resolve(
120 &self,
121 name: &str,
122 case_transform: CaseTransform,
123 ) -> Result<String, TemplateError> {
124 let value = self
125 .values
126 .get(name)
127 .ok_or_else(|| TemplateError::MissingPlaceholder(name.to_string()))?;
128
129 Ok(case_transform.apply(value))
130 }
131
132 pub fn resolve_with_default(
134 &self,
135 name: &str,
136 case_transform: CaseTransform,
137 default: Option<&str>,
138 ) -> Result<String, TemplateError> {
139 match self.values.get(name) {
140 Some(value) => Ok(case_transform.apply(value)),
141 None => {
142 if let Some(default_val) = default {
143 Ok(case_transform.apply(default_val))
144 } else {
145 Err(TemplateError::MissingPlaceholder(name.to_string()))
146 }
147 }
148 }
149 }
150
151 pub fn validate(&self) -> Result<(), TemplateError> {
153 for required_name in &self.required {
154 if !self.values.contains_key(required_name) {
155 return Err(TemplateError::MissingPlaceholder(required_name.clone()));
156 }
157 }
158 Ok(())
159 }
160
161 pub fn provided_names(&self) -> Vec<String> {
163 self.values.keys().cloned().collect()
164 }
165
166 pub fn has_value(&self, name: &str) -> bool {
168 self.values.contains_key(name)
169 }
170
171 pub fn extract_placeholder_names(&self, template: &str) -> Vec<String> {
174 let mut names = Vec::new();
175 let mut start = 0;
176
177 while let Some(pos) = template[start..].find("{{") {
178 let start_pos = start + pos;
179 if let Some(end_pos) = template[start_pos..].find("}}") {
180 let end_pos = start_pos + end_pos;
181 let content = &template[start_pos + 2..end_pos];
182
183 if let Ok((name, _)) = self.parse_placeholder_syntax(content) {
185 names.push(name);
186 }
187
188 start = end_pos + 2;
189 } else {
190 break;
191 }
192 }
193
194 names
195 }
196
197 pub fn resolve_nested(
200 &self,
201 name: &str,
202 case_transform: CaseTransform,
203 ) -> Result<String, TemplateError> {
204 self.resolve_nested_internal(name, case_transform, 0, &mut HashSet::new())
205 }
206
207 fn resolve_nested_internal(
209 &self,
210 name: &str,
211 case_transform: CaseTransform,
212 depth: usize,
213 visited: &mut HashSet<String>,
214 ) -> Result<String, TemplateError> {
215 if depth >= self.max_depth {
217 return Err(TemplateError::RenderError(format!(
218 "Maximum nesting depth ({}) exceeded for placeholder: {}",
219 self.max_depth, name
220 )));
221 }
222
223 if visited.contains(name) {
225 return Err(TemplateError::RenderError(format!(
226 "Circular reference detected for placeholder: {}",
227 name
228 )));
229 }
230
231 let value = self
232 .values
233 .get(name)
234 .ok_or_else(|| TemplateError::MissingPlaceholder(name.to_string()))?;
235
236 if value.contains("{{") && value.contains("}}") {
238 visited.insert(name.to_string());
239 let resolved = self.resolve_nested_value(value, depth + 1, visited)?;
240 visited.remove(name);
241 Ok(case_transform.apply(&resolved))
242 } else {
243 Ok(case_transform.apply(value))
244 }
245 }
246
247 fn resolve_nested_value(
249 &self,
250 value: &str,
251 depth: usize,
252 visited: &mut HashSet<String>,
253 ) -> Result<String, TemplateError> {
254 let mut result = value.to_string();
255 let mut changed = true;
256
257 while changed && depth <= self.max_depth {
259 changed = false;
260
261 if let Some(start) = result.find("{{") {
263 if let Some(end) = result[start..].find("}}") {
264 let end = start + end;
265 let placeholder_content = &result[start + 2..end];
266
267 let (placeholder_name, case_transform) =
269 self.parse_placeholder_syntax(placeholder_content)?;
270
271 let resolved = self.resolve_nested_internal(
273 &placeholder_name,
274 case_transform,
275 depth + 1,
276 visited,
277 )?;
278
279 result.replace_range(start..=end, &resolved);
281 changed = true;
282 }
283 }
284 }
285
286 Ok(result)
287 }
288
289 fn parse_placeholder_syntax(
292 &self,
293 content: &str,
294 ) -> Result<(String, CaseTransform), TemplateError> {
295 let content = content.trim();
296
297 if content.ends_with("_snake") {
299 let name = content.trim_end_matches("_snake").to_string();
300 Ok((name, CaseTransform::SnakeCase))
301 } else if content.ends_with("-kebab") {
302 let name = content.trim_end_matches("-kebab").to_string();
303 Ok((name, CaseTransform::KebabCase))
304 } else if content.ends_with("Camel") {
305 let name = content.trim_end_matches("Camel").to_string();
306 Ok((name, CaseTransform::CamelCase))
307 } else if content.chars().all(|c| c.is_uppercase() || c == '_') && content.len() > 1 {
308 Ok((content.to_string(), CaseTransform::UpperCase))
310 } else if content.chars().next().is_some_and(|c| c.is_uppercase()) {
311 Ok((content.to_string(), CaseTransform::PascalCase))
313 } else {
314 Ok((content.to_string(), CaseTransform::LowerCase))
316 }
317 }
318}
319
320impl Default for PlaceholderResolver {
321 fn default() -> Self {
322 Self::new()
323 }
324}
325
326#[cfg(test)]
327mod tests {
328 use super::*;
329
330 #[test]
331 fn test_case_transform_pascal_case() {
332 let result = CaseTransform::PascalCase.apply("my_project");
333 assert_eq!(result, "MyProject");
334 }
335
336 #[test]
337 fn test_case_transform_camel_case() {
338 let result = CaseTransform::CamelCase.apply("my_project");
339 assert_eq!(result, "myProject");
340 }
341
342 #[test]
343 fn test_case_transform_snake_case() {
344 let result = CaseTransform::SnakeCase.apply("MyProject");
345 assert_eq!(result, "my_project");
346 }
347
348 #[test]
349 fn test_case_transform_kebab_case() {
350 let result = CaseTransform::KebabCase.apply("MyProject");
351 assert_eq!(result, "my-project");
352 }
353
354 #[test]
355 fn test_case_transform_upper_case() {
356 let result = CaseTransform::UpperCase.apply("my_project");
357 assert_eq!(result, "MY_PROJECT");
358 }
359
360 #[test]
361 fn test_case_transform_lower_case() {
362 let result = CaseTransform::LowerCase.apply("MyProject");
363 assert_eq!(result, "myproject");
364 }
365
366 #[test]
367 fn test_placeholder_resolver_add_value() {
368 let mut resolver = PlaceholderResolver::new();
369 resolver.add_value("name", "my_project");
370 assert!(resolver.has_value("name"));
371 }
372
373 #[test]
374 fn test_placeholder_resolver_resolve() {
375 let mut resolver = PlaceholderResolver::new();
376 resolver.add_value("name", "my_project");
377 let result = resolver.resolve("name", CaseTransform::PascalCase);
378 assert_eq!(result.unwrap(), "MyProject");
379 }
380
381 #[test]
382 fn test_placeholder_resolver_missing_placeholder() {
383 let resolver = PlaceholderResolver::new();
384 let result = resolver.resolve("name", CaseTransform::PascalCase);
385 assert!(result.is_err());
386 }
387
388 #[test]
389 fn test_placeholder_resolver_validate_required() {
390 let mut resolver = PlaceholderResolver::new();
391 resolver.require("name");
392 let result = resolver.validate();
393 assert!(result.is_err());
394 }
395
396 #[test]
397 fn test_placeholder_resolver_validate_success() {
398 let mut resolver = PlaceholderResolver::new();
399 resolver.add_value("name", "my_project");
400 resolver.require("name");
401 let result = resolver.validate();
402 assert!(result.is_ok());
403 }
404
405 #[test]
406 fn test_placeholder_resolver_with_default() {
407 let resolver = PlaceholderResolver::new();
408 let result =
409 resolver.resolve_with_default("name", CaseTransform::PascalCase, Some("default_value"));
410 assert_eq!(result.unwrap(), "DefaultValue");
411 }
412
413 #[test]
414 fn test_placeholder_resolver_add_multiple_values() {
415 let mut resolver = PlaceholderResolver::new();
416 let mut values = HashMap::new();
417 values.insert("name".to_string(), "my_project".to_string());
418 values.insert("author".to_string(), "john_doe".to_string());
419 resolver.add_values(values);
420 assert!(resolver.has_value("name"));
421 assert!(resolver.has_value("author"));
422 }
423
424 #[test]
425 fn test_case_transform_with_numbers() {
426 let result = CaseTransform::PascalCase.apply("my_project_2");
427 assert_eq!(result, "MyProject2");
428 }
429
430 #[test]
431 fn test_case_transform_with_acronyms() {
432 let result = CaseTransform::SnakeCase.apply("HTTPServer");
433 assert_eq!(result, "http_server");
434 }
435
436 #[test]
437 fn test_case_transform_single_word() {
438 let result = CaseTransform::PascalCase.apply("project");
439 assert_eq!(result, "Project");
440 }
441
442 #[test]
443 fn test_case_transform_already_pascal() {
444 let result = CaseTransform::PascalCase.apply("MyProject");
445 assert_eq!(result, "MyProject");
446 }
447
448 #[test]
449 fn test_case_transform_kebab_to_pascal() {
450 let result = CaseTransform::PascalCase.apply("my-project");
451 assert_eq!(result, "MyProject");
452 }
453
454 #[test]
455 fn test_case_transform_empty_string() {
456 let result = CaseTransform::PascalCase.apply("");
457 assert_eq!(result, "");
458 }
459
460 #[test]
461 fn test_nested_placeholder_resolution() {
462 let mut resolver = PlaceholderResolver::new();
463 resolver.add_value("base", "my_project");
464 resolver.add_value("full_name", "{{base}}_extended");
465 let result = resolver.resolve_nested("full_name", CaseTransform::SnakeCase);
466 assert_eq!(result.unwrap(), "my_project_extended");
467 }
468
469 #[test]
470 fn test_nested_placeholder_with_case_transform() {
471 let mut resolver = PlaceholderResolver::new();
472 resolver.add_value("base", "my_project");
473 resolver.add_value("full_name", "{{base}}_extended");
474 let result = resolver.resolve_nested("full_name", CaseTransform::PascalCase);
475 assert_eq!(result.unwrap(), "MyProjectExtended");
476 }
477
478 #[test]
479 fn test_circular_reference_detection() {
480 let mut resolver = PlaceholderResolver::new();
481 resolver.add_value("a", "{{b}}");
482 resolver.add_value("b", "{{a}}");
483 let result = resolver.resolve_nested("a", CaseTransform::LowerCase);
484 assert!(result.is_err());
485 }
486
487 #[test]
488 fn test_self_reference_detection() {
489 let mut resolver = PlaceholderResolver::new();
490 resolver.add_value("a", "{{a}}");
491 let result = resolver.resolve_nested("a", CaseTransform::LowerCase);
492 assert!(result.is_err());
493 }
494
495 #[test]
496 fn test_max_depth_exceeded() {
497 let mut resolver = PlaceholderResolver::new().with_max_depth(2);
498 resolver.add_value("a", "{{b}}");
499 resolver.add_value("b", "{{c}}");
500 resolver.add_value("c", "{{d}}");
501 resolver.add_value("d", "value");
502 let result = resolver.resolve_nested("a", CaseTransform::LowerCase);
503 assert!(result.is_err());
504 }
505
506 #[test]
507 fn test_multiple_nested_placeholders() {
508 let mut resolver = PlaceholderResolver::new();
509 resolver.add_value("first", "hello");
510 resolver.add_value("second", "world");
511 resolver.add_value("combined", "{{first}}_{{second}}");
512 let result = resolver.resolve_nested("combined", CaseTransform::SnakeCase);
513 assert_eq!(result.unwrap(), "hello_world");
514 }
515
516 #[test]
517 fn test_parse_placeholder_syntax_pascal_case() {
518 let resolver = PlaceholderResolver::new();
519 let (name, transform) = resolver.parse_placeholder_syntax("Name").unwrap();
520 assert_eq!(name, "Name");
521 assert_eq!(transform, CaseTransform::PascalCase);
522 }
523
524 #[test]
525 fn test_parse_placeholder_syntax_snake_case() {
526 let resolver = PlaceholderResolver::new();
527 let (name, transform) = resolver.parse_placeholder_syntax("name_snake").unwrap();
528 assert_eq!(name, "name");
529 assert_eq!(transform, CaseTransform::SnakeCase);
530 }
531
532 #[test]
533 fn test_parse_placeholder_syntax_kebab_case() {
534 let resolver = PlaceholderResolver::new();
535 let (name, transform) = resolver.parse_placeholder_syntax("name-kebab").unwrap();
536 assert_eq!(name, "name");
537 assert_eq!(transform, CaseTransform::KebabCase);
538 }
539
540 #[test]
541 fn test_parse_placeholder_syntax_camel_case() {
542 let resolver = PlaceholderResolver::new();
543 let (name, transform) = resolver.parse_placeholder_syntax("nameCamel").unwrap();
544 assert_eq!(name, "name");
545 assert_eq!(transform, CaseTransform::CamelCase);
546 }
547
548 #[test]
549 fn test_parse_placeholder_syntax_uppercase() {
550 let resolver = PlaceholderResolver::new();
551 let (name, transform) = resolver.parse_placeholder_syntax("NAME").unwrap();
552 assert_eq!(name, "NAME");
553 assert_eq!(transform, CaseTransform::UpperCase);
554 }
555
556 #[test]
557 fn test_parse_placeholder_syntax_lowercase() {
558 let resolver = PlaceholderResolver::new();
559 let (name, transform) = resolver.parse_placeholder_syntax("name").unwrap();
560 assert_eq!(name, "name");
561 assert_eq!(transform, CaseTransform::LowerCase);
562 }
563}