memea/export.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
//! Export functionality for MemEA analysis results.
//!
//! This module provides multiple export formats for memory peripheral estimation
//! results, including CSV, JSON, YAML, and direct console output. It handles
//! file creation, overwrite confirmation, and format-specific serialization.
use serde::Serialize;
use std::collections::HashMap;
use std::fs::{metadata, File, OpenOptions};
use std::io::{self, Write};
use std::path::PathBuf;
use std::str;
use crate::db::DBError;
use crate::tabulate::{Report, Reports};
use crate::{infoln, query, Float, MemeaError};
/// Calculates the total area from a collection of reports.
///
/// # Arguments
/// * `reports` - Collection of reports to sum areas from
///
/// # Returns
/// Total area in square micrometers
pub fn area(reports: &Reports) -> Float {
reports.iter().map(|r| r.area).sum()
}
/// Exports analysis results to various formats based on file extension.
///
/// This function determines the output format from the file extension and handles
/// file creation with overwrite confirmation. Supported formats include CSV, JSON,
/// YAML, and direct console output.
///
/// # Arguments
/// * `reports` - HashMap of configuration names to their corresponding reports
/// * `filename` - Optional output file path. If None, outputs to stdout
///
/// # Returns
/// * `Ok(())` - Export completed successfully
/// * `Err(MemeaError)` - File I/O error, serialization error, or unsupported format
///
/// # Examples
/// ```no_run
/// use memea::export::export;
/// use std::path::PathBuf;
/// use std::collections::HashMap;
///
/// let reports = HashMap::new(); // populated with analysis results
/// let output_file = Some(PathBuf::from("results.csv"));
/// export(&reports, &output_file).expect("Export failed");
/// ```
pub fn export(
reports: &HashMap<String, Reports>,
filename: &Option<PathBuf>,
) -> Result<(), MemeaError> {
let buf = match filename {
Some(x) => {
if metadata(x).is_ok() {
let allow = query(
format!("'{}' already exists. Overwrite?", x.to_string_lossy()).as_str(),
true,
crate::QueryDefault::Yes,
)?;
if !allow {
infoln!("Aborting...");
return Ok(());
}
}
let f = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(x)?;
infoln!("Wrote output to {:#?}", x);
Some(f)
}
None => None,
};
let format = filename
.as_ref()
.and_then(|f| f.extension().and_then(|s| s.to_str()))
.unwrap_or("direct")
.to_lowercase();
match format.as_str() {
"csv" => export_csv(reports, buf)?,
"json" => export_json(reports, buf)?,
"yaml" | "yml" => export_yaml(reports, buf)?,
"direct" => export_direct(reports)?,
other => {
return Err(DBError::FileType(other.to_string()).into());
}
}
Ok(())
}
/// Exports reports to CSV format with configuration names included.
///
/// Each row in the CSV contains a configuration name along with flattened
/// report data for easy analysis in spreadsheet applications.
///
/// # Arguments
/// * `reports` - HashMap of configuration names to reports
/// * `buf` - Optional file buffer, uses stdout if None
///
/// # Returns
/// * `Ok(())` - CSV export completed successfully
/// * `Err(MemeaError)` - Serialization or I/O error
fn export_csv(reports: &HashMap<String, Reports>, buf: Option<File>) -> Result<(), MemeaError> {
let writer: Box<dyn Write> = match buf {
Some(file) => Box::new(file),
None => Box::new(io::stdout()),
};
let mut wtr = csv::Writer::from_writer(writer);
for (config, reps) in reports {
for rep in reps {
// Wrap report with config name so it's included in CSV
#[derive(Serialize)]
struct Row<'a> {
configuration: &'a str,
#[serde(flatten)]
report: &'a Report,
}
let row = Row {
configuration: config,
report: rep,
};
wtr.serialize(row)?;
}
}
wtr.flush()?;
Ok(())
}
/// Exports reports to JSON format with pretty printing.
///
/// # Arguments
/// * `reports` - HashMap of configuration names to reports
/// * `buf` - Optional file buffer, uses stdout if None
///
/// # Returns
/// * `Ok(())` - JSON export completed successfully
/// * `Err(MemeaError)` - Serialization or I/O error
fn export_json(reports: &HashMap<String, Reports>, buf: Option<File>) -> Result<(), MemeaError> {
match buf {
Some(file) => serde_json::to_writer_pretty(file, reports)?,
None => serde_json::to_writer_pretty(io::stdout(), reports)?,
}
Ok(())
}
/// Exports reports to YAML format.
///
/// # Arguments
/// * `reports` - HashMap of configuration names to reports
/// * `buf` - Optional file buffer, uses stdout if None
///
/// # Returns
/// * `Ok(())` - YAML export completed successfully
/// * `Err(MemeaError)` - Serialization or I/O error
fn export_yaml(reports: &HashMap<String, Reports>, buf: Option<File>) -> Result<(), MemeaError> {
match buf {
Some(mut file) => {
let s = serde_yaml::to_string(reports)?;
file.write_all(s.as_bytes())?;
}
None => {
let s = serde_yaml::to_string(reports)?;
println!("{s}");
}
}
Ok(())
}
/// Exports reports in human-readable table format to stdout.
///
/// This format provides a clean, formatted table showing area breakdown
/// by component type with totals for each configuration.
///
/// # Arguments
/// * `reports` - HashMap of configuration names to reports
///
/// # Returns
/// * `Ok(())` - Direct export completed successfully
/// * `Err(MemeaError)` - Formatting or I/O error
fn export_direct(reports: &HashMap<String, Reports>) -> Result<(), MemeaError> {
for (name, r) in reports {
println!("{}", fmt_direct(name, r));
}
Ok(())
}
/// Formats reports into a human-readable table string.
///
/// Creates a formatted table showing component breakdown with columns for
/// name, type, count, location, and area. Includes a total area summary.
///
/// # Arguments
/// * `input` - Configuration name to display as header
/// * `reports` - Collection of reports to format
///
/// # Returns
/// Formatted string containing the complete table
fn fmt_direct(input: &str, reports: &Reports) -> String {
let mut content = format!(
"\nConfiguration: {input}\n\
Area breakdown:\n \
Name | Type | Count | Location | Area (μm²)\n \
---------------------|----------|----------|----------|------------\n"
);
for report in reports.iter() {
content = format!(
"{} {:<20} | {:<8} | {:<8} | {:<8} | {:>11.1}\n",
content,
report.name,
report.celltype.to_string(),
report.count,
report.loc,
report.area
);
}
content = format!("{}Total area: {:.1} μm²\n", content, area(reports));
content
}