use std::ops::{Deref, DerefMut};
use rustc_hash::FxHashMap;
#[derive(Debug, Clone)]
pub struct DataTable {
rows: Vec<Vec<String>>,
}
impl DataTable {
pub fn new(rows: Vec<Vec<String>>) -> Self {
Self { rows }
}
pub fn raw(&self) -> &[Vec<String>] {
&self.rows
}
pub fn is_empty(&self) -> bool {
self.rows.is_empty()
}
pub fn len(&self) -> usize {
self.rows.len()
}
pub fn headers(&self) -> Option<&[String]> {
self.rows.first().map(|r| r.as_slice())
}
pub fn data_rows(&self) -> &[Vec<String>] {
if self.rows.len() > 1 { &self.rows[1..] } else { &[] }
}
pub fn hashes(&self) -> Vec<FxHashMap<&str, &str>> {
let Some(headers) = self.headers() else {
return Vec::new();
};
self
.data_rows()
.iter()
.map(|row| {
headers
.iter()
.zip(row.iter())
.map(|(h, v)| (h.as_str(), v.as_str()))
.collect()
})
.collect()
}
pub fn rows_hash(&self) -> FxHashMap<&str, &str> {
self
.rows
.iter()
.filter(|r| r.len() >= 2)
.map(|r| (r[0].as_str(), r[1].as_str()))
.collect()
}
pub fn transpose(&self) -> DataTable {
if self.rows.is_empty() {
return DataTable::new(Vec::new());
}
let max_cols = self.rows.iter().map(|r| r.len()).max().unwrap_or(0);
let mut transposed = vec![Vec::with_capacity(self.rows.len()); max_cols];
for row in &self.rows {
for (col_idx, cell) in row.iter().enumerate() {
transposed[col_idx].push(cell.clone());
}
}
DataTable::new(transposed)
}
pub fn cell(&self, row: usize, col: usize) -> Option<&str> {
self.rows.get(row).and_then(|r| r.get(col)).map(String::as_str)
}
}
impl Deref for DataTable {
type Target = [Vec<String>];
fn deref(&self) -> &[Vec<String>] {
&self.rows
}
}
impl DerefMut for DataTable {
fn deref_mut(&mut self) -> &mut [Vec<String>] {
&mut self.rows
}
}
impl From<Vec<Vec<String>>> for DataTable {
fn from(rows: Vec<Vec<String>>) -> Self {
Self::new(rows)
}
}
pub trait FromDataTable: Sized {
fn from_row(headers: &[String], row: &[String]) -> ferridriver::error::Result<Self>;
}
impl DataTable {
pub fn as_type<T: FromDataTable>(&self) -> ferridriver::error::Result<Vec<T>> {
let headers = self
.headers()
.ok_or_else(|| ferridriver::FerriError::invalid_argument("data-table", "table has no header row"))?;
self.data_rows().iter().map(|row| T::from_row(headers, row)).collect()
}
}