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)]
264pub struct OutputOptions {
265 pub objects: crate::objects::ObjectCompareMode,
267}
268
269impl Default for OutputOptions {
270 fn default() -> Self {
271 Self { objects: crate::objects::ObjectCompareMode::WarnIfPresent }
272 }
273}
274
275pub struct DiffOptions {
283 pub comparison: ComparisonOptions,
284 pub matching: MatchingOptions,
285 pub limits: Limits,
286 pub execution: ExecutionOptions,
287 pub diagnostics: DiagnosticOptions,
288 pub output: OutputOptions,
289}
290
291impl Default for DiffOptions {
292 fn default() -> Self {
293 Self {
294 comparison: ComparisonOptions::default(),
295 matching: MatchingOptions::default(),
296 limits: Limits::default(),
297 execution: ExecutionOptions::default(),
298 diagnostics: DiagnosticOptions::default(),
299 output: OutputOptions::default(),
300 }
301 }
302}
303
304impl DiffOptions {
305 pub fn builder() -> DiffOptionsBuilder {
306 DiffOptionsBuilder::new()
307 }
308
309 pub(crate) fn validate(&self) -> Result<(), SheetsDiffError> {
311 if self.comparison.formula == FormulaCompareMode::NormalizedText
313 || self.comparison.formula == FormulaCompareMode::RawAndNormalized
314 {
315 return Err(SheetsDiffError::InvalidOptions {
316 detail: "FormulaCompareMode::NormalizedText / RawAndNormalized is not \
317 available in v2.0; no formula normaliser is implemented yet"
318 .into(),
319 });
320 }
321 if self.comparison.format != FormatCompareMode::Ignore {
323 return Err(SheetsDiffError::InvalidOptions {
324 detail: "FormatCompareMode other than Ignore is not available in v2; \
325 calamine 0.35 does not expose a cell-style API"
326 .into(),
327 });
328 }
329 Ok(())
330 }
331}
332
333#[derive(Default)]
341pub struct DiffOptionsBuilder {
342 opts: DiffOptions,
343}
344
345impl DiffOptionsBuilder {
346 pub fn new() -> Self {
347 Self { opts: DiffOptions::default() }
348 }
349
350 pub fn formula_compare(mut self, mode: FormulaCompareMode) -> Self {
353 self.opts.comparison.formula = mode;
354 self
355 }
356
357 pub fn format_compare(mut self, mode: FormatCompareMode) -> Self {
358 self.opts.comparison.format = mode;
359 self
360 }
361
362 pub fn object_mode(mut self, mode: crate::objects::ObjectCompareMode) -> Self {
364 self.opts.output.objects = mode;
365 self
366 }
367
368 pub fn execution_mode(mut self, mode: ExecutionMode) -> Self {
370 self.opts.execution.mode = mode;
371 self
372 }
373
374 pub fn include_formula_cached_values(mut self, yes: bool) -> Self {
375 self.opts.comparison.include_formula_cached_values = yes;
376 self
377 }
378
379 pub fn number_compare(mut self, policy: NumberComparePolicy) -> Self {
380 self.opts.comparison.value.number = policy;
381 self
382 }
383
384 pub fn numeric_type_policy(mut self, policy: NumericTypePolicy) -> Self {
385 self.opts.comparison.value.numeric_type = policy;
386 self
387 }
388
389 pub fn type_mismatch_policy(mut self, policy: TypeMismatchPolicy) -> Self {
390 self.opts.comparison.value.type_mismatch = policy;
391 self
392 }
393
394 pub fn sheet_matching(mut self, mode: SheetMatchingMode) -> Self {
397 self.opts.matching.sheet_matching = mode;
398 self
399 }
400
401 pub fn max_sheets(mut self, n: u32) -> Self {
404 self.opts.limits.max_sheets = Some(n);
405 self
406 }
407
408 pub fn max_cells_compared(mut self, n: u64) -> Self {
409 self.opts.limits.max_cells_compared = Some(n);
410 self
411 }
412
413 pub fn max_diffs_returned(mut self, n: u64) -> Self {
414 self.opts.limits.max_diffs_returned = Some(n);
415 self
416 }
417
418 pub fn progress<S: ProgressSink + 'static>(mut self, sink: S) -> Self {
421 self.opts.execution.progress = Some(Box::new(sink));
422 self
423 }
424
425 pub fn cancellation<C: Cancellation + 'static>(mut self, token: C) -> Self {
426 self.opts.execution.cancellation = Some(Box::new(token));
427 self
428 }
429
430 pub fn build_with_matching(mut self, matching: MatchingOptions) -> Result<DiffOptions, SheetsDiffError> {
432 self.opts.matching = matching;
433 self.opts.validate()?;
434 Ok(self.opts)
435 }
436
437 pub fn build(self) -> Result<DiffOptions, SheetsDiffError> {
439 self.opts.validate()?;
440 Ok(self.opts)
441 }
442}