csv2xlsx 0.3.0

Convert CSV files to Excel (in XLSX format)
Documentation
use std::{collections::HashMap, str::FromStr};

use simple_excel_writer::{CellValue, ToCellValue};

use crate::{constants, Error, Result};

#[derive(Default)]
pub struct Options {
    pub delimiter: char,
    pub width_adjustment: bool,
    pub sheet_name: String,
    pub explicit_column_types_map: ExplicitColumnTypesMap,
}

impl Options {
    pub fn new() -> Self {
        Self {
            delimiter: constants::DEFAULT_DELIMITER,
            width_adjustment: constants::DEFAULT_WIDTH_ADJUSTMENT,
            sheet_name: constants::DEFAULT_SHEET_NAME.to_string(),
            explicit_column_types_map: ExplicitColumnTypesMap::default(),
        }
    }

    pub fn with_delimiter(mut self, delimiter: char) -> Self {
        self.delimiter = delimiter;
        self
    }

    pub fn with_width_adjustment(mut self, width_adjustment: bool) -> Self {
        self.width_adjustment = width_adjustment;
        self
    }

    pub fn with_sheet_name(mut self, sheet_name: String) -> Self {
        self.sheet_name = sheet_name;
        self
    }

    pub fn with_explicit_column_types(
        mut self,
        explicit_column_types: ExplicitColumnTypesMap,
    ) -> Self {
        self.explicit_column_types_map = explicit_column_types;
        self
    }
}

#[derive(Debug, Clone, Copy)]
pub(crate) enum ExplicitCellType {
    Bool,
    Number,
    String,
}

impl FromStr for ExplicitCellType {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self> {
        match s.to_ascii_lowercase().as_str() {
            "bool" => Ok(Self::Bool),
            "number" => Ok(Self::Number),
            "string" => Ok(Self::String),
            _ => Err(Error::UnparsableExplicitCellType),
        }
    }
}

#[derive(Debug)]
pub struct ExplicitColumnType(u16, ExplicitCellType);

impl FromStr for ExplicitColumnType {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self> {
        match s.split_once('=') {
            Some((idx, cell_type)) => {
                let idx: u16 = idx
                    .parse()
                    .map_err(|_| Error::UnparsableExplicitColumnType)?;
                Ok(Self(idx, ExplicitCellType::from_str(cell_type)?))
            }
            None => Err(Error::UnparsableExplicitColumnType),
        }
    }
}

#[derive(Debug, Default)]
pub struct ExplicitColumnTypesMap(HashMap<u16, ExplicitColumnType>);

impl From<Vec<ExplicitColumnType>> for ExplicitColumnTypesMap {
    fn from(vec: Vec<ExplicitColumnType>) -> Self {
        let mut map = HashMap::with_capacity(vec.len());
        for ect in vec {
            map.insert(ect.0, ect);
        }
        Self(map)
    }
}

impl ExplicitColumnTypesMap {
    pub(crate) fn get(&self, column_index: u16) -> Option<ExplicitCellType> {
        self.0.get(&column_index).map(|ect| ect.1)
    }

    pub(crate) fn to_cell_value(&self, column_index: u16, value: &str) -> Result<CellValue> {
        if let Some(ect) = self.get(column_index) {
            Ok(match ect {
                ExplicitCellType::Number => value
                    .parse::<f64>()
                    .map_err(|_| Error::UnparsableNumber)?
                    .to_cell_value(),
                ExplicitCellType::Bool => value
                    .parse::<bool>()
                    .map_err(|_| Error::UnparsableBool)?
                    .to_cell_value(),
                ExplicitCellType::String => value.to_cell_value(),
            })
        } else {
            Err(Error::UnmatchedExplicitColumnTypeIndex)
        }
    }
}