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