1use anyhow::{Context as _, Result};
2use clap::Parser;
3use serde_json::{Value, json};
4use std::fs;
5use std::path::PathBuf;
6
7#[derive(Parser, Debug)]
8#[clap(about = "Generate HTML explorer from JSON thread files")]
9struct Args {
10 /// Paths to JSON files containing thread data
11 #[clap(long, required = true, num_args = 1..)]
12 input: Vec<PathBuf>,
13
14 /// Path where the HTML explorer file will be written
15 #[clap(long)]
16 output: PathBuf,
17}
18
19pub fn generate_explorer_html(inputs: &[PathBuf], output: &PathBuf) -> Result<String> {
20 if let Some(parent) = output.parent() {
21 if !parent.exists() {
22 fs::create_dir_all(parent).context(format!(
23 "Failed to create output directory: {}",
24 parent.display()
25 ))?;
26 }
27 }
28
29 let template_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src/explorer.html");
30 let template = fs::read_to_string(&template_path).context(format!(
31 "Template file not found or couldn't be read: {}",
32 template_path.display()
33 ))?;
34
35 let threads = inputs
36 .iter()
37 .map(|input_path| {
38 let mut thread_data: Value = fs::read_to_string(input_path)
39 .context(format!("Failed to read file: {}", input_path.display()))?
40 .parse::<Value>()
41 .context(format!("Failed to parse JSON: {}", input_path.display()))?;
42 thread_data["filename"] = json!(input_path); // This will be shown in a thread heading
43 Ok(thread_data)
44 })
45 .collect::<Result<Vec<_>>>()?;
46
47 let all_threads = json!({ "threads": threads });
48 let html_content = inject_thread_data(template, all_threads)?;
49 fs::write(&output, &html_content)
50 .context(format!("Failed to write output: {}", output.display()))?;
51
52 println!("Saved {} thread(s) to {}", threads.len(), output.display());
53 Ok(html_content)
54}
55
56fn inject_thread_data(template: String, threads_data: Value) -> Result<String> {
57 let injection_marker = "let threadsData = window.threadsData || { threads: [dummyThread] };";
58 template
59 .find(injection_marker)
60 .context("Could not find the thread injection point in the template")?;
61
62 let threads_json = serde_json::to_string_pretty(&threads_data)
63 .context("Failed to serialize threads data to JSON")?;
64 let script_injection = format!("let threadsData = {};", threads_json);
65 let final_html = template.replacen(injection_marker, &script_injection, 1);
66
67 Ok(final_html)
68}
69
70#[cfg(not(any(test, doctest)))]
71#[allow(dead_code)]
72fn main() -> Result<()> {
73 let args = Args::parse();
74 generate_explorer_html(&args.input, &args.output).map(|_| ())
75}