metadata.rs (2350B)
1 use anyhow::{Context, Result}; 2 use calamine::{open_workbook_auto, Reader}; 3 use std::path::Path; 4 5 /// Info about a single sheet (without loading data). 6 #[derive(Debug, Clone)] 7 pub struct SheetInfo { 8 pub name: String, 9 pub rows: usize, // total rows including header 10 pub cols: usize, 11 } 12 13 /// Info about the whole workbook file. 14 #[derive(Debug)] 15 pub struct FileInfo { 16 pub file_size: u64, 17 pub sheets: Vec<SheetInfo>, 18 } 19 20 /// Read metadata: file size, sheet names, and dimensions. 21 pub fn read_file_info(path: &Path) -> Result<FileInfo> { 22 let file_size = std::fs::metadata(path) 23 .with_context(|| format!("Cannot read file: {}", path.display()))? 24 .len(); 25 26 let ext = path 27 .extension() 28 .and_then(|e| e.to_str()) 29 .map(|e| e.to_lowercase()); 30 31 match ext.as_deref() { 32 Some("xlsx") | Some("xls") | Some("xlsb") | Some("xlsm") => {} 33 Some(other) => anyhow::bail!("Expected .xls or .xlsx file, got: .{other}"), 34 None => anyhow::bail!("Expected .xls or .xlsx file, got: no extension"), 35 } 36 37 let mut workbook = open_workbook_auto(path) 38 .with_context(|| format!("Cannot open workbook: {}", path.display()))?; 39 40 let sheet_names: Vec<String> = workbook.sheet_names().to_vec(); 41 let mut sheets = Vec::new(); 42 43 for name in &sheet_names { 44 let range = workbook 45 .worksheet_range(name) 46 .with_context(|| format!("Cannot read sheet: {name}"))?; 47 let (rows, cols) = range.get_size(); 48 sheets.push(SheetInfo { 49 name: name.clone(), 50 rows, 51 cols, 52 }); 53 } 54 55 Ok(FileInfo { file_size, sheets }) 56 } 57 58 /// Format file size for display: "245 KB", "1.2 MB", etc. 59 pub fn format_file_size(bytes: u64) -> String { 60 if bytes < 1_024 { 61 format!("{bytes} B") 62 } else if bytes < 1_048_576 { 63 format!("{:.0} KB", bytes as f64 / 1_024.0) 64 } else if bytes < 1_073_741_824 { 65 format!("{:.1} MB", bytes as f64 / 1_048_576.0) 66 } else { 67 format!("{:.1} GB", bytes as f64 / 1_073_741_824.0) 68 } 69 } 70 71 #[cfg(test)] 72 mod tests { 73 use super::*; 74 75 #[test] 76 fn test_format_file_size() { 77 assert_eq!(format_file_size(500), "500 B"); 78 assert_eq!(format_file_size(2_048), "2 KB"); 79 assert_eq!(format_file_size(1_500_000), "1.4 MB"); 80 } 81 }