xron/
lib.rs

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/// An enum to represent all different data types that can appear as
52/// a value in a worksheet cell
53#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize)]
54pub enum Value {
55    /// Unsigned integer
56    Int(i64),
57    /// Float
58    Float(f64),
59    /// String
60    String(String),
61    /// Boolean
62    Bool(bool),
63    /// Error
64    Error,
65    /// Empty cell
66    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}