FullDiffList::isChangeCodeLine()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 3
c 1
b 0
f 0
dl 0
loc 5
ccs 3
cts 3
cp 1
rs 10
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
/**
4
 * This file is part of SebastianFeldmann\Git.
5
 *
6
 * (c) Sebastian Feldmann <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace SebastianFeldmann\Git\Command\Diff\Compare;
13
14
use SebastianFeldmann\Cli\Command\OutputFormatter;
15
use SebastianFeldmann\Git\Diff\Change;
16
use SebastianFeldmann\Git\Diff\File;
17
use SebastianFeldmann\Git\Diff\Line;
18
19
/**
20
 * FullDiffList output formatter.
21
 *
22
 * Returns a list of SebastianFeldmann\Git\Diff\File objects. Each containing
23
 * the list of changes that happened in that file.
24
 *
25
 * @author  Sebastian Feldmann <[email protected]>
26
 * @link    https://github.com/sebastianfeldmann/git
27
 * @since   Class available since Release 1.2.0
28
 */
29
class FullDiffList implements OutputFormatter
30
{
31
    /**
32
     * Available line types of git diff output.
33
     */
34
    private const LINE_TYPE_START      = 'Start';
35
    private const LINE_TYPE_HEADER     = 'Header';
36
    private const LINE_TYPE_SIMILARITY = 'HeaderSimilarity';
37
    private const LINE_TYPE_OP         = 'HeaderOp';
38
    private const LINE_TYPE_INDEX      = 'HeaderIndex';
39
    private const LINE_TYPE_FORMAT     = 'HeaderFormat';
40
    private const LINE_TYPE_POSITION   = 'ChangePosition';
41
    private const LINE_TYPE_CODE       = 'ChangeCode';
42
43
    /**
44
     * Search and parse strategy.
45
     *
46
     * Define possible follow up lines for each line type to minimize search effort.
47
     *
48
     * @var array
49
     */
50
    private static $lineTypesToCheck = [
51
        self::LINE_TYPE_START => [
52
            self::LINE_TYPE_HEADER
53
        ],
54
        self::LINE_TYPE_HEADER => [
55
            self::LINE_TYPE_SIMILARITY,
56
            self::LINE_TYPE_OP,
57
            self::LINE_TYPE_INDEX,
58
        ],
59
        self::LINE_TYPE_SIMILARITY => [
60
            self::LINE_TYPE_OP,
61
            self::LINE_TYPE_INDEX,
62
        ],
63
        self::LINE_TYPE_OP => [
64
            self::LINE_TYPE_OP,
65
            self::LINE_TYPE_INDEX
66
        ],
67
        self::LINE_TYPE_INDEX => [
68
            self::LINE_TYPE_FORMAT
69
        ],
70
        self::LINE_TYPE_FORMAT => [
71
            self::LINE_TYPE_FORMAT,
72
            self::LINE_TYPE_POSITION
73
        ],
74
        self::LINE_TYPE_POSITION => [
75
            self::LINE_TYPE_CODE
76
        ],
77
        self::LINE_TYPE_CODE => [
78
            self::LINE_TYPE_HEADER,
79
            self::LINE_TYPE_POSITION,
80
            self::LINE_TYPE_CODE
81
        ]
82
    ];
83
84
    /**
85
     * Maps git diff output to file operations.
86
     *
87
     * @var array
88
     */
89
    private static $opsMap = [
90
        'old'     => File::OP_MODIFIED,
91
        'new'     => File::OP_CREATED,
92
        'deleted' => File::OP_DELETED,
93
        'rename'  => File::OP_RENAMED,
94
        'copy'    => File::OP_COPIED,
95
    ];
96
97
    /**
98
     * List of diff File objects.
99
     *
100
     * @var \SebastianFeldmann\Git\Diff\File[]
101
     */
102
    private $files = [];
103
104
    /**
105
     * The currently processed file.
106
     *
107
     * @var \SebastianFeldmann\Git\Diff\File
108
     */
109
    private $currentFile;
110
111
    /**
112
     * The file name of the currently processed file.
113
     *
114
     * @var string
115
     */
116
    private $currentFileName;
117
118
    /**
119
     * The change position of the currently processed file.
120
     *
121
     * @var string
122
     */
123
    private $currentPosition;
124
125
    /**
126
     * The operation of the currently processed file.
127
     *
128
     * @var string
129
     */
130
    private $currentOperation;
131
132
    /**
133
     * List of collected changes.
134
     *
135
     * @var \SebastianFeldmann\Git\Diff\Change[]
136
     */
137
    private $currentChanges = [];
138
139
    /**
140
     * Format the output.
141
     *
142
     * @param  array $output
143 7
     * @return iterable
144
     */
145 7
    public function format(array $output): iterable
146
    {
147 7
        $previousLineType = self::LINE_TYPE_START;
148 7
        // for each line of the output
149
        for ($i = 0, $length = count($output); $i < $length; $i++) {
150
            $line = $output[$i];
151 7
            // depending on the previous line type
152 7
            // check for all possible following line types and handle it
153
            foreach (self::$lineTypesToCheck[$previousLineType] as $typeToCheck) {
154 7
                $call = 'is' . $typeToCheck . 'Line';
155
                // if the line type could be matched
156 7
                if ($this->$call($line)) {
157 7
                    // remember the line type
158
                    $previousLineType = $typeToCheck;
159
                    break;
160
                }
161 7
            }
162
        }
163 7
        $this->appendCollectedFileAndChanges();
164
165
        return $this->files;
166
    }
167
168
    /**
169
     * Is the given line a diff header line.
170
     *
171
     * diff --git a/some/file b/some/file
172
     *
173
     * @param  string $line
174 7
     * @return bool
175
     */
176 7
    private function isHeaderLine(string $line): bool
177 7
    {
178 7
        $matches = [];
179 7
        if (preg_match('#^diff --git [a|b|c|i|w|o]/(.*) [a|b|c|i|w|o]/(.*)#', $line, $matches)) {
180 7
            $this->appendCollectedFileAndChanges();
181 7
            $this->currentOperation = File::OP_MODIFIED;
182
            $this->currentFileName  = $matches[2];
183 7
            return true;
184
        }
185
        return false;
186
    }
187
188
    /**
189
     * Is the given line a diff header similarity line.
190
     *
191
     * similarity index 96%
192
     *
193
     * @param  string $line
194 7
     * @return bool
195
     */
196 7
    private function isHeaderSimilarityLine(string $line): bool
197 7
    {
198
        $matches = [];
199
        return (bool)preg_match('#^(similarity|dissimilarity) index [0-9]+%$#', $line, $matches);
200
    }
201
202
    /**
203
     * Is the given line a diff header operation line.
204
     *
205
     * new file mode 100644
206
     * delete file
207
     * rename from some/file
208
     * rename to some/other/file
209
     *
210
     * @param  string $line
211 7
     * @return bool
212
     */
213 7
    private function isHeaderOpLine(string $line): bool
214 7
    {
215 5
        $matches = [];
216 5
        if (preg_match('#^(old|new|deleted|rename|copy) (file mode|from|to) (.+)#', $line, $matches)) {
217
            $this->currentOperation = self::$opsMap[$matches[1]];
218 7
            return true;
219
        }
220
        return false;
221
    }
222
223
    /**
224
     * Is the given line a diff header index line.
225
     *
226
     * index f7fc435..7b5bd26 100644
227
     *
228
     * @param  string $line
229 7
     * @return bool
230
     */
231 7
    private function isHeaderIndexLine(string $line): bool
232 7
    {
233 7
        $matches = [];
234 7
        if (preg_match('#^index\s([a-z0-9]+)\.\.([a-z0-9]+)(.*)$#i', $line, $matches)) {
235
            $this->currentFile = new File($this->currentFileName, $this->currentOperation);
236 1
            return true;
237
        }
238
        return false;
239
    }
240
241
    /**
242
     * Is the given line a diff header format line.
243
     *
244
     * --- a/some/file
245
     * +++ b/some/file
246
     *
247
     * @param  string $line
248 7
     * @return bool
249
     */
250 7
    private function isHeaderFormatLine(string $line): bool
251 7
    {
252
        $matches = [];
253
        return (bool)preg_match('#^[\-\+]{3} [a|b|c|i|w|o]?/.*#', $line, $matches);
254
    }
255
256
    /**
257
     * Is the given line a diff change position line.
258
     *
259
     * @@ -4,3 +4,10 @@ some file hint
260
     *
261
     * @param  string $line
262 7
     * @return bool
263
     */
264 7
    private function isChangePositionLine(string $line): bool
265 7
    {
266 7
        $matches = [];
267 7
        if (preg_match('#^@@ (\-[0-9,]{3,} \+[0-9,]{3,}) @@ ?(.*)$#', $line, $matches)) {
268 7
            $this->currentPosition                        = $matches[1];
269
            $this->currentChanges[$this->currentPosition] = new Change($matches[1], $matches[2]);
270 7
            return true;
271
        }
272
        return false;
273
    }
274
275
    /**
276
     * In our case we treat every line as code line if no other line type matched before.
277
     *
278
     * @param  string $line
279 7
     * @return bool
280
     */
281 7
    private function isChangeCodeLine(string $line): bool
282 7
    {
283 7
        $line = $this->parseCodeLine($line);
284
        $this->currentChanges[$this->currentPosition]->addLine($line);
285
        return true;
286
    }
287
288
    /**
289
     * Determines the line type and cleans up the line.
290
     *
291
     * @param  string $line
292 7
     * @return \SebastianFeldmann\Git\Diff\Line
293
     */
294 7
    private function parseCodeLine(string $line): Line
295 1
    {
296
        if (strlen($line) == 0) {
297
            return new Line(Line::EXISTED, '');
298 7
        }
299 7
300
        $firstChar = $line[0];
301 7
        $cleanLine = rtrim(substr($line, 1));
302
303
        return new Line(Line::$opsMap[$firstChar], $cleanLine);
304
    }
305
306
    /**
307
     * Add all collected changes to its file.
308
     *
309 7
     * @return void
310
     */
311 7
    private function appendCollectedFileAndChanges(): void
312 7
    {
313 7
        if (!empty($this->currentFile)) {
314
            foreach ($this->currentChanges as $change) {
315 7
                $this->currentFile->addChange($change);
316
            }
317 7
            $this->files[] = $this->currentFile;
318 7
        }
319
        $this->currentChanges = [];
320
    }
321
}
322