use crate::codegen_lib::{GeneratedFile, GenerationMode, Queryable, Renderable, Result};
use std::path::PathBuf;
#[derive(Debug)]
pub struct Rule<Q: Queryable, T: Renderable> {
name: String,
query: Q,
template: T,
output_file: PathBuf,
mode: GenerationMode,
}
impl<Q: Queryable, T: Renderable> Rule<Q, T> {
pub fn new(
name: impl Into<String>, query: Q, template: T, output_file: impl Into<PathBuf>,
mode: GenerationMode,
) -> Self {
Self {
name: name.into(),
query,
template,
output_file: output_file.into(),
mode,
}
}
pub fn execute(&self) -> Result<Vec<GeneratedFile>> {
let binding_sets = self.query.execute()?;
let files = binding_sets
.iter()
.map(|bindings| {
let content = self.template.render(bindings)?;
let path = self.output_file.to_string_lossy().to_string();
let rendered_path = bindings.iter().fold(path, |acc, (key, value)| {
acc.replace(&format!("{{{{{}}}}}", key), value)
});
Ok(GeneratedFile::new(
PathBuf::from(rendered_path),
content,
self.name.clone(),
))
})
.collect::<Result<Vec<_>>>()?;
Ok(files)
}
pub fn name(&self) -> &str {
&self.name
}
pub fn mode(&self) -> GenerationMode {
self.mode
}
pub fn output_pattern(&self) -> &PathBuf {
&self.output_file
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
struct MockQuery {
name: String,
}
impl Queryable for MockQuery {
fn execute(&self) -> Result<Vec<HashMap<String, String>>> {
let mut bindings = HashMap::new();
bindings.insert("className".to_string(), "TestClass".to_string());
Ok(vec![bindings])
}
fn name(&self) -> &str {
&self.name
}
}
struct MockTemplate {
name: String,
}
impl Renderable for MockTemplate {
fn render(&self, bindings: &HashMap<String, String>) -> Result<String> {
Ok(format!(
"public class {} {{\n}}\n",
bindings.get("className").unwrap_or(&"Unknown".to_string())
))
}
fn name(&self) -> &str {
&self.name
}
}
#[test]
fn test_rule_creation() {
let query = MockQuery {
name: "test-query".to_string(),
};
let template = MockTemplate {
name: "test-template".to_string(),
};
let rule = Rule::new(
"test-rule",
query,
template,
"generated/Test.java",
GenerationMode::Overwrite,
);
assert_eq!(rule.name(), "test-rule");
assert_eq!(rule.mode(), GenerationMode::Overwrite);
}
#[test]
fn test_generated_file_determinism() {
let content1 = "public class Test {}";
let file1 = GeneratedFile::new(
PathBuf::from("Test.java"),
content1.to_string(),
"rule1".to_string(),
);
let file2 = GeneratedFile::new(
PathBuf::from("Test.java"),
content1.to_string(),
"rule1".to_string(),
);
assert_eq!(file1.content_hash, file2.content_hash);
}
}