Skip to content

Semantic diff for JSON, text, binary files (Rust) - Part of Tuulbelt

License

Notifications You must be signed in to change notification settings

tuulbelt/output-diffing-utility

Repository files navigation

Output Diffing Utility / odiff

Tests Version Rust Zero Dependencies Tests License

Part of Tuulbelt — A collection of focused, zero-dependency tools

Semantic diff tool for JSON, text, and binary files with zero dependencies.

Problem

When comparing files in tests, builds, or data pipelines, you need to see what changed in a structured, semantic way. Standard diff works for text but doesn't understand JSON structure or binary formats. This tool provides:

  • Text diffs with LCS algorithm (line-by-line comparison)
  • JSON diffs with structural understanding (field paths, not lines)
  • Binary diffs with byte-level comparison (hex output)

All with zero external dependencies.

Features

  • Zero runtime dependencies (uses only Rust standard library)
  • Multiple diff types: Text (LCS), JSON (structural), Binary (byte-by-byte)
  • Multiple output formats: Unified, JSON, side-by-side, compact
  • Cross-platform support (Linux, macOS, Windows)
  • Both library and CLI interfaces
  • Proper exit codes (0=identical, 1=differ, 2=error)
  • Composable with other Tuulbelt tools

Installation

From Source

git clone https://github.com/tuulbelt/output-diffing-utility.git
cd output-diffing-utility
cargo build --release

The binary supports both short and long command names:

  • Short (recommended): target/release/odiff
  • Long: target/release/output-diff

Recommended setup - install globally for easy access:

cargo install --path .
# Now use `odiff` anywhere
odiff --help

As a Library

Add to your Cargo.toml:

[dependencies]
output-diffing-utility = { git = "https://github.com/tuulbelt/output-diffing-utility.git" }

Usage

As a CLI

# Basic text diff
odiff file1.txt file2.txt

# JSON structural diff
odiff data1.json data2.json

# Binary diff
odiff image1.png image2.png

# With ANSI color codes (works for text, JSON, and binary diffs)
odiff --color always file1.txt file2.txt
odiff --color always data1.json data2.json
odiff --color always image1.png image2.png
# Or use 'auto' to enable color only when outputting to a terminal
odiff --color auto file1.txt file2.txt

# Verbose output
odiff --verbose file1.txt file2.txt

Color Support:

  • Text diffs: Green for additions, red for deletions
  • JSON diffs: Cyan for paths, green for added values, red for removed values, yellow for modifications
  • Binary diffs: Cyan for offsets, red for old bytes, green for new bytes
  • JSON/SideBySide/Compact formats: Color not applied (use --format unified for color)

Output:

--- file1.txt
+++ file2.txt
  line 1
- line 2
+ line 2 modified
  line 3
+ line 4

Exit codes:

  • 0 - Files are identical
  • 1 - Files differ
  • 2 - Error occurred

As a Library

use output_diffing_utility::{diff_text, DiffConfig};

let old = "line 1\nline 2";
let new = "line 1\nline 2 modified";

let config = DiffConfig::default();
let result = diff_text(old, new, &config);

if result.has_changes() {
    println!("Files differ!");
    println!("Additions: {}", result.additions());
    println!("Deletions: {}", result.deletions());
}

API

diff_text(old: &str, new: &str, config: &DiffConfig) -> DiffResult

Compare two text strings line-by-line using the LCS algorithm.

Returns: DiffResult with line-by-line changes.

diff_json(old: &str, new: &str, config: &DiffConfig) -> Result<JsonDiffResult, JsonError>

Compare two JSON strings structurally (field paths, not lines).

Returns: Result<JsonDiffResult, JsonError> with structural changes.

diff_binary(data1: &[u8], data2: &[u8], config: &DiffConfig) -> BinaryDiffResult

Compare two byte arrays byte-by-byte.

Returns: BinaryDiffResult with byte-level comparison.

DiffConfig

DiffConfig {
    context_lines: 3,              // Lines of context around changes
    format: OutputFormat::Unified, // Unified, JSON, SideBySide, Compact
    color: false,                  // Enable ANSI color codes
    verbose: false,                // Show detailed information
}

DiffResult, JsonDiffResult, BinaryDiffResult

Each result type provides:

  • has_changes() / is_identical() - Quick check
  • additions(), deletions() - Change counts
  • Iterator over changes for detailed analysis

Algorithm: Longest Common Subsequence (LCS)

The text diff uses dynamic programming to find the optimal diff:

  • Time complexity: O(m×n) where m, n are line counts
  • Space complexity: O(m×n)
  • Output: Minimal set of changes (additions/deletions)

See DIFF_ALGORITHMS_RESEARCH.md for detailed algorithm analysis.

Composability with Tuulbelt Tools

This tool demonstrates the power of composability by working seamlessly with other Tuulbelt tools. When in the monorepo, you can chain tools together via CLI interfaces:

Individual Tool Compositions

CLI Progress Reporting - Track progress for large file diffs:

./scripts/dogfood-progress.sh
# Shows: TypeScript (progress) ↔ Rust (diff) composition

Cross-Platform Path Normalizer - Handle Windows/Unix/mixed path formats:

./scripts/dogfood-paths.sh
# Shows: Path normalization → Rust diff pipeline

File-Based Semaphore - Protect concurrent diff cache access:

./scripts/dogfood-semaphore.sh
# Shows: Rust (semaphore) → Rust (diff) composition

Test Flakiness Detector - Validate test reliability:

./scripts/dogfood-flaky.sh
# Validates all 99 tests are deterministic

Complete Multi-Tool Pipeline

Run all 5 Phase 1 tools together in a single workflow:

./scripts/dogfood-pipeline.sh

This pipeline demonstrates:

  • Cross-language composition (TypeScript ↔ Rust)
  • Tools communicating via CLI interfaces only
  • No runtime dependencies between tools
  • Real-world use case (API version comparison)
  • Graceful degradation if tools missing

See DOGFOODING_STRATEGY.md for implementation details.

Development

# Run tests
cargo test

# Run with zero warnings
cargo clippy -- -D warnings

# Format code
cargo fmt

# Build optimized release
cargo build --release

Dogfooding

We dogfood this tool by composing it with other Tuulbelt tools:

# Validate test reliability (Test Flakiness Detector)
./scripts/dogfood-flaky.sh 20

# Show progress tracking (CLI Progress Reporting)
./scripts/dogfood-progress.sh

# Handle cross-platform paths (Path Normalizer)
./scripts/dogfood-paths.sh

# Protect concurrent access (File-Based Semaphore)
./scripts/dogfood-semaphore.sh

# Run complete multi-tool pipeline (all 5 tools)
./scripts/dogfood-pipeline.sh

These scripts demonstrate real-world tool composition and validate that all 99 tests are deterministic.

Examples

See examples/ directory for real-world usage patterns.

Demo

Demo

▶ View interactive recording on asciinema.org

Try it online: Open in StackBlitz

License

MIT — see LICENSE

Packages

No packages published

Contributors 4

  •  
  •  
  •  
  •