memscope_rs/cli/commands/
generate_report.rs1use clap::ArgMatches;
6use std::error::Error;
7
8pub fn run_generate_report(matches: &ArgMatches) -> Result<(), Box<dyn Error>> {
10 let input_file = matches
11 .get_one::<String>("input")
12 .ok_or("Input file is required")?;
13 let output_file = matches
14 .get_one::<String>("output")
15 .ok_or("Output file is required")?;
16 let format = matches
17 .get_one::<String>("format")
18 .map(|s| s.as_str())
19 .unwrap_or("html");
20
21 tracing::info!("📊 Generating report...");
22 tracing::info!("Input file: {}", input_file);
23 tracing::info!("Output file: {}", output_file);
24 tracing::info!("Format: {}", format);
25
26 match format {
27 "html" => {
28 let default_template = "report_template.html".to_string();
29 embed_json_to_html(input_file, &default_template, output_file)?;
30 }
31 _ => {
32 return Err(format!("Unsupported format: {format}").into());
33 }
34 }
35
36 Ok(())
37}
38
39fn _original_main() {
40 let args: Vec<String> = std::env::args().collect();
41
42 if args.len() < 2 {
43 _print_usage();
44 return;
45 }
46
47 match args[1].as_str() {
48 "template" => {
49 let default_output = "interactive_template.html".to_string();
50 let output = args.get(2).unwrap_or(&default_output);
51
52 if !std::path::Path::new("interactive_template.html").exists() {
54 tracing::error!("❌ Source template 'interactive_template.html' not found!");
55 tracing::error!("Please make sure the interactive_template.html file exists in the current directory.");
56 std::process::exit(1);
57 }
58
59 if let Err(e) = std::fs::copy("interactive_template.html", output) {
60 tracing::error!("❌ Error creating template: {e}");
61 std::process::exit(1);
62 }
63 tracing::info!("✅ Created interactive template: {output}");
64 }
65 "generate" => {
66 if args.len() < 4 {
67 tracing::error!(
68 "❌ Usage: generate_report generate <json_file> <output_file> [template_file]"
69 );
70 std::process::exit(1);
71 }
72
73 let json_file = &args[2];
74 let output_file = &args[3];
75 let default_template = "report_template.html".to_string();
76 let template_file = args.get(4).unwrap_or(&default_template);
77
78 if let Err(e) = embed_json_to_html(json_file, template_file, output_file) {
79 tracing::error!("❌ Error generating report: {e}");
80 std::process::exit(1);
81 }
82 }
83 _ => {
84 _print_usage();
85 }
86 }
87}
88
89fn embed_json_to_html(
90 json_file: &str,
91 template_file: &str,
92 output_file: &str,
93) -> Result<(), Box<dyn std::error::Error>> {
94 let json_content = std::fs::read_to_string(json_file)?;
95
96 let template_content = std::fs::read_to_string(template_file)?;
97 let inline_script = format!(
98 r#"<script type="text/javascript">
99// Embedded JSON data for offline analysis
100window.EMBEDDED_MEMORY_DATA = {json_content};
101console.log('📊 Loaded embedded memory analysis data');
102</script>"#
103 );
104
105 let final_html = template_content.replace("<!-- DATA_INJECTION_POINT -->", &inline_script);
106
107 std::fs::write(output_file, final_html)?;
108
109 tracing::info!("✅ Generated self-contained HTML report: {output_file}");
110 Ok(())
111}
112
113fn _print_usage() {
114 tracing::info!("🔍 Memory Analysis Report Generator");
115 tracing::info!("");
116 tracing::info!("Usage:");
117 tracing::info!(" generate_report template [output_file]");
118 tracing::info!(" Create a standalone HTML template");
119 tracing::info!("");
120 tracing::info!(" generate_report generate <json_file> <output_file> [template_file]");
121 tracing::info!(" Generate a self-contained HTML report from JSON data");
122 tracing::info!("");
123 tracing::info!("Examples:");
124 tracing::info!(" generate_report template report_template.html");
125 tracing::info!(" generate_report generate data.json report.html report_template.html");
126 tracing::info!("");
127 tracing::info!("The generated report.html is completely self-contained and can be:");
128 tracing::info!(" ✅ Opened directly in any browser (no server needed)");
129 tracing::info!(" ✅ Shared via email or file transfer");
130 tracing::info!(" ✅ Archived for historical analysis");
131 tracing::info!(" ✅ Viewed offline without any dependencies");
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137 use clap::{Arg, ArgMatches, Command as ClapCommand};
138 use std::fs;
139 use tempfile::TempDir;
140
141 fn create_test_matches(input: &str, output: &str, format: Option<&str>) -> ArgMatches {
142 let cmd = ClapCommand::new("test")
143 .arg(Arg::new("input"))
144 .arg(Arg::new("output"))
145 .arg(Arg::new("format").long("format"));
146
147 let mut args = vec!["test", input, output];
148 if let Some(fmt) = format {
149 args.extend_from_slice(&["--format", fmt]);
150 }
151
152 cmd.try_get_matches_from(args)
153 .expect("Failed to create test matches")
154 }
155
156 #[test]
157 fn test_argument_extraction() {
158 let matches = create_test_matches("input.json", "output.html", Some("html"));
160
161 let input_file = matches.get_one::<String>("input").unwrap();
162 let output_file = matches.get_one::<String>("output").unwrap();
163 let format = matches
164 .get_one::<String>("format")
165 .map(|s| s.as_str())
166 .unwrap_or("html");
167
168 assert_eq!(input_file, "input.json");
169 assert_eq!(output_file, "output.html");
170 assert_eq!(format, "html");
171 }
172
173 #[test]
174 fn test_default_format() {
175 let matches = create_test_matches("input.json", "output.html", None);
177
178 let format = matches
179 .get_one::<String>("format")
180 .map(|s| s.as_str())
181 .unwrap_or("html");
182
183 assert_eq!(format, "html");
184 }
185
186 #[test]
187 fn test_embed_json_to_html() {
188 let temp_dir = TempDir::new().expect("Failed to create temp directory");
190
191 let json_file = temp_dir.path().join("test_data.json");
193 let test_json = r#"{"memory_analysis": {"total_allocations": 100}}"#;
194 fs::write(&json_file, test_json).expect("Failed to write JSON file");
195
196 let template_file = temp_dir.path().join("template.html");
198 let template_content = r#"<!DOCTYPE html>
199<html>
200<head><title>Test Report</title></head>
201<body>
202 <h1>Memory Analysis Report</h1>
203 <!-- DATA_INJECTION_POINT -->
204 <div id="content"></div>
205</body>
206</html>"#;
207 fs::write(&template_file, template_content).expect("Failed to write template file");
208
209 let output_file = temp_dir.path().join("output.html");
211 let result = embed_json_to_html(
212 json_file.to_str().unwrap(),
213 template_file.to_str().unwrap(),
214 output_file.to_str().unwrap(),
215 );
216
217 assert!(result.is_ok());
218 assert!(output_file.exists());
219
220 let output_content = fs::read_to_string(&output_file).expect("Failed to read output file");
222 assert!(output_content.contains("window.EMBEDDED_MEMORY_DATA"));
223 assert!(output_content.contains("memory_analysis"));
224 assert!(output_content.contains("total_allocations"));
225 assert!(!output_content.contains("<!-- DATA_INJECTION_POINT -->"));
226 }
227
228 #[test]
229 fn test_run_generate_report_html_format() {
230 let temp_dir = TempDir::new().expect("Failed to create temp directory");
232
233 let json_file = temp_dir.path().join("input.json");
235 let template_file = temp_dir.path().join("report_template.html");
236 let output_file = temp_dir.path().join("output.html");
237
238 let test_json = r#"{"test": "data"}"#;
239 fs::write(&json_file, test_json).expect("Failed to write JSON file");
240
241 let template_content = r#"<html><body><!-- DATA_INJECTION_POINT --></body></html>"#;
242 fs::write(&template_file, template_content).expect("Failed to write template file");
243
244 let matches = create_test_matches(
245 json_file.to_str().unwrap(),
246 output_file.to_str().unwrap(),
247 Some("html"),
248 );
249
250 let original_dir = std::env::current_dir().expect("Failed to get current directory");
252 std::env::set_current_dir(&temp_dir).expect("Failed to change directory");
253
254 let result = run_generate_report(&matches);
255
256 std::env::set_current_dir(original_dir).expect("Failed to restore directory");
258
259 assert!(result.is_ok());
260 assert!(output_file.exists());
261 }
262
263 #[test]
264 fn test_run_generate_report_unsupported_format() {
265 let temp_dir = TempDir::new().expect("Failed to create temp directory");
267 let json_file = temp_dir.path().join("input.json");
268 let output_file = temp_dir.path().join("output.xml");
269
270 fs::write(&json_file, "{}").expect("Failed to write JSON file");
271
272 let matches = create_test_matches(
273 json_file.to_str().unwrap(),
274 output_file.to_str().unwrap(),
275 Some("xml"),
276 );
277
278 let result = run_generate_report(&matches);
279 assert!(result.is_err());
280 assert!(result
281 .unwrap_err()
282 .to_string()
283 .contains("Unsupported format: xml"));
284 }
285
286 #[test]
287 fn test_missing_arguments() {
288 let cmd = ClapCommand::new("test")
290 .arg(Arg::new("input"))
291 .arg(Arg::new("output"));
292
293 let matches = cmd.try_get_matches_from(vec!["test"]).unwrap();
294
295 let input_result = matches.get_one::<String>("input");
296 let output_result = matches.get_one::<String>("output");
297
298 assert!(input_result.is_none());
299 assert!(output_result.is_none());
300 }
301
302 #[test]
303 fn test_json_injection_script_generation() {
304 let test_json = r#"{"allocations": [{"id": 1}, {"id": 2}]}"#;
306
307 let inline_script = format!(
308 r#"<script type="text/javascript">
309// Embedded JSON data for offline analysis
310window.EMBEDDED_MEMORY_DATA = {test_json};
311console.log('📊 Loaded embedded memory analysis data');
312</script>"#
313 );
314
315 assert!(inline_script.contains("window.EMBEDDED_MEMORY_DATA"));
316 assert!(inline_script.contains("allocations"));
317 assert!(inline_script.contains("console.log"));
318 assert!(inline_script.contains("<script"));
319 assert!(inline_script.contains("</script>"));
320 }
321
322 #[test]
323 fn test_template_replacement() {
324 let template_content = r#"<html>
326<head><title>Report</title></head>
327<body>
328 <h1>Analysis</h1>
329 <!-- DATA_INJECTION_POINT -->
330 <footer>End</footer>
331</body>
332</html>"#;
333
334 let replacement_script = r#"<script>window.DATA = {};</script>"#;
335 let final_html =
336 template_content.replace("<!-- DATA_INJECTION_POINT -->", replacement_script);
337
338 assert!(!final_html.contains("<!-- DATA_INJECTION_POINT -->"));
339 assert!(final_html.contains("<script>window.DATA = {};</script>"));
340 assert!(final_html.contains("<title>Report</title>"));
341 assert!(final_html.contains("<footer>End</footer>"));
342 }
343
344 #[test]
345 fn test_file_operations() {
346 let temp_dir = TempDir::new().expect("Failed to create temp directory");
348
349 let test_file = temp_dir.path().join("test.txt");
351 let test_content = "Hello, World!";
352 let write_result = fs::write(&test_file, test_content);
353 assert!(write_result.is_ok());
354 assert!(test_file.exists());
355
356 let read_result = fs::read_to_string(&test_file);
358 assert!(read_result.is_ok());
359 assert_eq!(read_result.unwrap(), test_content);
360
361 let non_existent = temp_dir.path().join("non_existent.txt");
363 let read_error = fs::read_to_string(&non_existent);
364 assert!(read_error.is_err());
365 }
366
367 #[test]
368 fn test_format_validation() {
369 let supported_formats = ["html"];
371 let unsupported_formats = ["xml", "pdf", "docx", "txt"];
372
373 for format in supported_formats {
374 assert_eq!(format, "html");
375 }
376
377 for format in unsupported_formats {
378 assert_ne!(format, "html");
379 }
380 }
381
382 #[test]
383 fn test_complex_json_embedding() {
384 let temp_dir = TempDir::new().expect("Failed to create temp directory");
386
387 let complex_json = r#"{
388 "memory_analysis": {
389 "statistics": {
390 "total_allocations": 1000,
391 "active_allocations": 500,
392 "peak_memory": 1048576
393 },
394 "allocations": [
395 {"id": 1, "size": 1024, "type": "Vec<i32>"},
396 {"id": 2, "size": 2048, "type": "String"}
397 ]
398 }
399 }"#;
400
401 let json_file = temp_dir.path().join("complex.json");
402 let template_file = temp_dir.path().join("template.html");
403 let output_file = temp_dir.path().join("output.html");
404
405 fs::write(&json_file, complex_json).expect("Failed to write JSON");
406 fs::write(&template_file, "<html><!-- DATA_INJECTION_POINT --></html>")
407 .expect("Failed to write template");
408
409 let result = embed_json_to_html(
410 json_file.to_str().unwrap(),
411 template_file.to_str().unwrap(),
412 output_file.to_str().unwrap(),
413 );
414
415 assert!(result.is_ok());
416
417 let output_content = fs::read_to_string(&output_file).expect("Failed to read output");
418 assert!(output_content.contains("total_allocations"));
419 assert!(output_content.contains("1000"));
420 assert!(output_content.contains("Vec<i32>"));
421 }
422}