1pub trait Synthesizer {
18 type Input;
19 type Ast;
20 type Output;
21
22 fn synthesize(&self, input: &Self::Input) -> Self::Ast;
23 fn render(&self, ast: &Self::Ast) -> Self::Output;
24
25 fn generate(&self, input: &Self::Input) -> Self::Output {
26 let ast = self.synthesize(input);
27 self.render(&ast)
28 }
29}
30
31pub trait MultiSynthesizer {
35 type Input: ?Sized;
36 fn generate_all(&self, input: &Self::Input) -> Vec<Artifact>;
37}
38
39#[derive(Debug, Clone, PartialEq, Eq)]
41pub struct Artifact {
42 pub path: String,
43 pub content: String,
44}
45
46impl Artifact {
47 pub fn new(path: impl Into<String>, content: impl Into<String>) -> Self {
48 Self {
49 path: path.into(),
50 content: content.into(),
51 }
52 }
53}
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58
59 struct Upper;
60 impl Synthesizer for Upper {
61 type Input = String;
62 type Ast = String;
63 type Output = String;
64 fn synthesize(&self, s: &String) -> String {
65 s.to_uppercase()
66 }
67 fn render(&self, ast: &String) -> String {
68 format!("// rendered\n{ast}")
69 }
70 }
71
72 #[test]
73 fn generate_composes_synthesize_then_render() {
74 let u = Upper;
75 assert_eq!(u.generate(&"hello".into()), "// rendered\nHELLO");
76 }
77
78 struct TwoFile;
79 impl MultiSynthesizer for TwoFile {
80 type Input = &'static str;
81 fn generate_all(&self, input: &&'static str) -> Vec<Artifact> {
82 vec![
83 Artifact::new("a.txt", input.to_string()),
84 Artifact::new("b.txt", input.chars().rev().collect::<String>()),
85 ]
86 }
87 }
88
89 #[test]
90 fn multi_synthesizer_emits_several_artifacts() {
91 let out = TwoFile.generate_all(&"hi");
92 assert_eq!(out.len(), 2);
93 assert_eq!(out[0].path, "a.txt");
94 assert_eq!(out[0].content, "hi");
95 assert_eq!(out[1].content, "ih");
96 }
97}