use crate::error::SheetsDiffError;
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub enum FormulaCompareMode {
#[default]
RawText,
NormalizedText,
RawAndNormalized,
Ignore,
}
#[derive(Clone, Copy, PartialEq, Debug, Default)]
pub enum NumberComparePolicy {
#[default]
Exact,
AbsoluteTolerance(f64),
RelativeTolerance(f64),
AbsoluteOrRelative {
abs: f64,
rel: f64,
},
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub enum NumericTypePolicy {
#[default]
PreserveType,
CompareMathematicalValue,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub enum DateComparePolicy {
#[default]
ExactRepresentation,
NormalizeEquivalentDateTimes,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub enum TypeMismatchPolicy {
#[default]
Different,
CompareDisplayString,
}
#[derive(Clone, Debug, Default)]
pub struct ValueCompareOptions {
pub number: NumberComparePolicy,
pub numeric_type: NumericTypePolicy,
pub date: DateComparePolicy,
pub type_mismatch: TypeMismatchPolicy,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub enum FormatCompareMode {
#[default]
Ignore,
NumberFormatOnly,
AllAvailable,
}
#[derive(Clone, Debug)]
pub struct ComparisonOptions {
pub value: ValueCompareOptions,
pub formula: FormulaCompareMode,
pub include_formula_cached_values: bool,
pub format: FormatCompareMode,
}
impl Default for ComparisonOptions {
fn default() -> Self {
Self {
value: ValueCompareOptions::default(),
formula: FormulaCompareMode::default(),
include_formula_cached_values: true,
format: FormatCompareMode::default(),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub enum SheetMatchingMode {
ExactNameOnly,
#[default]
ExactNameThenConservativeRename,
ExactNameThenIndex,
}
#[allow(dead_code)]
#[derive(Clone, Debug, Default)]
pub enum AlignmentMode {
#[default]
Positional,
RowKey { columns: Vec<u32> },
RowSignature { sample_columns: Option<Vec<u32>> },
#[allow(dead_code)]
HeaderColumn,
}
#[derive(Clone, Debug, Default)]
pub struct MatchingOptions {
pub sheet_matching: SheetMatchingMode,
pub alignment: AlignmentMode,
}
pub const DEFAULT_MAX_ALIGNMENT_PRODUCT: u64 = 25_000_000;
pub const DEFAULT_MAX_INPUT_BYTES: u64 = 500 * 1024 * 1024;
#[derive(Clone, Debug)]
pub struct Limits {
pub max_sheets: Option<u32>,
pub max_cells_read: Option<u64>,
pub max_cells_compared: Option<u64>,
pub max_diffs_returned: Option<u64>,
pub max_alignment_product: Option<u64>,
pub max_input_bytes: Option<u64>,
}
impl Default for Limits {
fn default() -> Self {
Self {
max_sheets: None,
max_cells_read: None,
max_cells_compared: None,
max_diffs_returned: None,
max_alignment_product: Some(DEFAULT_MAX_ALIGNMENT_PRODUCT),
max_input_bytes: Some(DEFAULT_MAX_INPUT_BYTES),
}
}
}
impl Limits {
pub fn hardened() -> Self {
Self {
max_sheets: Some(256),
max_cells_read: Some(5_000_000),
max_cells_compared: Some(5_000_000),
max_diffs_returned: Some(1_000_000),
max_alignment_product: Some(DEFAULT_MAX_ALIGNMENT_PRODUCT),
max_input_bytes: Some(50 * 1024 * 1024),
}
}
}
#[derive(Clone, Debug)]
pub enum DiffEvent {
Started,
OpeningWorkbook {
side: crate::model::Side,
},
WorkbookOpened {
side: crate::model::Side,
sheet_count: usize,
},
MatchingSheets,
SheetStarted {
index: usize,
total: usize,
name: String,
},
SheetFinished {
index: usize,
changed_cells: usize,
},
Finished,
}
pub trait ProgressSink: Send {
fn on_event(&mut self, event: DiffEvent);
}
impl<F: FnMut(DiffEvent) + Send> ProgressSink for F {
fn on_event(&mut self, event: DiffEvent) {
self(event);
}
}
pub trait Cancellation: Send + Sync {
fn is_cancelled(&self) -> bool;
}
impl<F: Fn() -> bool + Send + Sync> Cancellation for F {
fn is_cancelled(&self) -> bool {
self()
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub enum ExecutionMode {
#[default]
Sequential,
}
#[derive(Default)]
pub struct ExecutionOptions {
pub progress: Option<Box<dyn ProgressSink>>,
pub cancellation: Option<Box<dyn Cancellation>>,
pub mode: ExecutionMode,
}
#[derive(Clone, Debug, Default)]
pub struct DiagnosticOptions {
pub min_severity: Option<crate::model::Severity>,
}
#[derive(Clone, Debug)]
pub struct OutputOptions {
pub objects: crate::objects::ObjectCompareMode,
}
impl Default for OutputOptions {
fn default() -> Self {
Self {
objects: crate::objects::ObjectCompareMode::WarnIfPresent,
}
}
}
#[derive(Default)]
pub struct DiffOptions {
pub comparison: ComparisonOptions,
pub matching: MatchingOptions,
pub limits: Limits,
pub execution: ExecutionOptions,
pub diagnostics: DiagnosticOptions,
pub output: OutputOptions,
}
impl DiffOptions {
pub fn builder() -> DiffOptionsBuilder {
DiffOptionsBuilder::new()
}
pub(crate) fn validate(&self) -> Result<(), SheetsDiffError> {
if self.comparison.formula == FormulaCompareMode::NormalizedText
|| self.comparison.formula == FormulaCompareMode::RawAndNormalized
{
return Err(SheetsDiffError::InvalidOptions {
detail: "FormulaCompareMode::NormalizedText / RawAndNormalized is not \
available; no formula normaliser is implemented yet"
.into(),
});
}
if self.comparison.format != FormatCompareMode::Ignore {
return Err(SheetsDiffError::InvalidOptions {
detail: "FormatCompareMode other than Ignore is not available in v2; \
calamine 0.36 does not expose a cell-style API"
.into(),
});
}
Ok(())
}
}
#[derive(Default)]
pub struct DiffOptionsBuilder {
opts: DiffOptions,
}
impl DiffOptionsBuilder {
pub fn new() -> Self {
Self {
opts: DiffOptions::default(),
}
}
pub fn formula_compare(mut self, mode: FormulaCompareMode) -> Self {
self.opts.comparison.formula = mode;
self
}
pub fn format_compare(mut self, mode: FormatCompareMode) -> Self {
self.opts.comparison.format = mode;
self
}
pub fn object_mode(mut self, mode: crate::objects::ObjectCompareMode) -> Self {
self.opts.output.objects = mode;
self
}
pub fn execution_mode(mut self, mode: ExecutionMode) -> Self {
self.opts.execution.mode = mode;
self
}
pub fn include_formula_cached_values(mut self, yes: bool) -> Self {
self.opts.comparison.include_formula_cached_values = yes;
self
}
pub fn number_compare(mut self, policy: NumberComparePolicy) -> Self {
self.opts.comparison.value.number = policy;
self
}
pub fn numeric_type_policy(mut self, policy: NumericTypePolicy) -> Self {
self.opts.comparison.value.numeric_type = policy;
self
}
pub fn type_mismatch_policy(mut self, policy: TypeMismatchPolicy) -> Self {
self.opts.comparison.value.type_mismatch = policy;
self
}
pub fn number_compare_policy(mut self, policy: NumberComparePolicy) -> Self {
self.opts.comparison.value.number = policy;
self
}
pub fn sheet_matching(mut self, mode: SheetMatchingMode) -> Self {
self.opts.matching.sheet_matching = mode;
self
}
pub fn max_sheets(mut self, n: u32) -> Self {
self.opts.limits.max_sheets = Some(n);
self
}
pub fn max_cells_compared(mut self, n: u64) -> Self {
self.opts.limits.max_cells_compared = Some(n);
self
}
pub fn max_diffs_returned(mut self, n: u64) -> Self {
self.opts.limits.max_diffs_returned = Some(n);
self
}
pub fn max_alignment_product(mut self, limit: Option<u64>) -> Self {
self.opts.limits.max_alignment_product = limit;
self
}
pub fn max_input_bytes(mut self, limit: Option<u64>) -> Self {
self.opts.limits.max_input_bytes = limit;
self
}
pub fn limits(mut self, limits: Limits) -> Self {
self.opts.limits = limits;
self
}
pub fn progress<S: ProgressSink + 'static>(mut self, sink: S) -> Self {
self.opts.execution.progress = Some(Box::new(sink));
self
}
pub fn cancellation<C: Cancellation + 'static>(mut self, token: C) -> Self {
self.opts.execution.cancellation = Some(Box::new(token));
self
}
pub fn build_with_matching(
mut self,
matching: MatchingOptions,
) -> Result<DiffOptions, SheetsDiffError> {
self.opts.matching = matching;
self.opts.validate()?;
Ok(self.opts)
}
pub fn build(self) -> Result<DiffOptions, SheetsDiffError> {
self.opts.validate()?;
Ok(self.opts)
}
}