explorer.rs

 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}