use serde_json::{json, Error, Value};
use crate::headers::*;
use std::{path::Path, str::FromStr, sync::Arc};
pub const DEFAULT_MAX_ROWS: usize = 10_000;
#[derive(Debug, Clone, Default)]
pub struct RowOptionSet {
pub euro_number_format: bool, pub date_only: bool,
pub columns: Vec<Column>,
}
impl RowOptionSet {
pub fn column(&self, index: usize) -> Option<&Column> {
self.columns.get(index)
}
}
#[derive(Debug, Clone, Default)]
pub struct OptionSet {
pub sheet: Option<String>, pub index: u32, pub path: Option<String>, pub rows: RowOptionSet,
pub jsonl: bool,
pub max: Option<u32>,
pub omit_header: bool,
pub header_row: u8,
pub read_mode: ReadMode,
}
impl OptionSet {
pub fn new(path_str: &str) -> Self {
OptionSet {
sheet: None,
index: 0,
path: Some(path_str.to_string()),
rows: RowOptionSet::default(),
jsonl: false,
max: None,
omit_header: false,
header_row: 0,
read_mode: ReadMode::Sync,
}
}
pub fn sheet_name(&mut self, name: String) -> &mut Self {
self.sheet = Some(name);
self
}
pub fn sheet_index(&mut self, index: u32) -> &mut Self {
self.index = index;
self
}
pub fn json_lines(&mut self) -> &mut Self {
self.jsonl = true;
self
}
pub fn omit_header(&mut self) -> &mut Self {
self.omit_header = true;
self
}
pub fn header_row(&mut self, row: u8) -> &mut Self {
self.header_row = row;
self
}
pub fn max_row_count(&mut self, max: u32) -> &mut Self {
self.max = Some(max);
self
}
pub fn read_mode_async(&mut self) -> &mut Self {
self.read_mode = ReadMode::Async;
self
}
pub fn to_json(&self) -> Value {
json!({
"sheet": {
"key": self.sheet.clone().unwrap_or("".to_string()),
"index": self.index,
},
"path": self.path.clone().unwrap_or("".to_string()),
"euro_number_format": self.rows.euro_number_format,
"date_only": self.rows.date_only,
"columns": self.rows.columns.clone().into_iter().map(|c| c.to_json()).collect::<Vec<Value>>(),
"max": self.max.unwrap_or(0),
"header_row": self.header_row,
"omit_header": self.omit_header,
"jsonl": self.jsonl
})
}
pub fn header_row_index(&self) -> usize {
self.header_row as usize
}
pub fn max_rows(&self) -> usize {
if self.read_mode == ReadMode::PreviewAsync {
return 20
}
if let Some(mr) = self.max {
mr as usize
} else {
DEFAULT_MAX_ROWS
}
}
#[allow(dead_code)]
pub fn columns(&self) -> Vec<Column> {
self.rows.columns.clone()
}
pub fn read_mode(&self) -> ReadMode {
self.read_mode.clone()
}
pub fn is_async(&self) -> bool {
self.read_mode.is_async()
}
pub fn capture_rows(&self) -> bool {
match self.read_mode {
ReadMode::Async => false,
_ => true
}
}
}
#[derive(Debug, Clone)]
pub enum Format {
Auto, Text, Integer, Decimal(u8), Boolean, Date, DateTime, Truthy, #[allow(dead_code)]
TruthyCustom(Arc<str>, Arc<str>) }
impl ToString for Format {
fn to_string(&self) -> String {
let result = match self {
Self::Auto => "auto",
Self::Text => "text",
Self::Integer => "integer",
Self::Decimal(n) => &format!("decimal({})", n),
Self::Boolean => "boolean",
Self::Date => "date",
Self::DateTime => "datetime",
Self::Truthy => "truthy",
Self::TruthyCustom(yes, no) => &format!("truthy({},{})", yes, no),
};
result.to_string()
}
}
impl FromStr for Format {
type Err = Error;
fn from_str(key: &str) -> Result<Self, Self::Err> {
let fmt = match key {
"s" | "str" | "string" | "t" | "txt" | "text" => Self::Text,
"i" | "int" | "integer" => Self::Integer,
"d1" | "decimal_1" => Self::Decimal(1),
"d2" | "decimal_2" => Self::Decimal(2),
"d3" | "decimal_3" => Self::Decimal(3),
"d4" | "decimal_4" => Self::Decimal(4),
"d5" | "decimal_5" => Self::Decimal(5),
"d6" | "decimal_6" => Self::Decimal(6),
"b" | "bool" | "boolean" => Self::Boolean,
"da" | "date" => Self::Date,
"dt" | "datetime" => Self::DateTime,
"tr" | "truthy" => Self::Truthy,
_ => Self::Auto,
};
Ok(fmt)
}
}
impl Format {
#[allow(dead_code)]
pub fn truthy_custom(yes: &str, no: &str) -> Self {
Format::TruthyCustom(Arc::from(yes), Arc::from(no))
}
}
#[derive(Debug, Clone)]
pub struct Column {
pub key: Arc<str>,
pub format: Format,
pub default: Option<Value>,
pub date_only: bool, pub euro_number_format: bool, }
impl Column {
pub fn from_key_index(key_opt: Option<&str>, index: usize) -> Self {
Self::from_key_ref_with_format(key_opt, index, Format::Auto, None, false, false)
}
#[allow(dead_code)]
pub fn from_key_custom(key_opt: Option<&str>, index: usize, date_only: bool, euro_number_format: bool) -> Self {
Self::from_key_ref_with_format(key_opt, index, Format::Auto, None, date_only, euro_number_format)
}
pub fn from_key_ref_with_format(key_opt: Option<&str>, index: usize, format: Format, default: Option<Value>, date_only: bool, euro_number_format: bool) -> Self {
let key = key_opt.map(Arc::from).unwrap_or_else(|| Arc::from(to_head_key(index)));
Column {
key,
format,
default,
date_only,
euro_number_format
}
}
pub fn to_json(&self) -> Value {
json!({
"key": self.key.to_string(),
"format": self.format.to_string(),
"date_only": self.date_only,
"euro_number_format": self.euro_number_format,
"default": self.default
})
}
}
#[derive(Debug, Clone, Copy)]
pub enum Extension {
Unmatched,
Ods,
Xlsx,
Xls,
Csv,
Tsv,
}
impl Extension {
pub fn from_path(path:&Path) -> Extension {
if let Some(ext) = path.extension() {
if let Some(ext_str) = ext.to_str() {
let ext_lc = ext_str.to_lowercase();
return match ext_lc.as_str() {
"ods" => Extension::Ods,
"xlsx" => Extension::Xlsx,
"xls" => Extension::Xls,
"csv" => Extension::Csv,
"tsv" => Extension::Tsv,
_ => Extension::Unmatched
}
}
}
Extension::Unmatched
}
pub fn use_calamine(&self) -> bool {
match self {
Self::Ods | Self::Xlsx | Self::Xls => true,
_ => false
}
}
#[allow(dead_code)]
pub fn use_csv(&self) -> bool {
match self {
Self::Csv | Self::Tsv => true,
_ => false
}
}
}
impl ToString for Extension {
fn to_string(&self) -> String {
match self {
Self::Ods => "ods",
Self::Xlsx => "xlsx",
Self::Xls => "xls",
Self::Csv => "csv",
Self::Tsv => "tsv",
_ => ""
}.to_string()
}
}
pub struct PathData<'a> {
path: &'a Path,
ext: Extension
}
impl<'a> PathData<'a> {
pub fn new(path: &'a Path) -> Self {
PathData {
path,
ext: Extension::from_path(path)
}
}
pub fn mode(&self) -> Extension {
self.ext
}
pub fn extension(&self) -> String {
self.ext.to_string()
}
pub fn ext(&self) -> Extension {
self.ext
}
pub fn path(&self) -> &Path {
self.path
}
pub fn is_valid(&self) -> bool {
match self.ext {
Extension::Unmatched => false,
_ => true
}
}
pub fn use_calamine(&self) -> bool {
self.ext.use_calamine()
}
pub fn filename(&self) -> String {
if let Some(file_ref) = self.path.file_name() {
file_ref.to_string_lossy().to_string()
} else {
"".to_owned()
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub enum ReadMode {
#[default]
Sync,
PreviewAsync,
Async
}
impl ReadMode {
pub fn is_async(&self) -> bool {
match self {
Self::Sync => false,
_ => true
}
}
pub fn is_full_async(&self) -> bool {
match self {
Self::Async => true,
_ => false
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_format_mode() {
let custom_boolean = Format::truthy_custom("si", "no");
assert_eq!(custom_boolean.to_string(), "truthy(si,no)");
}
}