Chris@14: Chris@14: * Chris@14: * For the full copyright and license information, please view the LICENSE Chris@14: * file that was distributed with this source code. Chris@14: */ Chris@14: Chris@14: namespace SebastianBergmann\Diff\Output; Chris@14: Chris@14: /** Chris@14: * Builds a diff string representation in unified diff format in chunks. Chris@14: */ Chris@14: final class UnifiedDiffOutputBuilder extends AbstractChunkOutputBuilder Chris@14: { Chris@14: /** Chris@14: * @var string Chris@14: */ Chris@14: private $header; Chris@14: Chris@14: /** Chris@14: * @var bool Chris@14: */ Chris@14: private $addLineNumbers; Chris@14: Chris@14: public function __construct(string $header = "--- Original\n+++ New\n", bool $addLineNumbers = false) Chris@14: { Chris@14: $this->header = $header; Chris@14: $this->addLineNumbers = $addLineNumbers; Chris@14: } Chris@14: Chris@14: public function getDiff(array $diff): string Chris@14: { Chris@14: $buffer = \fopen('php://memory', 'r+b'); Chris@14: Chris@14: if ('' !== $this->header) { Chris@14: \fwrite($buffer, $this->header); Chris@14: if ("\n" !== \substr($this->header, -1, 1)) { Chris@14: \fwrite($buffer, "\n"); Chris@14: } Chris@14: } Chris@14: Chris@14: $this->writeDiffChunked($buffer, $diff, $this->getCommonChunks($diff)); Chris@14: Chris@14: $diff = \stream_get_contents($buffer, -1, 0); Chris@14: Chris@14: \fclose($buffer); Chris@14: Chris@14: return $diff; Chris@14: } Chris@14: Chris@14: // `old` is an array with key => value pairs . Each pair represents a start and end index of `diff` Chris@14: // of a list of elements all containing `same` (0) entries. Chris@14: private function writeDiffChunked($output, array $diff, array $old) Chris@14: { Chris@14: $upperLimit = \count($diff); Chris@14: $start = 0; Chris@14: $fromStart = 0; Chris@14: $toStart = 0; Chris@14: Chris@14: if (\count($old)) { // no common parts, list all diff entries Chris@14: \reset($old); Chris@14: Chris@14: // iterate the diff, go from chunk to chunk skipping common chunk of lines between those Chris@14: do { Chris@14: $commonStart = \key($old); Chris@14: $commonEnd = \current($old); Chris@14: Chris@14: if ($commonStart !== $start) { Chris@14: list($fromRange, $toRange) = $this->getChunkRange($diff, $start, $commonStart); Chris@14: $this->writeChunk($output, $diff, $start, $commonStart, $fromStart, $fromRange, $toStart, $toRange); Chris@14: Chris@14: $fromStart += $fromRange; Chris@14: $toStart += $toRange; Chris@14: } Chris@14: Chris@14: $start = $commonEnd + 1; Chris@14: $commonLength = $commonEnd - $commonStart + 1; // calculate number of non-change lines in the common part Chris@14: $fromStart += $commonLength; Chris@14: $toStart += $commonLength; Chris@14: } while (false !== \next($old)); Chris@14: Chris@14: \end($old); // short cut for finding possible last `change entry` Chris@14: $tmp = \key($old); Chris@14: \reset($old); Chris@14: if ($old[$tmp] === $upperLimit - 1) { Chris@14: $upperLimit = $tmp; Chris@14: } Chris@14: } Chris@14: Chris@14: if ($start < $upperLimit - 1) { // check for trailing (non) diff entries Chris@14: do { Chris@14: --$upperLimit; Chris@14: } while (isset($diff[$upperLimit][1]) && $diff[$upperLimit][1] === 0); Chris@14: ++$upperLimit; Chris@14: Chris@14: list($fromRange, $toRange) = $this->getChunkRange($diff, $start, $upperLimit); Chris@14: $this->writeChunk($output, $diff, $start, $upperLimit, $fromStart, $fromRange, $toStart, $toRange); Chris@14: } Chris@14: } Chris@14: Chris@14: private function writeChunk( Chris@14: $output, Chris@14: array $diff, Chris@14: int $diffStartIndex, Chris@14: int $diffEndIndex, Chris@14: int $fromStart, Chris@14: int $fromRange, Chris@14: int $toStart, Chris@14: int $toRange Chris@14: ) { Chris@14: if ($this->addLineNumbers) { Chris@14: \fwrite($output, '@@ -' . (1 + $fromStart)); Chris@14: Chris@14: if ($fromRange > 1) { Chris@14: \fwrite($output, ',' . $fromRange); Chris@14: } Chris@14: Chris@14: \fwrite($output, ' +' . (1 + $toStart)); Chris@14: if ($toRange > 1) { Chris@14: \fwrite($output, ',' . $toRange); Chris@14: } Chris@14: Chris@14: \fwrite($output, " @@\n"); Chris@14: } else { Chris@14: \fwrite($output, "@@ @@\n"); Chris@14: } Chris@14: Chris@14: for ($i = $diffStartIndex; $i < $diffEndIndex; ++$i) { Chris@14: if ($diff[$i][1] === 1 /* ADDED */) { Chris@14: \fwrite($output, '+' . $diff[$i][0]); Chris@14: } elseif ($diff[$i][1] === 2 /* REMOVED */) { Chris@14: \fwrite($output, '-' . $diff[$i][0]); Chris@14: } else { /* Not changed (old) 0 or Warning 3 */ Chris@14: \fwrite($output, ' ' . $diff[$i][0]); Chris@14: } Chris@14: Chris@14: $lc = \substr($diff[$i][0], -1); Chris@14: if ($lc !== "\n" && $lc !== "\r") { Chris@14: \fwrite($output, "\n"); // \No newline at end of file Chris@14: } Chris@14: } Chris@14: } Chris@14: Chris@14: private function getChunkRange(array $diff, int $diffStartIndex, int $diffEndIndex): array Chris@14: { Chris@14: $toRange = 0; Chris@14: $fromRange = 0; Chris@14: Chris@14: for ($i = $diffStartIndex; $i < $diffEndIndex; ++$i) { Chris@14: if ($diff[$i][1] === 1) { // added Chris@14: ++$toRange; Chris@14: } elseif ($diff[$i][1] === 2) { // removed Chris@14: ++$fromRange; Chris@14: } elseif ($diff[$i][1] === 0) { // same Chris@14: ++$fromRange; Chris@14: ++$toRange; Chris@14: } Chris@14: } Chris@14: Chris@14: return [$fromRange, $toRange]; Chris@14: } Chris@14: }