use crate::models::{Cell, CellValue, Column, IndexResult, Row, Sheet};
use crate::types::Result;
use std::collections::HashMap;
use std::io::{Error, ErrorKind};
pub type ColumnNameToId<'a> = HashMap<&'a str, u64>;
pub type ColumnIdToName<'a> = HashMap<u64, &'a str>;
pub type ColumnNameToCell<'a> = HashMap<&'a str, &'a Cell>;
pub struct ColumnMapper<'a> {
pub name_to_id: ColumnNameToId<'a>,
pub id_to_name: ColumnIdToName<'a>,
}
impl<'a> From<&'a Sheet> for ColumnMapper<'a> {
fn from(sheet_ref: &'a Sheet) -> Self {
Self::new(&sheet_ref.columns)
}
}
impl<'a> From<&'a Row> for ColumnMapper<'a> {
fn from(row_ref: &'a Row) -> Self {
Self::new(&row_ref.columns)
}
}
impl<'a> From<&'a IndexResult<Column>> for ColumnMapper<'a> {
fn from(index_ref: &'a IndexResult<Column>) -> Self {
Self::new(&index_ref.data)
}
}
impl<'a> ColumnMapper<'a> {
pub fn new(columns: &'a [Column]) -> Self {
let (name_to_id, id_to_name) = Self::get_mappings(columns);
Self {
name_to_id,
id_to_name,
}
}
fn get_mappings(columns: &'a [Column]) -> (ColumnNameToId, ColumnIdToName) {
let num_columns = columns.len();
if num_columns == 0 {
panic!("No column data for the Row - please ensure you call `get_row_with_column_data()` *or* \
pass `RowIncludeFlags::Columns` as an `include` argument to `get_row_with_params()`")
}
let mut name_to_id: ColumnNameToId<'a> = HashMap::with_capacity(num_columns);
let mut id_to_name: ColumnIdToName<'a> = HashMap::with_capacity(num_columns);
for c in columns {
let title = &c.title;
name_to_id.insert(title, c.id);
id_to_name.insert(c.id, title);
}
(name_to_id, id_to_name)
}
}
pub struct CellGetter<'a> {
column_name_to_id: &'a ColumnNameToId<'a>,
id_to_column_name: &'a ColumnIdToName<'a>,
}
impl<'a> CellGetter<'a> {
pub fn new(columns: &'a ColumnMapper<'a>) -> Self {
Self {
column_name_to_id: &columns.name_to_id,
id_to_column_name: &columns.id_to_name,
}
}
pub fn from_mapper(columns: &'a ColumnMapper<'a>) -> Self {
Self::new(columns)
}
pub fn by_name<'b>(&'a self, row: &'a Row, name: &'b str) -> Result<&Cell> {
match self.column_name_to_id.get(name) {
Some(&col_id) => row.get_cell_by_id(col_id),
None => Err(Box::from(Error::new(
ErrorKind::InvalidInput,
format!("A column named `{}` was not found in the Sheet", name),
))),
}
}
pub fn by_id(&'a self, row: &'a Row, column_id: u64) -> Result<&Cell> {
row.get_cell_by_id(column_id)
}
pub fn name_to_cell(&'a self, row: &'a Row) -> ColumnNameToCell<'a> {
let mut col_name_to_cell: ColumnNameToCell<'a> = HashMap::with_capacity(row.cells.len());
for cell in &row.cells {
if let Some(&col_name) = self.id_to_column_name.get(&cell.column_id) {
col_name_to_cell.insert(col_name, cell);
}
}
col_name_to_cell
}
}
pub struct RowGetter<'a> {
pub rows: &'a [Row],
pub column_name_to_id: &'a ColumnNameToId<'a>,
}
impl<'a> RowGetter<'a> {
pub fn new(rows: &'a [Row], columns: &'a ColumnMapper<'a>) -> RowGetter<'a> {
Self {
rows,
column_name_to_id: &columns.name_to_id,
}
}
pub fn where_eq<V: Into<CellValue>>(
&'a self,
column_name: &'a str,
value: V,
) -> Result<RowFinder<'a>> {
RowFinder::new(
self.rows,
self.column_name_to_id,
column_name,
value.into(),
Comp::EQ,
)
}
pub fn where_eq_by_id<V: Into<CellValue>>(&'a self, column_id: u64, value: V) -> RowFinder<'a> {
RowFinder::new_by_id(self.rows, column_id, value.into(), Comp::EQ)
}
pub fn where_ne<V: Into<CellValue>>(
&'a self,
column_name: &'a str,
value: V,
) -> Result<RowFinder<'a>> {
RowFinder::new(
self.rows,
self.column_name_to_id,
column_name,
value.into(),
Comp::NE,
)
}
pub fn where_ne_by_id<V: Into<CellValue>>(&'a self, column_id: u64, value: V) -> RowFinder<'a> {
RowFinder::new_by_id(self.rows, column_id, value.into(), Comp::NE)
}
}
pub enum Comp {
EQ,
NE,
}
impl Comp {
pub fn get_cell_comparator<'a>(&'a self) -> fn(&'a CellValue, &'a CellValue) -> bool {
match self {
Comp::EQ => |v1: &'a CellValue, v2: &'a CellValue| v1 == v2,
Comp::NE => |v1: &'a CellValue, v2: &'a CellValue| v1 != v2,
}
}
}
pub struct RowFinder<'a> {
pub rows: &'a [Row],
column_id: u64,
value: CellValue,
cmp: Comp,
}
impl<'a> RowFinder<'a> {
pub fn new(
rows: &'a [Row],
column_name_to_id: &'a ColumnNameToId<'a>,
column_name: &'a str,
value: CellValue,
cmp: Comp,
) -> Result<Self> {
let column_id = match column_name_to_id.get(column_name) {
Some(&v) => v,
None => {
return Err(Box::from(Error::new(
ErrorKind::NotFound,
format!(
"The column name `{}` does not exist in the sheet",
column_name
),
)));
}
};
Ok(Self::new_by_id(rows, column_id, value, cmp))
}
pub fn new_by_id(rows: &'a [Row], column_id: u64, value: CellValue, cmp: Comp) -> Self {
Self {
rows,
column_id,
value,
cmp,
}
}
pub fn first<'b>(&'b self) -> Result<&'a Row> {
let cmp = self.cmp.get_cell_comparator();
return match self.rows.iter().find(|row| {
if let Ok(cell) = row.get_cell_by_id(self.column_id) {
matches!(&cell.value, Some(cv) if cmp(&self.value, cv))
} else {
false
}
}) {
Some(row) => Ok(row),
None => Err(Box::from(Error::new(
ErrorKind::NotFound,
"No matching row for the condition",
))),
};
}
pub fn find_all<'b>(&'b self) -> Result<Vec<&'a Row>> {
let cmp = self.cmp.get_cell_comparator();
Ok(self
.rows
.iter()
.filter(|row| {
if let Ok(cell) = row.get_cell_by_id(self.column_id) {
matches!(&cell.value, Some(cv) if cmp(&self.value, cv))
} else {
false
}
})
.collect::<Vec<_>>())
}
}