PluginProbe ʕ •ᴥ•ʔ
Kubio AI Page Builder / 2.8.6
Kubio AI Page Builder v2.8.6
2.8.6 2.8.5 2.8.4 2.8.3 2.8.2 2.8.1 trunk 1.0.0 1.0.1 1.1.0 1.2.0 1.2.1 1.2.2 1.2.3 1.3.0 1.3.1 1.3.2 1.4.0 1.4.1 1.4.2 1.4.3 1.5.0 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.7.0 1.7.1 1.7.2 1.7.3 1.8.0 1.8.1 1.8.2 1.9.0 2.0.0 2.1.1 2.1.2 2.1.3 2.2.0 2.2.3 2.2.4 2.2.5 2.3.0 2.3.1 2.3.3 2.3.4 2.4.0 2.4.1 2.4.2 2.4.3 2.4.5 2.5.0 2.5.1 2.5.2 2.5.3 2.6.0 2.6.1 2.6.2 2.6.3 2.6.5 2.6.6 2.6.7 2.7.0 2.7.1 2.7.2 2.7.3 2.8.0
kubio / vendor / sebastian / diff / src / Output / StrictUnifiedDiffOutputBuilder.php
kubio / vendor / sebastian / diff / src / Output Last commit date
AbstractChunkOutputBuilder.php 1 year ago DiffOnlyOutputBuilder.php 1 year ago DiffOutputBuilderInterface.php 1 year ago StrictUnifiedDiffOutputBuilder.php 1 year ago UnifiedDiffOutputBuilder.php 1 year ago
StrictUnifiedDiffOutputBuilder.php
339 lines
1 <?php declare(strict_types=1);
2 /*
3 * This file is part of sebastian/diff.
4 *
5 * (c) Sebastian Bergmann <sebastian@phpunit.de>
6 *
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
9 */
10 namespace SebastianBergmann\Diff\Output;
11
12 use function array_merge;
13 use function array_splice;
14 use function count;
15 use function fclose;
16 use function fopen;
17 use function fwrite;
18 use function is_bool;
19 use function is_int;
20 use function is_string;
21 use function max;
22 use function min;
23 use function sprintf;
24 use function stream_get_contents;
25 use function substr;
26 use SebastianBergmann\Diff\ConfigurationException;
27 use SebastianBergmann\Diff\Differ;
28
29 /**
30 * Strict Unified diff output builder.
31 *
32 * Generates (strict) Unified diff's (unidiffs) with hunks.
33 */
34 final class StrictUnifiedDiffOutputBuilder implements DiffOutputBuilderInterface
35 {
36 private static $default = [
37 'collapseRanges' => true, // ranges of length one are rendered with the trailing `,1`
38 'commonLineThreshold' => 6, // number of same lines before ending a new hunk and creating a new one (if needed)
39 'contextLines' => 3, // like `diff: -u, -U NUM, --unified[=NUM]`, for patch/git apply compatibility best to keep at least @ 3
40 'fromFile' => null,
41 'fromFileDate' => null,
42 'toFile' => null,
43 'toFileDate' => null,
44 ];
45
46 /**
47 * @var bool
48 */
49 private $changed;
50
51 /**
52 * @var bool
53 */
54 private $collapseRanges;
55
56 /**
57 * @var int >= 0
58 */
59 private $commonLineThreshold;
60
61 /**
62 * @var string
63 */
64 private $header;
65
66 /**
67 * @var int >= 0
68 */
69 private $contextLines;
70
71 public function __construct(array $options = [])
72 {
73 $options = array_merge(self::$default, $options);
74
75 if (!is_bool($options['collapseRanges'])) {
76 throw new ConfigurationException('collapseRanges', 'a bool', $options['collapseRanges']);
77 }
78
79 if (!is_int($options['contextLines']) || $options['contextLines'] < 0) {
80 throw new ConfigurationException('contextLines', 'an int >= 0', $options['contextLines']);
81 }
82
83 if (!is_int($options['commonLineThreshold']) || $options['commonLineThreshold'] <= 0) {
84 throw new ConfigurationException('commonLineThreshold', 'an int > 0', $options['commonLineThreshold']);
85 }
86
87 $this->assertString($options, 'fromFile');
88 $this->assertString($options, 'toFile');
89 $this->assertStringOrNull($options, 'fromFileDate');
90 $this->assertStringOrNull($options, 'toFileDate');
91
92 $this->header = sprintf(
93 "--- %s%s\n+++ %s%s\n",
94 $options['fromFile'],
95 null === $options['fromFileDate'] ? '' : "\t" . $options['fromFileDate'],
96 $options['toFile'],
97 null === $options['toFileDate'] ? '' : "\t" . $options['toFileDate']
98 );
99
100 $this->collapseRanges = $options['collapseRanges'];
101 $this->commonLineThreshold = $options['commonLineThreshold'];
102 $this->contextLines = $options['contextLines'];
103 }
104
105 public function getDiff(array $diff): string
106 {
107 if (0 === count($diff)) {
108 return '';
109 }
110
111 $this->changed = false;
112
113 $buffer = fopen('php://memory', 'r+b');
114 fwrite($buffer, $this->header);
115
116 $this->writeDiffHunks($buffer, $diff);
117
118 if (!$this->changed) {
119 fclose($buffer);
120
121 return '';
122 }
123
124 $diff = stream_get_contents($buffer, -1, 0);
125
126 fclose($buffer);
127
128 // If the last char is not a linebreak: add it.
129 // This might happen when both the `from` and `to` do not have a trailing linebreak
130 $last = substr($diff, -1);
131
132 return "\n" !== $last && "\r" !== $last
133 ? $diff . "\n"
134 : $diff;
135 }
136
137 private function writeDiffHunks($output, array $diff): void
138 {
139 // detect "No newline at end of file" and insert into `$diff` if needed
140
141 $upperLimit = count($diff);
142
143 if (0 === $diff[$upperLimit - 1][1]) {
144 $lc = substr($diff[$upperLimit - 1][0], -1);
145
146 if ("\n" !== $lc) {
147 array_splice($diff, $upperLimit, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]);
148 }
149 } else {
150 // search back for the last `+` and `-` line,
151 // check if has trailing linebreak, else add under it warning under it
152 $toFind = [1 => true, 2 => true];
153
154 for ($i = $upperLimit - 1; $i >= 0; --$i) {
155 if (isset($toFind[$diff[$i][1]])) {
156 unset($toFind[$diff[$i][1]]);
157 $lc = substr($diff[$i][0], -1);
158
159 if ("\n" !== $lc) {
160 array_splice($diff, $i + 1, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]);
161 }
162
163 if (!count($toFind)) {
164 break;
165 }
166 }
167 }
168 }
169
170 // write hunks to output buffer
171
172 $cutOff = max($this->commonLineThreshold, $this->contextLines);
173 $hunkCapture = false;
174 $sameCount = $toRange = $fromRange = 0;
175 $toStart = $fromStart = 1;
176 $i = 0;
177
178 /** @var int $i */
179 foreach ($diff as $i => $entry) {
180 if (0 === $entry[1]) { // same
181 if (false === $hunkCapture) {
182 ++$fromStart;
183 ++$toStart;
184
185 continue;
186 }
187
188 ++$sameCount;
189 ++$toRange;
190 ++$fromRange;
191
192 if ($sameCount === $cutOff) {
193 $contextStartOffset = ($hunkCapture - $this->contextLines) < 0
194 ? $hunkCapture
195 : $this->contextLines;
196
197 // note: $contextEndOffset = $this->contextLines;
198 //
199 // because we never go beyond the end of the diff.
200 // with the cutoff/contextlines here the follow is never true;
201 //
202 // if ($i - $cutOff + $this->contextLines + 1 > \count($diff)) {
203 // $contextEndOffset = count($diff) - 1;
204 // }
205 //
206 // ; that would be true for a trailing incomplete hunk case which is dealt with after this loop
207
208 $this->writeHunk(
209 $diff,
210 $hunkCapture - $contextStartOffset,
211 $i - $cutOff + $this->contextLines + 1,
212 $fromStart - $contextStartOffset,
213 $fromRange - $cutOff + $contextStartOffset + $this->contextLines,
214 $toStart - $contextStartOffset,
215 $toRange - $cutOff + $contextStartOffset + $this->contextLines,
216 $output
217 );
218
219 $fromStart += $fromRange;
220 $toStart += $toRange;
221
222 $hunkCapture = false;
223 $sameCount = $toRange = $fromRange = 0;
224 }
225
226 continue;
227 }
228
229 $sameCount = 0;
230
231 if ($entry[1] === Differ::NO_LINE_END_EOF_WARNING) {
232 continue;
233 }
234
235 $this->changed = true;
236
237 if (false === $hunkCapture) {
238 $hunkCapture = $i;
239 }
240
241 if (Differ::ADDED === $entry[1]) { // added
242 ++$toRange;
243 }
244
245 if (Differ::REMOVED === $entry[1]) { // removed
246 ++$fromRange;
247 }
248 }
249
250 if (false === $hunkCapture) {
251 return;
252 }
253
254 // we end here when cutoff (commonLineThreshold) was not reached, but we where capturing a hunk,
255 // do not render hunk till end automatically because the number of context lines might be less than the commonLineThreshold
256
257 $contextStartOffset = $hunkCapture - $this->contextLines < 0
258 ? $hunkCapture
259 : $this->contextLines;
260
261 // prevent trying to write out more common lines than there are in the diff _and_
262 // do not write more than configured through the context lines
263 $contextEndOffset = min($sameCount, $this->contextLines);
264
265 $fromRange -= $sameCount;
266 $toRange -= $sameCount;
267
268 $this->writeHunk(
269 $diff,
270 $hunkCapture - $contextStartOffset,
271 $i - $sameCount + $contextEndOffset + 1,
272 $fromStart - $contextStartOffset,
273 $fromRange + $contextStartOffset + $contextEndOffset,
274 $toStart - $contextStartOffset,
275 $toRange + $contextStartOffset + $contextEndOffset,
276 $output
277 );
278 }
279
280 private function writeHunk(
281 array $diff,
282 int $diffStartIndex,
283 int $diffEndIndex,
284 int $fromStart,
285 int $fromRange,
286 int $toStart,
287 int $toRange,
288 $output
289 ): void {
290 fwrite($output, '@@ -' . $fromStart);
291
292 if (!$this->collapseRanges || 1 !== $fromRange) {
293 fwrite($output, ',' . $fromRange);
294 }
295
296 fwrite($output, ' +' . $toStart);
297
298 if (!$this->collapseRanges || 1 !== $toRange) {
299 fwrite($output, ',' . $toRange);
300 }
301
302 fwrite($output, " @@\n");
303
304 for ($i = $diffStartIndex; $i < $diffEndIndex; ++$i) {
305 if ($diff[$i][1] === Differ::ADDED) {
306 $this->changed = true;
307 fwrite($output, '+' . $diff[$i][0]);
308 } elseif ($diff[$i][1] === Differ::REMOVED) {
309 $this->changed = true;
310 fwrite($output, '-' . $diff[$i][0]);
311 } elseif ($diff[$i][1] === Differ::OLD) {
312 fwrite($output, ' ' . $diff[$i][0]);
313 } elseif ($diff[$i][1] === Differ::NO_LINE_END_EOF_WARNING) {
314 $this->changed = true;
315 fwrite($output, $diff[$i][0]);
316 }
317 //} elseif ($diff[$i][1] === Differ::DIFF_LINE_END_WARNING) { // custom comment inserted by PHPUnit/diff package
318 // skip
319 //} else {
320 // unknown/invalid
321 //}
322 }
323 }
324
325 private function assertString(array $options, string $option): void
326 {
327 if (!is_string($options[$option])) {
328 throw new ConfigurationException($option, 'a string', $options[$option]);
329 }
330 }
331
332 private function assertStringOrNull(array $options, string $option): void
333 {
334 if (null !== $options[$option] && !is_string($options[$option])) {
335 throw new ConfigurationException($option, 'a string or <null>', $options[$option]);
336 }
337 }
338 }
339