1use crate::types::{CellEdit, CoreWarning};
2use anyhow::{Context, Result, anyhow, bail};
3use std::path::Path;
4
5pub fn normalize_shorthand_edit(entry: &str) -> Result<(CellEdit, Vec<CoreWarning>)> {
6 let Some((address_raw, rhs_raw)) = entry.split_once('=') else {
7 bail!("invalid shorthand edit: '{entry}' (expected like 'A1=100' or 'B2==SUM(A1:A2)')");
8 };
9
10 let address = address_raw.trim();
11 if address.is_empty() {
12 bail!("invalid shorthand edit: '{entry}' (missing cell address before '=')");
13 }
14
15 let mut warnings = vec![CoreWarning {
16 code: "WARN_SHORTHAND_EDIT".to_string(),
17 message: format!("Parsed shorthand edit '{}'", entry),
18 }];
19
20 let rhs_trimmed = rhs_raw.trim_start();
21 if let Some(stripped) = rhs_trimmed.strip_prefix('=') {
22 warnings.push(CoreWarning {
23 code: "WARN_FORMULA_PREFIX".to_string(),
24 message: format!("Stripped leading '=' for formula '{}'", entry),
25 });
26 Ok((
27 CellEdit {
28 address: address.to_string(),
29 value: stripped.to_string(),
30 is_formula: true,
31 },
32 warnings,
33 ))
34 } else {
35 Ok((
36 CellEdit {
37 address: address.to_string(),
38 value: rhs_raw.to_string(),
39 is_formula: false,
40 },
41 warnings,
42 ))
43 }
44}
45
46pub fn normalize_object_edit(
47 address: &str,
48 value: Option<String>,
49 formula: Option<String>,
50 is_formula: Option<bool>,
51) -> Result<(CellEdit, Vec<CoreWarning>)> {
52 let address = address.trim();
53 if address.is_empty() {
54 bail!("edit address is required");
55 }
56
57 let mut warnings = Vec::new();
58 let (value, is_formula) = if let Some(formula) = formula {
59 if let Some(stripped) = formula.strip_prefix('=') {
60 warnings.push(CoreWarning {
61 code: "WARN_FORMULA_PREFIX".to_string(),
62 message: format!("Stripped leading '=' for formula at {}", address),
63 });
64 (stripped.to_string(), true)
65 } else {
66 (formula, true)
67 }
68 } else if let Some(value) = value {
69 if let Some(stripped) = value.strip_prefix('=') {
70 warnings.push(CoreWarning {
71 code: "WARN_FORMULA_PREFIX".to_string(),
72 message: format!("Stripped leading '=' for formula at {}", address),
73 });
74 (stripped.to_string(), true)
75 } else {
76 (value, is_formula.unwrap_or(false))
77 }
78 } else {
79 return Err(anyhow!("edit value or formula is required for {address}"));
80 };
81
82 Ok((
83 CellEdit {
84 address: address.to_string(),
85 value,
86 is_formula,
87 },
88 warnings,
89 ))
90}
91
92pub fn apply_edits_to_file(path: &Path, sheet_name: &str, edits: &[CellEdit]) -> Result<()> {
93 let mut book = umya_spreadsheet::reader::xlsx::read(path)
94 .with_context(|| format!("failed to open workbook '{}'", path.display()))?;
95
96 let sheet = book
97 .get_sheet_by_name_mut(sheet_name)
98 .ok_or_else(|| anyhow!("sheet '{}' not found", sheet_name))?;
99
100 for edit in edits {
101 let cell = sheet.get_cell_mut(edit.address.as_str());
102 if edit.is_formula {
103 cell.set_formula(edit.value.clone());
104 cell.get_cell_value_mut()
105 .set_formula_result_default(String::new());
106 } else {
107 cell.set_value(edit.value.clone());
108 }
109 }
110
111 umya_spreadsheet::writer::xlsx::write(&book, path)
112 .with_context(|| format!("failed to save workbook '{}'", path.display()))?;
113 Ok(())
114}