use std::fs;
use std::io::BufWriter;
use clap::Parser;
use crate::cli::{GlobalOpts, H_OUTPUT};
use crate::engine::analyzer::AnalysisResult;
use crate::report;
#[derive(Parser)]
pub(crate) struct ConvertArgs {
#[arg(short = 'i', long, value_name = "FILE", help_heading = H_OUTPUT)]
pub input: String,
#[arg(long, value_enum, default_value = "html", value_name = "FORMAT", help_heading = H_OUTPUT)]
pub format: ReportFormat,
#[arg(short = 'o', long, value_name = "FILE", help_heading = H_OUTPUT)]
pub output: Option<String>,
#[arg(long, default_value = "NFS Security Assessment", value_name = "TEXT", help_heading = H_OUTPUT)]
pub title: String,
}
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
pub(crate) enum ReportFormat {
Console,
Html,
Json,
Txt,
Markdown,
Csv,
}
pub(crate) fn run(args: &ConvertArgs, globals: &GlobalOpts) -> anyhow::Result<()> {
let content = fs::read_to_string(&args.input).map_err(|e| anyhow::anyhow!("cannot read {}: {e}", args.input))?;
let results: Vec<AnalysisResult> = serde_json::from_str::<Vec<AnalysisResult>>(&content).or_else(|_| serde_json::from_str::<AnalysisResult>(&content).map(|r| vec![r])).map_err(|e| anyhow::anyhow!("cannot parse {}: {e}", args.input))?;
let finding_count: usize = results.iter().map(|r| r.findings.len()).sum();
if let Some(path) = &args.output {
tracing::info!(input = %args.input, output = %path, "generating report");
let file = fs::File::create(path).map_err(|e| anyhow::anyhow!("cannot create {path}: {e}"))?;
let mut out = BufWriter::new(file);
report::generate(&results, args.format, &args.title, &mut out)?;
if !globals.quiet {
eprintln!("{}", crate::output::status_ok(&format!("Report written -> {path} ({} host(s), {finding_count} finding(s), {:?} format)", results.len(), args.format)));
}
} else {
tracing::info!(input = %args.input, output = "stdout", "generating report");
let mut out = BufWriter::new(std::io::stdout().lock());
report::generate(&results, args.format, &args.title, &mut out)?;
}
crate::cli::emit_replay(globals);
Ok(())
}