1use std::path::Path;
2
3use basic_xlsx_writer::CellValue;
4use calamine::{open_workbook, DataType, Range, Reader, Xlsx};
5use err_derive::Error;
6use indexmap::IndexMap;
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Error)]
10pub enum Error {
11 #[error(display = "Calamine error: {}", _0)]
12 Calamine(calamine::Error),
13 #[error(display = "IO error: {}", _0)]
14 IoError(#[error(cause)] std::io::Error),
15 #[error(display = "RON error: {}", _0)]
16 RonDeError(#[error(cause)] ron::de::Error),
17 #[error(display = "RON error: {}", _0)]
18 RonSerError(#[error(cause)] ron::ser::Error),
19}
20
21impl From<calamine::Error> for Error {
22 fn from(e: calamine::Error) -> Self {
23 Error::Calamine(e)
24 }
25}
26
27impl From<calamine::XlsxError> for Error {
28 fn from(e: calamine::XlsxError) -> Self {
29 Error::Calamine(e.into())
30 }
31}
32
33impl From<std::io::Error> for Error {
34 fn from(e: std::io::Error) -> Self {
35 Error::IoError(e)
36 }
37}
38
39impl From<ron::de::Error> for Error {
40 fn from(e: ron::de::Error) -> Self {
41 Error::RonDeError(e)
42 }
43}
44
45impl From<ron::ser::Error> for Error {
46 fn from(e: ron::ser::Error) -> Self {
47 Error::RonSerError(e)
48 }
49}
50
51#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize)]
54pub enum Value {
55 Int(i64),
57 Float(f64),
59 String(String),
61 Bool(bool),
63 Error,
65 Empty,
67}
68
69impl Value {
70 pub fn is_empty(&self) -> bool {
71 match self {
72 Value::Empty => true,
73 Value::String(s) if s.is_empty() => true,
74 _ => false,
75 }
76 }
77
78 pub fn to_string(&self) -> String {
79 match self {
80 Value::Int(x) => x.to_string(),
81 Value::Float(f) => f.to_string(),
82 Value::String(s) => s.clone(),
83 Value::Bool(s) => s.to_string(),
84 Value::Error => "Error".to_owned(),
85 Value::Empty => "".to_owned(),
86 }
87 }
88}
89
90impl Into<CellValue> for Value {
91 fn into(self: Value) -> CellValue {
92 match self {
93 Value::Int(i) => CellValue::Number(i as f64),
94 Value::Float(f) => CellValue::Number(f),
95 Value::Bool(b) => CellValue::Bool(b),
96 Value::String(s) => CellValue::String(s),
97 _ => CellValue::String("".to_owned()),
98 }
99 }
100}
101
102impl From<DataType> for Value {
103 fn from(d: DataType) -> Self {
104 match d {
105 DataType::Int(i) => Value::Int(i),
106 DataType::Float(f) => Value::Float(f),
107 DataType::String(s) => Value::String(s),
108 DataType::Bool(b) => Value::Bool(b),
109 DataType::Error(_) => Value::Error,
110 DataType::Empty => Value::Empty,
111 }
112 }
113}
114
115#[derive(Clone, Debug, Default, Deserialize, Serialize)]
116pub struct RonWorkbook {
117 pub sheets: IndexMap<String, RonWorksheet>,
118}
119
120impl RonWorkbook {
121 pub fn read_xlsx<P: AsRef<Path>>(file: P) -> Result<RonWorkbook, Error> {
122 let mut workbook: Xlsx<_> = open_workbook(file)?;
123
124 let mut ron_workbook = RonWorkbook {
125 sheets: Default::default(),
126 };
127
128 let sheet_names = workbook.sheet_names().to_vec();
129 for sheet in sheet_names {
130 let worksheet: Range<DataType> =
131 workbook.worksheet_range(&sheet).expect("This was Murphy")?;
132
133 let mut ron_worksheet = RonWorksheet {
134 rows: Default::default(),
135 };
136
137 for row in worksheet.rows() {
138 let l = ron_worksheet.rows.len();
139 ron_worksheet
140 .rows
141 .insert(l, row.iter().map(|x| x.clone().into()).collect());
142 }
143
144 ron_workbook.sheets.insert(sheet, ron_worksheet);
145 }
146
147 Ok(ron_workbook)
148 }
149
150 pub fn read_ron<P: AsRef<Path>>(file: P) -> Result<RonWorkbook, Error> {
151 let s = std::fs::read_to_string(file)?;
152 let workbook = ron::de::from_str(&s)?;
153
154 Ok(workbook)
155 }
156
157 pub fn write_xlsx<P: AsRef<Path>>(
158 &self,
159 file: P,
160 col_width: f32,
161 skip_empty: bool,
162 ) -> Result<(), Error> {
163 use basic_xlsx_writer::*;
164
165 let file = file.as_ref();
166
167 let mut wb = Workbook::create(file)?;
168
169 for (sheet_name, sheet) in self.sheets.iter() {
170 let mut sheet_rows = sheet.rows.clone();
171 sheet_rows.sort_keys();
172
173 let num_rows = sheet_rows.values().map(|row| row.len()).max().unwrap_or(0);
174
175 let mut xlsx_sheet = wb.create_sheet(sheet_name);
176
177 for _ in 0..num_rows {
178 xlsx_sheet.add_column(Column { width: col_width });
179 }
180
181 wb.write_sheet(&mut xlsx_sheet, |sheet_writer| {
182 let sw = sheet_writer;
183
184 let mut last_row_number = 0;
185 for (row_number, row) in sheet_rows {
186 if !skip_empty && row_number > last_row_number + 1 {
187 sw.append_blank_rows(row_number - (last_row_number + 1));
188 }
189
190 sw.append_row(Row::from_iter(
191 row.into_iter().map(|v| Value::to_string(&v)),
192 ))?;
193 last_row_number = row_number;
194 }
195
196 Ok(())
197 })?;
198 }
199
200 wb.close()?;
201
202 Ok(())
203 }
204
205 pub fn retain_unique(&mut self, column: usize) {
206 for (name, sheet) in &mut self.sheets {
207 println!("Sheet {:?}", name);
208
209 let dups = sheet.find_duplicates(column);
210 for dup in dups {
211 println!("Removing line {}...", dup + 1);
212 sheet.rows.remove(&dup);
213 }
214 }
215 }
216
217 pub fn retain_intersecting(&mut self, compare: &RonWorkbook) {
218 for (sheet, sheet_new) in self.sheets.values_mut().zip(compare.sheets.values()) {
219 let mut remove_rows: Vec<usize> = vec![];
220
221 for (i, value) in sheet.rows.iter() {
222 if !sheet_new.rows.values().any(|val| val[0] == value[0]) {
223 remove_rows.push(*i);
224 }
225 }
226
227 for row in remove_rows {
228 sheet.rows.remove(&row);
229 }
230 sheet.rows.sort_keys();
231 }
232 }
233}
234
235#[derive(Clone, Debug, Default, Deserialize, Serialize)]
236pub struct RonWorksheet {
237 pub rows: IndexMap<usize, Vec<Value>>,
238}
239
240impl RonWorksheet {
241 pub fn find_duplicates(&self, column: usize) -> Vec<usize> {
242 let mut encountered = IndexMap::new();
243 let mut duplicates = vec![];
244
245 for (index, value) in self.rows.iter() {
246 let value = value.get(column).unwrap_or(&Value::Empty).to_string();
247
248 if value.is_empty() {
249 continue;
250 }
251
252 if encountered.get(&value).is_some() {
253 println!(
254 "Found duplicate \"{}\" on line {} (first encounter: {})",
255 value,
256 index + 1,
257 encountered[&value] + 1
258 );
259 duplicates.push(*index);
260 } else {
261 encountered.insert(value, *index);
262 }
263 }
264
265 duplicates
266 }
267}