excel_cli/excel/workbook/
save.rs1use anyhow::Result;
2use chrono::Local;
3use rust_xlsxwriter::{Format, Workbook as XlsxWorkbook, Worksheet};
4use std::path::{Path, PathBuf};
5
6use super::Workbook;
7use crate::excel::{Cell, CellType, Sheet};
8
9impl Workbook {
10 pub fn save(&mut self) -> Result<()> {
11 if !self.is_modified {
12 println!("No changes to save.");
13 return Ok(());
14 }
15
16 self.ensure_all_sheets_loaded()?;
17
18 let mut workbook = XlsxWorkbook::new();
19 let new_filepath = timestamped_save_path(&self.file_path);
20 let number_format = Format::new().set_num_format("General");
21 let date_format = Format::new().set_num_format("yyyy-mm-dd");
22
23 for sheet in &self.sheets {
24 write_sheet(&mut workbook, sheet, &number_format, &date_format)?;
25 }
26
27 workbook.save(&new_filepath)?;
28 self.is_modified = false;
29
30 Ok(())
31 }
32}
33
34fn timestamped_save_path(file_path: &str) -> PathBuf {
35 let timestamp = Local::now().format("%Y%m%d_%H%M%S").to_string();
36 let path = Path::new(file_path);
37 let file_stem = path.file_stem().and_then(|s| s.to_str()).unwrap_or("sheet");
38 let extension = path.extension().and_then(|s| s.to_str()).unwrap_or("xlsx");
39 let parent_dir = path.parent().unwrap_or_else(|| Path::new(""));
40 parent_dir.join(format!("{file_stem}_{timestamp}.{extension}"))
41}
42
43fn write_sheet(
44 workbook: &mut XlsxWorkbook,
45 sheet: &Sheet,
46 number_format: &Format,
47 date_format: &Format,
48) -> Result<()> {
49 let worksheet = workbook.add_worksheet().set_name(&sheet.name)?;
50
51 if sheet.freeze_panes.is_frozen() {
52 worksheet.set_freeze_panes(
53 sheet.freeze_panes.rows as u32,
54 sheet.freeze_panes.cols as u16,
55 )?;
56 }
57
58 for col in 0..sheet.max_cols {
59 worksheet.set_column_width(col as u16, 15)?;
60 }
61
62 for row in 1..sheet.data.len() {
63 if row > sheet.max_rows {
64 continue;
65 }
66
67 for col in 1..sheet.data[0].len() {
68 if col > sheet.max_cols {
69 continue;
70 }
71
72 let cell = &sheet.data[row][col];
73 if cell.value.is_empty() {
74 continue;
75 }
76
77 let row_idx = (row - 1) as u32;
78 let col_idx = (col - 1) as u16;
79 write_cell(
80 worksheet,
81 cell,
82 row_idx,
83 col_idx,
84 number_format,
85 date_format,
86 )?;
87 }
88 }
89
90 Ok(())
91}
92
93fn write_cell(
94 worksheet: &mut Worksheet,
95 cell: &Cell,
96 row_idx: u32,
97 col_idx: u16,
98 number_format: &Format,
99 date_format: &Format,
100) -> Result<()> {
101 if cell.is_formula {
102 let formula_text = cell.formula.as_deref().unwrap_or(cell.value.as_str());
103 let formula = rust_xlsxwriter::Formula::new(formula_text);
104 worksheet.write_formula(row_idx, col_idx, formula)?;
105 if !cell.value.is_empty() && cell.value != formula_text {
106 worksheet.set_formula_result(row_idx, col_idx, &cell.value);
107 }
108 return Ok(());
109 }
110
111 match cell.cell_type {
112 CellType::Number => {
113 if let Ok(num) = cell.value.parse::<f64>() {
114 worksheet.write_number_with_format(row_idx, col_idx, num, number_format)?;
115 } else {
116 worksheet.write_string(row_idx, col_idx, &cell.value)?;
117 }
118 }
119 CellType::Date => {
120 worksheet.write_string_with_format(row_idx, col_idx, &cell.value, date_format)?;
121 }
122 CellType::Boolean => {
123 if let Ok(b) = cell.value.parse::<bool>() {
124 worksheet.write_boolean(row_idx, col_idx, b)?;
125 } else {
126 worksheet.write_string(row_idx, col_idx, &cell.value)?;
127 }
128 }
129 CellType::Text => {
130 worksheet.write_string(row_idx, col_idx, &cell.value)?;
131 }
132 CellType::Empty => {}
133 }
134
135 Ok(())
136}