Upgrade framework

This commit is contained in:
2023-11-14 16:54:35 +01:00
parent 1648a5cd42
commit 4fcf6fffcc
10548 changed files with 693138 additions and 466698 deletions

View File

@@ -1,4 +0,0 @@
/.idea
/composer.lock
/vendor
/.php_cs.cache

View File

@@ -1,79 +0,0 @@
<?php
$header = <<<'EOF'
This file is part of sebastian/diff.
(c) Sebastian Bergmann <sebastian@phpunit.de>
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' => 'long'],
'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'],
'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,
'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')
);

View File

@@ -1,31 +0,0 @@
language: php
php:
- 5.3
- 5.4
- 5.5
- 5.6
- 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

88
vendor/sebastian/diff/ChangeLog.md vendored Normal file
View File

@@ -0,0 +1,88 @@
# ChangeLog
All notable changes are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles.
## [4.0.4] - 2020-10-26
### Fixed
* `SebastianBergmann\Diff\Exception` now correctly extends `\Throwable`
## [4.0.3] - 2020-09-28
### Changed
* Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3`
## [4.0.2] - 2020-06-30
### Added
* This component is now supported on PHP 8
## [4.0.1] - 2020-05-08
### Fixed
* [#99](https://github.com/sebastianbergmann/diff/pull/99): Regression in unified diff output of identical strings
## [4.0.0] - 2020-02-07
### Removed
* Removed support for PHP 7.1 and PHP 7.2
## [3.0.2] - 2019-02-04
### Changed
* `Chunk::setLines()` now ensures that the `$lines` array only contains `Line` objects
## [3.0.1] - 2018-06-10
### Fixed
* Removed `"minimum-stability": "dev",` from `composer.json`
## [3.0.0] - 2018-02-01
* The `StrictUnifiedDiffOutputBuilder` implementation of the `DiffOutputBuilderInterface` was added
### Changed
* The default `DiffOutputBuilderInterface` implementation now generates context lines (unchanged lines)
### Removed
* Removed support for PHP 7.0
### Fixed
* [#70](https://github.com/sebastianbergmann/diff/issues/70): Diffing of arrays no longer works
## [2.0.1] - 2017-08-03
### 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
* [#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
[4.0.4]: https://github.com/sebastianbergmann/diff/compare/4.0.3...4.0.4
[4.0.3]: https://github.com/sebastianbergmann/diff/compare/4.0.2...4.0.3
[4.0.2]: https://github.com/sebastianbergmann/diff/compare/4.0.1...4.0.2
[4.0.1]: https://github.com/sebastianbergmann/diff/compare/4.0.0...4.0.1
[4.0.0]: https://github.com/sebastianbergmann/diff/compare/3.0.2...4.0.0
[3.0.2]: https://github.com/sebastianbergmann/diff/compare/3.0.1...3.0.2
[3.0.1]: https://github.com/sebastianbergmann/diff/compare/3.0.0...3.0.1
[3.0.0]: https://github.com/sebastianbergmann/diff/compare/2.0...3.0.0
[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

View File

@@ -1,6 +1,6 @@
sebastian/diff
Copyright (c) 2002-2017, Sebastian Bergmann <sebastian@phpunit.de>.
Copyright (c) 2002-2020, Sebastian Bergmann <sebastian@phpunit.de>.
All rights reserved.
Redistribution and use in source and binary forms, with or without

View File

@@ -1,22 +1,32 @@
# sebastian/diff
[![CI Status](https://github.com/sebastianbergmann/diff/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/diff/actions)
[![Type Coverage](https://shepherd.dev/github/sebastianbergmann/diff/coverage.svg)](https://shepherd.dev/github/sebastianbergmann/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
```
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
```
composer require --dev sebastian/diff
```
### Usage
#### Generating diff
The `Differ` class can be used to generate a textual representation of the difference between two strings:
```php
<?php
use SebastianBergmann\Diff\Differ;
$differ = new Differ;
@@ -24,12 +34,83 @@ print $differ->diff('foo', 'bar');
```
The code above yields the output below:
```diff
--- Original
+++ New
@@ @@
-foo
+bar
```
--- Original
+++ New
@@ @@
-foo
+bar
There are three output builders available in this package:
#### UnifiedDiffOutputBuilder
This is default builder, which generates the output close to udiff and is used by PHPUnit.
```php
<?php
use SebastianBergmann\Diff\Differ;
use SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder;
$builder = new UnifiedDiffOutputBuilder(
"--- Original\n+++ New\n", // custom header
false // do not add line numbers to the diff
);
$differ = new Differ($builder);
print $differ->diff('foo', 'bar');
```
#### StrictUnifiedDiffOutputBuilder
Generates (strict) Unified diff's (unidiffs) with hunks,
similar to `diff -u` and compatible with `patch` and `git apply`.
```php
<?php
use SebastianBergmann\Diff\Differ;
use SebastianBergmann\Diff\Output\StrictUnifiedDiffOutputBuilder;
$builder = new StrictUnifiedDiffOutputBuilder([
'collapseRanges' => true, // ranges of length one are rendered with the trailing `,1`
'commonLineThreshold' => 6, // number of same lines before ending a new hunk and creating a new one (if needed)
'contextLines' => 3, // like `diff: -u, -U NUM, --unified[=NUM]`, for patch/git apply compatibility best to keep at least @ 3
'fromFile' => null,
'fromFileDate' => null,
'toFile' => null,
'toFileDate' => null,
]);
$differ = new Differ($builder);
print $differ->diff('foo', 'bar');
```
#### DiffOnlyOutputBuilder
Output only the lines that differ.
```php
<?php
use SebastianBergmann\Diff\Differ;
use SebastianBergmann\Diff\Output\DiffOnlyOutputBuilder;
$builder = new DiffOnlyOutputBuilder(
"--- Original\n+++ New\n"
);
$differ = new Differ($builder);
print $differ->diff('foo', 'bar');
```
#### DiffOutputBuilderInterface
You can pass any output builder to the `Differ` class as longs as it implements the `DiffOutputBuilderInterface`.
#### Parsing diff
The `Parser` class can be used to parse a unified diff into an object graph:
@@ -114,13 +195,8 @@ The code above yields the output below:
[type:SebastianBergmann\Diff\Line:private] => 3
[content:SebastianBergmann\Diff\Line:private] => $b = new Money(2, new Currency('EUR'));
)
)
)
)
)
)

View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project name="diff" default="setup">
<target name="setup" depends="clean,composer"/>
<target name="clean" description="Cleanup build artifacts">
<delete dir="${basedir}/vendor"/>
<delete file="${basedir}/composer.lock"/>
</target>
<target name="composer" depends="clean" description="Install dependencies with Composer">
<exec executable="composer" taskname="composer">
<arg value="update"/>
<arg value="--no-interaction"/>
<arg value="--no-progress"/>
<arg value="--no-ansi"/>
<arg value="--no-suggest"/>
<arg value="--optimize-autoloader"/>
<arg value="--prefer-stable"/>
</exec>
</target>
</project>

View File

@@ -1,7 +1,7 @@
{
"name": "sebastian/diff",
"description": "Diff implementation",
"keywords": ["diff"],
"keywords": ["diff", "udiff", "unidiff", "unified diff"],
"homepage": "https://github.com/sebastianbergmann/diff",
"license": "BSD-3-Clause",
"authors": [
@@ -14,20 +14,34 @@
"email": "mail@kore-nordmann.de"
}
],
"prefer-stable": true,
"config": {
"platform": {
"php": "7.3.0"
},
"optimize-autoloader": true,
"sort-packages": true
},
"require": {
"php": "^5.3.3 || ^7.0"
"php": ">=7.3"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
"phpunit/phpunit": "^9.3",
"symfony/process": "^4.2 || ^5"
},
"autoload": {
"classmap": [
"src/"
]
},
"autoload-dev": {
"classmap": [
"tests/"
]
},
"extra": {
"branch-alias": {
"dev-master": "1.4-dev"
"dev-master": "4.0-dev"
}
}
}

View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/6.0/phpunit.xsd"
bootstrap="vendor/autoload.php"
forceCoversAnnotation="true"
beStrictAboutCoversAnnotation="true"
beStrictAboutOutputDuringTests="true"
beStrictAboutTodoAnnotatedTests="true"
verbose="true">
<testsuite>
<directory suffix="Test.php">tests</directory>
</testsuite>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">src</directory>
</whitelist>
</filter>
</phpunit>

View File

@@ -1,4 +1,4 @@
<?php
<?php declare(strict_types=1);
/*
* This file is part of sebastian/diff.
*
@@ -7,10 +7,9 @@
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Diff;
class Chunk
final class Chunk
{
/**
* @var int
@@ -33,71 +32,58 @@ class Chunk
private $endRange;
/**
* @var array
* @var Line[]
*/
private $lines;
/**
* @param int $start
* @param int $startRange
* @param int $end
* @param int $endRange
* @param array $lines
*/
public function __construct($start = 0, $startRange = 1, $end = 0, $endRange = 1, array $lines = array())
public function __construct(int $start = 0, int $startRange = 1, int $end = 0, int $endRange = 1, array $lines = [])
{
$this->start = (int) $start;
$this->startRange = (int) $startRange;
$this->end = (int) $end;
$this->endRange = (int) $endRange;
$this->start = $start;
$this->startRange = $startRange;
$this->end = $end;
$this->endRange = $endRange;
$this->lines = $lines;
}
/**
* @return int
*/
public function getStart()
public function getStart(): int
{
return $this->start;
}
/**
* @return int
*/
public function getStartRange()
public function getStartRange(): int
{
return $this->startRange;
}
/**
* @return int
*/
public function getEnd()
public function getEnd(): int
{
return $this->end;
}
/**
* @return int
*/
public function getEndRange()
public function getEndRange(): int
{
return $this->endRange;
}
/**
* @return array
* @return Line[]
*/
public function getLines()
public function getLines(): array
{
return $this->lines;
}
/**
* @param array $lines
* @param Line[] $lines
*/
public function setLines(array $lines)
public function setLines(array $lines): void
{
foreach ($lines as $line) {
if (!$line instanceof Line) {
throw new InvalidArgumentException;
}
}
$this->lines = $lines;
}
}

View File

@@ -1,4 +1,4 @@
<?php
<?php declare(strict_types=1);
/*
* This file is part of sebastian/diff.
*
@@ -7,10 +7,9 @@
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Diff;
class Diff
final class Diff
{
/**
* @var string
@@ -28,29 +27,21 @@ class Diff
private $chunks;
/**
* @param string $from
* @param string $to
* @param Chunk[] $chunks
*/
public function __construct($from, $to, array $chunks = array())
public function __construct(string $from, string $to, array $chunks = [])
{
$this->from = $from;
$this->to = $to;
$this->chunks = $chunks;
}
/**
* @return string
*/
public function getFrom()
public function getFrom(): string
{
return $this->from;
}
/**
* @return string
*/
public function getTo()
public function getTo(): string
{
return $this->to;
}
@@ -58,7 +49,7 @@ class Diff
/**
* @return Chunk[]
*/
public function getChunks()
public function getChunks(): array
{
return $this->chunks;
}
@@ -66,7 +57,7 @@ class Diff
/**
* @param Chunk[] $chunks
*/
public function setChunks(array $chunks)
public function setChunks(array $chunks): void
{
$this->chunks = $chunks;
}

View File

@@ -1,4 +1,4 @@
<?php
<?php declare(strict_types=1);
/*
* This file is part of sebastian/diff.
*
@@ -7,181 +7,90 @@
* 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\LCS\LongestCommonSubsequence;
use SebastianBergmann\Diff\LCS\TimeEfficientImplementation;
use SebastianBergmann\Diff\LCS\MemoryEfficientImplementation;
use const PHP_INT_SIZE;
use const PREG_SPLIT_DELIM_CAPTURE;
use const PREG_SPLIT_NO_EMPTY;
use function array_shift;
use function array_unshift;
use function array_values;
use function count;
use function current;
use function end;
use function get_class;
use function gettype;
use function is_array;
use function is_object;
use function is_string;
use function key;
use function min;
use function preg_split;
use function prev;
use function reset;
use function sprintf;
use function substr;
use SebastianBergmann\Diff\Output\DiffOutputBuilderInterface;
use SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder;
/**
* Diff implementation.
*/
class Differ
final class Differ
{
/**
* @var string
*/
private $header;
public const OLD = 0;
public const ADDED = 1;
public const REMOVED = 2;
public const DIFF_LINE_END_WARNING = 3;
public const NO_LINE_END_EOF_WARNING = 4;
/**
* @var bool
* @var DiffOutputBuilderInterface
*/
private $showNonDiffLines;
private $outputBuilder;
/**
* @param string $header
* @param bool $showNonDiffLines
* @param DiffOutputBuilderInterface $outputBuilder
*
* @throws InvalidArgumentException
*/
public function __construct($header = "--- Original\n+++ New\n", $showNonDiffLines = true)
public function __construct($outputBuilder = null)
{
$this->header = $header;
$this->showNonDiffLines = $showNonDiffLines;
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, <null> 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 LongestCommonSubsequence $lcs
*
* @return string
* @param array|string $from
* @param array|string $to
*/
public function diff($from, $to, LongestCommonSubsequence $lcs = null)
public function diff($from, $to, LongestCommonSubsequenceCalculator $lcs = null): string
{
$from = $this->validateDiffInput($from);
$to = $this->validateDiffInput($to);
$diff = $this->diffToArray($from, $to, $lcs);
$old = $this->checkIfDiffInOld($diff);
$start = isset($old[0]) ? $old[0] : 0;
$end = \count($diff);
$diff = $this->diffToArray(
$this->normalizeDiffInput($from),
$this->normalizeDiffInput($to),
$lcs
);
if ($tmp = \array_search($end, $old)) {
$end = $tmp;
}
return $this->getBuffer($diff, $old, $start, $end);
}
/**
* Casts variable to string if it is not a string or array.
*
* @param mixed $input
*
* @return string
*/
private function validateDiffInput($input)
{
if (!\is_array($input) && !\is_string($input)) {
return (string) $input;
}
return $input;
}
/**
* Takes input of the diff array and returns the old array.
* Iterates through diff line by line,
*
* @param array $diff
*
* @return array
*/
private function checkIfDiffInOld(array $diff)
{
$inOld = false;
$i = 0;
$old = array();
foreach ($diff as $line) {
if ($line[1] === 0 /* OLD */) {
if ($inOld === false) {
$inOld = $i;
}
} elseif ($inOld !== false) {
if (($i - $inOld) > 5) {
$old[$inOld] = $i - 1;
}
$inOld = false;
}
++$i;
}
return $old;
}
/**
* Generates buffer in string format, returning the patch.
*
* @param array $diff
* @param array $old
* @param int $start
* @param int $end
*
* @return string
*/
private function getBuffer(array $diff, array $old, $start, $end)
{
$buffer = $this->header;
if (!isset($old[$start])) {
$buffer = $this->getDiffBufferElementNew($diff, $buffer, $start);
++$start;
}
for ($i = $start; $i < $end; $i++) {
if (isset($old[$i])) {
$i = $old[$i];
$buffer = $this->getDiffBufferElementNew($diff, $buffer, $i);
} else {
$buffer = $this->getDiffBufferElement($diff, $buffer, $i);
}
}
return $buffer;
}
/**
* Gets individual buffer element.
*
* @param array $diff
* @param string $buffer
* @param int $diffIndex
*
* @return string
*/
private function getDiffBufferElement(array $diff, $buffer, $diffIndex)
{
if ($diff[$diffIndex][1] === 1 /* ADDED */) {
$buffer .= '+' . $diff[$diffIndex][0] . "\n";
} elseif ($diff[$diffIndex][1] === 2 /* REMOVED */) {
$buffer .= '-' . $diff[$diffIndex][0] . "\n";
} elseif ($this->showNonDiffLines === true) {
$buffer .= ' ' . $diff[$diffIndex][0] . "\n";
}
return $buffer;
}
/**
* Gets individual buffer element with opening.
*
* @param array $diff
* @param string $buffer
* @param int $diffIndex
*
* @return string
*/
private function getDiffBufferElementNew(array $diff, $buffer, $diffIndex)
{
if ($this->showNonDiffLines === true) {
$buffer .= "@@ @@\n";
}
return $this->getDiffBufferElement($diff, $buffer, $diffIndex);
return $this->outputBuilder->getDiff($diff);
}
/**
@@ -195,118 +104,97 @@ class Differ
* - 1: ADDED: $token was added to $from
* - 0: OLD: $token is not changed in $to
*
* @param array|string $from
* @param array|string $to
* @param LongestCommonSubsequence $lcs
*
* @return array
* @param array|string $from
* @param array|string $to
* @param LongestCommonSubsequenceCalculator $lcs
*/
public function diffToArray($from, $to, LongestCommonSubsequence $lcs = null)
public function diffToArray($from, $to, LongestCommonSubsequenceCalculator $lcs = null): array
{
if (\is_string($from)) {
$fromMatches = $this->getNewLineMatches($from);
$from = $this->splitStringByLines($from);
} elseif (\is_array($from)) {
$fromMatches = array();
} else {
throw new \InvalidArgumentException('"from" must be an array or string.');
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)) {
$toMatches = $this->getNewLineMatches($to);
$to = $this->splitStringByLines($to);
} elseif (\is_array($to)) {
$toMatches = array();
} else {
throw new \InvalidArgumentException('"to" 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);
[$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 = array();
if ($this->detectUnmatchedLineEndings($fromMatches, $toMatches)) {
$diff[] = array(
'#Warning: Strings contain different line endings!',
0
);
}
$common = $lcs->calculate(array_values($from), array_values($to));
$diff = [];
foreach ($start as $token) {
$diff[] = array($token, 0 /* OLD */);
$diff[] = [$token, self::OLD];
}
\reset($from);
\reset($to);
reset($from);
reset($to);
foreach ($common as $token) {
while (($fromToken = \reset($from)) !== $token) {
$diff[] = array(\array_shift($from), 2 /* REMOVED */);
while (($fromToken = reset($from)) !== $token) {
$diff[] = [array_shift($from), self::REMOVED];
}
while (($toToken = \reset($to)) !== $token) {
$diff[] = array(\array_shift($to), 1 /* ADDED */);
while (($toToken = reset($to)) !== $token) {
$diff[] = [array_shift($to), self::ADDED];
}
$diff[] = array($token, 0 /* OLD */);
$diff[] = [$token, self::OLD];
\array_shift($from);
\array_shift($to);
array_shift($from);
array_shift($to);
}
while (($token = \array_shift($from)) !== null) {
$diff[] = array($token, 2 /* REMOVED */);
while (($token = array_shift($from)) !== null) {
$diff[] = [$token, self::REMOVED];
}
while (($token = \array_shift($to)) !== null) {
$diff[] = array($token, 1 /* ADDED */);
while (($token = array_shift($to)) !== null) {
$diff[] = [$token, self::ADDED];
}
foreach ($end as $token) {
$diff[] = array($token, 0 /* OLD */);
$diff[] = [$token, self::OLD];
}
if ($this->detectUnmatchedLineEndings($diff)) {
array_unshift($diff, ["#Warning: Strings contain different line endings!\n", self::DIFF_LINE_END_WARNING]);
}
return $diff;
}
/**
* Get new strings denoting new lines from a given string.
* Casts variable to string if it is not a string or array.
*
* @param string $string
*
* @return array
* @return array|string
*/
private function getNewLineMatches($string)
private function normalizeDiffInput($input)
{
\preg_match_all('(\r\n|\r|\n)', $string, $stringMatches);
if (!is_array($input) && !is_string($input)) {
return (string) $input;
}
return $stringMatches;
return $input;
}
/**
* Checks if input is string, if so it will split it line-by-line.
*
* @param string $input
*
* @return array
*/
private function splitStringByLines($input)
private function splitStringByLines(string $input): array
{
return \preg_split('(\r\n|\r|\n)', $input);
return preg_split('/(.*\R)/', $input, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
}
/**
* @param array $from
* @param array $to
*
* @return LongestCommonSubsequence
*/
private function selectLcsImplementation(array $from, array $to)
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
@@ -315,57 +203,97 @@ class Differ
$memoryLimit = 100 * 1024 * 1024;
if ($this->calculateEstimatedFootprint($from, $to) > $memoryLimit) {
return new MemoryEfficientImplementation;
return new MemoryEfficientLongestCommonSubsequenceCalculator;
}
return new TimeEfficientImplementation;
return new TimeEfficientLongestCommonSubsequenceCalculator;
}
/**
* Calculates the estimated memory footprint for the DP-based method.
*
* @param array $from
* @param array $to
*
* @return int|float
* @return float|int
*/
private function calculateEstimatedFootprint(array $from, array $to)
{
$itemSize = PHP_INT_SIZE === 4 ? 76 : 144;
return $itemSize * \pow(\min(\count($from), \count($to)), 2);
return $itemSize * min(count($from), count($to)) ** 2;
}
/**
* Returns true if line ends don't match on fromMatches and toMatches.
*
* @param array $fromMatches
* @param array $toMatches
*
* @return bool
* Returns true if line ends don't match in a diff.
*/
private function detectUnmatchedLineEndings(array $fromMatches, array $toMatches)
private function detectUnmatchedLineEndings(array $diff): bool
{
return isset($fromMatches[0], $toMatches[0]) &&
\count($fromMatches[0]) === \count($toMatches[0]) &&
$fromMatches[0] !== $toMatches[0];
$newLineBreaks = ['' => true];
$oldLineBreaks = ['' => true];
foreach ($diff as $entry) {
if (self::OLD === $entry[1]) {
$ln = $this->getLinebreak($entry[0]);
$oldLineBreaks[$ln] = true;
$newLineBreaks[$ln] = true;
} elseif (self::ADDED === $entry[1]) {
$newLineBreaks[$this->getLinebreak($entry[0])] = true;
} elseif (self::REMOVED === $entry[1]) {
$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;
}
/**
* @param array $from
* @param array $to
*
* @return array
*/
private static function getArrayDiffParted(array &$from, array &$to)
private function getLinebreak($line): string
{
$start = array();
$end = array();
if (!is_string($line)) {
return '';
}
\reset($to);
$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);
$toK = key($to);
if ($toK === $k && $v === $to[$k]) {
$start[$k] = $v;
@@ -376,24 +304,24 @@ class Differ
}
}
\end($from);
\end($to);
end($from);
end($to);
do {
$fromK = \key($from);
$toK = \key($to);
$fromK = key($from);
$toK = key($to);
if (null === $fromK || null === $toK || \current($from) !== \current($to)) {
if (null === $fromK || null === $toK || current($from) !== current($to)) {
break;
}
\prev($from);
\prev($to);
prev($from);
prev($to);
$end = array($fromK => $from[$fromK]) + $end;
$end = [$fromK => $from[$fromK]] + $end;
unset($from[$fromK], $to[$toK]);
} while (true);
return array($from, $to, $start, $end);
return [$from, $to, $start, $end];
}
}

View File

@@ -0,0 +1,38 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/diff.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Diff;
use function get_class;
use function gettype;
use function is_object;
use function sprintf;
use Exception;
final class ConfigurationException extends InvalidArgumentException
{
public function __construct(
string $option,
string $expected,
$value,
int $code = 0,
Exception $previous = null
) {
parent::__construct(
sprintf(
'Option "%s" must be %s, got "%s".',
$option,
$expected,
is_object($value) ? get_class($value) : (null === $value ? '<null>' : gettype($value) . '#' . $value)
),
$code,
$previous
);
}
}

View File

@@ -0,0 +1,16 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/diff.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Diff;
use Throwable;
interface Exception extends Throwable
{
}

View File

@@ -0,0 +1,14 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/diff.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* 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
{
}

View File

@@ -1,95 +0,0 @@
<?php
/*
* This file is part of sebastian/diff.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Diff\LCS;
/**
* Memory-efficient implementation of longest common subsequence calculation.
*/
class MemoryEfficientImplementation implements LongestCommonSubsequence
{
/**
* Calculates the longest common subsequence of two arrays.
*
* @param array $from
* @param array $to
*
* @return array
*/
public function calculate(array $from, array $to)
{
$cFrom = \count($from);
$cTo = \count($to);
if ($cFrom === 0) {
return array();
}
if ($cFrom === 1) {
if (\in_array($from[0], $to, true)) {
return array($from[0]);
}
return array();
}
$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)
);
}
/**
* @param array $from
* @param array $to
*
* @return array
*/
private function length(array $from, array $to)
{
$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;
}
}

View File

@@ -1,4 +1,4 @@
<?php
<?php declare(strict_types=1);
/*
* This file is part of sebastian/diff.
*
@@ -7,14 +7,15 @@
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Diff;
class Line
final class Line
{
const ADDED = 1;
const REMOVED = 2;
const UNCHANGED = 3;
public const ADDED = 1;
public const REMOVED = 2;
public const UNCHANGED = 3;
/**
* @var int
@@ -26,28 +27,18 @@ class Line
*/
private $content;
/**
* @param int $type
* @param string $content
*/
public function __construct($type = self::UNCHANGED, $content = '')
public function __construct(int $type = self::UNCHANGED, string $content = '')
{
$this->type = $type;
$this->content = $content;
}
/**
* @return string
*/
public function getContent()
public function getContent(): string
{
return $this->content;
}
/**
* @return int
*/
public function getType()
public function getType(): int
{
return $this->type;
}

View File

@@ -1,4 +1,4 @@
<?php
<?php declare(strict_types=1);
/*
* This file is part of sebastian/diff.
*
@@ -7,21 +7,12 @@
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Diff;
namespace SebastianBergmann\Diff\LCS;
/**
* Interface for implementations of longest common subsequence calculation.
*/
interface LongestCommonSubsequence
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);
public function calculate(array $from, array $to): array;
}

View File

@@ -0,0 +1,88 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/diff.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Diff;
use function array_fill;
use function array_merge;
use function array_reverse;
use function array_slice;
use function count;
use function in_array;
use function max;
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;
}
}

View File

@@ -0,0 +1,52 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/diff.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Diff\Output;
use function count;
abstract class AbstractChunkOutputBuilder implements DiffOutputBuilderInterface
{
/**
* Takes input of the diff array and returns the common parts.
* Iterates through diff line by line.
*/
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;
}
}

View File

@@ -0,0 +1,72 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/diff.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Diff\Output;
use function fclose;
use function fopen;
use function fwrite;
use function stream_get_contents;
use function substr;
use SebastianBergmann\Diff\Differ;
/**
* 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] === Differ::ADDED) {
fwrite($buffer, '+' . $diffEntry[0]);
} elseif ($diffEntry[1] === Differ::REMOVED) {
fwrite($buffer, '-' . $diffEntry[0]);
} elseif ($diffEntry[1] === Differ::DIFF_LINE_END_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;
}
}

View File

@@ -0,0 +1,19 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/diff.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* 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;
}

View File

@@ -0,0 +1,338 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/diff.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Diff\Output;
use function array_merge;
use function array_splice;
use function count;
use function fclose;
use function fopen;
use function fwrite;
use function is_bool;
use function is_int;
use function is_string;
use function max;
use function min;
use function sprintf;
use function stream_get_contents;
use function substr;
use SebastianBergmann\Diff\ConfigurationException;
use SebastianBergmann\Diff\Differ;
/**
* Strict Unified diff output builder.
*
* Generates (strict) Unified diff's (unidiffs) with hunks.
*/
final class StrictUnifiedDiffOutputBuilder implements DiffOutputBuilderInterface
{
private static $default = [
'collapseRanges' => true, // ranges of length one are rendered with the trailing `,1`
'commonLineThreshold' => 6, // number of same lines before ending a new hunk and creating a new one (if needed)
'contextLines' => 3, // like `diff: -u, -U NUM, --unified[=NUM]`, for patch/git apply compatibility best to keep at least @ 3
'fromFile' => null,
'fromFileDate' => null,
'toFile' => null,
'toFileDate' => null,
];
/**
* @var bool
*/
private $changed;
/**
* @var bool
*/
private $collapseRanges;
/**
* @var int >= 0
*/
private $commonLineThreshold;
/**
* @var string
*/
private $header;
/**
* @var int >= 0
*/
private $contextLines;
public function __construct(array $options = [])
{
$options = array_merge(self::$default, $options);
if (!is_bool($options['collapseRanges'])) {
throw new ConfigurationException('collapseRanges', 'a bool', $options['collapseRanges']);
}
if (!is_int($options['contextLines']) || $options['contextLines'] < 0) {
throw new ConfigurationException('contextLines', 'an int >= 0', $options['contextLines']);
}
if (!is_int($options['commonLineThreshold']) || $options['commonLineThreshold'] <= 0) {
throw new ConfigurationException('commonLineThreshold', 'an int > 0', $options['commonLineThreshold']);
}
$this->assertString($options, 'fromFile');
$this->assertString($options, 'toFile');
$this->assertStringOrNull($options, 'fromFileDate');
$this->assertStringOrNull($options, 'toFileDate');
$this->header = sprintf(
"--- %s%s\n+++ %s%s\n",
$options['fromFile'],
null === $options['fromFileDate'] ? '' : "\t" . $options['fromFileDate'],
$options['toFile'],
null === $options['toFileDate'] ? '' : "\t" . $options['toFileDate']
);
$this->collapseRanges = $options['collapseRanges'];
$this->commonLineThreshold = $options['commonLineThreshold'];
$this->contextLines = $options['contextLines'];
}
public function getDiff(array $diff): string
{
if (0 === count($diff)) {
return '';
}
$this->changed = false;
$buffer = fopen('php://memory', 'r+b');
fwrite($buffer, $this->header);
$this->writeDiffHunks($buffer, $diff);
if (!$this->changed) {
fclose($buffer);
return '';
}
$diff = stream_get_contents($buffer, -1, 0);
fclose($buffer);
// If the last char is not a linebreak: add it.
// This might happen when both the `from` and `to` do not have a trailing linebreak
$last = substr($diff, -1);
return "\n" !== $last && "\r" !== $last
? $diff . "\n"
: $diff;
}
private function writeDiffHunks($output, array $diff): void
{
// detect "No newline at end of file" and insert into `$diff` if needed
$upperLimit = count($diff);
if (0 === $diff[$upperLimit - 1][1]) {
$lc = substr($diff[$upperLimit - 1][0], -1);
if ("\n" !== $lc) {
array_splice($diff, $upperLimit, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]);
}
} else {
// search back for the last `+` and `-` line,
// check if has trailing linebreak, else add under it warning under it
$toFind = [1 => true, 2 => true];
for ($i = $upperLimit - 1; $i >= 0; --$i) {
if (isset($toFind[$diff[$i][1]])) {
unset($toFind[$diff[$i][1]]);
$lc = substr($diff[$i][0], -1);
if ("\n" !== $lc) {
array_splice($diff, $i + 1, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]);
}
if (!count($toFind)) {
break;
}
}
}
}
// write hunks to output buffer
$cutOff = max($this->commonLineThreshold, $this->contextLines);
$hunkCapture = false;
$sameCount = $toRange = $fromRange = 0;
$toStart = $fromStart = 1;
$i = 0;
/** @var int $i */
foreach ($diff as $i => $entry) {
if (0 === $entry[1]) { // same
if (false === $hunkCapture) {
++$fromStart;
++$toStart;
continue;
}
++$sameCount;
++$toRange;
++$fromRange;
if ($sameCount === $cutOff) {
$contextStartOffset = ($hunkCapture - $this->contextLines) < 0
? $hunkCapture
: $this->contextLines;
// note: $contextEndOffset = $this->contextLines;
//
// because we never go beyond the end of the diff.
// with the cutoff/contextlines here the follow is never true;
//
// if ($i - $cutOff + $this->contextLines + 1 > \count($diff)) {
// $contextEndOffset = count($diff) - 1;
// }
//
// ; that would be true for a trailing incomplete hunk case which is dealt with after this loop
$this->writeHunk(
$diff,
$hunkCapture - $contextStartOffset,
$i - $cutOff + $this->contextLines + 1,
$fromStart - $contextStartOffset,
$fromRange - $cutOff + $contextStartOffset + $this->contextLines,
$toStart - $contextStartOffset,
$toRange - $cutOff + $contextStartOffset + $this->contextLines,
$output
);
$fromStart += $fromRange;
$toStart += $toRange;
$hunkCapture = false;
$sameCount = $toRange = $fromRange = 0;
}
continue;
}
$sameCount = 0;
if ($entry[1] === Differ::NO_LINE_END_EOF_WARNING) {
continue;
}
$this->changed = true;
if (false === $hunkCapture) {
$hunkCapture = $i;
}
if (Differ::ADDED === $entry[1]) { // added
++$toRange;
}
if (Differ::REMOVED === $entry[1]) { // removed
++$fromRange;
}
}
if (false === $hunkCapture) {
return;
}
// we end here when cutoff (commonLineThreshold) was not reached, but we where capturing a hunk,
// do not render hunk till end automatically because the number of context lines might be less than the commonLineThreshold
$contextStartOffset = $hunkCapture - $this->contextLines < 0
? $hunkCapture
: $this->contextLines;
// prevent trying to write out more common lines than there are in the diff _and_
// do not write more than configured through the context lines
$contextEndOffset = min($sameCount, $this->contextLines);
$fromRange -= $sameCount;
$toRange -= $sameCount;
$this->writeHunk(
$diff,
$hunkCapture - $contextStartOffset,
$i - $sameCount + $contextEndOffset + 1,
$fromStart - $contextStartOffset,
$fromRange + $contextStartOffset + $contextEndOffset,
$toStart - $contextStartOffset,
$toRange + $contextStartOffset + $contextEndOffset,
$output
);
}
private function writeHunk(
array $diff,
int $diffStartIndex,
int $diffEndIndex,
int $fromStart,
int $fromRange,
int $toStart,
int $toRange,
$output
): void {
fwrite($output, '@@ -' . $fromStart);
if (!$this->collapseRanges || 1 !== $fromRange) {
fwrite($output, ',' . $fromRange);
}
fwrite($output, ' +' . $toStart);
if (!$this->collapseRanges || 1 !== $toRange) {
fwrite($output, ',' . $toRange);
}
fwrite($output, " @@\n");
for ($i = $diffStartIndex; $i < $diffEndIndex; ++$i) {
if ($diff[$i][1] === Differ::ADDED) {
$this->changed = true;
fwrite($output, '+' . $diff[$i][0]);
} elseif ($diff[$i][1] === Differ::REMOVED) {
$this->changed = true;
fwrite($output, '-' . $diff[$i][0]);
} elseif ($diff[$i][1] === Differ::OLD) {
fwrite($output, ' ' . $diff[$i][0]);
} elseif ($diff[$i][1] === Differ::NO_LINE_END_EOF_WARNING) {
$this->changed = true;
fwrite($output, $diff[$i][0]);
}
//} elseif ($diff[$i][1] === Differ::DIFF_LINE_END_WARNING) { // custom comment inserted by PHPUnit/diff package
// skip
//} else {
// unknown/invalid
//}
}
}
private function assertString(array $options, string $option): void
{
if (!is_string($options[$option])) {
throw new ConfigurationException($option, 'a string', $options[$option]);
}
}
private function assertStringOrNull(array $options, string $option): void
{
if (null !== $options[$option] && !is_string($options[$option])) {
throw new ConfigurationException($option, 'a string or <null>', $options[$option]);
}
}
}

View File

@@ -0,0 +1,272 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/diff.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Diff\Output;
use function array_splice;
use function count;
use function fclose;
use function fopen;
use function fwrite;
use function max;
use function min;
use function stream_get_contents;
use function strlen;
use function substr;
use SebastianBergmann\Diff\Differ;
/**
* Builds a diff string representation in unified diff format in chunks.
*/
final class UnifiedDiffOutputBuilder extends AbstractChunkOutputBuilder
{
/**
* @var bool
*/
private $collapseRanges = true;
/**
* @var int >= 0
*/
private $commonLineThreshold = 6;
/**
* @var int >= 0
*/
private $contextLines = 3;
/**
* @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");
}
}
if (0 !== count($diff)) {
$this->writeDiffHunks($buffer, $diff);
}
$diff = stream_get_contents($buffer, -1, 0);
fclose($buffer);
// If the diff is non-empty and last char is not a linebreak: add it.
// This might happen when both the `from` and `to` do not have a trailing linebreak
$last = substr($diff, -1);
return 0 !== strlen($diff) && "\n" !== $last && "\r" !== $last
? $diff . "\n"
: $diff;
}
private function writeDiffHunks($output, array $diff): void
{
// detect "No newline at end of file" and insert into `$diff` if needed
$upperLimit = count($diff);
if (0 === $diff[$upperLimit - 1][1]) {
$lc = substr($diff[$upperLimit - 1][0], -1);
if ("\n" !== $lc) {
array_splice($diff, $upperLimit, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]);
}
} else {
// search back for the last `+` and `-` line,
// check if has trailing linebreak, else add under it warning under it
$toFind = [1 => true, 2 => true];
for ($i = $upperLimit - 1; $i >= 0; --$i) {
if (isset($toFind[$diff[$i][1]])) {
unset($toFind[$diff[$i][1]]);
$lc = substr($diff[$i][0], -1);
if ("\n" !== $lc) {
array_splice($diff, $i + 1, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]);
}
if (!count($toFind)) {
break;
}
}
}
}
// write hunks to output buffer
$cutOff = max($this->commonLineThreshold, $this->contextLines);
$hunkCapture = false;
$sameCount = $toRange = $fromRange = 0;
$toStart = $fromStart = 1;
$i = 0;
/** @var int $i */
foreach ($diff as $i => $entry) {
if (0 === $entry[1]) { // same
if (false === $hunkCapture) {
++$fromStart;
++$toStart;
continue;
}
++$sameCount;
++$toRange;
++$fromRange;
if ($sameCount === $cutOff) {
$contextStartOffset = ($hunkCapture - $this->contextLines) < 0
? $hunkCapture
: $this->contextLines;
// note: $contextEndOffset = $this->contextLines;
//
// because we never go beyond the end of the diff.
// with the cutoff/contextlines here the follow is never true;
//
// if ($i - $cutOff + $this->contextLines + 1 > \count($diff)) {
// $contextEndOffset = count($diff) - 1;
// }
//
// ; that would be true for a trailing incomplete hunk case which is dealt with after this loop
$this->writeHunk(
$diff,
$hunkCapture - $contextStartOffset,
$i - $cutOff + $this->contextLines + 1,
$fromStart - $contextStartOffset,
$fromRange - $cutOff + $contextStartOffset + $this->contextLines,
$toStart - $contextStartOffset,
$toRange - $cutOff + $contextStartOffset + $this->contextLines,
$output
);
$fromStart += $fromRange;
$toStart += $toRange;
$hunkCapture = false;
$sameCount = $toRange = $fromRange = 0;
}
continue;
}
$sameCount = 0;
if ($entry[1] === Differ::NO_LINE_END_EOF_WARNING) {
continue;
}
if (false === $hunkCapture) {
$hunkCapture = $i;
}
if (Differ::ADDED === $entry[1]) {
++$toRange;
}
if (Differ::REMOVED === $entry[1]) {
++$fromRange;
}
}
if (false === $hunkCapture) {
return;
}
// we end here when cutoff (commonLineThreshold) was not reached, but we where capturing a hunk,
// do not render hunk till end automatically because the number of context lines might be less than the commonLineThreshold
$contextStartOffset = $hunkCapture - $this->contextLines < 0
? $hunkCapture
: $this->contextLines;
// prevent trying to write out more common lines than there are in the diff _and_
// do not write more than configured through the context lines
$contextEndOffset = min($sameCount, $this->contextLines);
$fromRange -= $sameCount;
$toRange -= $sameCount;
$this->writeHunk(
$diff,
$hunkCapture - $contextStartOffset,
$i - $sameCount + $contextEndOffset + 1,
$fromStart - $contextStartOffset,
$fromRange + $contextStartOffset + $contextEndOffset,
$toStart - $contextStartOffset,
$toRange + $contextStartOffset + $contextEndOffset,
$output
);
}
private function writeHunk(
array $diff,
int $diffStartIndex,
int $diffEndIndex,
int $fromStart,
int $fromRange,
int $toStart,
int $toRange,
$output
): void {
if ($this->addLineNumbers) {
fwrite($output, '@@ -' . $fromStart);
if (!$this->collapseRanges || 1 !== $fromRange) {
fwrite($output, ',' . $fromRange);
}
fwrite($output, ' +' . $toStart);
if (!$this->collapseRanges || 1 !== $toRange) {
fwrite($output, ',' . $toRange);
}
fwrite($output, " @@\n");
} else {
fwrite($output, "@@ @@\n");
}
for ($i = $diffStartIndex; $i < $diffEndIndex; ++$i) {
if ($diff[$i][1] === Differ::ADDED) {
fwrite($output, '+' . $diff[$i][0]);
} elseif ($diff[$i][1] === Differ::REMOVED) {
fwrite($output, '-' . $diff[$i][0]);
} elseif ($diff[$i][1] === Differ::OLD) {
fwrite($output, ' ' . $diff[$i][0]);
} elseif ($diff[$i][1] === Differ::NO_LINE_END_EOF_WARNING) {
fwrite($output, "\n"); // $diff[$i][0]
} else { /* Not changed (old) Differ::OLD or Warning Differ::DIFF_LINE_END_WARNING */
fwrite($output, ' ' . $diff[$i][0]);
}
}
}
}

View File

@@ -1,4 +1,4 @@
<?php
<?php declare(strict_types=1);
/*
* This file is part of sebastian/diff.
*
@@ -7,47 +7,50 @@
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Diff;
use function array_pop;
use function count;
use function max;
use function preg_match;
use function preg_split;
/**
* Unified diff parser.
*/
class Parser
final class Parser
{
/**
* @param string $string
*
* @return Diff[]
*/
public function parse($string)
public function parse(string $string): array
{
$lines = \preg_split('(\r\n|\r|\n)', $string);
$lines = preg_split('(\r\n|\r|\n)', $string);
if (!empty($lines) && $lines[\count($lines) - 1] == '') {
\array_pop($lines);
if (!empty($lines) && $lines[count($lines) - 1] === '') {
array_pop($lines);
}
$lineCount = \count($lines);
$diffs = array();
$lineCount = count($lines);
$diffs = [];
$diff = null;
$collected = array();
$collected = [];
for ($i = 0; $i < $lineCount; ++$i) {
if (\preg_match('(^---\\s+(?P<file>\\S+))', $lines[$i], $fromMatch) &&
\preg_match('(^\\+\\+\\+\\s+(?P<file>\\S+))', $lines[$i + 1], $toMatch)) {
if (preg_match('#^---\h+"?(?P<file>[^\\v\\t"]+)#', $lines[$i], $fromMatch) &&
preg_match('#^\\+\\+\\+\\h+"?(?P<file>[^\\v\\t"]+)#', $lines[$i + 1], $toMatch)) {
if ($diff !== null) {
$this->parseFileDiff($diff, $collected);
$diffs[] = $diff;
$collected = array();
$collected = [];
}
$diff = new Diff($fromMatch['file'], $toMatch['file']);
++$i;
} else {
if (\preg_match('/^(?:diff --git |index [\da-f\.]+|[+-]{3} [ab])/', $lines[$i])) {
if (preg_match('/^(?:diff --git |index [\da-f\.]+|[+-]{3} [ab])/', $lines[$i])) {
continue;
}
@@ -55,7 +58,7 @@ class Parser
}
}
if ($diff !== null && \count($collected)) {
if ($diff !== null && count($collected)) {
$this->parseFileDiff($diff, $collected);
$diffs[] = $diff;
@@ -64,31 +67,28 @@ class Parser
return $diffs;
}
/**
* @param Diff $diff
* @param array $lines
*/
private function parseFileDiff(Diff $diff, array $lines)
private function parseFileDiff(Diff $diff, array $lines): void
{
$chunks = array();
$chunk = null;
$chunks = [];
$chunk = null;
$diffLines = [];
foreach ($lines as $line) {
if (\preg_match('/^@@\s+-(?P<start>\d+)(?:,\s*(?P<startrange>\d+))?\s+\+(?P<end>\d+)(?:,\s*(?P<endrange>\d+))?\s+@@/', $line, $match)) {
if (preg_match('/^@@\s+-(?P<start>\d+)(?:,\s*(?P<startrange>\d+))?\s+\+(?P<end>\d+)(?:,\s*(?P<endrange>\d+))?\s+@@/', $line, $match)) {
$chunk = new Chunk(
$match['start'],
isset($match['startrange']) ? \max(1, $match['startrange']) : 1,
$match['end'],
isset($match['endrange']) ? \max(1, $match['endrange']) : 1
(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 = array();
$diffLines = [];
continue;
}
if (\preg_match('/^(?P<type>[+ -])?(?P<line>.*)/', $line, $match)) {
if (preg_match('/^(?P<type>[+ -])?(?P<line>.*)/', $line, $match)) {
$type = Line::UNCHANGED;
if ($match['type'] === '+') {

View File

@@ -1,4 +1,4 @@
<?php
<?php declare(strict_types=1);
/*
* This file is part of sebastian/diff.
*
@@ -7,29 +7,25 @@
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Diff;
namespace SebastianBergmann\Diff\LCS;
use function array_reverse;
use function count;
use function max;
use SplFixedArray;
/**
* Time-efficient implementation of longest common subsequence calculation.
*/
class TimeEfficientImplementation implements LongestCommonSubsequence
final class TimeEfficientLongestCommonSubsequenceCalculator implements LongestCommonSubsequenceCalculator
{
/**
* Calculates the longest common subsequence of two arrays.
*
* @param array $from
* @param array $to
*
* @return array
* {@inheritdoc}
*/
public function calculate(array $from, array $to)
public function calculate(array $from, array $to): array
{
$common = array();
$fromLength = \count($from);
$toLength = \count($to);
$common = [];
$fromLength = count($from);
$toLength = count($to);
$width = $fromLength + 1;
$matrix = new \SplFixedArray($width * ($toLength + 1));
$matrix = new SplFixedArray($width * ($toLength + 1));
for ($i = 0; $i <= $fromLength; ++$i) {
$matrix[$i] = 0;
@@ -42,7 +38,7 @@ class TimeEfficientImplementation implements LongestCommonSubsequence
for ($i = 1; $i <= $fromLength; ++$i) {
for ($j = 1; $j <= $toLength; ++$j) {
$o = ($j * $width) + $i;
$matrix[$o] = \max(
$matrix[$o] = max(
$matrix[$o - 1],
$matrix[$o - $width],
$from[$i - 1] === $to[$j - 1] ? $matrix[$o - $width - 1] + 1 : 0
@@ -69,6 +65,6 @@ class TimeEfficientImplementation implements LongestCommonSubsequence
}
}
return \array_reverse($common);
return array_reverse($common);
}
}

View File

@@ -1,68 +0,0 @@
<?php
/*
* This file is part of sebastian/diff.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* 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
*/
class ChunkTest extends TestCase
{
/**
* @var Chunk
*/
private $chunk;
protected function setUp()
{
$this->chunk = new Chunk;
}
public function testCanBeCreatedWithoutArguments()
{
$this->assertInstanceOf('SebastianBergmann\Diff\Chunk', $this->chunk);
}
public function testStartCanBeRetrieved()
{
$this->assertEquals(0, $this->chunk->getStart());
}
public function testStartRangeCanBeRetrieved()
{
$this->assertEquals(1, $this->chunk->getStartRange());
}
public function testEndCanBeRetrieved()
{
$this->assertEquals(0, $this->chunk->getEnd());
}
public function testEndRangeCanBeRetrieved()
{
$this->assertEquals(1, $this->chunk->getEndRange());
}
public function testLinesCanBeRetrieved()
{
$this->assertEquals(array(), $this->chunk->getLines());
}
public function testLinesCanBeSet()
{
$this->assertEquals(array(), $this->chunk->getLines());
$testValue = array('line0', 'line1');
$this->chunk->setLines($testValue);
$this->assertEquals($testValue, $this->chunk->getLines());
}
}

View File

@@ -1,55 +0,0 @@
<?php
/*
* This file is part of sebastian/diff.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* 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(array(), $diff->getChunks(), 'Expect chunks to be default value "array()".');
}
public function testGettersAfterConstructionWithChunks()
{
$from = 'line1b';
$to = 'line2b';
$chunks = array(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(array(), $diff->getChunks(), 'Expect chunks to be default value "array()".');
$chunks = array(new Chunk(), new Chunk(2, 3));
$diff->setChunks($chunks);
$this->assertSame($chunks, $diff->getChunks(), 'Expect chunks to be passed value.');
}
}

View File

@@ -1,415 +0,0 @@
<?php
/*
* This file is part of sebastian/diff.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* 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\LCS\MemoryEfficientImplementation;
use SebastianBergmann\Diff\LCS\TimeEfficientImplementation;
use PHPUnit\Framework\TestCase;
/**
* @covers SebastianBergmann\Diff\Differ
*
* @uses SebastianBergmann\Diff\LCS\MemoryEfficientImplementation
* @uses SebastianBergmann\Diff\LCS\TimeEfficientImplementation
* @uses SebastianBergmann\Diff\Chunk
* @uses SebastianBergmann\Diff\Diff
* @uses SebastianBergmann\Diff\Line
* @uses SebastianBergmann\Diff\Parser
*/
class DifferTest extends TestCase
{
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->assertEquals($expected, $this->differ->diffToArray($from, $to, new TimeEfficientImplementation));
}
/**
* @param string $expected
* @param string $from
* @param string $to
* @dataProvider textProvider
*/
public function testTextRepresentationOfDiffCanBeRenderedUsingTimeEfficientLcsImplementation($expected, $from, $to)
{
$this->assertEquals($expected, $this->differ->diff($from, $to, new TimeEfficientImplementation));
}
/**
* @param array $expected
* @param string|array $from
* @param string|array $to
* @dataProvider arrayProvider
*/
public function testArrayRepresentationOfDiffCanBeRenderedUsingMemoryEfficientLcsImplementation(array $expected, $from, $to)
{
$this->assertEquals($expected, $this->differ->diffToArray($from, $to, new MemoryEfficientImplementation));
}
/**
* @param string $expected
* @param string $from
* @param string $to
* @dataProvider textProvider
*/
public function testTextRepresentationOfDiffCanBeRenderedUsingMemoryEfficientLcsImplementation($expected, $from, $to)
{
$this->assertEquals($expected, $this->differ->diff($from, $to, new MemoryEfficientImplementation));
}
public function testCustomHeaderCanBeUsed()
{
$differ = new Differ('CUSTOM HEADER');
$this->assertEquals(
"CUSTOM HEADER@@ @@\n-a\n+b\n",
$differ->diff('a', 'b')
);
}
public function testTypesOtherThanArrayAndStringCanBePassed()
{
$this->assertEquals(
"--- 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($diff, array $expected)
{
$parser = new Parser;
$result = $parser->parse($diff);
$this->assertEquals($expected, $result);
}
public function arrayProvider()
{
return array(
array(
array(
array('a', self::REMOVED),
array('b', self::ADDED)
),
'a',
'b'
),
array(
array(
array('ba', self::REMOVED),
array('bc', self::ADDED)
),
'ba',
'bc'
),
array(
array(
array('ab', self::REMOVED),
array('cb', self::ADDED)
),
'ab',
'cb'
),
array(
array(
array('abc', self::REMOVED),
array('adc', self::ADDED)
),
'abc',
'adc'
),
array(
array(
array('ab', self::REMOVED),
array('abc', self::ADDED)
),
'ab',
'abc'
),
array(
array(
array('bc', self::REMOVED),
array('abc', self::ADDED)
),
'bc',
'abc'
),
array(
array(
array('abc', self::REMOVED),
array('abbc', self::ADDED)
),
'abc',
'abbc'
),
array(
array(
array('abcdde', self::REMOVED),
array('abcde', self::ADDED)
),
'abcdde',
'abcde'
),
'same start' => array(
array(
array(17, self::OLD),
array('b', self::REMOVED),
array('d', self::ADDED),
),
array(30 => 17, 'a' => 'b'),
array(30 => 17, 'c' => 'd'),
),
'same end' => array(
array(
array(1, self::REMOVED),
array(2, self::ADDED),
array('b', self::OLD),
),
array(1 => 1, 'a' => 'b'),
array(1 => 2, 'a' => 'b'),
),
'same start (2), same end (1)' => array(
array(
array(17, self::OLD),
array(2, self::OLD),
array(4, self::REMOVED),
array('a', self::ADDED),
array(5, self::ADDED),
array('x', self::OLD),
),
array(30 => 17, 1 => 2, 2 => 4, 'z' => 'x'),
array(30 => 17, 1 => 2, 3 => 'a', 2 => 5, 'z' => 'x'),
),
'same' => array(
array(
array('x', self::OLD),
),
array('z' => 'x'),
array('z' => 'x'),
),
'diff' => array(
array(
array('y', self::REMOVED),
array('x', self::ADDED),
),
array('x' => 'y'),
array('z' => 'x'),
),
'diff 2' => array(
array(
array('y', self::REMOVED),
array('b', self::REMOVED),
array('x', self::ADDED),
array('d', self::ADDED),
),
array('x' => 'y', 'a' => 'b'),
array('z' => 'x', 'c' => 'd'),
),
'test line diff detection' => array(
array(
array(
'#Warning: Strings contain different line endings!',
self::OLD,
),
array(
'<?php',
self::OLD,
),
array(
'',
self::OLD,
),
),
"<?php\r\n",
"<?php\n"
)
);
}
public function textProvider()
{
return array(
array(
"--- Original\n+++ New\n@@ @@\n-a\n+b\n",
'a',
'b'
),
array(
"--- Original\n+++ New\n@@ @@\n-ba\n+bc\n",
'ba',
'bc'
),
array(
"--- Original\n+++ New\n@@ @@\n-ab\n+cb\n",
'ab',
'cb'
),
array(
"--- Original\n+++ New\n@@ @@\n-abc\n+adc\n",
'abc',
'adc'
),
array(
"--- Original\n+++ New\n@@ @@\n-ab\n+abc\n",
'ab',
'abc'
),
array(
"--- Original\n+++ New\n@@ @@\n-bc\n+abc\n",
'bc',
'abc'
),
array(
"--- Original\n+++ New\n@@ @@\n-abc\n+abbc\n",
'abc',
'abbc'
),
array(
"--- Original\n+++ New\n@@ @@\n-abcdde\n+abcde\n",
'abcdde',
'abcde'
),
array(
"--- Original\n+++ New\n@@ @@\n-A\n+A1\n B\n",
"A\nB",
"A1\nB",
),
array(
<<<EOF
--- Original
+++ New
@@ @@
a
-b
+p
@@ @@
i
-j
+w
k
EOF
,
"a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk",
"a\np\nc\nd\ne\nf\ng\nh\ni\nw\nk",
),
array(
<<<EOF
--- Original
+++ New
@@ @@
a
-b
+p
@@ @@
i
-j
+w
k
EOF
,
"a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk",
"a\np\nc\nd\ne\nf\ng\nh\ni\nw\nk",
),
);
}
public function diffProvider()
{
$serialized_arr = <<<EOL
a:1:{i:0;O:27:"SebastianBergmann\Diff\Diff":3:{s:33:"SebastianBergmann\Diff\Difffrom";s:7:"old.txt";s:31:"SebastianBergmann\Diff\Diffto";s:7:"new.txt";s:35:"SebastianBergmann\Diff\Diffchunks";a:3:{i:0;O:28:"SebastianBergmann\Diff\Chunk":5:{s:35:"SebastianBergmann\Diff\Chunkstart";i:1;s:40:"SebastianBergmann\Diff\ChunkstartRange";i:3;s:33:"SebastianBergmann\Diff\Chunkend";i:1;s:38:"SebastianBergmann\Diff\ChunkendRange";i:4;s:35:"SebastianBergmann\Diff\Chunklines";a:4:{i:0;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:1;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"2222111";}i:1;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"1111111";}i:2;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"1111111";}i:3;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"1111111";}}}i:1;O:28:"SebastianBergmann\Diff\Chunk":5:{s:35:"SebastianBergmann\Diff\Chunkstart";i:5;s:40:"SebastianBergmann\Diff\ChunkstartRange";i:10;s:33:"SebastianBergmann\Diff\Chunkend";i:6;s:38:"SebastianBergmann\Diff\ChunkendRange";i:8;s:35:"SebastianBergmann\Diff\Chunklines";a:11:{i:0;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"1111111";}i:1;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"1111111";}i:2;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"1111111";}i:3;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:8:"+1121211";}i:4;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"1111111";}i:5;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:8:"-1111111";}i:6;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:8:"-1111111";}i:7;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:8:"-2222222";}i:8;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"2222222";}i:9;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"2222222";}i:10;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"2222222";}}}i:2;O:28:"SebastianBergmann\Diff\Chunk":5:{s:35:"SebastianBergmann\Diff\Chunkstart";i:17;s:40:"SebastianBergmann\Diff\ChunkstartRange";i:5;s:33:"SebastianBergmann\Diff\Chunkend";i:16;s:38:"SebastianBergmann\Diff\ChunkendRange";i:6;s:35:"SebastianBergmann\Diff\Chunklines";a:6:{i:0;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"2222222";}i:1;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"2222222";}i:2;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"2222222";}i:3;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:8:"+2122212";}i:4;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"2222222";}i:5;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"2222222";}}}}}}
EOL;
return array(
array(
"--- old.txt 2014-11-04 08:51:02.661868729 +0300\n+++ new.txt 2014-11-04 08:51:02.665868730 +0300\n@@ -1,3 +1,4 @@\n+2222111\n 1111111\n 1111111\n 1111111\n@@ -5,10 +6,8 @@\n 1111111\n 1111111\n 1111111\n +1121211\n 1111111\n -1111111\n -1111111\n -2222222\n 2222222\n 2222222\n 2222222\n@@ -17,5 +16,6 @@\n 2222222\n 2222222\n 2222222\n +2122212\n 2222222\n 2222222\n",
\unserialize($serialized_arr)
)
);
}
/**
* @param string $expected
* @param string $from
* @param string $to
* @dataProvider textForNoNonDiffLinesProvider
*/
public function testDiffDoNotShowNonDiffLines($expected, $from, $to)
{
$differ = new Differ('', false);
$this->assertSame($expected, $differ->diff($from, $to));
}
public function textForNoNonDiffLinesProvider()
{
return array(
array(
'', 'a', 'a'
),
array(
"-A\n+C\n",
"A\n\n\nB",
"C\n\n\nB",
),
);
}
/**
* @requires PHPUnit 5.7
*/
public function testDiffToArrayInvalidFromType()
{
$differ = new Differ;
$this->expectException('\InvalidArgumentException');
$this->expectExceptionMessageRegExp('#^"from" must be an array or string\.$#');
$differ->diffToArray(null, '');
}
/**
* @requires PHPUnit 5.7
*/
public function testDiffInvalidToType()
{
$differ = new Differ;
$this->expectException('\InvalidArgumentException');
$this->expectExceptionMessageRegExp('#^"to" must be an array or string\.$#');
$differ->diffToArray('', new \stdClass);
}
}

View File

@@ -1,198 +0,0 @@
<?php
/*
* This file is part of sebastian/diff.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Diff\LCS;
use PHPUnit\Framework\TestCase;
abstract class LongestCommonSubsequenceTest extends TestCase
{
/**
* @var LongestCommonSubsequence
*/
private $implementation;
/**
* @var string
*/
private $memoryLimit;
/**
* @var int[]
*/
private $stress_sizes = array(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 LongestCommonSubsequence
*/
abstract protected function createImplementation();
protected function tearDown()
{
\ini_set('memory_limit', $this->memoryLimit);
}
public function testBothEmpty()
{
$from = array();
$to = array();
$common = $this->implementation->calculate($from, $to);
$this->assertEquals(array(), $common);
}
public function testIsStrictComparison()
{
$from = array(
false, 0, 0.0, '', null, array(),
true, 1, 1.0, 'foo', array('foo', 'bar'), array('foo' => 'bar')
);
$to = $from;
$common = $this->implementation->calculate($from, $to);
$this->assertEquals($from, $common);
$to = array(
false, false, false, false, false, false,
true, true, true, true, true, true
);
$expected = array(
false,
true,
);
$common = $this->implementation->calculate($from, $to);
$this->assertEquals($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->assertEquals($range, $common);
}
}
public function testDistinctSequences()
{
$from = array('A');
$to = array('B');
$common = $this->implementation->calculate($from, $to);
$this->assertEquals(array(), $common);
$from = array('A', 'B', 'C');
$to = array('D', 'E', 'F');
$common = $this->implementation->calculate($from, $to);
$this->assertEquals(array(), $common);
foreach ($this->stress_sizes as $size) {
$from = \range(1, $size);
$to = \range($size + 1, $size * 2);
$common = $this->implementation->calculate($from, $to);
$this->assertEquals(array(), $common);
}
}
public function testCommonSubsequence()
{
$from = array('A', 'C', 'E', 'F', 'G');
$to = array('A', 'B', 'D', 'E', 'H');
$expected = array('A', 'E');
$common = $this->implementation->calculate($from, $to);
$this->assertEquals($expected, $common);
$from = array('A', 'C', 'E', 'F', 'G');
$to = array('B', 'C', 'D', 'E', 'F', 'H');
$expected = array('C', 'E', 'F');
$common = $this->implementation->calculate($from, $to);
$this->assertEquals($expected, $common);
foreach ($this->stress_sizes as $size) {
$from = $size < 2 ? array(1) : \range(1, $size + 1, 2);
$to = $size < 3 ? array(1) : \range(1, $size + 1, 3);
$expected = $size < 6 ? array(1) : \range(1, $size + 1, 6);
$common = $this->implementation->calculate($from, $to);
$this->assertEquals($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->assertEquals($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->assertEquals($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->assertEquals($to, $common);
}
}
public function testReversedSequences()
{
$from = array('A', 'B');
$to = array('B', 'A');
$expected = array('A');
$common = $this->implementation->calculate($from, $to);
$this->assertEquals($expected, $common);
foreach ($this->stress_sizes as $size) {
$from = \range(1, $size);
$to = \array_reverse($from);
$common = $this->implementation->calculate($from, $to);
$this->assertEquals(array(1), $common);
}
}
public function testStrictTypeCalculate()
{
$diff = $this->implementation->calculate(array('5'), array('05'));
$this->assertInternalType('array', $diff);
$this->assertCount(0, $diff);
}
}

View File

@@ -1,22 +0,0 @@
<?php
/*
* This file is part of sebastian/diff.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Diff\LCS;
/**
* @covers SebastianBergmann\Diff\LCS\MemoryEfficientImplementation
*/
class MemoryEfficientImplementationTest extends LongestCommonSubsequenceTest
{
protected function createImplementation()
{
return new MemoryEfficientImplementation;
}
}

View File

@@ -1,22 +0,0 @@
<?php
/*
* This file is part of sebastian/diff.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Diff\LCS;
/**
* @covers SebastianBergmann\Diff\LCS\TimeEfficientImplementation
*/
class TimeEfficientImplementationTest extends LongestCommonSubsequenceTest
{
protected function createImplementation()
{
return new TimeEfficientImplementation;
}
}

View File

@@ -1,44 +0,0 @@
<?php
/*
* This file is part of sebastian/diff.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* 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
*/
class LineTest extends TestCase
{
/**
* @var Line
*/
private $line;
protected function setUp()
{
$this->line = new Line;
}
public function testCanBeCreatedWithoutArguments()
{
$this->assertInstanceOf('SebastianBergmann\Diff\Line', $this->line);
}
public function testTypeCanBeRetrieved()
{
$this->assertEquals(Line::UNCHANGED, $this->line->getType());
}
public function testContentCanBeRetrieved()
{
$this->assertEquals('', $this->line->getContent());
}
}

View File

@@ -1,151 +0,0 @@
<?php
/*
* This file is part of sebastian/diff.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* 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
*/
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('SebastianBergmann\Diff\Diff', $diffs);
$this->assertCount(1, $diffs);
$chunks = $diffs[0]->getChunks();
$this->assertInternalType('array', $chunks);
$this->assertContainsOnlyInstancesOf('SebastianBergmann\Diff\Chunk', $chunks);
$this->assertCount(1, $chunks);
$this->assertEquals(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->assertEquals(20, $chunks[0]->getStart());
$this->assertEquals(320, $chunks[1]->getStart());
$this->assertEquals(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 = <<<A
diff --git a/Test.txt b/Test.txt
index abcdefg..abcdefh 100644
--- a/Test.txt
+++ b/Test.txt
@@ -49,9 +49,8 @@
A
-B
A;
$diffs = $this->parser->parse($content);
$this->assertInternalType('array', $diffs);
$this->assertContainsOnlyInstancesOf('SebastianBergmann\Diff\Diff', $diffs);
$this->assertCount(1, $diffs);
$chunks = $diffs[0]->getChunks();
$this->assertInternalType('array', $chunks);
$this->assertContainsOnlyInstancesOf('SebastianBergmann\Diff\Chunk', $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('SebastianBergmann\Diff\Line', $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 = <<<A
diff --git a/Test.txt b/Test.txt
index abcdefg..abcdefh 100644
--- a/Test.txt
+++ b/Test.txt
@@ -1,3 +1,2 @@
A
-B
diff --git a/Test123.txt b/Test123.txt
index abcdefg..abcdefh 100644
--- a/Test2.txt
+++ b/Test2.txt
@@ -1,2 +1,3 @@
A
+B
A;
$diffs = $this->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());
}
}

View File

@@ -1,9 +0,0 @@
diff --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;

View File

@@ -1,21 +0,0 @@
diff --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';
}