or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

comparison-functions.mdextension-points.mdfile-comparators.mdfilters-and-patterns.mdindex.md
tile.json

file-comparators.mddocs/

File Comparators

File content comparison handlers for binary and text-based comparison strategies with extensive customization options.

Capabilities

Built-in File Compare Handlers

Collection of pre-built file comparison handlers for different comparison strategies.

interface FileCompareHandlers {
  /**
   * Binary file content comparator (default).
   * Compares files byte-by-byte for exact binary equality.
   */
  defaultFileCompare: CompareFileHandler;
  
  /**
   * Line-based text file comparator with whitespace and line-ending options.
   * Supports ignoring whitespace differences, line endings, and empty lines.
   */
  lineBasedFileCompare: CompareFileHandler;
}

interface CompareFileHandler {
  compareSync: CompareFileSync;
  compareAsync: CompareFileAsync;
}

Default Binary File Comparator

Binary comparison for exact file content matching.

/**
 * Synchronous binary file comparison
 * @param path1 Left file path
 * @param stat1 Left file stats
 * @param path2 Right file path  
 * @param stat2 Right file stats
 * @param options Comparison options
 * @returns true if files are identical, false otherwise
 */
type CompareFileSync = (
  path1: string, 
  stat1: fs.Stats,
  path2: string, 
  stat2: fs.Stats, 
  options: Options
) => boolean;

/**
 * Asynchronous binary file comparison
 * @param path1 Left file path
 * @param stat1 Left file stats
 * @param path2 Right file path
 * @param stat2 Right file stats
 * @param options Comparison options
 * @returns Promise resolving to true if files are identical
 */
type CompareFileAsync = (
  path1: string, 
  stat1: fs.Stats,
  path2: string, 
  stat2: fs.Stats, 
  options: Options
) => Promise<boolean>;

Usage Examples:

import { compareSync, fileCompareHandlers } from "dir-compare";

// Use default binary comparator (this is the default behavior)
const binaryResult = compareSync("/dir1", "/dir2", {
  compareContent: true,
  compareFileSync: fileCompareHandlers.defaultFileCompare.compareSync,
  compareFileAsync: fileCompareHandlers.defaultFileCompare.compareAsync
});

// Binary comparison is exact - any byte difference results in 'distinct'
console.log(`Binary comparison found ${binaryResult.distinctFiles} different files`);

Line-Based Text File Comparator

Advanced text comparison with options to ignore whitespace and line-ending differences.

interface LineBasedOptions extends Options {
  /**
   * Ignore cr/lf line ending differences (default: false)
   * Similar to 'diff --strip-trailing-cr'
   */
  ignoreLineEnding?: boolean;
  
  /**
   * Ignore white spaces at beginning and end of lines (default: false)
   * Similar to 'diff -b'
   */
  ignoreWhiteSpaces?: boolean;
  
  /**
   * Ignore all white space differences (default: false)
   * Similar to 'diff -w'
   */
  ignoreAllWhiteSpaces?: boolean;
  
  /**
   * Ignore differences caused by empty lines (default: false)
   * Similar to 'diff -B'
   */
  ignoreEmptyLines?: boolean;
}

Usage Examples:

import { compareSync, compare, fileCompareHandlers } from "dir-compare";

// Basic line-based comparison
const textOptions = {
  compareContent: true,
  compareFileSync: fileCompareHandlers.lineBasedFileCompare.compareSync,
  compareFileAsync: fileCompareHandlers.lineBasedFileCompare.compareAsync,
  ignoreLineEnding: true,    // Ignore CRLF vs LF differences
  ignoreWhiteSpaces: true    // Ignore leading/trailing whitespace
};

const textResult = compareSync("/src", "/backup/src", textOptions);

// Advanced text comparison ignoring various whitespace differences
const advancedTextOptions = {
  compareContent: true,
  compareFileSync: fileCompareHandlers.lineBasedFileCompare.compareSync,
  compareFileAsync: fileCompareHandlers.lineBasedFileCompare.compareAsync,
  ignoreLineEnding: true,        // Ignore line ending differences
  ignoreWhiteSpaces: true,       // Ignore leading/trailing spaces
  ignoreAllWhiteSpaces: true,    // Ignore all whitespace differences
  ignoreEmptyLines: true,        // Ignore empty line differences
  includeFilter: "*.txt,*.md,*.js,*.ts" // Only compare text files
};

const flexibleResult = await compare("/docs1", "/docs2", advancedTextOptions);

// Process results focusing on content differences
flexibleResult.diffSet?.forEach(diff => {
  if (diff.state === 'distinct' && diff.reason === 'different-content') {
    console.log(`Content differs: ${diff.relativePath}/${diff.name1}`);
  }
});

Custom File Comparators

Create custom file comparison logic for specialized needs.

/**
 * Custom file comparator example for JSON files
 */
function createJsonComparator(): CompareFileHandler {
  const compareJsonSync: CompareFileSync = (path1, stat1, path2, stat2, options) => {
    try {
      const content1 = fs.readFileSync(path1, 'utf8');
      const content2 = fs.readFileSync(path2, 'utf8');
      
      const json1 = JSON.parse(content1);
      const json2 = JSON.parse(content2);
      
      return JSON.stringify(json1) === JSON.stringify(json2);
    } catch (error) {
      // Fall back to binary comparison on parse errors
      return fileCompareHandlers.defaultFileCompare.compareSync(path1, stat1, path2, stat2, options);
    }
  };

  const compareJsonAsync: CompareFileAsync = async (path1, stat1, path2, stat2, options) => {
    try {
      const [content1, content2] = await Promise.all([
        fs.promises.readFile(path1, 'utf8'),
        fs.promises.readFile(path2, 'utf8')
      ]);
      
      const json1 = JSON.parse(content1);
      const json2 = JSON.parse(content2);
      
      return JSON.stringify(json1) === JSON.stringify(json2);
    } catch (error) {
      return fileCompareHandlers.defaultFileCompare.compareAsync(path1, stat1, path2, stat2, options);
    }
  };

  return {
    compareSync: compareJsonSync,
    compareAsync: compareJsonAsync
  };
}

Usage with Custom Comparators:

import { compareSync } from "dir-compare";

// Use custom JSON comparator
const jsonComparator = createJsonComparator();

const jsonComparisonOptions = {
  compareContent: true,
  compareFileSync: jsonComparator.compareSync,
  compareFileAsync: jsonComparator.compareAsync,
  includeFilter: "*.json" // Only compare JSON files
};

const result = compareSync("/config1", "/config2", jsonComparisonOptions);

// Custom size-based comparator (files equal if size matches)
const sizeOnlyComparator: CompareFileHandler = {
  compareSync: (path1, stat1, path2, stat2, options) => {
    return stat1.size === stat2.size;
  },
  compareAsync: async (path1, stat1, path2, stat2, options) => {
    return stat1.size === stat2.size;
  }
};

const sizeBasedResult = compareSync("/images1", "/images2", {
  compareContent: true,
  compareFileSync: sizeOnlyComparator.compareSync,
  includeFilter: "*.jpg,*.png"
});

File Comparator Performance Considerations

import { compare, fileCompareHandlers } from "dir-compare";

// For large files, consider size pre-check
const efficientOptions = {
  compareSize: true,         // Quick size check first
  compareContent: true,      // Only do content comparison if sizes match
  compareFileAsync: fileCompareHandlers.defaultFileCompare.compareAsync,
  excludeFilter: "*.iso,*.dmg,*.zip" // Exclude large binary files
};

// Line-based comparison is slower but more flexible for text files
const textFileOptions = {
  compareContent: true,
  compareFileAsync: fileCompareHandlers.lineBasedFileCompare.compareAsync,
  ignoreWhiteSpaces: true,
  ignoreLineEnding: true,
  includeFilter: "*.txt,*.md,*.js,*.ts,*.html,*.css" // Text files only
};

const mixedResult = await compare("/project1", "/project2", {
  compareSize: true,    // Fast initial check
  compareContent: true, // Detailed content check
  // Use default binary comparator (will be set automatically)
});

Error Handling in File Comparators

import { compareSync, fileCompareHandlers } from "dir-compare";

// Robust comparison with error handling
const robustOptions = {
  compareContent: true,
  handlePermissionDenied: true, // Continue on access errors
  compareFileSync: (path1, stat1, path2, stat2, options) => {
    try {
      // Use default binary comparator
      return fileCompareHandlers.defaultFileCompare.compareSync(path1, stat1, path2, stat2, options);
    } catch (error) {
      console.warn(`Failed to compare ${path1} and ${path2}:`, error.message);
      // Consider files different if comparison fails
      return false;
    }
  }
};

const result = compareSync("/fragile/dir1", "/fragile/dir2", robustOptions);

// Check for comparison errors
result.diffSet?.forEach(diff => {
  if (diff.permissionDeniedState !== 'access-ok') {
    console.log(`Access error: ${diff.relativePath} - ${diff.permissionDeniedState}`);
  }
});