pax_global_header00006660000000000000000000000064131405551120014507gustar00rootroot0000000000000052 comment=347c1d8b49c5c3ee30c7040ea6fc446790e6bddd diff-2.0.1/000077500000000000000000000000001314055511200124175ustar00rootroot00000000000000diff-2.0.1/.gitignore000066400000000000000000000000731314055511200144070ustar00rootroot00000000000000/.idea /composer.lock /vendor /.php_cs.cache /from.txt.origdiff-2.0.1/.php_cs000066400000000000000000000060211314055511200136730ustar00rootroot00000000000000 For the full copyright and license information, please view the LICENSE file that was distributed with this source code. EOF; return PhpCsFixer\Config::create() ->setRiskyAllowed(true) ->setRules( [ 'array_syntax' => ['syntax' => 'short'], 'binary_operator_spaces' => [ 'align_double_arrow' => true, 'align_equals' => true ], 'blank_line_after_namespace' => true, 'blank_line_before_return' => true, 'braces' => true, 'cast_spaces' => true, 'concat_space' => ['spacing' => 'one'], 'declare_strict_types' => true, 'elseif' => true, 'encoding' => true, 'full_opening_tag' => true, 'function_declaration' => true, 'header_comment' => ['header' => $header, 'separate' => 'none'], 'indentation_type' => true, 'line_ending' => true, 'lowercase_constants' => true, 'lowercase_keywords' => true, 'method_argument_space' => true, 'native_function_invocation' => true, 'no_alias_functions' => true, 'no_blank_lines_after_class_opening' => true, 'no_blank_lines_after_phpdoc' => true, 'no_closing_tag' => true, 'no_empty_phpdoc' => true, 'no_empty_statement' => true, 'no_extra_consecutive_blank_lines' => true, 'no_leading_namespace_whitespace' => true, 'no_singleline_whitespace_before_semicolons' => true, 'no_spaces_after_function_name' => true, 'no_spaces_inside_parenthesis' => true, 'no_trailing_comma_in_list_call' => true, 'no_trailing_whitespace' => true, 'no_unused_imports' => true, 'no_whitespace_in_blank_line' => true, 'phpdoc_align' => true, 'phpdoc_indent' => true, 'phpdoc_no_access' => true, 'phpdoc_no_empty_return' => true, 'phpdoc_no_package' => true, 'phpdoc_scalar' => true, 'phpdoc_separation' => true, 'phpdoc_to_comment' => true, 'phpdoc_trim' => true, 'phpdoc_types' => true, 'phpdoc_var_without_name' => true, 'pow_to_exponentiation' => true, 'self_accessor' => true, 'simplified_null_return' => true, 'single_blank_line_at_eof' => true, 'single_import_per_statement' => true, 'single_line_after_imports' => true, 'single_quote' => true, 'ternary_operator_spaces' => true, 'trim_array_spaces' => true, 'visibility_required' => true, ] ) ->setFinder( PhpCsFixer\Finder::create() ->files() ->in(__DIR__ . '/src') ->in(__DIR__ . '/tests') ->name('*.php') ); diff-2.0.1/.travis.yml000066400000000000000000000007031314055511200145300ustar00rootroot00000000000000language: php php: - 7.0 - 7.0snapshot - 7.1 - 7.1snapshot - master sudo: false before_install: - composer self-update - composer clear-cache install: - travis_retry composer update --no-interaction --no-ansi --no-progress --no-suggest --optimize-autoloader --prefer-stable script: - ./vendor/bin/phpunit --coverage-clover=coverage.xml after_success: - bash <(curl -s https://codecov.io/bash) notifications: email: false diff-2.0.1/ChangeLog.md000066400000000000000000000013611314055511200145710ustar00rootroot00000000000000# ChangeLog All notable changes are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. ## [2.0.1] - 2017-08-03 ### Fixed * Fixed [#66](https://github.com/sebastianbergmann/diff/pull/66): Restored backwards compatibility for PHPUnit 6.1.4, 6.2.0, 6.2.1, 6.2.2, and 6.2.3 ## [2.0.0] - 2017-07-11 [YANKED] ### Added * Implemented [#64](https://github.com/sebastianbergmann/diff/pull/64): Show line numbers for chunks of a diff ### Removed * This component is no longer supported on PHP 5.6 [2.0.1]: https://github.com/sebastianbergmann/diff/compare/c341c98ce083db77f896a0aa64f5ee7652915970...2.0.1 [2.0.0]: https://github.com/sebastianbergmann/diff/compare/1.4...c341c98ce083db77f896a0aa64f5ee7652915970 diff-2.0.1/LICENSE000066400000000000000000000030151314055511200134230ustar00rootroot00000000000000sebastian/diff Copyright (c) 2002-2017, Sebastian Bergmann . All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Sebastian Bergmann nor the names of his contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff-2.0.1/README.md000066400000000000000000000126141314055511200137020ustar00rootroot00000000000000# sebastian/diff Diff implementation for PHP, factored out of PHPUnit into a stand-alone component. ## Installation You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/): composer require sebastian/diff If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency: composer require --dev sebastian/diff ### Usage The `Differ` class can be used to generate a textual representation of the difference between two strings: ```php use SebastianBergmann\Diff\Differ; $differ = new Differ; print $differ->diff('foo', 'bar'); ``` The code above yields the output below: --- Original +++ New @@ @@ -foo +bar The `Parser` class can be used to parse a unified diff into an object graph: ```php use SebastianBergmann\Diff\Parser; use SebastianBergmann\Git; $git = new Git('/usr/local/src/money'); $diff = $git->getDiff( '948a1a07768d8edd10dcefa8315c1cbeffb31833', 'c07a373d2399f3e686234c4f7f088d635eb9641b' ); $parser = new Parser; print_r($parser->parse($diff)); ``` The code above yields the output below: Array ( [0] => SebastianBergmann\Diff\Diff Object ( [from:SebastianBergmann\Diff\Diff:private] => a/tests/MoneyTest.php [to:SebastianBergmann\Diff\Diff:private] => b/tests/MoneyTest.php [chunks:SebastianBergmann\Diff\Diff:private] => Array ( [0] => SebastianBergmann\Diff\Chunk Object ( [start:SebastianBergmann\Diff\Chunk:private] => 87 [startRange:SebastianBergmann\Diff\Chunk:private] => 7 [end:SebastianBergmann\Diff\Chunk:private] => 87 [endRange:SebastianBergmann\Diff\Chunk:private] => 7 [lines:SebastianBergmann\Diff\Chunk:private] => Array ( [0] => SebastianBergmann\Diff\Line Object ( [type:SebastianBergmann\Diff\Line:private] => 3 [content:SebastianBergmann\Diff\Line:private] => * @covers SebastianBergmann\Money\Money::add ) [1] => SebastianBergmann\Diff\Line Object ( [type:SebastianBergmann\Diff\Line:private] => 3 [content:SebastianBergmann\Diff\Line:private] => * @covers SebastianBergmann\Money\Money::newMoney ) [2] => SebastianBergmann\Diff\Line Object ( [type:SebastianBergmann\Diff\Line:private] => 3 [content:SebastianBergmann\Diff\Line:private] => */ ) [3] => SebastianBergmann\Diff\Line Object ( [type:SebastianBergmann\Diff\Line:private] => 2 [content:SebastianBergmann\Diff\Line:private] => public function testAnotherMoneyWithSameCurrencyObjectCanBeAdded() ) [4] => SebastianBergmann\Diff\Line Object ( [type:SebastianBergmann\Diff\Line:private] => 1 [content:SebastianBergmann\Diff\Line:private] => public function testAnotherMoneyObjectWithSameCurrencyCanBeAdded() ) [5] => SebastianBergmann\Diff\Line Object ( [type:SebastianBergmann\Diff\Line:private] => 3 [content:SebastianBergmann\Diff\Line:private] => { ) [6] => SebastianBergmann\Diff\Line Object ( [type:SebastianBergmann\Diff\Line:private] => 3 [content:SebastianBergmann\Diff\Line:private] => $a = new Money(1, new Currency('EUR')); ) [7] => SebastianBergmann\Diff\Line Object ( [type:SebastianBergmann\Diff\Line:private] => 3 [content:SebastianBergmann\Diff\Line:private] => $b = new Money(2, new Currency('EUR')); ) ) ) ) ) ) diff-2.0.1/build.xml000066400000000000000000000014161314055511200142420ustar00rootroot00000000000000 diff-2.0.1/composer.json000066400000000000000000000013001314055511200151330ustar00rootroot00000000000000{ "name": "sebastian/diff", "description": "Diff implementation", "keywords": ["diff"], "homepage": "https://github.com/sebastianbergmann/diff", "license": "BSD-3-Clause", "authors": [ { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de" }, { "name": "Kore Nordmann", "email": "mail@kore-nordmann.de" } ], "require": { "php": "^7.0" }, "require-dev": { "phpunit/phpunit": "^6.2" }, "autoload": { "classmap": [ "src/" ] }, "extra": { "branch-alias": { "dev-master": "2.0-dev" } } } diff-2.0.1/phpunit.xml000066400000000000000000000012651314055511200146340ustar00rootroot00000000000000 tests src diff-2.0.1/src/000077500000000000000000000000001314055511200132065ustar00rootroot00000000000000diff-2.0.1/src/Chunk.php000066400000000000000000000025711314055511200147740ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; final class Chunk { /** * @var int */ private $start; /** * @var int */ private $startRange; /** * @var int */ private $end; /** * @var int */ private $endRange; /** * @var array */ private $lines; public function __construct(int $start = 0, int $startRange = 1, int $end = 0, int $endRange = 1, array $lines = []) { $this->start = $start; $this->startRange = $startRange; $this->end = $end; $this->endRange = $endRange; $this->lines = $lines; } public function getStart(): int { return $this->start; } public function getStartRange(): int { return $this->startRange; } public function getEnd(): int { return $this->end; } public function getEndRange(): int { return $this->endRange; } public function getLines(): array { return $this->lines; } public function setLines(array $lines) { $this->lines = $lines; } } diff-2.0.1/src/Diff.php000066400000000000000000000022321314055511200145660ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; final class Diff { /** * @var string */ private $from; /** * @var string */ private $to; /** * @var Chunk[] */ private $chunks; /** * @param string $from * @param string $to * @param Chunk[] $chunks */ public function __construct(string $from, string $to, array $chunks = []) { $this->from = $from; $this->to = $to; $this->chunks = $chunks; } public function getFrom(): string { return $this->from; } public function getTo(): string { return $this->to; } /** * @return Chunk[] */ public function getChunks(): array { return $this->chunks; } /** * @param Chunk[] $chunks */ public function setChunks(array $chunks) { $this->chunks = $chunks; } } diff-2.0.1/src/Differ.php000066400000000000000000000221201314055511200151130ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; use SebastianBergmann\Diff\Output\DiffOutputBuilderInterface; use SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder; /** * Diff implementation. */ final class Differ { /** * @var DiffOutputBuilderInterface */ private $outputBuilder; /** * @param DiffOutputBuilderInterface $outputBuilder * * @throws InvalidArgumentException */ public function __construct($outputBuilder = null) { if ($outputBuilder instanceof DiffOutputBuilderInterface) { $this->outputBuilder = $outputBuilder; } elseif (null === $outputBuilder) { $this->outputBuilder = new UnifiedDiffOutputBuilder; } elseif (\is_string($outputBuilder)) { // PHPUnit 6.1.4, 6.2.0, 6.2.1, 6.2.2, and 6.2.3 support // @see https://github.com/sebastianbergmann/phpunit/issues/2734#issuecomment-314514056 // @deprecated $this->outputBuilder = new UnifiedDiffOutputBuilder($outputBuilder); } else { throw new InvalidArgumentException( \sprintf( 'Expected builder to be an instance of DiffOutputBuilderInterface, or a string, got %s.', \is_object($outputBuilder) ? 'instance of "' . \get_class($outputBuilder) . '"' : \gettype($outputBuilder) . ' "' . $outputBuilder . '"' ) ); } } /** * Returns the diff between two arrays or strings as string. * * @param array|string $from * @param array|string $to * @param LongestCommonSubsequenceCalculator|null $lcs * * @return string */ public function diff($from, $to, LongestCommonSubsequenceCalculator $lcs = null): string { $from = $this->validateDiffInput($from); $to = $this->validateDiffInput($to); $diff = $this->diffToArray($from, $to, $lcs); return $this->outputBuilder->getDiff($diff); } /** * Casts variable to string if it is not a string or array. * * @param mixed $input * * @return string */ private function validateDiffInput($input): string { if (!\is_array($input) && !\is_string($input)) { return (string) $input; } return $input; } /** * Returns the diff between two arrays or strings as array. * * Each array element contains two elements: * - [0] => mixed $token * - [1] => 2|1|0 * * - 2: REMOVED: $token was removed from $from * - 1: ADDED: $token was added to $from * - 0: OLD: $token is not changed in $to * * @param array|string $from * @param array|string $to * @param LongestCommonSubsequenceCalculator $lcs * * @return array */ public function diffToArray($from, $to, LongestCommonSubsequenceCalculator $lcs = null): array { if (\is_string($from)) { $from = $this->splitStringByLines($from); } elseif (!\is_array($from)) { throw new \InvalidArgumentException('"from" must be an array or string.'); } if (\is_string($to)) { $to = $this->splitStringByLines($to); } elseif (!\is_array($to)) { throw new \InvalidArgumentException('"to" must be an array or string.'); } list($from, $to, $start, $end) = self::getArrayDiffParted($from, $to); if ($lcs === null) { $lcs = $this->selectLcsImplementation($from, $to); } $common = $lcs->calculate(\array_values($from), \array_values($to)); $diff = []; foreach ($start as $token) { $diff[] = [$token, 0 /* OLD */]; } \reset($from); \reset($to); foreach ($common as $token) { while (($fromToken = \reset($from)) !== $token) { $diff[] = [\array_shift($from), 2 /* REMOVED */]; } while (($toToken = \reset($to)) !== $token) { $diff[] = [\array_shift($to), 1 /* ADDED */]; } $diff[] = [$token, 0 /* OLD */]; \array_shift($from); \array_shift($to); } while (($token = \array_shift($from)) !== null) { $diff[] = [$token, 2 /* REMOVED */]; } while (($token = \array_shift($to)) !== null) { $diff[] = [$token, 1 /* ADDED */]; } foreach ($end as $token) { $diff[] = [$token, 0 /* OLD */]; } if ($this->detectUnmatchedLineEndings($diff)) { \array_unshift($diff, ["#Warning: Strings contain different line endings!\n", 3]); } return $diff; } /** * Checks if input is string, if so it will split it line-by-line. * * @param string $input * * @return array */ private function splitStringByLines(string $input): array { return \preg_split('/(.*\R)/', $input, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); } /** * @param array $from * @param array $to * * @return LongestCommonSubsequenceCalculator */ private function selectLcsImplementation(array $from, array $to): LongestCommonSubsequenceCalculator { // We do not want to use the time-efficient implementation if its memory // footprint will probably exceed this value. Note that the footprint // calculation is only an estimation for the matrix and the LCS method // will typically allocate a bit more memory than this. $memoryLimit = 100 * 1024 * 1024; if ($this->calculateEstimatedFootprint($from, $to) > $memoryLimit) { return new MemoryEfficientLongestCommonSubsequenceCalculator; } return new TimeEfficientLongestCommonSubsequenceCalculator; } /** * Calculates the estimated memory footprint for the DP-based method. * * @param array $from * @param array $to * * @return int|float */ private function calculateEstimatedFootprint(array $from, array $to) { $itemSize = PHP_INT_SIZE === 4 ? 76 : 144; return $itemSize * \min(\count($from), \count($to)) ** 2; } /** * Returns true if line ends don't match in a diff. * * @param array $diff * * @return bool */ private function detectUnmatchedLineEndings(array $diff): bool { $newLineBreaks = ['' => true]; $oldLineBreaks = ['' => true]; foreach ($diff as $entry) { if (0 === $entry[1]) { /* OLD */ $ln = $this->getLinebreak($entry[0]); $oldLineBreaks[$ln] = true; $newLineBreaks[$ln] = true; } elseif (1 === $entry[1]) { /* ADDED */ $newLineBreaks[$this->getLinebreak($entry[0])] = true; } elseif (2 === $entry[1]) { /* REMOVED */ $oldLineBreaks[$this->getLinebreak($entry[0])] = true; } } // if either input or output is a single line without breaks than no warning should be raised if (['' => true] === $newLineBreaks || ['' => true] === $oldLineBreaks) { return false; } // two way compare foreach ($newLineBreaks as $break => $set) { if (!isset($oldLineBreaks[$break])) { return true; } } foreach ($oldLineBreaks as $break => $set) { if (!isset($newLineBreaks[$break])) { return true; } } return false; } private function getLinebreak($line): string { if (!\is_string($line)) { return ''; } $lc = \substr($line, -1); if ("\r" === $lc) { return "\r"; } if ("\n" !== $lc) { return ''; } if ("\r\n" === \substr($line, -2)) { return "\r\n"; } return "\n"; } private static function getArrayDiffParted(array &$from, array &$to): array { $start = []; $end = []; \reset($to); foreach ($from as $k => $v) { $toK = \key($to); if ($toK === $k && $v === $to[$k]) { $start[$k] = $v; unset($from[$k], $to[$k]); } else { break; } } \end($from); \end($to); do { $fromK = \key($from); $toK = \key($to); if (null === $fromK || null === $toK || \current($from) !== \current($to)) { break; } \prev($from); \prev($to); $end = [$fromK => $from[$fromK]] + $end; unset($from[$fromK], $to[$toK]); } while (true); return [$from, $to, $start, $end]; } } diff-2.0.1/src/Exception/000077500000000000000000000000001314055511200151445ustar00rootroot00000000000000diff-2.0.1/src/Exception/Exception.php000066400000000000000000000005001314055511200176060ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; interface Exception { } diff-2.0.1/src/Exception/InvalidArgumentException.php000066400000000000000000000006021314055511200226230ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; class InvalidArgumentException extends \InvalidArgumentException implements Exception { } diff-2.0.1/src/Line.php000066400000000000000000000014721314055511200146120ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; final class Line { const ADDED = 1; const REMOVED = 2; const UNCHANGED = 3; /** * @var int */ private $type; /** * @var string */ private $content; public function __construct(int $type = self::UNCHANGED, string $content = '') { $this->type = $type; $this->content = $content; } public function getContent(): string { return $this->content; } public function getType(): int { return $this->type; } } diff-2.0.1/src/LongestCommonSubsequenceCalculator.php000066400000000000000000000010741314055511200227220ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; interface LongestCommonSubsequenceCalculator { /** * Calculates the longest common subsequence of two arrays. * * @param array $from * @param array $to * * @return array */ public function calculate(array $from, array $to): array; } diff-2.0.1/src/MemoryEfficientLongestCommonSubsequenceCalculator.php000066400000000000000000000041261314055511200257310ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; final class MemoryEfficientLongestCommonSubsequenceCalculator implements LongestCommonSubsequenceCalculator { /** * {@inheritdoc} */ public function calculate(array $from, array $to): array { $cFrom = \count($from); $cTo = \count($to); if ($cFrom === 0) { return []; } if ($cFrom === 1) { if (\in_array($from[0], $to, true)) { return [$from[0]]; } return []; } $i = (int) ($cFrom / 2); $fromStart = \array_slice($from, 0, $i); $fromEnd = \array_slice($from, $i); $llB = $this->length($fromStart, $to); $llE = $this->length(\array_reverse($fromEnd), \array_reverse($to)); $jMax = 0; $max = 0; for ($j = 0; $j <= $cTo; $j++) { $m = $llB[$j] + $llE[$cTo - $j]; if ($m >= $max) { $max = $m; $jMax = $j; } } $toStart = \array_slice($to, 0, $jMax); $toEnd = \array_slice($to, $jMax); return \array_merge( $this->calculate($fromStart, $toStart), $this->calculate($fromEnd, $toEnd) ); } private function length(array $from, array $to): array { $current = \array_fill(0, \count($to) + 1, 0); $cFrom = \count($from); $cTo = \count($to); for ($i = 0; $i < $cFrom; $i++) { $prev = $current; for ($j = 0; $j < $cTo; $j++) { if ($from[$i] === $to[$j]) { $current[$j + 1] = $prev[$j] + 1; } else { $current[$j + 1] = \max($current[$j], $prev[$j + 1]); } } } return $current; } } diff-2.0.1/src/Output/000077500000000000000000000000001314055511200145065ustar00rootroot00000000000000diff-2.0.1/src/Output/AbstractChunkOutputBuilder.php000066400000000000000000000031171314055511200225050ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff\Output; abstract class AbstractChunkOutputBuilder implements DiffOutputBuilderInterface { /** * Takes input of the diff array and returns the common parts. * Iterates through diff line by line. * * @param array $diff * @param int $lineThreshold * * @return array */ protected function getCommonChunks(array $diff, int $lineThreshold = 5): array { $diffSize = \count($diff); $capturing = false; $chunkStart = 0; $chunkSize = 0; $commonChunks = []; for ($i = 0; $i < $diffSize; ++$i) { if ($diff[$i][1] === 0 /* OLD */) { if ($capturing === false) { $capturing = true; $chunkStart = $i; $chunkSize = 0; } else { ++$chunkSize; } } elseif ($capturing !== false) { if ($chunkSize >= $lineThreshold) { $commonChunks[$chunkStart] = $chunkStart + $chunkSize; } $capturing = false; } } if ($capturing !== false && $chunkSize >= $lineThreshold) { $commonChunks[$chunkStart] = $chunkStart + $chunkSize; } return $commonChunks; } } diff-2.0.1/src/Output/DiffOnlyOutputBuilder.php000066400000000000000000000036241314055511200214660ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff\Output; /** * Builds a diff string representation in a loose unified diff format * listing only changes lines. Does not include line numbers. */ final class DiffOnlyOutputBuilder implements DiffOutputBuilderInterface { /** * @var string */ private $header; public function __construct(string $header = "--- Original\n+++ New\n") { $this->header = $header; } public function getDiff(array $diff): string { $buffer = \fopen('php://memory', 'r+b'); if ('' !== $this->header) { \fwrite($buffer, $this->header); if ("\n" !== \substr($this->header, -1, 1)) { \fwrite($buffer, "\n"); } } foreach ($diff as $diffEntry) { if ($diffEntry[1] === 1 /* ADDED */) { \fwrite($buffer, '+' . $diffEntry[0]); } elseif ($diffEntry[1] === 2 /* REMOVED */) { \fwrite($buffer, '-' . $diffEntry[0]); } elseif ($diffEntry[1] === 3 /* WARNING */) { \fwrite($buffer, ' ' . $diffEntry[0]); continue; // Warnings should not be tested for line break, it will always be there } else { /* Not changed (old) 0 */ continue; // we didn't write the non changs line, so do not add a line break either } $lc = \substr($diffEntry[0], -1); if ($lc !== "\n" && $lc !== "\r") { \fwrite($buffer, "\n"); // \No newline at end of file } } $diff = \stream_get_contents($buffer, -1, 0); \fclose($buffer); return $diff; } } diff-2.0.1/src/Output/DiffOutputBuilderInterface.php000066400000000000000000000010111314055511200224310ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff\Output; /** * Defines how an output builder should take a generated * diff array and return a string representation of that diff. */ interface DiffOutputBuilderInterface { public function getDiff(array $diff): string; } diff-2.0.1/src/Output/UnifiedDiffOutputBuilder.php000066400000000000000000000120701314055511200221230ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff\Output; /** * Builds a diff string representation in unified diff format in chunks. */ final class UnifiedDiffOutputBuilder extends AbstractChunkOutputBuilder { /** * @var string */ private $header; /** * @var bool */ private $addLineNumbers; public function __construct(string $header = "--- Original\n+++ New\n", bool $addLineNumbers = false) { $this->header = $header; $this->addLineNumbers = $addLineNumbers; } public function getDiff(array $diff): string { $buffer = \fopen('php://memory', 'r+b'); if ('' !== $this->header) { \fwrite($buffer, $this->header); if ("\n" !== \substr($this->header, -1, 1)) { \fwrite($buffer, "\n"); } } $this->writeDiffChunked($buffer, $diff, $this->getCommonChunks($diff)); $diff = \stream_get_contents($buffer, -1, 0); \fclose($buffer); return $diff; } // `old` is an array with key => value pairs . Each pair represents a start and end index of `diff` // of a list of elements all containing `same` (0) entries. private function writeDiffChunked($output, array $diff, array $old) { $upperLimit = \count($diff); $start = 0; $fromStart = 0; $toStart = 0; if (\count($old)) { // no common parts, list all diff entries \reset($old); // iterate the diff, go from chunk to chunk skipping common chunk of lines between those do { $commonStart = \key($old); $commonEnd = \current($old); if ($commonStart !== $start) { list($fromRange, $toRange) = $this->getChunkRange($diff, $start, $commonStart); $this->writeChunk($output, $diff, $start, $commonStart, $fromStart, $fromRange, $toStart, $toRange); $fromStart += $fromRange; $toStart += $toRange; } $start = $commonEnd + 1; $commonLength = $commonEnd - $commonStart + 1; // calculate number of non-change lines in the common part $fromStart += $commonLength; $toStart += $commonLength; } while (false !== \next($old)); \end($old); // short cut for finding possible last `change entry` $tmp = \key($old); \reset($old); if ($old[$tmp] === $upperLimit - 1) { $upperLimit = $tmp; } } if ($start < $upperLimit - 1) { // check for trailing (non) diff entries do { --$upperLimit; } while (isset($diff[$upperLimit][1]) && $diff[$upperLimit][1] === 0); ++$upperLimit; list($fromRange, $toRange) = $this->getChunkRange($diff, $start, $upperLimit); $this->writeChunk($output, $diff, $start, $upperLimit, $fromStart, $fromRange, $toStart, $toRange); } } private function writeChunk( $output, array $diff, int $diffStartIndex, int $diffEndIndex, int $fromStart, int $fromRange, int $toStart, int $toRange ) { if ($this->addLineNumbers) { \fwrite($output, '@@ -' . (1 + $fromStart)); if ($fromRange > 1) { \fwrite($output, ',' . $fromRange); } \fwrite($output, ' +' . (1 + $toStart)); if ($toRange > 1) { \fwrite($output, ',' . $toRange); } \fwrite($output, " @@\n"); } else { \fwrite($output, "@@ @@\n"); } for ($i = $diffStartIndex; $i < $diffEndIndex; ++$i) { if ($diff[$i][1] === 1 /* ADDED */) { \fwrite($output, '+' . $diff[$i][0]); } elseif ($diff[$i][1] === 2 /* REMOVED */) { \fwrite($output, '-' . $diff[$i][0]); } else { /* Not changed (old) 0 or Warning 3 */ \fwrite($output, ' ' . $diff[$i][0]); } $lc = \substr($diff[$i][0], -1); if ($lc !== "\n" && $lc !== "\r") { \fwrite($output, "\n"); // \No newline at end of file } } } private function getChunkRange(array $diff, int $diffStartIndex, int $diffEndIndex): array { $toRange = 0; $fromRange = 0; for ($i = $diffStartIndex; $i < $diffEndIndex; ++$i) { if ($diff[$i][1] === 1) { // added ++$toRange; } elseif ($diff[$i][1] === 2) { // removed ++$fromRange; } elseif ($diff[$i][1] === 0) { // same ++$fromRange; ++$toRange; } } return [$fromRange, $toRange]; } } diff-2.0.1/src/Parser.php000066400000000000000000000056401314055511200151600ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; /** * Unified diff parser. */ final class Parser { /** * @param string $string * * @return Diff[] */ public function parse(string $string): array { $lines = \preg_split('(\r\n|\r|\n)', $string); if (!empty($lines) && $lines[\count($lines) - 1] === '') { \array_pop($lines); } $lineCount = \count($lines); $diffs = []; $diff = null; $collected = []; for ($i = 0; $i < $lineCount; ++$i) { if (\preg_match('(^---\\s+(?P\\S+))', $lines[$i], $fromMatch) && \preg_match('(^\\+\\+\\+\\s+(?P\\S+))', $lines[$i + 1], $toMatch)) { if ($diff !== null) { $this->parseFileDiff($diff, $collected); $diffs[] = $diff; $collected = []; } $diff = new Diff($fromMatch['file'], $toMatch['file']); ++$i; } else { if (\preg_match('/^(?:diff --git |index [\da-f\.]+|[+-]{3} [ab])/', $lines[$i])) { continue; } $collected[] = $lines[$i]; } } if ($diff !== null && \count($collected)) { $this->parseFileDiff($diff, $collected); $diffs[] = $diff; } return $diffs; } private function parseFileDiff(Diff $diff, array $lines) { $chunks = []; $chunk = null; foreach ($lines as $line) { if (\preg_match('/^@@\s+-(?P\d+)(?:,\s*(?P\d+))?\s+\+(?P\d+)(?:,\s*(?P\d+))?\s+@@/', $line, $match)) { $chunk = new Chunk( (int) $match['start'], isset($match['startrange']) ? \max(1, (int) $match['startrange']) : 1, (int) $match['end'], isset($match['endrange']) ? \max(1, (int) $match['endrange']) : 1 ); $chunks[] = $chunk; $diffLines = []; continue; } if (\preg_match('/^(?P[+ -])?(?P.*)/', $line, $match)) { $type = Line::UNCHANGED; if ($match['type'] === '+') { $type = Line::ADDED; } elseif ($match['type'] === '-') { $type = Line::REMOVED; } $diffLines[] = new Line($type, $match['line']); if (null !== $chunk) { $chunk->setLines($diffLines); } } } $diff->setChunks($chunks); } } diff-2.0.1/src/TimeEfficientLongestCommonSubsequenceCalculator.php000066400000000000000000000034141314055511200253560ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; final class TimeEfficientLongestCommonSubsequenceCalculator implements LongestCommonSubsequenceCalculator { /** * {@inheritdoc} */ public function calculate(array $from, array $to): array { $common = []; $fromLength = \count($from); $toLength = \count($to); $width = $fromLength + 1; $matrix = new \SplFixedArray($width * ($toLength + 1)); for ($i = 0; $i <= $fromLength; ++$i) { $matrix[$i] = 0; } for ($j = 0; $j <= $toLength; ++$j) { $matrix[$j * $width] = 0; } for ($i = 1; $i <= $fromLength; ++$i) { for ($j = 1; $j <= $toLength; ++$j) { $o = ($j * $width) + $i; $matrix[$o] = \max( $matrix[$o - 1], $matrix[$o - $width], $from[$i - 1] === $to[$j - 1] ? $matrix[$o - $width - 1] + 1 : 0 ); } } $i = $fromLength; $j = $toLength; while ($i > 0 && $j > 0) { if ($from[$i - 1] === $to[$j - 1]) { $common[] = $from[$i - 1]; --$i; --$j; } else { $o = ($j * $width) + $i; if ($matrix[$o - $width] > $matrix[$o - 1]) { --$j; } else { --$i; } } } return \array_reverse($common); } } diff-2.0.1/tests/000077500000000000000000000000001314055511200135615ustar00rootroot00000000000000diff-2.0.1/tests/ChunkTest.php000066400000000000000000000027721314055511200162120ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; use PHPUnit\Framework\TestCase; /** * @covers SebastianBergmann\Diff\Chunk */ final class ChunkTest extends TestCase { /** * @var Chunk */ private $chunk; protected function setUp() { $this->chunk = new Chunk; } public function testCanBeCreatedWithoutArguments() { $this->assertInstanceOf(Chunk::class, $this->chunk); } public function testStartCanBeRetrieved() { $this->assertSame(0, $this->chunk->getStart()); } public function testStartRangeCanBeRetrieved() { $this->assertSame(1, $this->chunk->getStartRange()); } public function testEndCanBeRetrieved() { $this->assertSame(0, $this->chunk->getEnd()); } public function testEndRangeCanBeRetrieved() { $this->assertSame(1, $this->chunk->getEndRange()); } public function testLinesCanBeRetrieved() { $this->assertSame([], $this->chunk->getLines()); } public function testLinesCanBeSet() { $this->assertSame([], $this->chunk->getLines()); $testValue = ['line0', 'line1']; $this->chunk->setLines($testValue); $this->assertSame($testValue, $this->chunk->getLines()); } } diff-2.0.1/tests/DiffTest.php000066400000000000000000000031431314055511200160030ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; use PHPUnit\Framework\TestCase; /** * @covers SebastianBergmann\Diff\Diff * * @uses SebastianBergmann\Diff\Chunk */ final class DiffTest extends TestCase { public function testGettersAfterConstructionWithDefault() { $from = 'line1a'; $to = 'line2a'; $diff = new Diff($from, $to); $this->assertSame($from, $diff->getFrom()); $this->assertSame($to, $diff->getTo()); $this->assertSame([], $diff->getChunks(), 'Expect chunks to be default value "array()".'); } public function testGettersAfterConstructionWithChunks() { $from = 'line1b'; $to = 'line2b'; $chunks = [new Chunk(), new Chunk(2, 3)]; $diff = new Diff($from, $to, $chunks); $this->assertSame($from, $diff->getFrom()); $this->assertSame($to, $diff->getTo()); $this->assertSame($chunks, $diff->getChunks(), 'Expect chunks to be passed value.'); } public function testSetChunksAfterConstruction() { $diff = new Diff('line1c', 'line2c'); $this->assertSame([], $diff->getChunks(), 'Expect chunks to be default value "array()".'); $chunks = [new Chunk(), new Chunk(2, 3)]; $diff->setChunks($chunks); $this->assertSame($chunks, $diff->getChunks(), 'Expect chunks to be passed value.'); } } diff-2.0.1/tests/DifferTest.php000066400000000000000000000720531314055511200163400ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; use PHPUnit\Framework\TestCase; use SebastianBergmann\Diff\Output\AbstractChunkOutputBuilder; use SebastianBergmann\Diff\Output\DiffOnlyOutputBuilder; use SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder; /** * @covers SebastianBergmann\Diff\Differ * @covers SebastianBergmann\Diff\Output\AbstractChunkOutputBuilder * @covers SebastianBergmann\Diff\Output\DiffOnlyOutputBuilder * @covers SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder * * @uses SebastianBergmann\Diff\MemoryEfficientLongestCommonSubsequenceCalculator * @uses SebastianBergmann\Diff\TimeEfficientLongestCommonSubsequenceCalculator * @uses SebastianBergmann\Diff\Chunk * @uses SebastianBergmann\Diff\Diff * @uses SebastianBergmann\Diff\Line * @uses SebastianBergmann\Diff\Parser */ final class DifferTest extends TestCase { const WARNING = 3; const REMOVED = 2; const ADDED = 1; const OLD = 0; /** * @var Differ */ private $differ; protected function setUp() { $this->differ = new Differ; } /** * @param array $expected * @param string|array $from * @param string|array $to * @dataProvider arrayProvider */ public function testArrayRepresentationOfDiffCanBeRenderedUsingTimeEfficientLcsImplementation(array $expected, $from, $to) { $this->assertSame($expected, $this->differ->diffToArray($from, $to, new TimeEfficientLongestCommonSubsequenceCalculator)); } /** * @param string $expected * @param string $from * @param string $to * @dataProvider textProvider */ public function testTextRepresentationOfDiffCanBeRenderedUsingTimeEfficientLcsImplementation(string $expected, string $from, string $to) { $this->assertSame($expected, $this->differ->diff($from, $to, new TimeEfficientLongestCommonSubsequenceCalculator)); } /** * @param array $expected * @param string|array $from * @param string|array $to * @dataProvider arrayProvider */ public function testArrayRepresentationOfDiffCanBeRenderedUsingMemoryEfficientLcsImplementation(array $expected, $from, $to) { $this->assertSame($expected, $this->differ->diffToArray($from, $to, new MemoryEfficientLongestCommonSubsequenceCalculator)); } /** * @param string $expected * @param string $from * @param string $to * @dataProvider textProvider */ public function testTextRepresentationOfDiffCanBeRenderedUsingMemoryEfficientLcsImplementation(string $expected, string $from, string $to) { $this->assertSame($expected, $this->differ->diff($from, $to, new MemoryEfficientLongestCommonSubsequenceCalculator)); } /** * @param string $expected * @param string $from * @param string $to * @param string $header * @dataProvider headerProvider */ public function testCustomHeaderCanBeUsed(string $expected, string $from, string $to, string $header) { $differ = new Differ(new UnifiedDiffOutputBuilder($header)); $this->assertSame( $expected, $differ->diff($from, $to) ); } public function headerProvider() { return [ [ "CUSTOM HEADER\n@@ @@\n-a\n+b\n", 'a', 'b', 'CUSTOM HEADER' ], [ "CUSTOM HEADER\n@@ @@\n-a\n+b\n", 'a', 'b', "CUSTOM HEADER\n" ], [ "CUSTOM HEADER\n\n@@ @@\n-a\n+b\n", 'a', 'b', "CUSTOM HEADER\n\n" ], [ "@@ @@\n-a\n+b\n", 'a', 'b', '' ], ]; } public function testTypesOtherThanArrayAndStringCanBePassed() { $this->assertSame( "--- Original\n+++ New\n@@ @@\n-1\n+2\n", $this->differ->diff(1, 2) ); } /** * @param string $diff * @param Diff[] $expected * @dataProvider diffProvider */ public function testParser(string $diff, array $expected) { $parser = new Parser; $result = $parser->parse($diff); $this->assertEquals($expected, $result); } public function arrayProvider(): array { return [ [ [ ['a', self::REMOVED], ['b', self::ADDED] ], 'a', 'b' ], [ [ ['ba', self::REMOVED], ['bc', self::ADDED] ], 'ba', 'bc' ], [ [ ['ab', self::REMOVED], ['cb', self::ADDED] ], 'ab', 'cb' ], [ [ ['abc', self::REMOVED], ['adc', self::ADDED] ], 'abc', 'adc' ], [ [ ['ab', self::REMOVED], ['abc', self::ADDED] ], 'ab', 'abc' ], [ [ ['bc', self::REMOVED], ['abc', self::ADDED] ], 'bc', 'abc' ], [ [ ['abc', self::REMOVED], ['abbc', self::ADDED] ], 'abc', 'abbc' ], [ [ ['abcdde', self::REMOVED], ['abcde', self::ADDED] ], 'abcdde', 'abcde' ], 'same start' => [ [ [17, self::OLD], ['b', self::REMOVED], ['d', self::ADDED], ], [30 => 17, 'a' => 'b'], [30 => 17, 'c' => 'd'], ], 'same end' => [ [ [1, self::REMOVED], [2, self::ADDED], ['b', self::OLD], ], [1 => 1, 'a' => 'b'], [1 => 2, 'a' => 'b'], ], 'same start (2), same end (1)' => [ [ [17, self::OLD], [2, self::OLD], [4, self::REMOVED], ['a', self::ADDED], [5, self::ADDED], ['x', self::OLD], ], [30 => 17, 1 => 2, 2 => 4, 'z' => 'x'], [30 => 17, 1 => 2, 3 => 'a', 2 => 5, 'z' => 'x'], ], 'same' => [ [ ['x', self::OLD], ], ['z' => 'x'], ['z' => 'x'], ], 'diff' => [ [ ['y', self::REMOVED], ['x', self::ADDED], ], ['x' => 'y'], ['z' => 'x'], ], 'diff 2' => [ [ ['y', self::REMOVED], ['b', self::REMOVED], ['x', self::ADDED], ['d', self::ADDED], ], ['x' => 'y', 'a' => 'b'], ['z' => 'x', 'c' => 'd'], ], 'test line diff detection' => [ [ [ "#Warning: Strings contain different line endings!\n", self::WARNING, ], [ " [ [ [ "#Warning: Strings contain different line endings!\n", self::WARNING, ], [ "assertSame($expected, $differ->diff($from, $to)); } public function textForNoNonDiffLinesProvider(): array { return [ [ " #Warning: Strings contain different line endings!\n-A\r\n+B\n", "A\r\n", "B\n", ], [ "-A\n+B\n", "\nA", "\nB" ], [ '', 'a', 'a' ], [ "-A\n+C\n", "A\n\n\nB", "C\n\n\nB", ], [ "header\n", 'a', 'a', 'header' ], [ "header\n", 'a', 'a', "header\n" ], ]; } public function testDiffToArrayInvalidFromType() { $this->expectException('\InvalidArgumentException'); $this->expectExceptionMessageRegExp('#^"from" must be an array or string\.$#'); $this->differ->diffToArray(null, ''); } public function testDiffInvalidToType() { $this->expectException('\InvalidArgumentException'); $this->expectExceptionMessageRegExp('#^"to" must be an array or string\.$#'); $this->differ->diffToArray('', new \stdClass); } /** * @param array $expected * @param string $from * @param string $to * @param int $lineThreshold * @dataProvider provideGetCommonChunks */ public function testGetCommonChunks(array $expected, string $from, string $to, int $lineThreshold = 5) { $output = new class extends AbstractChunkOutputBuilder { public function getDiff(array $diff): string { return ''; } public function getChunks(array $diff, $lineThreshold) { return $this->getCommonChunks($diff, $lineThreshold); } }; $this->assertSame( $expected, $output->getChunks($this->differ->diffToArray($from, $to), $lineThreshold) ); } public function provideGetCommonChunks(): array { return[ 'same (with default threshold)' => [ [], 'A', 'A', ], 'same (threshold 0)' => [ [0 => 0], 'A', 'A', 0, ], 'empty' => [ [], '', '', ], 'single line diff' => [ [], 'A', 'B', ], 'below threshold I' => [ [], "A\nX\nC", "A\nB\nC", ], 'below threshold II' => [ [], "A\n\n\n\nX\nC", "A\n\n\n\nB\nC", ], 'below threshold III' => [ [0 => 5], "A\n\n\n\n\n\nB", "A\n\n\n\n\n\nA", ], 'same start' => [ [0 => 5], "A\n\n\n\n\n\nX\nC", "A\n\n\n\n\n\nB\nC", ], 'same start long' => [ [0 => 13], "\n\n\n\n\n\n\n\n\n\n\n\n\n\nA", "\n\n\n\n\n\n\n\n\n\n\n\n\n\nB", ], 'same part in between' => [ [2 => 8], "A\n\n\n\n\n\n\nX\nY\nZ\n\n", "B\n\n\n\n\n\n\nX\nA\nZ\n\n", ], 'same trailing' => [ [2 => 14], "A\n\n\n\n\n\n\n\n\n\n\n\n\n\n", "B\n\n\n\n\n\n\n\n\n\n\n\n\n\n", ], 'same part in between, same trailing' => [ [2 => 7, 10 => 15], "A\n\n\n\n\n\n\nA\n\n\n\n\n\n\n", "B\n\n\n\n\n\n\nB\n\n\n\n\n\n\n", ], 'below custom threshold I' => [ [], "A\n\nB", "A\n\nD", 2 ], 'custom threshold I' => [ [0 => 1], "A\n\nB", "A\n\nD", 1 ], 'custom threshold II' => [ [], "A\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n", "A\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n", 19 ], [ [3 => 9], "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk", "a\np\nc\nd\ne\nf\ng\nh\ni\nw\nk", ], [ [0 => 5, 8 => 13], "A\nA\nA\nA\nA\nA\nX\nC\nC\nC\nC\nC\nC", "A\nA\nA\nA\nA\nA\nB\nC\nC\nC\nC\nC\nC", ], [ [0 => 5, 8 => 13], "A\nA\nA\nA\nA\nA\nX\nC\nC\nC\nC\nC\nC\nX", "A\nA\nA\nA\nA\nA\nB\nC\nC\nC\nC\nC\nC\nY", ], ]; } /** * @param array $expected * @param string $input * @dataProvider provideSplitStringByLinesCases */ public function testSplitStringByLines(array $expected, string $input) { $reflection = new \ReflectionObject($this->differ); $method = $reflection->getMethod('splitStringByLines'); $method->setAccessible(true); $this->assertSame($expected, $method->invoke($this->differ, $input)); } public function provideSplitStringByLinesCases(): array { return [ [ [], '' ], [ ['a'], 'a' ], [ ["a\n"], "a\n" ], [ ["a\r"], "a\r" ], [ ["a\r\n"], "a\r\n" ], [ ["\n"], "\n" ], [ ["\r"], "\r" ], [ ["\r\n"], "\r\n" ], [ [ "A\n", "B\n", "\n", "C\n" ], "A\nB\n\nC\n", ], [ [ "A\r\n", "B\n", "\n", "C\r" ], "A\r\nB\n\nC\r", ], [ [ "\n", "A\r\n", "B\n", "\n", 'C' ], "\nA\r\nB\n\nC", ], ]; } /** * @param string $expected * @param string $from * @param string $to * @dataProvider provideDiffWithLineNumbers */ public function testDiffWithLineNumbers($expected, $from, $to) { $differ = new Differ(new UnifiedDiffOutputBuilder("--- Original\n+++ New\n", true)); $this->assertSame($expected, $differ->diff($from, $to)); } public function provideDiffWithLineNumbers(): array { return [ 'diff line 1 non_patch_compat' => [ '--- Original +++ New @@ -1 +1 @@ -AA +BA ', 'AA', 'BA', ], 'diff line +1 non_patch_compat' => [ '--- Original +++ New @@ -1 +1,2 @@ -AZ + +B ', 'AZ', "\nB", ], 'diff line -1 non_patch_compat' => [ '--- Original +++ New @@ -1,2 +1 @@ - -AF +B ', "\nAF", 'B', ], 'II non_patch_compat' => [ '--- Original +++ New @@ -1,2 +1 @@ - - ' , "\n\nA\n1", "A\n1", ], 'diff last line II - no trailing linebreak non_patch_compat' => [ '--- Original +++ New @@ -8 +8 @@ -E +B ', "A\n\n\n\n\n\n\nE", "A\n\n\n\n\n\n\nB", ], [ "--- Original\n+++ New\n@@ -1,2 +1 @@\n \n-\n", "\n\n", "\n", ], 'diff line endings non_patch_compat' => [ "--- Original\n+++ New\n@@ -1 +1 @@\n #Warning: Strings contain different line endings!\n- [ '--- Original +++ New ', "AT\n", "AT\n", ], [ '--- Original +++ New @@ -1 +1 @@ -b +a ', "b\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n", "a\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" ], 'diff line @1' => [ '--- Original +++ New @@ -1,2 +1,2 @@ ' . ' -AG +B ', "\nAG\n", "\nB\n", ], 'same multiple lines' => [ '--- Original +++ New @@ -1,3 +1,3 @@ ' . ' ' . ' -V +B ' , "\n\nV\nC213", "\n\nB\nC213", ], 'diff last line I' => [ '--- Original +++ New @@ -8 +8 @@ -E +B ', "A\n\n\n\n\n\n\nE\n", "A\n\n\n\n\n\n\nB\n", ], 'diff line middle' => [ '--- Original +++ New @@ -8 +8 @@ -X +Z ', "A\n\n\n\n\n\n\nX\n\n\n\n\n\n\nAY", "A\n\n\n\n\n\n\nZ\n\n\n\n\n\n\nAY", ], 'diff last line III' => [ '--- Original +++ New @@ -15 +15 @@ -A +B ', "A\n\n\n\n\n\n\nA\n\n\n\n\n\n\nA\n", "A\n\n\n\n\n\n\nA\n\n\n\n\n\n\nB\n", ], [ '--- Original +++ New @@ -1,7 +1,7 @@ A -B +B1 D E EE F -G +G1 ', "A\nB\nD\nE\nEE\nF\nG\nH", "A\nB1\nD\nE\nEE\nF\nG1\nH", ], [ '--- Original +++ New @@ -1 +1,2 @@ Z + @@ -10 +11 @@ -i +x ', 'Z a b c d e f g h i j', 'Z a b c d e f g h x j' ], [ '--- Original +++ New @@ -1,5 +1,3 @@ - -a +b A -a - +b ', "\na\nA\na\n\n\nA", "b\nA\nb\n\nA" ], [ <<assertAttributeInstanceOf( UnifiedDiffOutputBuilder::class, 'outputBuilder', new Differ(null) ); } public function testConstructorString() { $this->assertAttributeInstanceOf( UnifiedDiffOutputBuilder::class, 'outputBuilder', new Differ("--- Original\n+++ New\n") ); } public function testConstructorInvalidArgInt() { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessageRegExp('/^Expected builder to be an instance of DiffOutputBuilderInterface, or a string, got integer "1"\.$/'); new Differ(1); } public function testConstructorInvalidArgObject() { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessageRegExp('/^Expected builder to be an instance of DiffOutputBuilderInterface, or a string, got instance of "SplFileInfo"\.$/'); new Differ(new \SplFileInfo(__FILE__)); } } diff-2.0.1/tests/DifferTestTest.php000066400000000000000000000044151314055511200171750ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; use PHPUnit\Framework\TestCase; /** * @requires OS Linux */ final class DifferTestTest extends TestCase { private $fileFrom; private $filePatch; protected function setUp() { $dir = \realpath(__DIR__ . '/../') . '/'; $this->fileFrom = $dir . 'from.txt'; $this->filePatch = $dir . 'patch.txt'; } /** * @dataProvider provideDiffWithLineNumbers */ public function testTheTestProvideDiffWithLineNumbers($expected, $from, $to) { $this->runThisTest($expected, $from, $to); } public function provideDiffWithLineNumbers() { require_once __DIR__ . '/DifferTest.php'; $test = new DifferTest(); $tests = $test->provideDiffWithLineNumbers(); $tests = \array_filter( $tests, function ($key) { return !\is_string($key) || false === \strpos($key, 'non_patch_compat'); }, ARRAY_FILTER_USE_KEY ); return $tests; } private function runThisTest(string $expected, string $from, string $to) { $expected = \str_replace('--- Original', '--- from.txt', $expected); $expected = \str_replace('+++ New', '+++ from.txt', $expected); @\unlink($this->fileFrom); @\unlink($this->filePatch); $this->assertNotFalse(\file_put_contents($this->fileFrom, $from)); $this->assertNotFalse(\file_put_contents($this->filePatch, $expected)); $command = \sprintf( 'patch -u --verbose %s < %s', // --posix \escapeshellarg($this->fileFrom), \escapeshellarg($this->filePatch) ); \exec($command, $output, $d); $this->assertSame(0, $d, \sprintf('%s | %s', $command, \implode("\n", $output))); $patched = \file_get_contents($this->fileFrom); $this->assertSame($patched, $to); @\unlink($this->fileFrom . '.orig'); @\unlink($this->fileFrom); @\unlink($this->filePatch); } } diff-2.0.1/tests/LineTest.php000066400000000000000000000016261314055511200160260ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; use PHPUnit\Framework\TestCase; /** * @covers SebastianBergmann\Diff\Line */ final class LineTest extends TestCase { /** * @var Line */ private $line; protected function setUp() { $this->line = new Line; } public function testCanBeCreatedWithoutArguments() { $this->assertInstanceOf(Line::class, $this->line); } public function testTypeCanBeRetrieved() { $this->assertSame(Line::UNCHANGED, $this->line->getType()); } public function testContentCanBeRetrieved() { $this->assertSame('', $this->line->getContent()); } } diff-2.0.1/tests/LongestCommonSubsequenceTest.php000066400000000000000000000130501314055511200221200ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; use PHPUnit\Framework\TestCase; /** * @coversNothing */ abstract class LongestCommonSubsequenceTest extends TestCase { /** * @var LongestCommonSubsequenceCalculator */ private $implementation; /** * @var string */ private $memoryLimit; /** * @var int[] */ private $stress_sizes = [1, 2, 3, 100, 500, 1000, 2000]; protected function setUp() { $this->memoryLimit = \ini_get('memory_limit'); \ini_set('memory_limit', '256M'); $this->implementation = $this->createImplementation(); } /** * @return LongestCommonSubsequenceCalculator */ abstract protected function createImplementation(); protected function tearDown() { \ini_set('memory_limit', $this->memoryLimit); } public function testBothEmpty() { $from = []; $to = []; $common = $this->implementation->calculate($from, $to); $this->assertSame([], $common); } public function testIsStrictComparison() { $from = [ false, 0, 0.0, '', null, [], true, 1, 1.0, 'foo', ['foo', 'bar'], ['foo' => 'bar'] ]; $to = $from; $common = $this->implementation->calculate($from, $to); $this->assertSame($from, $common); $to = [ false, false, false, false, false, false, true, true, true, true, true, true ]; $expected = [ false, true, ]; $common = $this->implementation->calculate($from, $to); $this->assertSame($expected, $common); } public function testEqualSequences() { foreach ($this->stress_sizes as $size) { $range = \range(1, $size); $from = $range; $to = $range; $common = $this->implementation->calculate($from, $to); $this->assertSame($range, $common); } } public function testDistinctSequences() { $from = ['A']; $to = ['B']; $common = $this->implementation->calculate($from, $to); $this->assertSame([], $common); $from = ['A', 'B', 'C']; $to = ['D', 'E', 'F']; $common = $this->implementation->calculate($from, $to); $this->assertSame([], $common); foreach ($this->stress_sizes as $size) { $from = \range(1, $size); $to = \range($size + 1, $size * 2); $common = $this->implementation->calculate($from, $to); $this->assertSame([], $common); } } public function testCommonSubsequence() { $from = ['A', 'C', 'E', 'F', 'G']; $to = ['A', 'B', 'D', 'E', 'H']; $expected = ['A', 'E']; $common = $this->implementation->calculate($from, $to); $this->assertSame($expected, $common); $from = ['A', 'C', 'E', 'F', 'G']; $to = ['B', 'C', 'D', 'E', 'F', 'H']; $expected = ['C', 'E', 'F']; $common = $this->implementation->calculate($from, $to); $this->assertSame($expected, $common); foreach ($this->stress_sizes as $size) { $from = $size < 2 ? [1] : \range(1, $size + 1, 2); $to = $size < 3 ? [1] : \range(1, $size + 1, 3); $expected = $size < 6 ? [1] : \range(1, $size + 1, 6); $common = $this->implementation->calculate($from, $to); $this->assertSame($expected, $common); } } public function testSingleElementSubsequenceAtStart() { foreach ($this->stress_sizes as $size) { $from = \range(1, $size); $to = \array_slice($from, 0, 1); $common = $this->implementation->calculate($from, $to); $this->assertSame($to, $common); } } public function testSingleElementSubsequenceAtMiddle() { foreach ($this->stress_sizes as $size) { $from = \range(1, $size); $to = \array_slice($from, (int) ($size / 2), 1); $common = $this->implementation->calculate($from, $to); $this->assertSame($to, $common); } } public function testSingleElementSubsequenceAtEnd() { foreach ($this->stress_sizes as $size) { $from = \range(1, $size); $to = \array_slice($from, $size - 1, 1); $common = $this->implementation->calculate($from, $to); $this->assertSame($to, $common); } } public function testReversedSequences() { $from = ['A', 'B']; $to = ['B', 'A']; $expected = ['A']; $common = $this->implementation->calculate($from, $to); $this->assertSame($expected, $common); foreach ($this->stress_sizes as $size) { $from = \range(1, $size); $to = \array_reverse($from); $common = $this->implementation->calculate($from, $to); $this->assertSame([1], $common); } } public function testStrictTypeCalculate() { $diff = $this->implementation->calculate(['5'], ['05']); $this->assertInternalType('array', $diff); $this->assertCount(0, $diff); } } diff-2.0.1/tests/MemoryEfficientImplementationTest.php000066400000000000000000000011331314055511200231230ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; /** * @covers SebastianBergmann\Diff\MemoryEfficientLongestCommonSubsequenceCalculator */ final class MemoryEfficientImplementationTest extends LongestCommonSubsequenceTest { protected function createImplementation() { return new MemoryEfficientLongestCommonSubsequenceCalculator; } } diff-2.0.1/tests/ParserTest.php000066400000000000000000000101111314055511200163600ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; use PHPUnit\Framework\TestCase; /** * @covers SebastianBergmann\Diff\Parser * * @uses SebastianBergmann\Diff\Chunk * @uses SebastianBergmann\Diff\Diff * @uses SebastianBergmann\Diff\Line */ final class ParserTest extends TestCase { /** * @var Parser */ private $parser; protected function setUp() { $this->parser = new Parser; } public function testParse() { $content = \file_get_contents(__DIR__ . '/fixtures/patch.txt'); $diffs = $this->parser->parse($content); $this->assertInternalType('array', $diffs); $this->assertContainsOnlyInstancesOf(Diff::class, $diffs); $this->assertCount(1, $diffs); $chunks = $diffs[0]->getChunks(); $this->assertInternalType('array', $chunks); $this->assertContainsOnlyInstancesOf(Chunk::class, $chunks); $this->assertCount(1, $chunks); $this->assertSame(20, $chunks[0]->getStart()); $this->assertCount(4, $chunks[0]->getLines()); } public function testParseWithMultipleChunks() { $content = \file_get_contents(__DIR__ . '/fixtures/patch2.txt'); $diffs = $this->parser->parse($content); $this->assertCount(1, $diffs); $chunks = $diffs[0]->getChunks(); $this->assertCount(3, $chunks); $this->assertSame(20, $chunks[0]->getStart()); $this->assertSame(320, $chunks[1]->getStart()); $this->assertSame(600, $chunks[2]->getStart()); $this->assertCount(5, $chunks[0]->getLines()); $this->assertCount(5, $chunks[1]->getLines()); $this->assertCount(4, $chunks[2]->getLines()); } public function testParseWithRemovedLines() { $content = <<parser->parse($content); $this->assertInternalType('array', $diffs); $this->assertContainsOnlyInstancesOf(Diff::class, $diffs); $this->assertCount(1, $diffs); $chunks = $diffs[0]->getChunks(); $this->assertInternalType('array', $chunks); $this->assertContainsOnlyInstancesOf(Chunk::class, $chunks); $this->assertCount(1, $chunks); $chunk = $chunks[0]; $this->assertSame(49, $chunk->getStart()); $this->assertSame(49, $chunk->getEnd()); $this->assertSame(9, $chunk->getStartRange()); $this->assertSame(8, $chunk->getEndRange()); $lines = $chunk->getLines(); $this->assertInternalType('array', $lines); $this->assertContainsOnlyInstancesOf(Line::class, $lines); $this->assertCount(2, $lines); /** @var Line $line */ $line = $lines[0]; $this->assertSame('A', $line->getContent()); $this->assertSame(Line::UNCHANGED, $line->getType()); $line = $lines[1]; $this->assertSame('B', $line->getContent()); $this->assertSame(Line::REMOVED, $line->getType()); } public function testParseDiffForMulitpleFiles() { $content = <<parser->parse($content); $this->assertCount(2, $diffs); /** @var Diff $diff */ $diff = $diffs[0]; $this->assertSame('a/Test.txt', $diff->getFrom()); $this->assertSame('b/Test.txt', $diff->getTo()); $this->assertCount(1, $diff->getChunks()); $diff = $diffs[1]; $this->assertSame('a/Test2.txt', $diff->getFrom()); $this->assertSame('b/Test2.txt', $diff->getTo()); $this->assertCount(1, $diff->getChunks()); } } diff-2.0.1/tests/TimeEfficientImplementationTest.php000066400000000000000000000011251314055511200225520ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; /** * @covers SebastianBergmann\Diff\TimeEfficientLongestCommonSubsequenceCalculator */ final class TimeEfficientImplementationTest extends LongestCommonSubsequenceTest { protected function createImplementation() { return new TimeEfficientLongestCommonSubsequenceCalculator; } } diff-2.0.1/tests/fixtures/000077500000000000000000000000001314055511200154325ustar00rootroot00000000000000diff-2.0.1/tests/fixtures/patch.txt000066400000000000000000000003101314055511200172640ustar00rootroot00000000000000diff --git a/Foo.php b/Foo.php index abcdefg..abcdefh 100644 --- a/Foo.php +++ b/Foo.php @@ -20,4 +20,5 @@ class Foo const ONE = 1; const TWO = 2; + const THREE = 3; const FOUR = 4; diff-2.0.1/tests/fixtures/patch2.txt000066400000000000000000000006321314055511200173550ustar00rootroot00000000000000diff --git a/Foo.php b/Foo.php index abcdefg..abcdefh 100644 --- a/Foo.php +++ b/Foo.php @@ -20,4 +20,5 @@ class Foo const ONE = 1; const TWO = 2; + const THREE = 3; const FOUR = 4; @@ -320,4 +320,5 @@ class Foo const A = 'A'; const B = 'B'; + const C = 'C'; const D = 'D'; @@ -600,4 +600,5 @@ class Foo public function doSomething() { + return 'foo'; }