1<?php 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 11namespace SebastianBergmann\Diff; 12 13/** 14 * Unified diff parser. 15 */ 16class Parser 17{ 18 /** 19 * @param string $string 20 * 21 * @return Diff[] 22 */ 23 public function parse($string) 24 { 25 $lines = \preg_split('(\r\n|\r|\n)', $string); 26 27 if (!empty($lines) && $lines[\count($lines) - 1] == '') { 28 \array_pop($lines); 29 } 30 31 $lineCount = \count($lines); 32 $diffs = array(); 33 $diff = null; 34 $collected = array(); 35 36 for ($i = 0; $i < $lineCount; ++$i) { 37 if (\preg_match('(^---\\s+(?P<file>\\S+))', $lines[$i], $fromMatch) && 38 \preg_match('(^\\+\\+\\+\\s+(?P<file>\\S+))', $lines[$i + 1], $toMatch)) { 39 if ($diff !== null) { 40 $this->parseFileDiff($diff, $collected); 41 42 $diffs[] = $diff; 43 $collected = array(); 44 } 45 46 $diff = new Diff($fromMatch['file'], $toMatch['file']); 47 48 ++$i; 49 } else { 50 if (\preg_match('/^(?:diff --git |index [\da-f\.]+|[+-]{3} [ab])/', $lines[$i])) { 51 continue; 52 } 53 54 $collected[] = $lines[$i]; 55 } 56 } 57 58 if ($diff !== null && \count($collected)) { 59 $this->parseFileDiff($diff, $collected); 60 61 $diffs[] = $diff; 62 } 63 64 return $diffs; 65 } 66 67 /** 68 * @param Diff $diff 69 * @param array $lines 70 */ 71 private function parseFileDiff(Diff $diff, array $lines) 72 { 73 $chunks = array(); 74 $chunk = null; 75 76 foreach ($lines as $line) { 77 if (\preg_match('/^@@\s+-(?P<start>\d+)(?:,\s*(?P<startrange>\d+))?\s+\+(?P<end>\d+)(?:,\s*(?P<endrange>\d+))?\s+@@/', $line, $match)) { 78 $chunk = new Chunk( 79 $match['start'], 80 isset($match['startrange']) ? \max(1, $match['startrange']) : 1, 81 $match['end'], 82 isset($match['endrange']) ? \max(1, $match['endrange']) : 1 83 ); 84 85 $chunks[] = $chunk; 86 $diffLines = array(); 87 88 continue; 89 } 90 91 if (\preg_match('/^(?P<type>[+ -])?(?P<line>.*)/', $line, $match)) { 92 $type = Line::UNCHANGED; 93 94 if ($match['type'] === '+') { 95 $type = Line::ADDED; 96 } elseif ($match['type'] === '-') { 97 $type = Line::REMOVED; 98 } 99 100 $diffLines[] = new Line($type, $match['line']); 101 102 if (null !== $chunk) { 103 $chunk->setLines($diffLines); 104 } 105 } 106 } 107 108 $diff->setChunks($chunks); 109 } 110} 111