use crate::csv_handler::CellRange;
use anyhow::{Context, Result};
pub fn filter_by_range(data: &[Vec<String>], range: &CellRange) -> Vec<Vec<String>> {
let mut result = Vec::new();
for (row_idx, row) in data.iter().enumerate() {
if row_idx < range.start_row {
continue;
}
if row_idx > range.end_row {
break;
}
let filtered_row: Vec<String> = row
.iter()
.enumerate()
.filter(|(col_idx, _)| *col_idx >= range.start_col && *col_idx <= range.end_col)
.map(|(_, val)| val.clone())
.collect();
result.push(filtered_row);
}
result
}
pub fn default_column_names(num_cols: usize, prefix: &str) -> Vec<String> {
(0..num_cols).map(|i| format!("{}_{}", prefix, i)).collect()
}
pub fn max_column_count(data: &[Vec<String>]) -> usize {
data.iter().map(|r| r.len()).max().unwrap_or(0)
}
pub fn matches_extension(path: &str, extensions: &[&str]) -> bool {
let path_lower = path.to_lowercase();
extensions
.iter()
.any(|ext| path_lower.ends_with(&format!(".{}", ext)))
}
pub fn parse_safe_f64(value: &str, min: Option<f64>, max: Option<f64>) -> Result<f64> {
let num = value
.trim()
.parse::<f64>()
.with_context(|| format!("Invalid numeric value: '{}'", value))?;
if !num.is_finite() {
anyhow::bail!("Numeric value must be finite: '{}'", value);
}
if let Some(min_val) = min {
if num < min_val {
anyhow::bail!("Value {} is below minimum {}", num, min_val);
}
}
if let Some(max_val) = max {
if num > max_val {
anyhow::bail!("Value {} exceeds maximum {}", num, max_val);
}
}
Ok(num)
}
pub fn parse_safe_i64(value: &str, min: Option<i64>, max: Option<i64>) -> Result<i64> {
let num = value
.trim()
.parse::<i64>()
.with_context(|| format!("Invalid integer value: '{}'", value))?;
if let Some(min_val) = min {
if num < min_val {
anyhow::bail!("Value {} is below minimum {}", num, min_val);
}
}
if let Some(max_val) = max {
if num > max_val {
anyhow::bail!("Value {} exceeds maximum {}", num, max_val);
}
}
Ok(num)
}
pub fn parse_safe_usize(value: &str, max: Option<usize>) -> Result<usize> {
let trimmed = value.trim();
if trimmed.starts_with('-') {
anyhow::bail!("Index cannot be negative: '{}'", value);
}
let num = trimmed
.parse::<usize>()
.with_context(|| format!("Invalid index value: '{}'", value))?;
if let Some(max_val) = max {
if num > max_val {
anyhow::bail!("Index {} exceeds maximum {}", num, max_val);
}
}
Ok(num)
}
pub fn with_file_context<T>(result: Result<T>, file_path: &str) -> Result<T> {
result.with_context(|| format!("Error processing file: '{}'", file_path))
}
pub fn with_cell_context<T>(result: Result<T>, row: usize, col: usize) -> Result<T> {
result.with_context(|| format!("Error at row {}, column {}", row, col))
}
pub fn with_full_context<T>(result: Result<T>, file_path: &str, row: usize, col: usize) -> Result<T> {
result.with_context(|| format!("Error in '{}' at row {}, column {}", file_path, row, col))
}
pub fn validate_row_index(data: &[Vec<String>], row: usize) -> Result<()> {
if row >= data.len() {
anyhow::bail!("Row index {} out of bounds (data has {} rows)", row, data.len());
}
Ok(())
}
pub fn validate_column_index(data: &[Vec<String>], col: usize) -> Result<()> {
if data.is_empty() {
anyhow::bail!("Cannot validate column index: data is empty");
}
if col >= data[0].len() {
anyhow::bail!("Column index {} out of bounds (row has {} columns)", col, data[0].len());
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::filter_by_range;
use crate::csv_handler::CellRange;
#[test]
fn filter_by_range_a1_b2() {
let data = vec![
vec!["a".into(), "b".into(), "c".into()],
vec!["d".into(), "e".into(), "f".into()],
vec!["g".into(), "h".into(), "i".into()],
];
let r = CellRange::parse("A1:B2").unwrap();
let out = filter_by_range(&data, &r);
assert_eq!(
out,
vec![
vec!["a".to_string(), "b".to_string()],
vec!["d".to_string(), "e".to_string()],
]
);
}
#[test]
fn filter_by_range_single_cell() {
let data = vec![vec!["x".into(), "y".into()]];
let r = CellRange::parse("B1").unwrap();
let out = filter_by_range(&data, &r);
assert_eq!(out, vec![vec!["y".to_string()]]);
}
}