1use crate::domain::{ElfMagicError, SolanaProgram};
2use minijinja::{context, Environment};
3
4const LIB_TEMPLATE: &str = r#"
6// This file is auto-generated by elf-magic
7// DO NOT EDIT MANUALLY - Changes will be overwritten
8// Generated at: {{ timestamp }}
9
10{% for constant in constants -%}
11/// ELF binary for the {{ constant.program_name }} Solana program
12pub const {{ constant.constant_name }}: &[u8] = include_bytes!(env!("{{ constant.env_var }}"));
13
14{% endfor -%}
15
16/// Get all available Solana program ELF binaries
17/// Returns a vector of (program_name, elf_bytes) tuples
18pub fn elves() -> Vec<(&'static str, &'static [u8])> {
19 vec![
20{%- for constant in constants %}
21 ("{{ constant.program_name }}", {{ constant.constant_name }}),
22{%- endfor %}
23 ]
24}
25"#;
26
27pub fn render_lib_file(programs: &[SolanaProgram]) -> Result<String, ElfMagicError> {
32 let template_data = programs
34 .iter()
35 .map(|program| {
36 serde_json::json!({
37 "constant_name": program.constant_name(),
38 "env_var": program.env_var_name(),
39 "program_name": program.name.clone()
40 })
41 })
42 .collect::<Vec<_>>();
43
44 let mut env = Environment::new();
46
47 env.add_template("lib", LIB_TEMPLATE)
48 .map_err(|e| ElfMagicError::CodeGeneration(format!("Failed to add template: {}", e)))?;
49
50 let template = env
51 .get_template("lib")
52 .map_err(|e| ElfMagicError::CodeGeneration(format!("Failed to get template: {}", e)))?;
53
54 let rendered_content = template
55 .render(context! {
56 constants => template_data,
57 timestamp => chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC").to_string()
58 })
59 .map_err(|e| ElfMagicError::CodeGeneration(format!("Failed to render template: {}", e)))?;
60
61 Ok(rendered_content)
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67 use std::path::PathBuf;
68
69 #[test]
70 fn test_render_lib_file() {
71 let programs = vec![
73 SolanaProgram {
74 name: "token_manager".to_string(),
75 path: PathBuf::from("programs/token-manager"),
76 manifest_path: PathBuf::from("programs/token-manager/Cargo.toml"),
77 },
78 SolanaProgram {
79 name: "governance".to_string(),
80 path: PathBuf::from("programs/governance"),
81 manifest_path: PathBuf::from("programs/governance/Cargo.toml"),
82 },
83 ];
84
85 let result = render_lib_file(&programs);
87 assert!(result.is_ok());
88
89 let rendered_content = result.unwrap();
90 assert!(rendered_content.contains("auto-generated by elf-magic"));
94 assert!(rendered_content.contains("TOKEN_MANAGER_ELF"));
95 assert!(rendered_content.contains("GOVERNANCE_ELF"));
96 assert!(rendered_content
97 .contains("include_bytes!(env!(\"PROGRAM_TOKEN_MANAGER_ELF_MAGIC_PATH\"))"));
98 assert!(rendered_content
99 .contains("include_bytes!(env!(\"PROGRAM_GOVERNANCE_ELF_MAGIC_PATH\"))"));
100 assert!(rendered_content.contains("pub fn elves()"));
101 assert!(rendered_content.contains("(\"token_manager\", TOKEN_MANAGER_ELF)"));
102 assert!(rendered_content.contains("(\"governance\", GOVERNANCE_ELF)"));
103 }
104
105 #[test]
106 fn test_render_lib_file_empty_constants() {
107 let programs: Vec<SolanaProgram> = Vec::new();
108
109 let result = render_lib_file(&programs);
110 assert!(result.is_ok());
111
112 let rendered_content = result.unwrap();
113
114 assert!(rendered_content.contains("auto-generated by elf-magic"));
116 assert!(rendered_content.contains("pub fn elves()"));
117 assert!(rendered_content.contains("vec!["));
118 assert!(rendered_content.contains("]"));
119
120 assert!(!rendered_content.contains("pub const"));
122 assert!(!rendered_content.contains("include_bytes!"));
123 }
124
125 #[test]
126 fn test_render_lib_file_no_template_artifacts() {
127 let programs = vec![SolanaProgram {
129 name: "test_program".to_string(),
130 path: PathBuf::from("programs/test-program"),
131 manifest_path: PathBuf::from("programs/test-program/Cargo.toml"),
132 }];
133
134 let result = render_lib_file(&programs);
135 assert!(result.is_ok());
136
137 let rendered_content = result.unwrap();
138
139 assert!(rendered_content.contains("pub const TEST_PROGRAM_ELF"));
141 assert!(rendered_content.contains("include_bytes!"));
142 assert!(rendered_content.contains("pub fn elves()"));
143
144 assert!(!rendered_content.contains("{{"));
146 assert!(!rendered_content.contains("}}"));
147 assert!(!rendered_content.contains("{%"));
148 assert!(!rendered_content.contains("%}"));
149 }
150}