1 #![deny(clippy::all)]
2 #![warn(clippy::pedantic)]
3 #![allow(clippy::similar_names)]
4 
5 use calamine::{open_workbook_auto, DataType, Reader};
6 use std::error::Error;
7 use std::fmt::{self, Display, Debug, Formatter, Write};
8 use std::io;
9 use clap::{App, Arg, crate_name, crate_version};
10 use std::convert::TryFrom;
11 
12 pub enum Errors {
13     InvalidSeparator,
14     Empty, NotFound(String),
15     Csv(csv::Error),
16     Spreadsheet(calamine::Error),
17     CellError(calamine::CellErrorType),
18 }
19 
20 impl Error for Errors {}
21 // delegate to Display so the error message is not crap
22 impl Debug for Errors {
fmt(&self, fmt: &mut Formatter) -> fmt::Result23     fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
24         (&self as &dyn Display).fmt(fmt)
25     }
26 }
27 impl Display for Errors {
fmt(&self, fmt: &mut Formatter) -> fmt::Result28     fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
29         use Errors::*;
30         match self {
31             Empty => write!(fmt, "Empty spreadsheet"),
32             NotFound(ref s) => write!(fmt, "Could not find sheet \"{}\" in spreadsheet", s),
33             InvalidSeparator => write!(fmt, "A provided separator is invalid, separators need to be a single ascii chacter"),
34             Csv(ref e) => write!(fmt, "{}", e),
35             Spreadsheet(ref e) => write!(fmt, "{}", e),
36             CellError(ref e) => write!(fmt, "Error found in cell ({:?})", e)
37         }
38     }
39 }
40 impl From<csv::Error> for Errors {
from(err: csv::Error) -> Self41     fn from(err: csv::Error) -> Self {
42         Self::Csv(err)
43     }
44 }
45 impl From<calamine::Error> for Errors {
from(err: calamine::Error) -> Self46     fn from(err: calamine::Error) -> Self {
47         Self::Spreadsheet(err)
48     }
49 }
50 
separator_to_byte(s: &str) -> Result<u8, Errors>51 fn separator_to_byte(s: &str) -> Result<u8, Errors> {
52     s.chars().next()
53         .ok_or(Errors::InvalidSeparator)
54         .and_then(|c| u8::try_from(c as u32).map_err(|_| Errors::InvalidSeparator))
55 }
run(n: &'static str, default_rs: &'static str, default_fs: &'static str) -> Result<(), Errors>56 pub fn run(n: &'static str, default_rs: &'static str, default_fs: &'static str) -> Result<(), Errors> {
57 
58     let matches = App::new(crate_name!())
59         .version(crate_version!())
60         .about("Converts spreadsheets to text")
61         .long_about(&format!("Converts the first sheet of the spreadsheet at PATH (or <sheet> if \
62 requested) to {} sent to stdout.
63 
64 Should be able to convert from (and automatically guess between) XLS, XLSX, XLSB and ODS.", n) as &str)
65         .arg(Arg::with_name("PATH").help("Spreadsheet file path").required(true))
66         .arg(Arg::with_name("sheet")
67              .short("s").long("sheet")
68              .default_value("1")
69              .help("Name or index (1 is first) of the sheet to convert")
70         )
71         .arg(Arg::with_name("RS")
72              .short("r").long("rs").long("record-separator")
73              .takes_value(true)
74              .help("Record separator (a single character)")
75         )
76         .arg(Arg::with_name("FS")
77              .short("f").long("fs").long("field-separator")
78              .takes_value(true)
79              .help("Field separator (a single character)")
80         )
81         .get_matches();
82 
83     let rs = separator_to_byte(matches.value_of("RS").unwrap_or(default_rs))?;
84     let fs = separator_to_byte(matches.value_of("FS").unwrap_or(default_fs))?;
85 
86     let mut workbook = open_workbook_auto(matches.value_of("PATH").unwrap())?;
87 
88     let sheet = matches.value_of("sheet").unwrap_or("1");
89     // if sheet is a number get corresponding sheet in list
90     let name = String::from(
91         sheet.parse::<usize>().ok()
92             .and_then(|n| workbook.sheet_names().get(n.saturating_sub(1)))
93             .map_or(sheet, |s| s as &str)
94     );
95 
96     let range = if let Some(Ok(r)) = workbook.worksheet_range(&name) {
97         r
98     } else {
99         return Err(Errors::NotFound(name));
100     };
101 
102     let stdout = io::stdout();
103     let mut out = csv::WriterBuilder::new()
104         .terminator(csv::Terminator::Any(rs))
105         .delimiter(fs)
106         .from_writer(stdout.lock());
107 
108     let mut contents = vec![String::new();range.width()];
109     for row in range.rows() {
110         for (c, cell) in row.iter().zip(contents.iter_mut()) {
111             cell.clear();
112             match *c {
113                 DataType::Error(ref e) => return Err(Errors::CellError(e.clone())),
114                 // don't go through fmt for strings
115                 DataType::String(ref s) => cell.push_str(s),
116                 ref rest => write!(cell, "{}", rest).expect("formatting basic types to a string should never fail"),
117             };
118         }
119         out.write_record(&contents)?;
120     }
121 
122     Ok(())
123 }
124