xl-cli-tools

CLI tools for viewing and editing Excel files
Log | Files | Refs | README | LICENSE

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 }