Skip to main content

steeldb/projectors/
csv_proj.rs

1//! Relational / CSV explorer: one situation per row, a `column/value` token per non-empty cell.
2//! Raw cells are kept for display so the explorer shows the original table.
3
4use crate::projector::{slug, CorpusKind, Projector, Situation};
5use std::path::PathBuf;
6
7pub struct CsvProjector {
8    path: PathBuf,
9    columns: Vec<String>,
10}
11
12impl CsvProjector {
13    /// Opens the file to read the header now (so `columns()` is known before projection).
14    pub fn open(path: impl Into<PathBuf>) -> std::io::Result<CsvProjector> {
15        let path = path.into();
16        let mut rdr = csv::ReaderBuilder::new().flexible(true).from_path(&path)?;
17        let columns = rdr.headers()?.iter().map(slug).collect();
18        Ok(CsvProjector { path, columns })
19    }
20}
21
22impl Projector for CsvProjector {
23    fn columns(&self) -> Vec<String> {
24        self.columns.clone()
25    }
26    fn kind(&self) -> CorpusKind {
27        CorpusKind::Csv
28    }
29    fn source(&self) -> String {
30        self.path.display().to_string()
31    }
32    fn project(self: Box<Self>, sink: &mut dyn FnMut(Situation)) -> std::io::Result<()> {
33        let mut rdr = csv::ReaderBuilder::new().flexible(true).from_path(&self.path)?;
34        for rec in rdr.records() {
35            let rec = match rec {
36                Ok(r) => r,
37                Err(_) => continue,
38            };
39            let cells: Vec<String> = rec.iter().map(|c| c.to_string()).collect();
40            let mut tokens = Vec::with_capacity(cells.len());
41            let mut numbers = Vec::new();
42            for (i, cell) in cells.iter().enumerate() {
43                let v = cell.trim();
44                if v.is_empty() {
45                    continue;
46                }
47                let col = self.columns.get(i).map(|s| s.as_str()).unwrap_or("col");
48                tokens.push(format!("{col}/{}", slug(v)));
49                // numeric cells also become a queryable numeric field for `(num <col> <op> <value>)`
50                if let Some(n) = crate::units::parse_number(v) {
51                    numbers.push((col.to_string(), n));
52                }
53            }
54            sink(Situation { tokens, display: cells, numbers, beliefs: Vec::new() });
55        }
56        Ok(())
57    }
58}