use std::path::Path;
use basic_xlsx_writer::CellValue;
use calamine::{open_workbook, DataType, Range, Reader, Xlsx};
use err_derive::Error;
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
#[derive(Debug, Error)]
pub enum Error {
#[error(display = "Calamine error: {}", _0)]
Calamine(calamine::Error),
#[error(display = "IO error: {}", _0)]
IoError(#[error(cause)] std::io::Error),
#[error(display = "RON error: {}", _0)]
RonDeError(#[error(cause)] ron::de::Error),
#[error(display = "RON error: {}", _0)]
RonSerError(#[error(cause)] ron::ser::Error),
}
impl From<calamine::Error> for Error {
fn from(e: calamine::Error) -> Self {
Error::Calamine(e)
}
}
impl From<calamine::XlsxError> for Error {
fn from(e: calamine::XlsxError) -> Self {
Error::Calamine(e.into())
}
}
impl From<std::io::Error> for Error {
fn from(e: std::io::Error) -> Self {
Error::IoError(e)
}
}
impl From<ron::de::Error> for Error {
fn from(e: ron::de::Error) -> Self {
Error::RonDeError(e)
}
}
impl From<ron::ser::Error> for Error {
fn from(e: ron::ser::Error) -> Self {
Error::RonSerError(e)
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize)]
pub enum Value {
Int(i64),
Float(f64),
String(String),
Bool(bool),
Error,
Empty,
}
impl Value {
pub fn is_empty(&self) -> bool {
match self {
Value::Empty => true,
Value::String(s) if s.is_empty() => true,
_ => false,
}
}
pub fn to_string(&self) -> String {
match self {
Value::Int(x) => x.to_string(),
Value::Float(f) => f.to_string(),
Value::String(s) => s.clone(),
Value::Bool(s) => s.to_string(),
Value::Error => "Error".to_owned(),
Value::Empty => "".to_owned(),
}
}
}
impl Into<CellValue> for Value {
fn into(self: Value) -> CellValue {
match self {
Value::Int(i) => CellValue::Number(i as f64),
Value::Float(f) => CellValue::Number(f),
Value::Bool(b) => CellValue::Bool(b),
Value::String(s) => CellValue::String(s),
_ => CellValue::String("".to_owned()),
}
}
}
impl From<DataType> for Value {
fn from(d: DataType) -> Self {
match d {
DataType::Int(i) => Value::Int(i),
DataType::Float(f) => Value::Float(f),
DataType::String(s) => Value::String(s),
DataType::Bool(b) => Value::Bool(b),
DataType::Error(_) => Value::Error,
DataType::Empty => Value::Empty,
}
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct RonWorkbook {
pub sheets: IndexMap<String, RonWorksheet>,
}
impl RonWorkbook {
pub fn read_xlsx<P: AsRef<Path>>(file: P) -> Result<RonWorkbook, Error> {
let mut workbook: Xlsx<_> = open_workbook(file)?;
let mut ron_workbook = RonWorkbook {
sheets: Default::default(),
};
let sheet_names = workbook.sheet_names().to_vec();
for sheet in sheet_names {
let worksheet: Range<DataType> =
workbook.worksheet_range(&sheet).expect("This was Murphy")?;
let mut ron_worksheet = RonWorksheet {
rows: Default::default(),
};
for row in worksheet.rows() {
let l = ron_worksheet.rows.len();
ron_worksheet
.rows
.insert(l, row.iter().map(|x| x.clone().into()).collect());
}
ron_workbook.sheets.insert(sheet, ron_worksheet);
}
Ok(ron_workbook)
}
pub fn read_ron<P: AsRef<Path>>(file: P) -> Result<RonWorkbook, Error> {
let s = std::fs::read_to_string(file)?;
let workbook = ron::de::from_str(&s)?;
Ok(workbook)
}
pub fn write_xlsx<P: AsRef<Path>>(
&self,
file: P,
col_width: f32,
skip_empty: bool,
) -> Result<(), Error> {
use basic_xlsx_writer::*;
let file = file.as_ref();
let mut wb = Workbook::create(file)?;
for (sheet_name, sheet) in self.sheets.iter() {
let mut sheet_rows = sheet.rows.clone();
sheet_rows.sort_keys();
let num_rows = sheet_rows.values().map(|row| row.len()).max().unwrap_or(0);
let mut xlsx_sheet = wb.create_sheet(sheet_name);
for _ in 0..num_rows {
xlsx_sheet.add_column(Column { width: col_width });
}
wb.write_sheet(&mut xlsx_sheet, |sheet_writer| {
let sw = sheet_writer;
let mut last_row_number = 0;
for (row_number, row) in sheet_rows {
if !skip_empty && row_number > last_row_number + 1 {
sw.append_blank_rows(row_number - (last_row_number + 1));
}
sw.append_row(Row::from_iter(
row.into_iter().map(|v| Value::to_string(&v)),
))?;
last_row_number = row_number;
}
Ok(())
})?;
}
wb.close()?;
Ok(())
}
pub fn retain_unique(&mut self, column: usize) {
for (name, sheet) in &mut self.sheets {
println!("Sheet {:?}", name);
let dups = sheet.find_duplicates(column);
for dup in dups {
println!("Removing line {}...", dup + 1);
sheet.rows.remove(&dup);
}
}
}
pub fn retain_intersecting(&mut self, compare: &RonWorkbook) {
for (sheet, sheet_new) in self.sheets.values_mut().zip(compare.sheets.values()) {
let mut remove_rows: Vec<usize> = vec![];
for (i, value) in sheet.rows.iter() {
if !sheet_new.rows.values().any(|val| val[0] == value[0]) {
remove_rows.push(*i);
}
}
for row in remove_rows {
sheet.rows.remove(&row);
}
sheet.rows.sort_keys();
}
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct RonWorksheet {
pub rows: IndexMap<usize, Vec<Value>>,
}
impl RonWorksheet {
pub fn find_duplicates(&self, column: usize) -> Vec<usize> {
let mut encountered = IndexMap::new();
let mut duplicates = vec![];
for (index, value) in self.rows.iter() {
let value = value.get(column).unwrap_or(&Value::Empty).to_string();
if value.is_empty() {
continue;
}
if encountered.get(&value).is_some() {
println!(
"Found duplicate \"{}\" on line {} (first encounter: {})",
value,
index + 1,
encountered[&value] + 1
);
duplicates.push(*index);
} else {
encountered.insert(value, *index);
}
}
duplicates
}
}