use hashbrown::HashMap;
use once_cell::sync::Lazy;
use rayon::prelude::*;
use rust_xlsxwriter::{DocProperties, Format, FormatAlign, Workbook, Worksheet, XlsxSerialize};
use serde::Serialize;
use std::path::PathBuf;
use crate::MyResult;
const HEADER_SIZE: f64 = 11.0;
const FONT_SIZE: f64 = 12.0;
const FONT_NAME: &str = "Liberation Mono";
const MAX_NUMBER_OF_ROWS: usize = 1_000_000;
pub fn write_xlsx<T>(lines: &[T], sheet_name: &str, path: PathBuf) -> MyResult<()>
where
T: Serialize + XlsxSerialize + Sync,
{
if lines.is_empty() {
return Ok(());
}
let worksheets: MyResult<Vec<Worksheet>> = lines
.par_chunks(MAX_NUMBER_OF_ROWS) .enumerate()
.map(|(index, data)| -> MyResult<Worksheet> {
let mut new_name = sheet_name.to_string();
if index >= 1 {
new_name = format!("{} {}", sheet_name, index + 1);
}
let worksheet: Worksheet = get_worksheet(data, &new_name)?;
Ok(worksheet)
})
.collect();
let mut workbook = Workbook::new();
let properties = get_properties()?;
workbook.set_properties(&properties);
worksheets?.into_iter().for_each(|worksheet| {
workbook.push_worksheet(worksheet);
});
workbook.save(&path).map_err(|xlsx_error| {
eprintln!("fn write_xlsx()");
eprintln!("Error: Failed to write XLSX file {:?}", path);
xlsx_error
})?;
Ok(())
}
fn get_properties() -> MyResult<DocProperties> {
let properties = DocProperties::new()
.set_title("Find Duplicate Files")
.set_subject("Find duplicate files according to their size and hashing algorithm")
.set_author("Claudio FSR (https://github.com/claudiofsr/find_duplicate_files)")
.set_keywords("find, duplicate, hash algorithm")
.set_comment("Built with Rust")
.set_hyperlink_base("https://github.com/claudiofsr/find_duplicate_files");
Ok(properties)
}
fn get_worksheet<T>(lines: &[T], sheet_name: &str) -> MyResult<Worksheet>
where
T: Serialize + XlsxSerialize,
{
let mut worksheet = Worksheet::new();
let fmt_header = get_xlsx_format("header");
worksheet
.set_name(sheet_name)?
.set_row_height(0, 32)?
.set_row_format(0, fmt_header)?
.set_freeze_panes(1, 0)?;
worksheet.set_serialize_headers::<T>(0, 0)?;
for line in lines {
worksheet.serialize(line)?;
}
worksheet.autofit();
Ok(worksheet)
}
static XLSX_FORMATS: Lazy<HashMap<&'static str, Format>> = Lazy::new(|| {
let fmt_header: Format = Format::new()
.set_align(FormatAlign::Center) .set_align(FormatAlign::VerticalCenter)
.set_text_wrap()
.set_font_size(HEADER_SIZE);
let fmt_center = Format::new()
.set_align(FormatAlign::Center)
.set_align(FormatAlign::VerticalCenter)
.set_font_name(FONT_NAME)
.set_font_size(FONT_SIZE);
let fmt_integer = Format::new()
.set_align(FormatAlign::VerticalCenter)
.set_num_format("#,##0")
.set_font_name(FONT_NAME)
.set_font_size(FONT_SIZE);
let fmt_default = Format::new()
.set_align(FormatAlign::VerticalCenter)
.set_font_name(FONT_NAME)
.set_font_size(FONT_SIZE);
let formats = [
("header", fmt_header),
("center", fmt_center),
("integer", fmt_integer),
("default", fmt_default),
];
HashMap::from(formats)
});
pub fn get_xlsx_format(name: &str) -> &Format {
match XLSX_FORMATS.get(name) {
Some(format) => format,
None => panic!("Format {} not defined!", name),
}
}