use crate::error::{Result, TronError};
use crate::template::TronTemplate;
#[derive(Debug, Clone)]
pub struct TronRef {
template: TronTemplate,
dependencies: Vec<String>,
}
impl TronRef {
pub fn new(template: TronTemplate) -> Self {
Self {
template,
dependencies: Vec::new(),
}
}
pub fn with_dependency(mut self, dependency: &str) -> Self {
self.dependencies.push(dependency.to_string());
self
}
pub fn with_dependencies<I, S>(mut self, dependencies: I) -> Self
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
for dep in dependencies {
self.dependencies.push(dep.as_ref().to_string());
}
self
}
pub fn add_dependency(&mut self, dependency: &str) {
self.dependencies.push(dependency.to_string());
}
pub fn dependencies(&self) -> &[String] {
&self.dependencies
}
pub fn clear_dependencies(&mut self) {
self.dependencies.clear();
}
pub fn inner(&self) -> &TronTemplate {
&self.template
}
pub fn inner_mut(&mut self) -> &mut TronTemplate {
&mut self.template
}
pub fn set(&mut self, placeholder: &str, value: &str) -> Result<()> {
self.template.set(placeholder, value)
}
pub fn set_ref(&mut self, placeholder: &str, template_ref: TronRef) -> Result<()> {
let rendered = template_ref.template.render()?;
self.template.set(placeholder, &rendered)?;
self.dependencies.extend(template_ref.dependencies);
Ok(())
}
#[cfg(feature = "execute")]
pub async fn execute(&self) -> Result<String> {
use std::process::Command;
use tempfile::NamedTempFile;
use std::io::Write;
use which::which;
which("rust-script").map_err(|_| {
TronError::ExecutionError("rust-script not found. Install with: cargo install rust-script".into())
})?;
let rendered = self.template.render()?;
let mut temp_file = NamedTempFile::new()
.map_err(|e| TronError::ExecutionError(format!("Failed to create temp file: {}", e)))?;
let mut script_content = String::new();
if !self.dependencies.is_empty() {
script_content.push_str("//! ```cargo\n//! [dependencies]\n");
for dep in &self.dependencies {
script_content.push_str(&format!("//! {}\n", dep));
}
script_content.push_str("//! ```\n\n");
}
script_content.push_str(&rendered);
temp_file.write_all(script_content.as_bytes())
.map_err(|e| TronError::ExecutionError(format!("Failed to write temp file: {}", e)))?;
let output = Command::new("rust-script")
.arg(temp_file.path())
.output()
.map_err(|e| TronError::ExecutionError(format!("Failed to execute script: {}", e)))?;
if !output.status.success() {
return Err(TronError::ExecutionError(
String::from_utf8_lossy(&output.stderr).into_owned()
));
}
Ok(String::from_utf8_lossy(&output.stdout).into_owned())
}
pub fn render(&self) -> Result<String> {
self.template.render()
}
pub fn render_partial(&self) -> Result<String> {
self.template.render_partial()
}
}
impl From<TronTemplate> for TronRef {
fn from(template: TronTemplate) -> Self {
Self::new(template)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::template::TronTemplate;
#[test]
fn test_new_template_ref() -> Result<()> {
let template = TronTemplate::new("Hello @[name]@!")?;
let template_ref = TronRef::new(template);
assert!(template_ref.dependencies().is_empty());
assert!(template_ref.inner().has_placeholder("name"));
Ok(())
}
#[test]
fn test_with_dependency() -> Result<()> {
let template = TronTemplate::new("fn main() {}")?;
let template_ref = TronRef::new(template)
.with_dependency("serde = \"1.0\"");
assert_eq!(template_ref.dependencies(), &["serde = \"1.0\""]);
Ok(())
}
#[test]
fn test_with_dependencies() -> Result<()> {
let template = TronTemplate::new("fn main() {}")?;
let template_ref = TronRef::new(template)
.with_dependencies(&[
"serde = \"1.0\"",
"tokio = \"1.0\"",
]);
assert_eq!(template_ref.dependencies().len(), 2);
assert!(template_ref.dependencies().contains(&"serde = \"1.0\"".to_string()));
assert!(template_ref.dependencies().contains(&"tokio = \"1.0\"".to_string()));
Ok(())
}
#[test]
fn test_add_dependency() -> Result<()> {
let template = TronTemplate::new("fn main() {}")?;
let mut template_ref = TronRef::new(template);
template_ref.add_dependency("serde = \"1.0\"");
assert_eq!(template_ref.dependencies(), &["serde = \"1.0\""]);
Ok(())
}
#[test]
fn test_clear_dependencies() -> Result<()> {
let template = TronTemplate::new("fn main() {}")?;
let mut template_ref = TronRef::new(template)
.with_dependency("serde = \"1.0\"");
template_ref.clear_dependencies();
assert!(template_ref.dependencies().is_empty());
Ok(())
}
#[test]
fn test_set_and_render() -> Result<()> {
let template = TronTemplate::new("Hello @[name]@!")?;
let mut template_ref = TronRef::new(template);
template_ref.set("name", "World")?;
let result = template_ref.render()?;
assert_eq!(result, "Hello World!");
Ok(())
}
#[test]
fn test_template_composition() -> Result<()> {
let outer = TronTemplate::new("mod @[name]@ {\n @[content]@\n}")?;
let mut outer_ref = TronRef::new(outer);
let inner = TronTemplate::new("fn @[func_name]@() {\n @[body]@\n}")?;
let mut inner_ref = TronRef::new(inner)
.with_dependency("serde = \"1.0\"");
inner_ref.set("func_name", "example")?;
inner_ref.set("body", "println!(\"Hello!\");")?;
outer_ref.set("name", "generated")?;
outer_ref.set_ref("content", inner_ref)?;
let result = outer_ref.render()?;
assert!(result.contains("mod generated"));
assert!(result.contains("fn example()"));
assert!(result.contains("println!(\"Hello!\");"));
assert!(outer_ref.dependencies().contains(&"serde = \"1.0\"".to_string()));
Ok(())
}
#[test]
fn test_from_template() -> Result<()> {
let template = TronTemplate::new("Hello @[name]@!")?;
let template_ref: TronRef = template.into();
assert!(template_ref.inner().has_placeholder("name"));
Ok(())
}
#[test]
fn test_render_partial() -> Result<()> {
let template = TronTemplate::new("@[greeting]@ @[name]@!")?;
let mut template_ref = TronRef::new(template);
template_ref.set("greeting", "Hello")?;
let result = template_ref.render_partial()?;
assert_eq!(result, "Hello @[name]@!");
Ok(())
}
}