Skip to main content

sheets_diff/
lib.rs

1#![forbid(unsafe_code)]
2
3//! # sheets-diff
4//!
5//! Structured diff engine for Microsoft Excel `.xlsx` workbooks.
6//!
7//! ## Quick start
8//!
9//! ```rust,no_run
10//! use sheets_diff::compare_paths;
11//!
12//! let diff = compare_paths("old.xlsx", "new.xlsx")?;
13//! println!("changed cells: {}", diff.summary.cells_changed);
14//! # Ok::<(), sheets_diff::SheetsDiffError>(())
15//! ```
16//!
17//! ## Input sources
18//!
19//! | Function | When to use |
20//! |---|---|
21//! | [`compare_paths`] | Simplest; caller provides file paths |
22//! | [`compare_bytes`] | You already have the bytes (e.g. from a cache or repo) |
23//! | [`compare_readers`] | You have open `Read + Seek` handles |
24//! | `compare_*_with_options` variants | Any of the above plus [`DiffOptions`] |
25//!
26//! See [`DiffOptions`] / [`DiffOptionsBuilder`] for all configuration knobs.
27
28// ---------------------------------------------------------------------------
29// Internal modules (not pub)
30// ---------------------------------------------------------------------------
31
32pub mod address;
33mod align;
34mod diff;
35mod error;
36mod matcher;
37mod meta;
38mod normalize;
39mod objects;
40mod open;
41
42pub(crate) mod compare;
43
44// ---------------------------------------------------------------------------
45// Public modules
46// ---------------------------------------------------------------------------
47
48/// Typed result model (`WorkbookDiff`, `SheetDiff`, `CellDiff`, `CellValue`, …).
49pub mod model;
50
51/// Comparison options and builder (`DiffOptions`, `DiffOptionsBuilder`, …).
52pub mod options;
53
54/// Output formatters (text summary, unified diff).
55pub mod output;
56
57// ---------------------------------------------------------------------------
58// Re-exports — the stable public API surface (RFC-002, RFC-031)
59// ---------------------------------------------------------------------------
60
61// Error types
62pub use error::{LimitKind, OpenErrorKind, ReadErrorKind, SheetsDiffError};
63
64// Model
65pub use model::{
66    AlignmentSummary, CellChangeKind, CellDateTime, CellDiff, CellDisplay, CellDuration, CellError,
67    CellNumberFormat, CellSnapshot, CellValue, DateTimeKind, Diagnostic, DiagnosticKind,
68    DiagnosticLocation, DiagnosticSummary, DiffMetrics, DiffStage, DiffSummary, DisplaySource,
69    FormatChange, FormulaChange, FormulaText, MatchConfidence, Severity, SheetChange, SheetDiff,
70    SheetMatchReason, SheetRef, SheetSummary, Side, SourceDescription, SourceKind, ValueChange,
71    ValueDifferenceKind, WorkbookChange, WorkbookDiff, WorkbookObjectChange, WorkbookSideInfo,
72};
73
74// Address
75pub use address::{CellAddress, ComparedRange, MAX_COL, MAX_COL_LABEL, MAX_ROW};
76
77// Options
78pub use objects::ObjectCompareMode;
79pub use options::{
80    AlignmentMode, Cancellation, ComparisonOptions, DateComparePolicy, DiagnosticOptions,
81    DiffEvent, DiffOptions, DiffOptionsBuilder, ExecutionMode, ExecutionOptions, FormatCompareMode,
82    FormulaCompareMode, Limits, MatchingOptions, NumberComparePolicy, NumericTypePolicy,
83    OutputOptions, ProgressSink, SheetMatchingMode, TypeMismatchPolicy, ValueCompareOptions,
84};
85
86// ---------------------------------------------------------------------------
87// Public entry points (RFC-033 §12)
88// ---------------------------------------------------------------------------
89
90use std::io::{Read, Seek};
91use std::path::Path;
92
93/// Compare two workbooks given their filesystem paths.
94///
95/// Uses [`DiffOptions::default()`].
96/// Compare two workbooks given their filesystem paths.
97///
98/// # Path handling
99///
100/// `old` and `new` accept any `AsRef<Path>`, and the raw `Path` is passed to
101/// `std::fs::read` unchanged — there is **no internal `to_str()`/`unwrap()` on
102/// the path**, so non-UTF-8 paths (common on Linux) are fully supported and
103/// never cause a panic. The only UTF-8-dependent step is the cosmetic
104/// `SourceDescription.display_name`, which is set to `None` for a non-UTF-8
105/// file name rather than failing.
106pub fn compare_paths(
107    old: impl AsRef<Path>,
108    new: impl AsRef<Path>,
109) -> Result<WorkbookDiff, SheetsDiffError> {
110    diff::run_compare_paths(old, new, DiffOptions::default())
111}
112
113/// Compare two workbooks given their filesystem paths, with explicit options.
114pub fn compare_paths_with_options(
115    old: impl AsRef<Path>,
116    new: impl AsRef<Path>,
117    opts: DiffOptions,
118) -> Result<WorkbookDiff, SheetsDiffError> {
119    diff::run_compare_paths(old, new, opts)
120}
121
122/// Compare two workbooks given byte slices.
123pub fn compare_bytes(
124    old: impl AsRef<[u8]>,
125    new: impl AsRef<[u8]>,
126) -> Result<WorkbookDiff, SheetsDiffError> {
127    diff::run_compare_bytes(old, new, DiffOptions::default())
128}
129
130/// Compare two workbooks given byte slices, with explicit options.
131pub fn compare_bytes_with_options(
132    old: impl AsRef<[u8]>,
133    new: impl AsRef<[u8]>,
134    opts: DiffOptions,
135) -> Result<WorkbookDiff, SheetsDiffError> {
136    diff::run_compare_bytes(old, new, opts)
137}
138
139/// Compare two workbooks given `Read + Seek` readers.
140///
141/// `.xlsx` is ZIP-based and requires seeking.
142pub fn compare_readers<R1, R2>(old: R1, new: R2) -> Result<WorkbookDiff, SheetsDiffError>
143where
144    R1: Read + Seek,
145    R2: Read + Seek,
146{
147    diff::run_compare_readers(old, new, DiffOptions::default())
148}
149
150/// Compare two workbooks given `Read + Seek` readers, with explicit options.
151pub fn compare_readers_with_options<R1, R2>(
152    old: R1,
153    new: R2,
154    opts: DiffOptions,
155) -> Result<WorkbookDiff, SheetsDiffError>
156where
157    R1: Read + Seek,
158    R2: Read + Seek,
159{
160    diff::run_compare_readers(old, new, opts)
161}