crabxl 0.1.0-alpha.11

A Rust spreadsheet library with bounded streaming XLSX reads
Documentation
//! Read and sum numeric cells while reusing one sparse row allocation.
use crabxl::{
    AccessPattern, CellValue, MemoryPolicy, ReadData, ResourceLimits, Row, RowIndex, WorkbookReader,
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut arguments = std::env::args().skip(1);
    let path = arguments
        .next()
        .ok_or("Usage: sum <workbook.xlsx> [sheet] [buffer bytes] [stream|materialized|auto-scan|auto-repeat] [budget bytes|auto] [passes]")?;
    let requested_sheet = arguments.next();
    let mut limits = ResourceLimits::default();
    if let Some(bytes) = arguments.next() {
        limits.input_buffer_bytes = bytes.parse()?;
    }
    let mode = arguments.next().unwrap_or_else(|| "stream".into());
    let budget = arguments
        .next()
        .filter(|value| value != "auto")
        .map(|bytes| bytes.parse::<usize>())
        .transpose()?;
    if let Some(bytes) = budget {
        limits.max_materialized_bytes = bytes;
    }
    let passes = arguments
        .next()
        .map(|value| value.parse::<usize>())
        .transpose()?
        .unwrap_or(1);
    if passes == 0 {
        return Err("Pass count must be positive".into());
    }
    let mut workbook = WorkbookReader::open_with_limits(path, limits)?;
    let sheet = requested_sheet
        .or_else(|| workbook.sheets().first().map(|s| s.name().to_owned()))
        .ok_or("Workbook has no sheets")?;
    let mut count = 0u64;
    let mut checksum = 0f64;
    let mut accumulate = |row: &Row| -> Result<(), &str> {
        for cell in &row.cells {
            let value = match cell.value {
                CellValue::Number(value) => Some(value),
                CellValue::Integer(value)
                    if (-9_007_199_254_740_992..=9_007_199_254_740_992).contains(&value) =>
                {
                    Some(value as f64)
                }
                CellValue::Integer(_) | CellValue::BigInteger(_) => {
                    return Err("Example f64 checksum does not accept integers beyond 2^53");
                }
                _ => None,
            };
            if let Some(value) = value {
                count += 1;
                checksum += value;
            }
        }
        Ok(())
    };
    match mode.as_str() {
        "stream" => {
            for _ in 0..passes {
                let mut rows = workbook.rows(&sheet)?;
                let mut row = Row::new(RowIndex::new(0)?);
                while rows.read_row_into(&mut row)? {
                    accumulate(&row)?;
                }
            }
        }
        "materialized" => {
            let data = workbook.read_sheet(&sheet)?;
            for _ in 0..passes {
                for row in &data.rows {
                    accumulate(row)?;
                }
            }
        }
        "auto-scan" | "auto-repeat" => {
            let access = if mode == "auto-scan" {
                AccessPattern::Scan
            } else {
                AccessPattern::RepeatedAccess
            };
            let policy = budget.map(MemoryPolicy::Budget).unwrap_or_default();
            let stream_more = {
                let output = workbook.read_with_policy(&sheet, access, policy)?;
                eprintln!("DECISION {:?}", output.decision);
                match output.data {
                    ReadData::Streaming(mut rows) => {
                        let mut row = Row::new(RowIndex::new(0)?);
                        while rows.read_row_into(&mut row)? {
                            accumulate(&row)?;
                        }
                        true
                    }
                    ReadData::Materialized(data) => {
                        for _ in 0..passes {
                            for row in &data.rows {
                                accumulate(row)?;
                            }
                        }
                        false
                    }
                }
            };
            if stream_more {
                for _ in 1..passes {
                    let mut rows = workbook.rows(&sheet)?;
                    let mut row = Row::new(RowIndex::new(0)?);
                    while rows.read_row_into(&mut row)? {
                        accumulate(&row)?;
                    }
                }
            }
        }
        _ => return Err("Unknown read mode".into()),
    }
    println!("{count} {checksum:.0}");
    Ok(())
}