h5inspect 1.7.0

A terminal based HDF5 file inspector
use crate::app::App;
use clap;
use color_eyre::Result;
use std::error::Error;
use tui_logger;

mod analysis;
mod app;
mod events;
mod h5_utils;
mod hist_plot;
mod num_utils;
mod tree;
mod ui;

fn main() -> Result<(), Box<dyn Error>> {
    // crate::h5_utils::generate_dummy_file()?;
    let matches = clap::Command::new("h5inspect")
        .author("Hal Frigaard")
        .about("Simple TUI to inspect h5 files")
        .color(clap::ColorChoice::Auto)
        .styles(
            clap::builder::Styles::styled()
                .header(
                    clap::builder::styling::AnsiColor::Yellow.on_default()
                        | clap::builder::styling::Effects::BOLD,
                )
                .usage(
                    clap::builder::styling::AnsiColor::Yellow.on_default()
                        | clap::builder::styling::Effects::BOLD,
                )
                .literal(
                    clap::builder::styling::AnsiColor::Green.on_default()
                        | clap::builder::styling::Effects::BOLD,
                )
                .placeholder(clap::builder::styling::AnsiColor::White.on_default())
                .error(
                    clap::builder::styling::AnsiColor::Red.on_default()
                        | clap::builder::styling::Effects::BOLD,
                )
                .valid(
                    clap::builder::styling::AnsiColor::Green.on_default()
                        | clap::builder::styling::Effects::BOLD,
                )
                .invalid(
                    clap::builder::styling::AnsiColor::Red.on_default()
                        | clap::builder::styling::Effects::BOLD,
                ),
        )
        .arg(
            clap::Arg::new("h5file")
                .value_name("FILE")
                .help("Name of hdf5 file to inspect")
                .value_hint(clap::ValueHint::FilePath)
                .required_unless_present("generate-dummy-file"),
        )
        .arg(
            clap::Arg::new("logs")
                .long("logs")
                .value_name("FILE")
                .help("File path to print logs to")
                .value_hint(clap::ValueHint::FilePath)
                .required(false),
        )
        .arg(
            clap::Arg::new("generate-dummy-file")
                .long("generate-dummy-file")
                .help("Generate a dummy hdf5 file for testing purposes")
                .action(clap::ArgAction::SetTrue),
        )
        .version(env!("CARGO_PKG_VERSION"))
        .get_matches();

    // Handle generate-dummy-file flag first (doesn't require h5file)
    if matches.get_flag("generate-dummy-file") {
        h5_utils::generate_dummy_file()?;
        return Ok(());
    }

    // For all other operations, h5file is required
    let h5_file_name: &String = matches
        .get_one("h5file")
        .expect("clap should have enforced presence of h5file argument");
    let h5_file_path = std::path::PathBuf::from(h5_file_name);

    initialize_logger(matches.get_one::<String>("logs"))?;

    log::info!("Starting app");

    let runtime = build_runtime();

    color_eyre::install()?;
    let app = App::new(h5_file_path.clone());

    let res = runtime.block_on(app.run());

    match res {
        Ok(_) => Ok(()),
        Err(e) => Err(e),
    }
}

fn initialize_logger(log_file_path: Option<&String>) -> Result<(), Box<dyn Error>> {
    // Initialize tui_logger as the main logger
    tui_logger::init_logger(log::LevelFilter::Trace)?;
    tui_logger::set_default_level(log::LevelFilter::Trace);
    tui_logger::set_level_for_target("plotters_ratatui_backend::widget", log::LevelFilter::Off);
    tui_logger::set_level_for_target("mio::poll", log::LevelFilter::Off);

    // Set up file logging if --logs argument is provided
    if let Some(log_file_path) = log_file_path {
        // Set up tui_logger to also output to file via custom dispatch
        // We'll use the tui_logger's built-in move_events functionality
        tui_logger::set_log_file(tui_logger::TuiLoggerFile::new(log_file_path));
    }
    Ok(())
}

fn build_runtime() -> tokio::runtime::Runtime {
    tokio::runtime::Builder::new_multi_thread()
        .worker_threads(2)
        .max_blocking_threads(512)
        .enable_all()
        .build()
        .unwrap()
}

// #[cfg(test)]
// mod tests {
//     use super::*;

//     #[test]
//     fn run_app_startup() -> Result<(), Box<dyn Error>> {
//         h5_utils::generate_dummy_file()?;
//         let h5_file_path = std::path::PathBuf::from("dummy.h5");
//         run_app(h5_file_path)
//     }

//     #[test]
//     #[should_panic(expected = "File path doesn't exist")]
//     fn run_app_on_non_existent_file() {
//         let h5_file_path = std::path::PathBuf::from("non_existent.h5");
//         run_app(h5_file_path).unwrap();
//     }

//     #[test]
//     #[should_panic(expected = "Couldn't open file")]
//     fn run_app_on_non_h5_file() {
//         let h5_file_path = std::path::PathBuf::from("src/main.rs");
//         run_app(h5_file_path).unwrap();
//     }

//     #[test]
//     fn run_app_on_split_file() -> Result<(), Box<dyn Error>> {
//         h5_utils::generate_dummy_split_file()?;
//         let h5_file_path = std::path::PathBuf::from("dummy_split.h5");
//         run_app(h5_file_path)
//     }

//     fn run_app(h5_file_path: std::path::PathBuf) -> Result<(), Box<dyn Error>> {
//         let app = App::new(h5_file_path);

//         let backend = ratatui::backend::TestBackend::new(200, 120);
//         let terminal = ratatui::Terminal::new(backend).unwrap();

//         let runtime = build_runtime();
//         let res = runtime.block_on(async {
//             tokio::select! {
//                 res = app.run() => {
//                     res.map(|_| ())
//                 }
//                 _ = tokio::time::sleep(std::time::Duration::from_secs(2)) => {
//                     println!("Timer expired before app returned, nice.");
//                     Ok(())
//                 }
//             }
//         });

//         res
//     }
// }