use quarb::{AstAdapter, NodeId, Value};
pub struct CsvAdapter {
columns: Vec<String>,
rows: Vec<Vec<String>>,
}
impl CsvAdapter {
pub fn parse(text: &str) -> Result<Self, csv::Error> {
Self::parse_with_delimiter(text, b',')
}
pub fn parse_with_delimiter(text: &str, delimiter: u8) -> Result<Self, csv::Error> {
let mut reader = csv::ReaderBuilder::new()
.delimiter(delimiter)
.from_reader(text.as_bytes());
let columns: Vec<String> = reader
.headers()?
.iter()
.map(|h| h.trim().to_string())
.collect();
let mut rows = Vec::new();
for record in reader.records() {
let record = record?;
rows.push(record.iter().map(|c| c.to_string()).collect());
}
Ok(CsvAdapter { columns, rows })
}
pub fn locator(&self, node: NodeId) -> String {
if node.0 == 0 {
"/".to_string()
} else {
format!("/row[{}]", node.0)
}
}
fn cell(&self, node: NodeId, column: &str) -> Option<&str> {
let row = self.rows.get((node.0 as usize).checked_sub(1)?)?;
let idx = self.columns.iter().position(|c| c == column)?;
row.get(idx).map(|c| c.as_str()).filter(|c| !c.is_empty())
}
}
impl AstAdapter for CsvAdapter {
fn root(&self) -> NodeId {
NodeId(0)
}
fn children(&self, node: NodeId) -> Vec<NodeId> {
if node.0 == 0 {
(1..=self.rows.len() as u64).map(NodeId).collect()
} else {
Vec::new()
}
}
fn name(&self, node: NodeId) -> Option<String> {
if node.0 == 0 {
None
} else {
Some("row".to_string())
}
}
fn parent(&self, node: NodeId) -> Option<NodeId> {
if node.0 == 0 { None } else { Some(NodeId(0)) }
}
fn property(&self, node: NodeId, name: &str) -> Option<Value> {
self.cell(node, name).map(|c| Value::Str(c.to_string()))
}
fn metadata(&self, node: NodeId, key: &str) -> Option<Value> {
if node.0 == 0 {
return match key {
"columns" => Some(Value::List(
self.columns.iter().map(|c| Value::Str(c.clone())).collect(),
)),
"n-rows" => Some(Value::Int(self.rows.len() as i64)),
_ => None,
};
}
match key {
"n-fields" => self
.rows
.get(node.0 as usize - 1)
.map(|r| Value::Int(r.len() as i64)),
other => self.property(node, other),
}
}
}