use crate::projector::{slug, CorpusKind, Projector, Situation};
use std::path::PathBuf;
pub struct CsvProjector {
path: PathBuf,
columns: Vec<String>,
}
impl CsvProjector {
pub fn open(path: impl Into<PathBuf>) -> std::io::Result<CsvProjector> {
let path = path.into();
let mut rdr = csv::ReaderBuilder::new().flexible(true).from_path(&path)?;
let columns = rdr.headers()?.iter().map(slug).collect();
Ok(CsvProjector { path, columns })
}
}
impl Projector for CsvProjector {
fn columns(&self) -> Vec<String> {
self.columns.clone()
}
fn kind(&self) -> CorpusKind {
CorpusKind::Csv
}
fn source(&self) -> String {
self.path.display().to_string()
}
fn project(self: Box<Self>, sink: &mut dyn FnMut(Situation)) -> std::io::Result<()> {
let mut rdr = csv::ReaderBuilder::new().flexible(true).from_path(&self.path)?;
for rec in rdr.records() {
let rec = match rec {
Ok(r) => r,
Err(_) => continue,
};
let cells: Vec<String> = rec.iter().map(|c| c.to_string()).collect();
let mut tokens = Vec::with_capacity(cells.len());
let mut numbers = Vec::new();
for (i, cell) in cells.iter().enumerate() {
let v = cell.trim();
if v.is_empty() {
continue;
}
let col = self.columns.get(i).map(|s| s.as_str()).unwrap_or("col");
tokens.push(format!("{col}/{}", slug(v)));
if let Some(n) = crate::units::parse_number(v) {
numbers.push((col.to_string(), n));
}
}
sink(Situation { tokens, display: cells, numbers, beliefs: Vec::new() });
}
Ok(())
}
}