Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 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_splice;
13
use function count;
14
use function fclose;
15
use function fopen;
16
use function fwrite;
17
use function max;
18
use function min;
19
use function stream_get_contents;
20
use function strlen;
21
use function substr;
22
use SebastianBergmann\Diff\Differ;
23
 
24
/**
25
 * Builds a diff string representation in unified diff format in chunks.
26
 */
27
final class UnifiedDiffOutputBuilder extends AbstractChunkOutputBuilder
28
{
29
    /**
30
     * @var bool
31
     */
32
    private $collapseRanges = true;
33
 
34
    /**
35
     * @var int >= 0
36
     */
37
    private $commonLineThreshold = 6;
38
 
39
    /**
40
     * @var int >= 0
41
     */
42
    private $contextLines = 3;
43
 
44
    /**
45
     * @var string
46
     */
47
    private $header;
48
 
49
    /**
50
     * @var bool
51
     */
52
    private $addLineNumbers;
53
 
54
    public function __construct(string $header = "--- Original\n+++ New\n", bool $addLineNumbers = false)
55
    {
56
        $this->header         = $header;
57
        $this->addLineNumbers = $addLineNumbers;
58
    }
59
 
60
    public function getDiff(array $diff): string
61
    {
62
        $buffer = fopen('php://memory', 'r+b');
63
 
64
        if ('' !== $this->header) {
65
            fwrite($buffer, $this->header);
66
 
67
            if ("\n" !== substr($this->header, -1, 1)) {
68
                fwrite($buffer, "\n");
69
            }
70
        }
71
 
72
        if (0 !== count($diff)) {
73
            $this->writeDiffHunks($buffer, $diff);
74
        }
75
 
76
        $diff = stream_get_contents($buffer, -1, 0);
77
 
78
        fclose($buffer);
79
 
80
        // If the diff is non-empty and last char is not a linebreak: add it.
81
        // This might happen when both the `from` and `to` do not have a trailing linebreak
82
        $last = substr($diff, -1);
83
 
84
        return 0 !== strlen($diff) && "\n" !== $last && "\r" !== $last
85
            ? $diff . "\n"
86
            : $diff;
87
    }
88
 
89
    private function writeDiffHunks($output, array $diff): void
90
    {
91
        // detect "No newline at end of file" and insert into `$diff` if needed
92
 
93
        $upperLimit = count($diff);
94
 
95
        if (0 === $diff[$upperLimit - 1][1]) {
96
            $lc = substr($diff[$upperLimit - 1][0], -1);
97
 
98
            if ("\n" !== $lc) {
99
                array_splice($diff, $upperLimit, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]);
100
            }
101
        } else {
102
            // search back for the last `+` and `-` line,
103
            // check if has trailing linebreak, else add under it warning under it
104
            $toFind = [1 => true, 2 => true];
105
 
106
            for ($i = $upperLimit - 1; $i >= 0; --$i) {
107
                if (isset($toFind[$diff[$i][1]])) {
108
                    unset($toFind[$diff[$i][1]]);
109
                    $lc = substr($diff[$i][0], -1);
110
 
111
                    if ("\n" !== $lc) {
112
                        array_splice($diff, $i + 1, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]);
113
                    }
114
 
115
                    if (!count($toFind)) {
116
                        break;
117
                    }
118
                }
119
            }
120
        }
121
 
122
        // write hunks to output buffer
123
 
124
        $cutOff      = max($this->commonLineThreshold, $this->contextLines);
125
        $hunkCapture = false;
126
        $sameCount   = $toRange   = $fromRange = 0;
127
        $toStart     = $fromStart = 1;
128
        $i           = 0;
129
 
130
        /** @var int $i */
131
        foreach ($diff as $i => $entry) {
132
            if (0 === $entry[1]) { // same
133
                if (false === $hunkCapture) {
134
                    ++$fromStart;
135
                    ++$toStart;
136
 
137
                    continue;
138
                }
139
 
140
                ++$sameCount;
141
                ++$toRange;
142
                ++$fromRange;
143
 
144
                if ($sameCount === $cutOff) {
145
                    $contextStartOffset = ($hunkCapture - $this->contextLines) < 0
146
                        ? $hunkCapture
147
                        : $this->contextLines;
148
 
149
                    // note: $contextEndOffset = $this->contextLines;
150
                    //
151
                    // because we never go beyond the end of the diff.
152
                    // with the cutoff/contextlines here the follow is never true;
153
                    //
154
                    // if ($i - $cutOff + $this->contextLines + 1 > \count($diff)) {
155
                    //    $contextEndOffset = count($diff) - 1;
156
                    // }
157
                    //
158
                    // ; that would be true for a trailing incomplete hunk case which is dealt with after this loop
159
 
160
                    $this->writeHunk(
161
                        $diff,
162
                        $hunkCapture - $contextStartOffset,
163
                        $i - $cutOff + $this->contextLines + 1,
164
                        $fromStart - $contextStartOffset,
165
                        $fromRange - $cutOff + $contextStartOffset + $this->contextLines,
166
                        $toStart - $contextStartOffset,
167
                        $toRange - $cutOff + $contextStartOffset + $this->contextLines,
168
                        $output
169
                    );
170
 
171
                    $fromStart += $fromRange;
172
                    $toStart += $toRange;
173
 
174
                    $hunkCapture = false;
175
                    $sameCount   = $toRange = $fromRange = 0;
176
                }
177
 
178
                continue;
179
            }
180
 
181
            $sameCount = 0;
182
 
183
            if ($entry[1] === Differ::NO_LINE_END_EOF_WARNING) {
184
                continue;
185
            }
186
 
187
            if (false === $hunkCapture) {
188
                $hunkCapture = $i;
189
            }
190
 
191
            if (Differ::ADDED === $entry[1]) {
192
                ++$toRange;
193
            }
194
 
195
            if (Differ::REMOVED === $entry[1]) {
196
                ++$fromRange;
197
            }
198
        }
199
 
200
        if (false === $hunkCapture) {
201
            return;
202
        }
203
 
204
        // we end here when cutoff (commonLineThreshold) was not reached, but we where capturing a hunk,
205
        // do not render hunk till end automatically because the number of context lines might be less than the commonLineThreshold
206
 
207
        $contextStartOffset = $hunkCapture - $this->contextLines < 0
208
            ? $hunkCapture
209
            : $this->contextLines;
210
 
211
        // prevent trying to write out more common lines than there are in the diff _and_
212
        // do not write more than configured through the context lines
213
        $contextEndOffset = min($sameCount, $this->contextLines);
214
 
215
        $fromRange -= $sameCount;
216
        $toRange -= $sameCount;
217
 
218
        $this->writeHunk(
219
            $diff,
220
            $hunkCapture - $contextStartOffset,
221
            $i - $sameCount + $contextEndOffset + 1,
222
            $fromStart - $contextStartOffset,
223
            $fromRange + $contextStartOffset + $contextEndOffset,
224
            $toStart - $contextStartOffset,
225
            $toRange + $contextStartOffset + $contextEndOffset,
226
            $output
227
        );
228
    }
229
 
230
    private function writeHunk(
231
        array $diff,
232
        int $diffStartIndex,
233
        int $diffEndIndex,
234
        int $fromStart,
235
        int $fromRange,
236
        int $toStart,
237
        int $toRange,
238
        $output
239
    ): void {
240
        if ($this->addLineNumbers) {
241
            fwrite($output, '@@ -' . $fromStart);
242
 
243
            if (!$this->collapseRanges || 1 !== $fromRange) {
244
                fwrite($output, ',' . $fromRange);
245
            }
246
 
247
            fwrite($output, ' +' . $toStart);
248
 
249
            if (!$this->collapseRanges || 1 !== $toRange) {
250
                fwrite($output, ',' . $toRange);
251
            }
252
 
253
            fwrite($output, " @@\n");
254
        } else {
255
            fwrite($output, "@@ @@\n");
256
        }
257
 
258
        for ($i = $diffStartIndex; $i < $diffEndIndex; ++$i) {
259
            if ($diff[$i][1] === Differ::ADDED) {
260
                fwrite($output, '+' . $diff[$i][0]);
261
            } elseif ($diff[$i][1] === Differ::REMOVED) {
262
                fwrite($output, '-' . $diff[$i][0]);
263
            } elseif ($diff[$i][1] === Differ::OLD) {
264
                fwrite($output, ' ' . $diff[$i][0]);
265
            } elseif ($diff[$i][1] === Differ::NO_LINE_END_EOF_WARNING) {
266
                fwrite($output, "\n"); // $diff[$i][0]
267
            } else { /* Not changed (old) Differ::OLD or Warning Differ::DIFF_LINE_END_WARNING */
268
                fwrite($output, ' ' . $diff[$i][0]);
269
            }
270
        }
271
    }
272
}