pub trait Synthesizer {
type Input;
type Ast;
type Output;
fn synthesize(&self, input: &Self::Input) -> Self::Ast;
fn render(&self, ast: &Self::Ast) -> Self::Output;
fn generate(&self, input: &Self::Input) -> Self::Output {
let ast = self.synthesize(input);
self.render(&ast)
}
}
pub trait MultiSynthesizer {
type Input: ?Sized;
fn generate_all(&self, input: &Self::Input) -> Vec<Artifact>;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Artifact {
pub path: String,
pub content: String,
}
impl Artifact {
pub fn new(path: impl Into<String>, content: impl Into<String>) -> Self {
Self {
path: path.into(),
content: content.into(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
struct Upper;
impl Synthesizer for Upper {
type Input = String;
type Ast = String;
type Output = String;
fn synthesize(&self, s: &String) -> String {
s.to_uppercase()
}
fn render(&self, ast: &String) -> String {
format!("// rendered\n{ast}")
}
}
#[test]
fn generate_composes_synthesize_then_render() {
let u = Upper;
assert_eq!(u.generate(&"hello".into()), "// rendered\nHELLO");
}
struct TwoFile;
impl MultiSynthesizer for TwoFile {
type Input = &'static str;
fn generate_all(&self, input: &&'static str) -> Vec<Artifact> {
vec![
Artifact::new("a.txt", input.to_string()),
Artifact::new("b.txt", input.chars().rev().collect::<String>()),
]
}
}
#[test]
fn multi_synthesizer_emits_several_artifacts() {
let out = TwoFile.generate_all(&"hi");
assert_eq!(out.len(), 2);
assert_eq!(out[0].path, "a.txt");
assert_eq!(out[0].content, "hi");
assert_eq!(out[1].content, "ih");
}
}