use std::{fs, path::Path};
use minijinja::{context, Environment};
use crate::{error::Error, programs::SolanaProgram};
const LIB_TEMPLATE: &str = r#"
// This file is auto-generated by elf-magic
// DO NOT EDIT MANUALLY - Changes will be overwritten
{% for constant in constants -%}
/// ELF binary for the {{ constant.program_name }} Solana program
pub const {{ constant.constant_name }}: &[u8] = include_bytes!(env!("{{ constant.env_var }}"));
{% endfor -%}
/// Get all available Solana program ELF binaries
/// Returns a vector of (program_name, elf_bytes) tuples
pub fn elves() -> Vec<(&'static str, &'static [u8])> {
vec![
{%- for constant in constants %}
("{{ constant.program_name }}", {{ constant.constant_name }}),
{%- endfor %}
]
}
"#;
pub fn generate(programs: &[SolanaProgram]) -> Result<String, Error> {
let template_data = programs
.iter()
.map(|program| {
serde_json::json!({
"constant_name": program.constant_name(),
"env_var": program.env_var_name(),
"program_name": program.target_name.clone()
})
})
.collect::<Vec<_>>();
let mut env = Environment::new();
env.add_template("lib", LIB_TEMPLATE).map_err(|e| {
let msg = format!("Failed to add template: {}", e);
Error::CodeGeneration(msg)
})?;
let template = env.get_template("lib").map_err(|e| {
let msg = format!("Failed to get template: {}", e);
Error::CodeGeneration(msg)
})?;
let rendered_content = template
.render(context! {
constants => template_data,
})
.map_err(|e| {
let msg = format!("Failed to render template: {}", e);
Error::CodeGeneration(msg)
})?;
Ok(rendered_content)
}
pub fn save(manifest_dir: &Path, code: &str) -> Result<(), Error> {
let output_path = manifest_dir.join("src").join("lib.rs");
fs::write(&output_path, code).map_err(|e| {
let message = format!("Failed to write lib.rs: {}", e);
Error::CodeGeneration(message)
})
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
fn sample_programs() -> Vec<SolanaProgram> {
vec![
SolanaProgram {
package_name: "package1".to_string(),
target_name: "target1".to_string(),
manifest_path: PathBuf::from("/path/to/Cargo.toml"),
},
SolanaProgram {
package_name: "package2".to_string(),
target_name: "target2".to_string(),
manifest_path: PathBuf::from("/path/to/other/Cargo.toml"),
},
]
}
#[test]
fn test_generate_empty_programs() {
let result = generate(&[]).unwrap();
assert!(result.contains("pub fn elves() -> Vec<(&'static str, &'static [u8])> {"));
assert!(result.contains("vec![\n ]"));
assert!(result.contains("// This file is auto-generated by elf-magic"));
}
#[test]
fn test_generate_single_program() {
let programs = vec![SolanaProgram {
package_name: "my_package".to_string(),
target_name: "my_target".to_string(),
manifest_path: PathBuf::from("/path/to/Cargo.toml"),
}];
let result = generate(&programs).unwrap();
assert!(result.contains(
"pub const MY_TARGET_ELF: &[u8] = include_bytes!(env!(\"MY_TARGET_ELF_PATH\"));"
));
assert!(result.contains("(\"my_target\", MY_TARGET_ELF),"));
assert!(result.contains("/// ELF binary for the my_target Solana program"));
}
#[test]
fn test_generate_multiple_programs() {
let programs = sample_programs();
let result = generate(&programs).unwrap();
assert!(result.contains("pub const TARGET1_ELF"));
assert!(result.contains("pub const TARGET2_ELF"));
assert!(result.contains("env!(\"TARGET1_ELF_PATH\")"));
assert!(result.contains("env!(\"TARGET2_ELF_PATH\")"));
assert!(result.contains("(\"target1\", TARGET1_ELF),"));
assert!(result.contains("(\"target2\", TARGET2_ELF),"));
assert!(result.contains("/// ELF binary for the target1 Solana program"));
assert!(result.contains("/// ELF binary for the target2 Solana program"));
}
#[test]
fn test_generate_with_special_characters() {
let programs = vec![SolanaProgram {
package_name: "my-special-package".to_string(),
target_name: "my_target_name".to_string(),
manifest_path: PathBuf::from("/path/to/Cargo.toml"),
}];
let result = generate(&programs).unwrap();
assert!(result.contains("(\"my_target_name\", MY_TARGET_NAME_ELF),"));
assert!(result.contains("pub const MY_TARGET_NAME_ELF"));
assert!(result.contains("env!(\"MY_TARGET_NAME_ELF_PATH\")"));
}
#[test]
fn test_generated_code_is_valid_rust() {
let programs = sample_programs();
let result = generate(&programs).unwrap();
assert!(result.contains("pub const"));
assert!(result.contains("pub fn elves()"));
assert!(result.contains("vec!["));
assert!(!result.contains("{{")); assert!(!result.contains("}}"));
let lines: Vec<&str> = result.lines().collect();
assert!(lines.len() > 5);
assert!(result.contains("// This file is auto-generated by elf-magic"));
assert!(result.contains("// DO NOT EDIT MANUALLY"));
}
#[test]
fn test_template_render_error_handling() {
let programs = vec![];
let result = generate(&programs);
assert!(result.is_ok());
}
}