Unified-diff engine — hunks, writer, and patch scanner
A unified-diff engine the whole ecosystem can share: LCS line diffing, numbered context hunks, a GNU diff -u writer, and a scanner that numbers the rows of any patch. Extracted from sugar-crush's BuildsUnifiedDiff trait and the DiffGutter region model, so every surface that produces or displays patches reads the same line numbers out of the same engine. First-party — there is no 1:1 upstream Go library.
composer require sugarcraft/sugar-diff:@dev
use SugarCraft\Diff\Diff;
use SugarCraft\Diff\DiffOptions;
use SugarCraft\Diff\Reader\UnifiedScan;
$diff = Diff::compute("Hello World\n", "Hello PHP\n");
echo $diff->unified('file.txt');
// --- a/file.txt
// +++ b/file.txt
// @@ -1,1 +1,1 @@
// -Hello World
// +Hello PHP
// Immutable, fluent options — defaults reproduce sugar-crush byte-for-byte.
$wide = Diff::compute($before, $after, DiffOptions::new()
->withContextLines(10)
->withIgnoreWhitespace()
->withNoNewlineMarker()
->withDevNullOnEmptySide());
// Number the rows of an existing patch (gutter/viewer support):
foreach (UnifiedScan::lines($patchText) as $row) {
// $row->old, $row->new, $row->fileHeader
}
| Class | Method | Description |
|---|---|---|
| Diff | compute(string|list<string> $before, string|list<string> $after, ?DiffOptions $options = null): self | Line-mode LCS diff (byte-for-byte faithful to sugar-crush defaults) |
| Diff | unified(string $path, ?string $newPath = null): string | Full diff -u text with --- / +++ headers |
| Diff | hunkText(): string | Hunks only, no file headers |
| Diff | isEmpty() / addedLines() / removedLines(): int|bool | Summary counts |
| DiffOptions | withContextLines(int) / withMaxLcsCells(int) | Context window (default 3) and LCS cell budget (default 250 000) |
| DiffOptions | withIgnoreWhitespace() / withIgnoreCase() | Fold comparisons; emitted rows keep original bytes |
| DiffOptions | withNoNewlineMarker() / withDevNullOnEmptySide() | GNU annotations, opt-in |
| UnifiedFormat | write(Diff, string, ?string) / hunksOnly(Diff): string | The writer behind Diff::unified()/hunkText() |
| UnifiedScan | lines(string|list<string>): list<ScannedLine> | Per-row old/new line numbers + fileHeader flag |
| UnifiedScan | fileHeaders(string|list<string>): list<bool> | Which rows start a new file |