1use crate::ast::{Block, Document, Value};
6use std::collections::{BTreeMap, HashMap};
7
8#[derive(Debug, Clone)]
10pub struct GeneratorConfig {
11 pub indent: &'static str,
13 pub comments: bool,
15}
16
17impl Default for GeneratorConfig {
18 fn default() -> Self {
19 Self {
20 indent: " ",
21 comments: false,
22 }
23 }
24}
25
26pub struct Generator {
28 config: GeneratorConfig,
29}
30
31impl Generator {
32 pub fn new() -> Self {
33 Self {
34 config: GeneratorConfig::default(),
35 }
36 }
37
38 pub fn with_config(config: GeneratorConfig) -> Self {
39 Self { config }
40 }
41
42 pub fn generate(&self, doc: &Document) -> String {
44 let mut output = String::new();
45 self.write_document(doc, &mut output, 0);
46 output
47 }
48
49 fn write_document(&self, doc: &Document, out: &mut String, indent: usize) {
50 for (i, block) in doc.blocks.iter().enumerate() {
51 if i > 0 && !out.ends_with('\n') {
52 out.push('\n');
53 }
54 self.write_block(block, out, indent);
55 }
56 }
57
58 fn write_block(&self, block: &Block, out: &mut String, indent: usize) {
59 self.write_indent(out, indent);
60
61 if block.labels.is_empty() && block.blocks.is_empty() && block.attributes.len() == 1 {
65 if let Some((key, value)) = block.attributes.iter().next() {
66 if key != &block.name {
67 self.write_regular_block(block, out, indent);
68 return;
69 }
70 out.push_str(&block.name);
71 out.push_str(" = ");
72 self.write_value(value, out);
73 out.push('\n');
74 return;
75 }
76 }
77
78 self.write_regular_block(block, out, indent);
79 }
80
81 fn write_regular_block(&self, block: &Block, out: &mut String, indent: usize) {
82 out.push_str(&block.name);
84
85 for label in &block.labels {
86 out.push(' ');
87 self.write_string(label, out);
88 }
89
90 if block.blocks.is_empty() && block.attributes.is_empty() {
91 out.push_str(" { }");
92 return;
93 }
94
95 out.push_str(" {\n");
96
97 let mut attrs: Vec<_> = block.attributes.iter().collect();
99 attrs.sort_by(|a, b| a.0.cmp(b.0));
100
101 for (key, value) in attrs {
102 self.write_indent(out, indent + 1);
103 out.push_str(key);
104 out.push_str(" = ");
105 self.write_value(value, out);
106 out.push('\n');
107 }
108
109 for block in &block.blocks {
111 self.write_block(block, out, indent + 1);
112 out.push('\n');
113 }
114
115 self.write_indent(out, indent);
116 out.push('}');
117 }
118
119 fn write_value(&self, value: &Value, out: &mut String) {
120 match value {
121 Value::String(s) => self.write_string(s, out),
122 Value::Number(n) => {
123 let mut buffer = ryu_js::Buffer::new();
124 out.push_str(buffer.format(*n));
125 }
126 Value::Bool(b) => out.push_str(if *b { "true" } else { "false" }),
127 Value::Null => out.push_str("null"),
128 Value::List(items) => {
129 out.push('[');
130 for (i, item) in items.iter().enumerate() {
131 if i > 0 {
132 out.push_str(", ");
133 }
134 self.write_value(item, out);
135 }
136 out.push(']');
137 }
138 Value::Object(pairs) => {
139 let mut canonical_pairs = BTreeMap::new();
140 for (key, value) in pairs {
141 canonical_pairs.insert(key, value);
142 }
143 out.push('{');
144 for (i, (key, value)) in canonical_pairs.into_iter().enumerate() {
145 if i > 0 {
146 out.push_str(", ");
147 }
148 out.push_str(key);
149 out.push_str(" = ");
150 self.write_value(value, out);
151 }
152 out.push('}');
153 }
154 Value::Call(name, args) => {
155 out.push_str(name);
156 out.push('(');
157 for (i, arg) in args.iter().enumerate() {
158 if i > 0 {
159 out.push_str(", ");
160 }
161 self.write_value(arg, out);
162 }
163 out.push(')');
164 }
165 }
166 }
167
168 fn write_string(&self, s: &str, out: &mut String) {
169 out.push('"');
172 for c in s.chars() {
173 match c {
174 '"' => out.push_str("\\\""),
175 '\\' => out.push_str("\\\\"),
176 '\n' => out.push_str("\\n"),
177 '\r' => out.push_str("\\r"),
178 '\t' => out.push_str("\\t"),
179 _ => out.push(c),
180 }
181 }
182 out.push('"');
183 }
184
185 fn write_indent(&self, out: &mut String, indent: usize) {
186 for _ in 0..indent {
187 out.push_str(self.config.indent);
188 }
189 }
190
191 pub fn generate_from_map(&self, data: &HashMap<String, Value>) -> String {
193 let mut doc = Document::default();
194
195 for (key, value) in data {
197 let mut block = Block {
198 name: key.clone(),
199 labels: Vec::new(),
200 blocks: Vec::new(),
201 attributes: HashMap::new(),
202 };
203
204 if let Value::Object(pairs) = value {
205 for (k, v) in pairs {
206 block.attributes.insert(k.clone(), v.clone());
207 }
208 } else {
209 block.attributes.insert("_value".to_string(), value.clone());
210 }
211
212 doc.blocks.push(block);
213 }
214
215 self.generate(&doc)
216 }
217}
218
219impl Default for Generator {
220 fn default() -> Self {
221 Self::new()
222 }
223}
224
225pub fn generate(doc: &Document) -> String {
227 Generator::new().generate(doc)
228}
229
230pub fn generate_from_map(data: &HashMap<String, Value>) -> String {
232 Generator::new().generate_from_map(data)
233}
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238
239 #[test]
240 fn test_generate_simple() {
241 let mut attrs = HashMap::new();
242 attrs.insert("name".to_string(), Value::String("test".to_string()));
243
244 let block = Block {
245 name: "name".to_string(),
246 labels: Vec::new(),
247 blocks: Vec::new(),
248 attributes: attrs,
249 };
250
251 let doc = Document {
252 blocks: vec![block],
253 };
254
255 let output = generate(&doc);
256 assert!(output.contains("name = \"test\""));
257 }
258
259 #[test]
260 fn test_string_escaping() {
261 let gen = Generator::new();
262 let mut out = String::new();
263 gen.write_string("hello world", &mut out);
264 assert_eq!(out, "\"hello world\"");
265
266 out.clear();
267 gen.write_string("hello\"world", &mut out);
268 assert_eq!(out, "\"hello\\\"world\"");
269
270 out.clear();
272 let with_tab = "line\tbreak";
273 gen.write_string(with_tab, &mut out);
274 assert!(out.contains("line"));
276 assert!(out.contains("break"));
277
278 out.clear();
280 let with_backslash = "path\\file";
281 gen.write_string(with_backslash, &mut out);
282 assert!(out.contains("path"));
283 assert!(out.contains("file"));
284 }
285
286 #[test]
287 fn test_string_escape_carriage_return() {
288 let gen = Generator::new();
289 let mut out = String::new();
290 let with_cr = String::from("line") + &"\r" + "break";
292 assert!(with_cr.contains('\r')); gen.write_string(&with_cr, &mut out);
295 assert!(out.contains("\\r"), "Expected \\r escape, got: {:?}", out);
297 }
298
299 #[test]
300 fn test_string_escape_newline() {
301 let gen = Generator::new();
302 let mut out = String::new();
303 let with_nl = String::from("line") + &"\n" + "break";
304 assert!(with_nl.contains('\n'));
305
306 gen.write_string(&with_nl, &mut out);
307 assert!(out.contains("\\n"), "Expected \\n escape, got: {:?}", out);
308 }
309
310 #[test]
311 fn test_string_escape_tab() {
312 let gen = Generator::new();
313 let mut out = String::new();
314 let with_tab = String::from("line") + &"\t" + "break";
315 assert!(with_tab.contains('\t'));
316
317 gen.write_string(&with_tab, &mut out);
318 assert!(out.contains("\\t"), "Expected \\t escape, got: {:?}", out);
319 }
320
321 #[test]
322 fn test_string_escape_backslash() {
323 let gen = Generator::new();
324 let mut out = String::new();
325 let with_backslash = String::from("path") + &"\\" + "file";
327 assert!(with_backslash.contains('\\'));
328
329 gen.write_string(&with_backslash, &mut out);
330 assert!(
332 out.contains("\\\\"),
333 "Expected \\\\ backslash escape, got: {:?}",
334 out
335 );
336 }
337
338 #[test]
339 fn test_generate_block_with_object_value() {
340 let mut attrs = HashMap::new();
341 attrs.insert(
342 "obj".to_string(),
343 Value::Object(vec![("key".to_string(), Value::String("val".to_string()))]),
344 );
345
346 let block = Block {
347 name: "config".to_string(),
348 labels: vec![],
349 blocks: vec![],
350 attributes: attrs,
351 };
352
353 let doc = Document {
354 blocks: vec![block],
355 };
356 let output = generate(&doc);
357 assert!(output.contains("obj = {key = \"val\"}"));
358 }
359
360 #[test]
361 fn test_generate_nested_block_attrs_and_blocks() {
362 let inner = Block {
363 name: "inner".to_string(),
364 labels: vec![],
365 blocks: vec![],
366 attributes: vec![("key".to_string(), Value::String("val".to_string()))]
367 .into_iter()
368 .collect(),
369 };
370
371 let block = Block {
372 name: "outer".to_string(),
373 labels: vec![],
374 blocks: vec![inner],
375 attributes: vec![("attr".to_string(), Value::Number(1.0))]
376 .into_iter()
377 .collect(),
378 };
379
380 let doc = Document {
381 blocks: vec![block],
382 };
383 let output = generate(&doc);
384 assert!(output.contains("outer"));
385 assert!(output.contains("inner"));
386 assert!(output.contains("attr"));
387 }
388
389 #[test]
390 fn test_generate_block_with_braces() {
391 let mut attrs = HashMap::new();
392 attrs.insert("name".to_string(), Value::String("test".to_string()));
393 attrs.insert("count".to_string(), Value::Number(42.0));
394
395 let block = Block {
396 name: "config".to_string(),
397 labels: Vec::new(),
398 blocks: Vec::new(),
399 attributes: attrs,
400 };
401
402 let doc = Document {
403 blocks: vec![block],
404 };
405 let output = generate(&doc);
406 assert!(output.contains("config {"));
407 assert!(output.contains("name = \"test\""));
408 assert!(output.contains("count = 42"));
409 }
410
411 #[test]
412 fn test_generate_block_with_labels() {
413 let mut attrs = HashMap::new();
414 attrs.insert("api_key".to_string(), Value::String("sk-xxx".to_string()));
415
416 let block = Block {
417 name: "provider".to_string(),
418 labels: vec!["openai".to_string()],
419 blocks: Vec::new(),
420 attributes: attrs,
421 };
422
423 let doc = Document {
424 blocks: vec![block],
425 };
426 let output = generate(&doc);
427 assert!(output.contains("provider \"openai\""));
428 assert!(output.contains("api_key = \"sk-xxx\""));
429 }
430
431 #[test]
432 fn test_generate_nested_blocks() {
433 let inner_block = Block {
434 name: "inner".to_string(),
435 labels: vec![],
436 blocks: Vec::new(),
437 attributes: vec![("key".to_string(), Value::String("val".to_string()))]
438 .into_iter()
439 .collect(),
440 };
441
442 let block = Block {
443 name: "outer".to_string(),
444 labels: vec![],
445 blocks: vec![inner_block],
446 attributes: HashMap::new(),
447 };
448
449 let doc = Document {
450 blocks: vec![block],
451 };
452 let output = generate(&doc);
453 assert!(output.contains("outer"));
454 assert!(output.contains("inner"));
455 }
456
457 #[test]
458 fn test_generate_boolean_values() {
459 let mut attrs = HashMap::new();
460 attrs.insert("enabled".to_string(), Value::Bool(true));
461 attrs.insert("disabled".to_string(), Value::Bool(false));
462
463 let block = Block {
464 name: "config".to_string(),
465 labels: vec![],
466 blocks: Vec::new(),
467 attributes: attrs,
468 };
469
470 let doc = Document {
471 blocks: vec![block],
472 };
473 let output = generate(&doc);
474 assert!(output.contains("enabled = true"));
475 assert!(output.contains("disabled = false"));
476 }
477
478 #[test]
479 fn test_generate_null_value() {
480 let mut attrs = HashMap::new();
481 attrs.insert("value".to_string(), Value::Null);
482
483 let block = Block {
484 name: "config".to_string(),
485 labels: vec![],
486 blocks: Vec::new(),
487 attributes: attrs,
488 };
489
490 let doc = Document {
491 blocks: vec![block],
492 };
493 let output = generate(&doc);
494 assert!(output.contains("value = null"));
495 }
496
497 #[test]
498 fn test_generate_list_values() {
499 let mut attrs = HashMap::new();
500 attrs.insert(
501 "items".to_string(),
502 Value::List(vec![
503 Value::Number(1.0),
504 Value::Number(2.0),
505 Value::Number(3.0),
506 ]),
507 );
508
509 let block = Block {
510 name: "config".to_string(),
511 labels: vec![],
512 blocks: Vec::new(),
513 attributes: attrs,
514 };
515
516 let doc = Document {
517 blocks: vec![block],
518 };
519 let output = generate(&doc);
520 assert!(output.contains("items = [1, 2, 3]"));
521 }
522
523 #[test]
524 fn test_generate_string_list() {
525 let mut attrs = HashMap::new();
526 attrs.insert(
527 "names".to_string(),
528 Value::List(vec![
529 Value::String("a".to_string()),
530 Value::String("b".to_string()),
531 ]),
532 );
533
534 let block = Block {
535 name: "config".to_string(),
536 labels: vec![],
537 blocks: Vec::new(),
538 attributes: attrs,
539 };
540
541 let doc = Document {
542 blocks: vec![block],
543 };
544 let output = generate(&doc);
545 assert!(output.contains("names"));
546 assert!(output.contains("a"));
547 assert!(output.contains("b"));
548 }
549
550 #[test]
551 fn test_generate_empty_list() {
552 let mut attrs = HashMap::new();
553 attrs.insert("items".to_string(), Value::List(vec![]));
554
555 let block = Block {
556 name: "config".to_string(),
557 labels: vec![],
558 blocks: Vec::new(),
559 attributes: attrs,
560 };
561
562 let doc = Document {
563 blocks: vec![block],
564 };
565 let output = generate(&doc);
566 assert!(output.contains("items = []"));
567 }
568
569 #[test]
570 fn test_generate_object_value() {
571 let mut attrs = HashMap::new();
572 attrs.insert(
573 "obj".to_string(),
574 Value::Object(vec![
575 ("key1".to_string(), Value::String("val1".to_string())),
576 ("key2".to_string(), Value::Number(42.0)),
577 ]),
578 );
579
580 let block = Block {
581 name: "config".to_string(),
582 labels: vec![],
583 blocks: Vec::new(),
584 attributes: attrs,
585 };
586
587 let doc = Document {
588 blocks: vec![block],
589 };
590 let output = generate(&doc);
591 assert!(output.contains("obj"));
592 assert!(output.contains("key1"));
593 assert!(output.contains("key2"));
594 }
595
596 #[test]
597 fn test_generate_multiple_blocks() {
598 let block1 = Block {
599 name: "a".to_string(),
600 labels: vec![],
601 blocks: Vec::new(),
602 attributes: vec![("x".to_string(), Value::Number(1.0))]
603 .into_iter()
604 .collect(),
605 };
606 let block2 = Block {
607 name: "b".to_string(),
608 labels: vec![],
609 blocks: Vec::new(),
610 attributes: vec![("y".to_string(), Value::Number(2.0))]
611 .into_iter()
612 .collect(),
613 };
614
615 let doc = Document {
616 blocks: vec![block1, block2],
617 };
618 let output = generate(&doc);
619 assert!(output.contains("a {"));
620 assert!(output.contains("b {"));
621 }
622
623 #[test]
624 fn test_generate_from_map() {
625 let mut data = HashMap::new();
626 data.insert(
627 "key".to_string(),
628 Value::Object(
629 vec![("nested".to_string(), Value::String("value".to_string()))]
630 .into_iter()
631 .collect(),
632 ),
633 );
634
635 let output = generate_from_map(&data);
636 assert!(output.contains("key"));
637 assert!(output.contains("value"));
638 }
639
640 #[test]
641 fn test_generate_from_map_non_object() {
642 let mut data = HashMap::new();
643 data.insert("string_key".to_string(), Value::String("test".to_string()));
645 data.insert("number_key".to_string(), Value::Number(42.0));
646
647 let output = generate_from_map(&data);
648 assert!(output.contains("string_key"));
649 assert!(output.contains("_value"));
650 }
651
652 #[test]
653 fn test_generator_with_config() {
654 let config = GeneratorConfig {
655 indent: "\t",
656 comments: true,
657 };
658 let gen = Generator::with_config(config);
659 assert_eq!(gen.config.indent, "\t");
660 assert!(gen.config.comments);
661 }
662
663 #[test]
664 fn test_generator_default() {
665 let gen = Generator::default();
666 assert_eq!(gen.config.indent, " ");
667 assert!(!gen.config.comments);
668 }
669
670 #[test]
671 fn test_generate_string_with_special_chars() {
672 let gen = Generator::new();
673 let mut out = String::new();
674 gen.write_string("true", &mut out);
675 assert_eq!(out, "\"true\"");
676
677 out.clear();
678 gen.write_string("false", &mut out);
679 assert_eq!(out, "\"false\"");
680
681 out.clear();
682 gen.write_string("null", &mut out);
683 assert_eq!(out, "\"null\"");
684
685 out.clear();
686 gen.write_string("123", &mut out);
687 assert_eq!(out, "\"123\"");
688
689 out.clear();
690 gen.write_string(".hello", &mut out);
691 assert_eq!(out, "\".hello\"");
692
693 out.clear();
694 gen.write_string("-test", &mut out);
695 assert_eq!(out, "\"-test\"");
696 }
697
698 #[test]
699 fn test_generate_empty_block() {
700 let block = Block {
701 name: "empty".to_string(),
702 labels: vec![],
703 blocks: Vec::new(),
704 attributes: HashMap::new(),
705 };
706
707 let doc = Document {
708 blocks: vec![block],
709 };
710 let output = generate(&doc);
711 assert!(output.contains("empty"));
712 }
713
714 #[test]
715 fn test_generate_block_sorted_attrs() {
716 let mut attrs = HashMap::new();
717 attrs.insert("z".to_string(), Value::Number(1.0));
718 attrs.insert("a".to_string(), Value::Number(2.0));
719 attrs.insert("m".to_string(), Value::Number(3.0));
720
721 let block = Block {
722 name: "config".to_string(),
723 labels: vec![],
724 blocks: Vec::new(),
725 attributes: attrs,
726 };
727
728 let doc = Document {
729 blocks: vec![block],
730 };
731 let output = generate(&doc);
732 let a_pos = output.find("a = 2").expect("should have a = 2");
734 let m_pos = output.find("m = 3").expect("should have m = 3");
735 let z_pos = output.find("z = 1").expect("should have z = 1");
736 assert!(a_pos < m_pos);
737 assert!(m_pos < z_pos);
738 }
739
740 #[test]
741 fn test_generate_float_numbers() {
742 let mut attrs = HashMap::new();
743 attrs.insert("pi".to_string(), Value::Number(3.14159));
744 attrs.insert("e".to_string(), Value::Number(2.71828));
745
746 let block = Block {
747 name: "config".to_string(),
748 labels: vec![],
749 blocks: Vec::new(),
750 attributes: attrs,
751 };
752
753 let doc = Document {
754 blocks: vec![block],
755 };
756 let output = generate(&doc);
757 assert!(output.contains("pi = 3.14159"));
758 assert!(output.contains("e = 2.71828"));
759 }
760
761 #[test]
762 fn test_generate_integer_as_integer() {
763 let mut attrs = HashMap::new();
764 attrs.insert("count".to_string(), Value::Number(42.0));
765
766 let block = Block {
767 name: "config".to_string(),
768 labels: vec![],
769 blocks: Vec::new(),
770 attributes: attrs,
771 };
772
773 let doc = Document {
774 blocks: vec![block],
775 };
776 let output = generate(&doc);
777 assert!(output.contains("count = 42"));
779 assert!(!output.contains("count = 42.0"));
780 }
781
782 #[test]
783 fn test_generate_function_call() {
784 let mut attrs = HashMap::new();
785 attrs.insert(
786 "api_key".to_string(),
787 Value::Call(
788 "env".to_string(),
789 vec![Value::String("API_KEY".to_string())],
790 ),
791 );
792
793 let block = Block {
794 name: "config".to_string(),
795 labels: vec![],
796 blocks: Vec::new(),
797 attributes: attrs,
798 };
799
800 let doc = Document {
801 blocks: vec![block],
802 };
803 let output = generate(&doc);
804 assert!(output.contains("api_key = env(\"API_KEY\")"));
806 }
807
808 #[test]
809 fn test_generate_function_call_multiple_args() {
810 let mut attrs = HashMap::new();
811 attrs.insert(
812 "path".to_string(),
813 Value::Call(
814 "concat".to_string(),
815 vec![
816 Value::String("hello".to_string()),
817 Value::String("world".to_string()),
818 ],
819 ),
820 );
821
822 let block = Block {
823 name: "config".to_string(),
824 labels: vec![],
825 blocks: Vec::new(),
826 attributes: attrs,
827 };
828
829 let doc = Document {
830 blocks: vec![block],
831 };
832 let output = generate(&doc);
833 assert!(output.contains("path = concat(\"hello\", \"world\")"));
835 }
836
837 #[test]
838 fn test_generate_function_call_empty_args() {
839 let mut attrs = HashMap::new();
840 attrs.insert("val".to_string(), Value::Call("getenv".to_string(), vec![]));
841
842 let block = Block {
843 name: "config".to_string(),
844 labels: vec![],
845 blocks: Vec::new(),
846 attributes: attrs,
847 };
848
849 let doc = Document {
850 blocks: vec![block],
851 };
852 let output = generate(&doc);
853 assert!(output.contains("val = getenv()"));
854 }
855}