Chris@14
|
1 <?php declare(strict_types=1);
|
Chris@14
|
2 /*
|
Chris@14
|
3 * This file is part of sebastian/diff.
|
Chris@14
|
4 *
|
Chris@14
|
5 * (c) Sebastian Bergmann <sebastian@phpunit.de>
|
Chris@14
|
6 *
|
Chris@14
|
7 * For the full copyright and license information, please view the LICENSE
|
Chris@14
|
8 * file that was distributed with this source code.
|
Chris@14
|
9 */
|
Chris@14
|
10
|
Chris@14
|
11 namespace SebastianBergmann\Diff\Output;
|
Chris@14
|
12
|
Chris@14
|
13 /**
|
Chris@14
|
14 * Builds a diff string representation in unified diff format in chunks.
|
Chris@14
|
15 */
|
Chris@14
|
16 final class UnifiedDiffOutputBuilder extends AbstractChunkOutputBuilder
|
Chris@14
|
17 {
|
Chris@14
|
18 /**
|
Chris@14
|
19 * @var string
|
Chris@14
|
20 */
|
Chris@14
|
21 private $header;
|
Chris@14
|
22
|
Chris@14
|
23 /**
|
Chris@14
|
24 * @var bool
|
Chris@14
|
25 */
|
Chris@14
|
26 private $addLineNumbers;
|
Chris@14
|
27
|
Chris@14
|
28 public function __construct(string $header = "--- Original\n+++ New\n", bool $addLineNumbers = false)
|
Chris@14
|
29 {
|
Chris@14
|
30 $this->header = $header;
|
Chris@14
|
31 $this->addLineNumbers = $addLineNumbers;
|
Chris@14
|
32 }
|
Chris@14
|
33
|
Chris@14
|
34 public function getDiff(array $diff): string
|
Chris@14
|
35 {
|
Chris@14
|
36 $buffer = \fopen('php://memory', 'r+b');
|
Chris@14
|
37
|
Chris@14
|
38 if ('' !== $this->header) {
|
Chris@14
|
39 \fwrite($buffer, $this->header);
|
Chris@14
|
40 if ("\n" !== \substr($this->header, -1, 1)) {
|
Chris@14
|
41 \fwrite($buffer, "\n");
|
Chris@14
|
42 }
|
Chris@14
|
43 }
|
Chris@14
|
44
|
Chris@14
|
45 $this->writeDiffChunked($buffer, $diff, $this->getCommonChunks($diff));
|
Chris@14
|
46
|
Chris@14
|
47 $diff = \stream_get_contents($buffer, -1, 0);
|
Chris@14
|
48
|
Chris@14
|
49 \fclose($buffer);
|
Chris@14
|
50
|
Chris@14
|
51 return $diff;
|
Chris@14
|
52 }
|
Chris@14
|
53
|
Chris@14
|
54 // `old` is an array with key => value pairs . Each pair represents a start and end index of `diff`
|
Chris@14
|
55 // of a list of elements all containing `same` (0) entries.
|
Chris@14
|
56 private function writeDiffChunked($output, array $diff, array $old)
|
Chris@14
|
57 {
|
Chris@14
|
58 $upperLimit = \count($diff);
|
Chris@14
|
59 $start = 0;
|
Chris@14
|
60 $fromStart = 0;
|
Chris@14
|
61 $toStart = 0;
|
Chris@14
|
62
|
Chris@14
|
63 if (\count($old)) { // no common parts, list all diff entries
|
Chris@14
|
64 \reset($old);
|
Chris@14
|
65
|
Chris@14
|
66 // iterate the diff, go from chunk to chunk skipping common chunk of lines between those
|
Chris@14
|
67 do {
|
Chris@14
|
68 $commonStart = \key($old);
|
Chris@14
|
69 $commonEnd = \current($old);
|
Chris@14
|
70
|
Chris@14
|
71 if ($commonStart !== $start) {
|
Chris@14
|
72 list($fromRange, $toRange) = $this->getChunkRange($diff, $start, $commonStart);
|
Chris@14
|
73 $this->writeChunk($output, $diff, $start, $commonStart, $fromStart, $fromRange, $toStart, $toRange);
|
Chris@14
|
74
|
Chris@14
|
75 $fromStart += $fromRange;
|
Chris@14
|
76 $toStart += $toRange;
|
Chris@14
|
77 }
|
Chris@14
|
78
|
Chris@14
|
79 $start = $commonEnd + 1;
|
Chris@14
|
80 $commonLength = $commonEnd - $commonStart + 1; // calculate number of non-change lines in the common part
|
Chris@14
|
81 $fromStart += $commonLength;
|
Chris@14
|
82 $toStart += $commonLength;
|
Chris@14
|
83 } while (false !== \next($old));
|
Chris@14
|
84
|
Chris@14
|
85 \end($old); // short cut for finding possible last `change entry`
|
Chris@14
|
86 $tmp = \key($old);
|
Chris@14
|
87 \reset($old);
|
Chris@14
|
88 if ($old[$tmp] === $upperLimit - 1) {
|
Chris@14
|
89 $upperLimit = $tmp;
|
Chris@14
|
90 }
|
Chris@14
|
91 }
|
Chris@14
|
92
|
Chris@14
|
93 if ($start < $upperLimit - 1) { // check for trailing (non) diff entries
|
Chris@14
|
94 do {
|
Chris@14
|
95 --$upperLimit;
|
Chris@14
|
96 } while (isset($diff[$upperLimit][1]) && $diff[$upperLimit][1] === 0);
|
Chris@14
|
97 ++$upperLimit;
|
Chris@14
|
98
|
Chris@14
|
99 list($fromRange, $toRange) = $this->getChunkRange($diff, $start, $upperLimit);
|
Chris@14
|
100 $this->writeChunk($output, $diff, $start, $upperLimit, $fromStart, $fromRange, $toStart, $toRange);
|
Chris@14
|
101 }
|
Chris@14
|
102 }
|
Chris@14
|
103
|
Chris@14
|
104 private function writeChunk(
|
Chris@14
|
105 $output,
|
Chris@14
|
106 array $diff,
|
Chris@14
|
107 int $diffStartIndex,
|
Chris@14
|
108 int $diffEndIndex,
|
Chris@14
|
109 int $fromStart,
|
Chris@14
|
110 int $fromRange,
|
Chris@14
|
111 int $toStart,
|
Chris@14
|
112 int $toRange
|
Chris@14
|
113 ) {
|
Chris@14
|
114 if ($this->addLineNumbers) {
|
Chris@14
|
115 \fwrite($output, '@@ -' . (1 + $fromStart));
|
Chris@14
|
116
|
Chris@14
|
117 if ($fromRange > 1) {
|
Chris@14
|
118 \fwrite($output, ',' . $fromRange);
|
Chris@14
|
119 }
|
Chris@14
|
120
|
Chris@14
|
121 \fwrite($output, ' +' . (1 + $toStart));
|
Chris@14
|
122 if ($toRange > 1) {
|
Chris@14
|
123 \fwrite($output, ',' . $toRange);
|
Chris@14
|
124 }
|
Chris@14
|
125
|
Chris@14
|
126 \fwrite($output, " @@\n");
|
Chris@14
|
127 } else {
|
Chris@14
|
128 \fwrite($output, "@@ @@\n");
|
Chris@14
|
129 }
|
Chris@14
|
130
|
Chris@14
|
131 for ($i = $diffStartIndex; $i < $diffEndIndex; ++$i) {
|
Chris@14
|
132 if ($diff[$i][1] === 1 /* ADDED */) {
|
Chris@14
|
133 \fwrite($output, '+' . $diff[$i][0]);
|
Chris@14
|
134 } elseif ($diff[$i][1] === 2 /* REMOVED */) {
|
Chris@14
|
135 \fwrite($output, '-' . $diff[$i][0]);
|
Chris@14
|
136 } else { /* Not changed (old) 0 or Warning 3 */
|
Chris@14
|
137 \fwrite($output, ' ' . $diff[$i][0]);
|
Chris@14
|
138 }
|
Chris@14
|
139
|
Chris@14
|
140 $lc = \substr($diff[$i][0], -1);
|
Chris@14
|
141 if ($lc !== "\n" && $lc !== "\r") {
|
Chris@14
|
142 \fwrite($output, "\n"); // \No newline at end of file
|
Chris@14
|
143 }
|
Chris@14
|
144 }
|
Chris@14
|
145 }
|
Chris@14
|
146
|
Chris@14
|
147 private function getChunkRange(array $diff, int $diffStartIndex, int $diffEndIndex): array
|
Chris@14
|
148 {
|
Chris@14
|
149 $toRange = 0;
|
Chris@14
|
150 $fromRange = 0;
|
Chris@14
|
151
|
Chris@14
|
152 for ($i = $diffStartIndex; $i < $diffEndIndex; ++$i) {
|
Chris@14
|
153 if ($diff[$i][1] === 1) { // added
|
Chris@14
|
154 ++$toRange;
|
Chris@14
|
155 } elseif ($diff[$i][1] === 2) { // removed
|
Chris@14
|
156 ++$fromRange;
|
Chris@14
|
157 } elseif ($diff[$i][1] === 0) { // same
|
Chris@14
|
158 ++$fromRange;
|
Chris@14
|
159 ++$toRange;
|
Chris@14
|
160 }
|
Chris@14
|
161 }
|
Chris@14
|
162
|
Chris@14
|
163 return [$fromRange, $toRange];
|
Chris@14
|
164 }
|
Chris@14
|
165 }
|