1use crate::error::SheetsDiffError;
4
5#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
11pub enum FormulaCompareMode {
12 #[default]
14 RawText,
15 NormalizedText,
18 RawAndNormalized,
20 Ignore,
22}
23
24#[derive(Clone, Copy, PartialEq, Debug, Default)]
30pub enum NumberComparePolicy {
31 #[default]
33 Exact,
34 AbsoluteTolerance(f64),
35 RelativeTolerance(f64),
36 AbsoluteOrRelative { abs: f64, rel: f64 },
37}
38
39#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
41pub enum NumericTypePolicy {
42 #[default]
44 PreserveType,
45 CompareMathematicalValue,
47}
48
49#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
51pub enum DateComparePolicy {
52 #[default]
54 ExactRepresentation,
55 NormalizeEquivalentDateTimes,
57}
58
59#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
61pub enum TypeMismatchPolicy {
62 #[default]
64 Different,
65 CompareDisplayString,
67}
68
69#[derive(Clone, Debug, Default)]
71pub struct ValueCompareOptions {
72 pub number: NumberComparePolicy,
73 pub numeric_type: NumericTypePolicy,
74 pub date: DateComparePolicy,
75 pub type_mismatch: TypeMismatchPolicy,
76}
77
78#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
87pub enum FormatCompareMode {
88 #[default]
90 Ignore,
91 NumberFormatOnly,
93 AllAvailable,
95}
96
97#[derive(Clone, Debug)]
103pub struct ComparisonOptions {
104 pub value: ValueCompareOptions,
105 pub formula: FormulaCompareMode,
106 pub include_formula_cached_values: bool,
108 pub format: FormatCompareMode,
110}
111
112impl Default for ComparisonOptions {
113 fn default() -> Self {
114 Self {
115 value: ValueCompareOptions::default(),
116 formula: FormulaCompareMode::default(),
117 include_formula_cached_values: true,
118 format: FormatCompareMode::default(),
119 }
120 }
121}
122
123#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
129pub enum SheetMatchingMode {
130 ExactNameOnly,
132 #[default]
135 ExactNameThenConservativeRename,
136 ExactNameThenIndex,
138}
139
140#[allow(dead_code)]
142#[derive(Clone, Debug, Default)]
143pub enum AlignmentMode {
144 #[default]
146 Positional,
147 RowKey { columns: Vec<u32> },
150 RowSignature { sample_columns: Option<Vec<u32>> },
154 #[allow(dead_code)]
156 HeaderColumn,
157}
158
159#[derive(Clone, Debug, Default)]
161pub struct MatchingOptions {
162 pub sheet_matching: SheetMatchingMode,
163 pub alignment: AlignmentMode,
164}
165
166#[derive(Clone, Debug, Default)]
174pub struct Limits {
175 pub max_sheets: Option<u32>,
176 pub max_cells_read: Option<u64>,
177 pub max_cells_compared: Option<u64>,
178 pub max_diffs_returned: Option<u64>,
179}
180
181#[derive(Clone, Debug)]
187pub enum DiffEvent {
188 Started,
189 OpeningWorkbook { side: crate::model::Side },
190 WorkbookOpened { side: crate::model::Side, sheet_count: usize },
191 MatchingSheets,
192 SheetStarted { index: usize, total: usize, name: String },
193 SheetFinished { index: usize, changed_cells: usize },
194 Finished,
195}
196
197pub trait ProgressSink: Send {
202 fn on_event(&mut self, event: DiffEvent);
203}
204
205impl<F: FnMut(DiffEvent) + Send> ProgressSink for F {
206 fn on_event(&mut self, event: DiffEvent) {
207 self(event);
208 }
209}
210
211pub trait Cancellation: Send + Sync {
213 fn is_cancelled(&self) -> bool;
214}
215
216impl<F: Fn() -> bool + Send + Sync> Cancellation for F {
217 fn is_cancelled(&self) -> bool {
218 self()
219 }
220}
221
222#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
224pub enum ExecutionMode {
225 #[default]
227 Sequential,
228 }
230
231pub struct ExecutionOptions {
233 pub progress: Option<Box<dyn ProgressSink>>,
234 pub cancellation: Option<Box<dyn Cancellation>>,
235 pub mode: ExecutionMode,
236}
237
238impl Default for ExecutionOptions {
239 fn default() -> Self {
240 Self {
241 progress: None,
242 cancellation: None,
243 mode: ExecutionMode::default(),
244 }
245 }
246}
247
248#[derive(Clone, Debug, Default)]
253pub struct DiagnosticOptions {
254 pub min_severity: Option<crate::model::Severity>,
256}
257
258#[derive(Clone, Debug, Default)]
263pub struct OutputOptions {
264 }
266
267pub struct DiffOptions {
275 pub comparison: ComparisonOptions,
276 pub matching: MatchingOptions,
277 pub limits: Limits,
278 pub execution: ExecutionOptions,
279 pub diagnostics: DiagnosticOptions,
280 pub output: OutputOptions,
281}
282
283impl Default for DiffOptions {
284 fn default() -> Self {
285 Self {
286 comparison: ComparisonOptions::default(),
287 matching: MatchingOptions::default(),
288 limits: Limits::default(),
289 execution: ExecutionOptions::default(),
290 diagnostics: DiagnosticOptions::default(),
291 output: OutputOptions::default(),
292 }
293 }
294}
295
296impl DiffOptions {
297 pub fn builder() -> DiffOptionsBuilder {
298 DiffOptionsBuilder::new()
299 }
300
301 pub(crate) fn validate(&self) -> Result<(), SheetsDiffError> {
303 if self.comparison.formula == FormulaCompareMode::NormalizedText
305 || self.comparison.formula == FormulaCompareMode::RawAndNormalized
306 {
307 return Err(SheetsDiffError::InvalidOptions {
308 detail: "FormulaCompareMode::NormalizedText / RawAndNormalized is not \
309 available in v2.0; no formula normaliser is implemented yet"
310 .into(),
311 });
312 }
313 if self.comparison.format != FormatCompareMode::Ignore {
315 return Err(SheetsDiffError::InvalidOptions {
316 detail: "FormatCompareMode other than Ignore is not available in v2; \
317 calamine 0.35 does not expose a cell-style API"
318 .into(),
319 });
320 }
321 Ok(())
322 }
323}
324
325#[derive(Default)]
333pub struct DiffOptionsBuilder {
334 opts: DiffOptions,
335}
336
337impl DiffOptionsBuilder {
338 pub fn new() -> Self {
339 Self { opts: DiffOptions::default() }
340 }
341
342 pub fn formula_compare(mut self, mode: FormulaCompareMode) -> Self {
345 self.opts.comparison.formula = mode;
346 self
347 }
348
349 pub fn format_compare(mut self, mode: FormatCompareMode) -> Self {
350 self.opts.comparison.format = mode;
351 self
352 }
353
354 pub fn include_formula_cached_values(mut self, yes: bool) -> Self {
355 self.opts.comparison.include_formula_cached_values = yes;
356 self
357 }
358
359 pub fn number_compare(mut self, policy: NumberComparePolicy) -> Self {
360 self.opts.comparison.value.number = policy;
361 self
362 }
363
364 pub fn numeric_type_policy(mut self, policy: NumericTypePolicy) -> Self {
365 self.opts.comparison.value.numeric_type = policy;
366 self
367 }
368
369 pub fn type_mismatch_policy(mut self, policy: TypeMismatchPolicy) -> Self {
370 self.opts.comparison.value.type_mismatch = policy;
371 self
372 }
373
374 pub fn sheet_matching(mut self, mode: SheetMatchingMode) -> Self {
377 self.opts.matching.sheet_matching = mode;
378 self
379 }
380
381 pub fn max_sheets(mut self, n: u32) -> Self {
384 self.opts.limits.max_sheets = Some(n);
385 self
386 }
387
388 pub fn max_cells_compared(mut self, n: u64) -> Self {
389 self.opts.limits.max_cells_compared = Some(n);
390 self
391 }
392
393 pub fn max_diffs_returned(mut self, n: u64) -> Self {
394 self.opts.limits.max_diffs_returned = Some(n);
395 self
396 }
397
398 pub fn progress<S: ProgressSink + 'static>(mut self, sink: S) -> Self {
401 self.opts.execution.progress = Some(Box::new(sink));
402 self
403 }
404
405 pub fn cancellation<C: Cancellation + 'static>(mut self, token: C) -> Self {
406 self.opts.execution.cancellation = Some(Box::new(token));
407 self
408 }
409
410 pub fn build_with_matching(mut self, matching: MatchingOptions) -> Result<DiffOptions, SheetsDiffError> {
412 self.opts.matching = matching;
413 self.opts.validate()?;
414 Ok(self.opts)
415 }
416
417 pub fn build(self) -> Result<DiffOptions, SheetsDiffError> {
419 self.opts.validate()?;
420 Ok(self.opts)
421 }
422}