investments 4.4.4

Helps you with managing your investments
Documentation
use std::ops::Index;

use calamine::{Range, Reader, open_workbook_auto};

use crate::core::GenericResult;

use super::{Cell, is_empty_row};

pub struct SheetReader {
    sheet: Range<Cell>,
    parser: Box<dyn SheetParser>,

    prev_row_id: Option<usize>,
    next_row_id: usize,
    eof_reached: bool,
}

impl SheetReader {
    pub fn new(path: &str, parser: Box<dyn SheetParser>) -> GenericResult<SheetReader> {
        let sheet_name = parser.sheet_name();
        let sheet = open_sheet(path, sheet_name)?.ok_or_else(|| format!(
            "There is no {:?} sheet in the workbook", sheet_name))?;

        Ok(SheetReader {
            sheet, parser,
            prev_row_id: None,
            next_row_id: 0,
            eof_reached: false,
        })
    }

    pub fn repeatable_table_column_titles(&self) -> bool {
        self.parser.repeatable_table_column_titles()
    }

    pub fn next_row(&mut self) -> Option<&[Cell]> {
        while self.next_row_id < self.sheet.height() {
            let row = self.sheet.index(self.next_row_id);
            if self.parser.skip_row(row) {
                self.next_row_id += 1;
                continue;
            }

            self.prev_row_id.replace(self.next_row_id);
            self.next_row_id += 1;
            return Some(row);
        }

        self.eof_reached = true;
        None
    }

    pub fn next_row_checked(&mut self) -> GenericResult<&[Cell]> {
        Ok(self.next_row().ok_or("Got an unexpected end of sheet")?)
    }

    pub fn step_back(&mut self) {
        self.next_row_id = self.prev_row_id.take().unwrap();
        self.eof_reached = false;
    }

    #[allow(dead_code)]
    pub fn skip_empty_rows(&mut self) {
        while let Some(row) = self.next_row() {
            if !is_empty_row(row) {
                self.step_back();
                break;
            }
        }
    }

    pub fn detalize_error(&self, error: &str) -> String {
        if self.next_row_id == 0 || self.eof_reached {
            error.to_owned()
        } else {
            format!("Starting from #{} row: {:?}: {}",
                    self.next_row_id, self.sheet.index(self.next_row_id - 1), error)
        }
    }
}

pub trait SheetParser {
    fn sheet_name(&self) -> &str;

    fn repeatable_table_column_titles(&self) -> bool {
        false
    }

    fn skip_row(&self, _row: &[Cell]) -> bool {
        false
    }
}

pub fn open_sheet(path: &str, name: &str) -> GenericResult<Option<Range<Cell>>> {
    let mut workbook = open_workbook_auto(path)?;
    Ok(workbook.worksheet_range(name).transpose()?)
}