mirror of
https://github.com/FriendsOfTYPO3/tea.git
synced 2025-01-13 14:26:10 +01:00
2502 lines
69 KiB
Text
2502 lines
69 KiB
Text
|
#!/usr/bin/env php
|
|||
|
<?php declare(strict_types=1);
|
|||
|
if (version_compare('7.3.0', PHP_VERSION, '>')) {
|
|||
|
fwrite(
|
|||
|
STDERR,
|
|||
|
sprintf(
|
|||
|
'This version of PHPCPD requires PHP 7.3 (or later).' . PHP_EOL .
|
|||
|
'You are using PHP %s%s.' . PHP_EOL,
|
|||
|
PHP_VERSION,
|
|||
|
defined('PHP_BINARY') ? ' (' . PHP_BINARY . ')' : ''
|
|||
|
)
|
|||
|
);
|
|||
|
|
|||
|
die(1);
|
|||
|
}
|
|||
|
|
|||
|
if ($_SERVER['SCRIPT_NAME'] != '-') {
|
|||
|
$phar = realpath($_SERVER['SCRIPT_NAME']);
|
|||
|
} else {
|
|||
|
$files = get_included_files();
|
|||
|
$phar = $files[0];
|
|||
|
}
|
|||
|
|
|||
|
define('__PHPCPD_PHAR__', str_replace(DIRECTORY_SEPARATOR, '/', $phar));
|
|||
|
define('__PHPCPD_PHAR_ROOT__', 'phar://phpcpd-6.0.3.phar');
|
|||
|
|
|||
|
spl_autoload_register(
|
|||
|
function ($class)
|
|||
|
{
|
|||
|
static $classes = NULL;
|
|||
|
|
|||
|
if ($classes === NULL) {
|
|||
|
$classes = array(
|
|||
|
'sebastianbergmann\\cliparser\\ambiguousoptionexception' => '/sebastian-cli-parser/exceptions/AmbiguousOptionException.php',
|
|||
|
'sebastianbergmann\\cliparser\\exception' => '/sebastian-cli-parser/exceptions/Exception.php',
|
|||
|
'sebastianbergmann\\cliparser\\optiondoesnotallowargumentexception' => '/sebastian-cli-parser/exceptions/OptionDoesNotAllowArgumentException.php',
|
|||
|
'sebastianbergmann\\cliparser\\parser' => '/sebastian-cli-parser/Parser.php',
|
|||
|
'sebastianbergmann\\cliparser\\requiredoptionargumentmissingexception' => '/sebastian-cli-parser/exceptions/RequiredOptionArgumentMissingException.php',
|
|||
|
'sebastianbergmann\\cliparser\\unknownoptionexception' => '/sebastian-cli-parser/exceptions/UnknownOptionException.php',
|
|||
|
'sebastianbergmann\\fileiterator\\facade' => '/phpunit-php-file-iterator/Facade.php',
|
|||
|
'sebastianbergmann\\fileiterator\\factory' => '/phpunit-php-file-iterator/Factory.php',
|
|||
|
'sebastianbergmann\\fileiterator\\iterator' => '/phpunit-php-file-iterator/Iterator.php',
|
|||
|
'sebastianbergmann\\phpcpd\\application' => '/src/CLI/Application.php',
|
|||
|
'sebastianbergmann\\phpcpd\\arguments' => '/src/CLI/Arguments.php',
|
|||
|
'sebastianbergmann\\phpcpd\\argumentsbuilder' => '/src/CLI/ArgumentsBuilder.php',
|
|||
|
'sebastianbergmann\\phpcpd\\argumentsbuilderexception' => '/src/Exceptions/ArgumentsBuilderException.php',
|
|||
|
'sebastianbergmann\\phpcpd\\codeclone' => '/src/CodeClone.php',
|
|||
|
'sebastianbergmann\\phpcpd\\codeclonefile' => '/src/CodeCloneFile.php',
|
|||
|
'sebastianbergmann\\phpcpd\\codeclonemap' => '/src/CodeCloneMap.php',
|
|||
|
'sebastianbergmann\\phpcpd\\codeclonemapiterator' => '/src/CodeCloneMapIterator.php',
|
|||
|
'sebastianbergmann\\phpcpd\\detector\\detector' => '/src/Detector/Detector.php',
|
|||
|
'sebastianbergmann\\phpcpd\\detector\\strategy\\abstractstrategy' => '/src/Detector/Strategy/AbstractStrategy.php',
|
|||
|
'sebastianbergmann\\phpcpd\\detector\\strategy\\defaultstrategy' => '/src/Detector/Strategy/DefaultStrategy.php',
|
|||
|
'sebastianbergmann\\phpcpd\\exception' => '/src/Exceptions/Exception.php',
|
|||
|
'sebastianbergmann\\phpcpd\\log\\abstractxmllogger' => '/src/Log/AbstractXmlLogger.php',
|
|||
|
'sebastianbergmann\\phpcpd\\log\\pmd' => '/src/Log/PMD.php',
|
|||
|
'sebastianbergmann\\phpcpd\\log\\text' => '/src/Log/Text.php',
|
|||
|
'sebastianbergmann\\timer\\duration' => '/phpunit-php-timer/Duration.php',
|
|||
|
'sebastianbergmann\\timer\\exception' => '/phpunit-php-timer/exceptions/Exception.php',
|
|||
|
'sebastianbergmann\\timer\\noactivetimerexception' => '/phpunit-php-timer/exceptions/NoActiveTimerException.php',
|
|||
|
'sebastianbergmann\\timer\\resourceusageformatter' => '/phpunit-php-timer/ResourceUsageFormatter.php',
|
|||
|
'sebastianbergmann\\timer\\timer' => '/phpunit-php-timer/Timer.php',
|
|||
|
'sebastianbergmann\\timer\\timesincestartofrequestnotavailableexception' => '/phpunit-php-timer/exceptions/TimeSinceStartOfRequestNotAvailableException.php',
|
|||
|
'sebastianbergmann\\version' => '/sebastian-version/Version.php'
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
$class = strtolower($class);
|
|||
|
|
|||
|
if (isset($classes[$class])) {
|
|||
|
require 'phar://phpcpd-6.0.3.phar' . $classes[$class];
|
|||
|
}
|
|||
|
}
|
|||
|
);
|
|||
|
|
|||
|
Phar::mapPhar('phpcpd-6.0.3.phar');
|
|||
|
|
|||
|
if (isset($_SERVER['argv'][1]) && $_SERVER['argv'][1] == '--manifest') {
|
|||
|
print file_get_contents(__PHPCPD_PHAR_ROOT__ . '/manifest.txt');
|
|||
|
exit;
|
|||
|
}
|
|||
|
|
|||
|
exit((new \SebastianBergmann\PHPCPD\Application)->run($_SERVER['argv']));
|
|||
|
|
|||
|
__HALT_COMPILER(); ?>
|
|||
|
$phpcpd-6.0.3.pharphpunit-php-timer/Timer.php<68><17><>_<EFBFBD><13><0F>*phpunit-php-timer/exceptions/Exception.phpe<17><>_eѷtӤ7phpunit-php-timer/exceptions/NoActiveTimerException.php<68><17><>_<EFBFBD>Ɔ<><C686><EFBFBD>Mphpunit-php-timer/exceptions/TimeSinceStartOfRequestNotAvailableException.php<68><17><>_<EFBFBD><00>N<><4E>phpunit-php-timer/Duration.phpv
|
|||
|
<17><>_v
|
|||
|
<00>^Τ,phpunit-php-timer/ResourceUsageFormatter.php_<17><>__<08><>'<27>phpunit-php-timer/LICENSE<17><>_x<><78><EFBFBD><EFBFBD>src/CLI/Arguments.php`
|
|||
|
<17><>_`
|
|||
|
<00>`Ƙ<>src/CLI/ArgumentsBuilder.phpV<17><>_V<00>!<21>Ťsrc/CLI/Application.phpG<17><>_GS45<34><35>src/CodeCloneMap.php<68><17><>_<EFBFBD><00>
|
|||
|
A<EFBFBD><EFBFBD>src/CodeCloneFile.php<68><17><>_<EFBFBD>8B <04>src/Log/AbstractXmlLogger.phpR
|
|||
|
<17><>_R
|
|||
|
<00><><EFBFBD>Ӥsrc/Log/PMD.php)<17><>_)<0C>_<EFBFBD><5F>src/Log/Text.php<68><17><>_<EFBFBD>P<><50><EFBFBD>,src/Exceptions/ArgumentsBuilderException.php<68><17><>_<EFBFBD>ﮆ<><EFAE86>src/Exceptions/Exception.phpu<17><>_u<00>]<5D>Ѥ*src/Detector/Strategy/AbstractStrategy.php<17><>_?
|
|||
|
B<>)src/Detector/Strategy/DefaultStrategy.php<68><17><>_<EFBFBD><00>v<EFBFBD>Y<EFBFBD>src/Detector/Detector.phpx<17><>_x9#<23>b<EFBFBD>src/CodeClone.php<68><17><>_<EFBFBD><00>L<18>src/CodeCloneMapIterator.php_<17><>__<00>=A<><41>manifest.txt<78><17><>_<EFBFBD>l<><15>-sebastian-cli-parser/exceptions/Exception.phpl<17><>_l@1<11><>Gsebastian-cli-parser/exceptions/OptionDoesNotAllowArgumentException.php<68><17><>_<EFBFBD>5<><35><EFBFBD><EFBFBD>:sebastian-cli-parser/exceptions/UnknownOptionException.phpz<17><>_z<00><><EFBFBD><EFBFBD><EFBFBD>Jsebastian-cli-parser/exceptions/RequiredOptionArgumentMissingException.php<68><17><>_<EFBFBD>w~<7E><><sebastian-cli-parser/exceptions/AmbiguousOptionException.php<68><17><>_<EFBFBD><00><>=<3D><>sebastian-cli-parser/Parser.php<68><17><>_<EFBFBD>s<>Vޤsebastian-cli-parser/LICENSE<17><>_<00><>u<>sebastian-version/Version.php<17><>_A<>z*<2A>sebastian-version/LICENSE<17><>_<00>Z<EFBFBD><5A><EFBFBD>%phpunit-php-file-iterator/Factory.php<68> <17><>_<EFBFBD> ַ<>"<22>&phpunit-php-file-iterator/Iterator.php<68><17><>_<EFBFBD>[4eo<65>$phpunit-php-file-iterator/Facade.phpe
|
|||
|
<17><>_e
|
|||
|
<00><>ZҤ!phpunit-php-file-iterator/LICENSE<17><>_<00><>.<2E><?php declare(strict_types=1);
|
|||
|
/*
|
|||
|
* This file is part of phpunit/php-timer.
|
|||
|
*
|
|||
|
* (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\Timer;
|
|||
|
|
|||
|
use function array_pop;
|
|||
|
use function hrtime;
|
|||
|
|
|||
|
final class Timer
|
|||
|
{
|
|||
|
/**
|
|||
|
* @psalm-var list<float>
|
|||
|
*/
|
|||
|
private $startTimes = [];
|
|||
|
|
|||
|
public function start(): void
|
|||
|
{
|
|||
|
$this->startTimes[] = (float) hrtime(true);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* @throws NoActiveTimerException
|
|||
|
*/
|
|||
|
public function stop(): Duration
|
|||
|
{
|
|||
|
if (empty($this->startTimes)) {
|
|||
|
throw new NoActiveTimerException(
|
|||
|
'Timer::start() has to be called before Timer::stop()'
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
return Duration::fromNanoseconds((float) hrtime(true) - array_pop($this->startTimes));
|
|||
|
}
|
|||
|
}
|
|||
|
<?php declare(strict_types=1);
|
|||
|
/*
|
|||
|
* This file is part of phpunit/php-timer.
|
|||
|
*
|
|||
|
* (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\Timer;
|
|||
|
|
|||
|
use Throwable;
|
|||
|
|
|||
|
interface Exception extends Throwable
|
|||
|
{
|
|||
|
}
|
|||
|
<?php declare(strict_types=1);
|
|||
|
/*
|
|||
|
* This file is part of phpunit/php-timer.
|
|||
|
*
|
|||
|
* (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\Timer;
|
|||
|
|
|||
|
use LogicException;
|
|||
|
|
|||
|
final class NoActiveTimerException extends LogicException implements Exception
|
|||
|
{
|
|||
|
}
|
|||
|
<?php declare(strict_types=1);
|
|||
|
/*
|
|||
|
* This file is part of phpunit/php-timer.
|
|||
|
*
|
|||
|
* (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\Timer;
|
|||
|
|
|||
|
use RuntimeException;
|
|||
|
|
|||
|
final class TimeSinceStartOfRequestNotAvailableException extends RuntimeException implements Exception
|
|||
|
{
|
|||
|
}
|
|||
|
<?php declare(strict_types=1);
|
|||
|
/*
|
|||
|
* This file is part of phpunit/php-timer.
|
|||
|
*
|
|||
|
* (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\Timer;
|
|||
|
|
|||
|
use function floor;
|
|||
|
use function sprintf;
|
|||
|
|
|||
|
/**
|
|||
|
* @psalm-immutable
|
|||
|
*/
|
|||
|
final class Duration
|
|||
|
{
|
|||
|
/**
|
|||
|
* @var float
|
|||
|
*/
|
|||
|
private $nanoseconds;
|
|||
|
|
|||
|
/**
|
|||
|
* @var int
|
|||
|
*/
|
|||
|
private $hours;
|
|||
|
|
|||
|
/**
|
|||
|
* @var int
|
|||
|
*/
|
|||
|
private $minutes;
|
|||
|
|
|||
|
/**
|
|||
|
* @var int
|
|||
|
*/
|
|||
|
private $seconds;
|
|||
|
|
|||
|
/**
|
|||
|
* @var int
|
|||
|
*/
|
|||
|
private $milliseconds;
|
|||
|
|
|||
|
public static function fromMicroseconds(float $microseconds): self
|
|||
|
{
|
|||
|
return new self($microseconds * 1000);
|
|||
|
}
|
|||
|
|
|||
|
public static function fromNanoseconds(float $nanoseconds): self
|
|||
|
{
|
|||
|
return new self($nanoseconds);
|
|||
|
}
|
|||
|
|
|||
|
private function __construct(float $nanoseconds)
|
|||
|
{
|
|||
|
$this->nanoseconds = $nanoseconds;
|
|||
|
$timeInMilliseconds = $nanoseconds / 1000000;
|
|||
|
$hours = floor($timeInMilliseconds / 60 / 60 / 1000);
|
|||
|
$hoursInMilliseconds = $hours * 60 * 60 * 1000;
|
|||
|
$minutes = floor($timeInMilliseconds / 60 / 1000) % 60;
|
|||
|
$minutesInMilliseconds = $minutes * 60 * 1000;
|
|||
|
$seconds = floor(($timeInMilliseconds - $hoursInMilliseconds - $minutesInMilliseconds) / 1000);
|
|||
|
$secondsInMilliseconds = $seconds * 1000;
|
|||
|
$milliseconds = $timeInMilliseconds - $hoursInMilliseconds - $minutesInMilliseconds - $secondsInMilliseconds;
|
|||
|
$this->hours = (int) $hours;
|
|||
|
$this->minutes = $minutes;
|
|||
|
$this->seconds = (int) $seconds;
|
|||
|
$this->milliseconds = (int) $milliseconds;
|
|||
|
}
|
|||
|
|
|||
|
public function asNanoseconds(): float
|
|||
|
{
|
|||
|
return $this->nanoseconds;
|
|||
|
}
|
|||
|
|
|||
|
public function asMicroseconds(): float
|
|||
|
{
|
|||
|
return $this->nanoseconds / 1000;
|
|||
|
}
|
|||
|
|
|||
|
public function asMilliseconds(): float
|
|||
|
{
|
|||
|
return $this->nanoseconds / 1000000;
|
|||
|
}
|
|||
|
|
|||
|
public function asSeconds(): float
|
|||
|
{
|
|||
|
return $this->nanoseconds / 1000000000;
|
|||
|
}
|
|||
|
|
|||
|
public function asString(): string
|
|||
|
{
|
|||
|
$result = '';
|
|||
|
|
|||
|
if ($this->hours > 0) {
|
|||
|
$result = sprintf('%02d', $this->hours) . ':';
|
|||
|
}
|
|||
|
|
|||
|
$result .= sprintf('%02d', $this->minutes) . ':';
|
|||
|
$result .= sprintf('%02d', $this->seconds);
|
|||
|
|
|||
|
if ($this->milliseconds > 0) {
|
|||
|
$result .= '.' . sprintf('%03d', $this->milliseconds);
|
|||
|
}
|
|||
|
|
|||
|
return $result;
|
|||
|
}
|
|||
|
}
|
|||
|
<?php declare(strict_types=1);
|
|||
|
/*
|
|||
|
* This file is part of phpunit/php-timer.
|
|||
|
*
|
|||
|
* (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\Timer;
|
|||
|
|
|||
|
use function is_float;
|
|||
|
use function memory_get_peak_usage;
|
|||
|
use function microtime;
|
|||
|
use function sprintf;
|
|||
|
|
|||
|
final class ResourceUsageFormatter
|
|||
|
{
|
|||
|
/**
|
|||
|
* @psalm-var array<string,int>
|
|||
|
*/
|
|||
|
private const SIZES = [
|
|||
|
'GB' => 1073741824,
|
|||
|
'MB' => 1048576,
|
|||
|
'KB' => 1024,
|
|||
|
];
|
|||
|
|
|||
|
public function resourceUsage(Duration $duration): string
|
|||
|
{
|
|||
|
return sprintf(
|
|||
|
'Time: %s, Memory: %s',
|
|||
|
$duration->asString(),
|
|||
|
$this->bytesToString(memory_get_peak_usage(true))
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* @throws TimeSinceStartOfRequestNotAvailableException
|
|||
|
*/
|
|||
|
public function resourceUsageSinceStartOfRequest(): string
|
|||
|
{
|
|||
|
if (!isset($_SERVER['REQUEST_TIME_FLOAT'])) {
|
|||
|
throw new TimeSinceStartOfRequestNotAvailableException(
|
|||
|
'Cannot determine time at which the request started because $_SERVER[\'REQUEST_TIME_FLOAT\'] is not available'
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
if (!is_float($_SERVER['REQUEST_TIME_FLOAT'])) {
|
|||
|
throw new TimeSinceStartOfRequestNotAvailableException(
|
|||
|
'Cannot determine time at which the request started because $_SERVER[\'REQUEST_TIME_FLOAT\'] is not of type float'
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
return $this->resourceUsage(
|
|||
|
Duration::fromMicroseconds(
|
|||
|
(1000000 * (microtime(true) - $_SERVER['REQUEST_TIME_FLOAT']))
|
|||
|
)
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
private function bytesToString(int $bytes): string
|
|||
|
{
|
|||
|
foreach (self::SIZES as $unit => $value) {
|
|||
|
if ($bytes >= $value) {
|
|||
|
return sprintf('%.2f %s', $bytes >= 1024 ? $bytes / $value : $bytes, $unit);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// @codeCoverageIgnoreStart
|
|||
|
return $bytes . ' byte' . ($bytes !== 1 ? 's' : '');
|
|||
|
// @codeCoverageIgnoreEnd
|
|||
|
}
|
|||
|
}
|
|||
|
phpunit/php-timer
|
|||
|
|
|||
|
Copyright (c) 2010-2020, Sebastian Bergmann <sebastian@phpunit.de>.
|
|||
|
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.
|
|||
|
<?php declare(strict_types=1);
|
|||
|
/*
|
|||
|
* This file is part of PHP Copy/Paste Detector (PHPCPD).
|
|||
|
*
|
|||
|
* (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\PHPCPD;
|
|||
|
|
|||
|
final class Arguments
|
|||
|
{
|
|||
|
/**
|
|||
|
* @psalm-var list<string>
|
|||
|
*/
|
|||
|
private $directories;
|
|||
|
|
|||
|
/**
|
|||
|
* @psalm-var list<string>
|
|||
|
*/
|
|||
|
private $suffixes;
|
|||
|
|
|||
|
/**
|
|||
|
* @psalm-var list<string>
|
|||
|
*/
|
|||
|
private $exclude;
|
|||
|
|
|||
|
/**
|
|||
|
* @var ?string
|
|||
|
*/
|
|||
|
private $pmdCpdXmlLogfile;
|
|||
|
|
|||
|
/**
|
|||
|
* @var int
|
|||
|
*/
|
|||
|
private $linesThreshold;
|
|||
|
|
|||
|
/**
|
|||
|
* @var int
|
|||
|
*/
|
|||
|
private $tokensThreshold;
|
|||
|
|
|||
|
/**
|
|||
|
* @var bool
|
|||
|
*/
|
|||
|
private $fuzzy;
|
|||
|
|
|||
|
/**
|
|||
|
* @var bool
|
|||
|
*/
|
|||
|
private $verbose;
|
|||
|
|
|||
|
/**
|
|||
|
* @var bool
|
|||
|
*/
|
|||
|
private $help;
|
|||
|
|
|||
|
/**
|
|||
|
* @var bool
|
|||
|
*/
|
|||
|
private $version;
|
|||
|
|
|||
|
public function __construct(array $directories, array $suffixes, array $exclude, ?string $pmdCpdXmlLogfile, int $linesThreshold, int $tokensThreshold, bool $fuzzy, bool $verbose, bool $help, bool $version)
|
|||
|
{
|
|||
|
$this->directories = $directories;
|
|||
|
$this->suffixes = $suffixes;
|
|||
|
$this->exclude = $exclude;
|
|||
|
$this->pmdCpdXmlLogfile = $pmdCpdXmlLogfile;
|
|||
|
$this->linesThreshold = $linesThreshold;
|
|||
|
$this->tokensThreshold = $tokensThreshold;
|
|||
|
$this->fuzzy = $fuzzy;
|
|||
|
$this->verbose = $verbose;
|
|||
|
$this->help = $help;
|
|||
|
$this->version = $version;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* @psalm-return list<string>
|
|||
|
*/
|
|||
|
public function directories(): array
|
|||
|
{
|
|||
|
return $this->directories;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* @psalm-return list<string>
|
|||
|
*/
|
|||
|
public function suffixes(): array
|
|||
|
{
|
|||
|
return $this->suffixes;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* @psalm-return list<string>
|
|||
|
*/
|
|||
|
public function exclude(): array
|
|||
|
{
|
|||
|
return $this->exclude;
|
|||
|
}
|
|||
|
|
|||
|
public function pmdCpdXmlLogfile(): ?string
|
|||
|
{
|
|||
|
return $this->pmdCpdXmlLogfile;
|
|||
|
}
|
|||
|
|
|||
|
public function linesThreshold(): int
|
|||
|
{
|
|||
|
return $this->linesThreshold;
|
|||
|
}
|
|||
|
|
|||
|
public function tokensThreshold(): int
|
|||
|
{
|
|||
|
return $this->tokensThreshold;
|
|||
|
}
|
|||
|
|
|||
|
public function fuzzy(): bool
|
|||
|
{
|
|||
|
return $this->fuzzy;
|
|||
|
}
|
|||
|
|
|||
|
public function verbose(): bool
|
|||
|
{
|
|||
|
return $this->verbose;
|
|||
|
}
|
|||
|
|
|||
|
public function help(): bool
|
|||
|
{
|
|||
|
return $this->help;
|
|||
|
}
|
|||
|
|
|||
|
public function version(): bool
|
|||
|
{
|
|||
|
return $this->version;
|
|||
|
}
|
|||
|
}
|
|||
|
<?php declare(strict_types=1);
|
|||
|
/*
|
|||
|
* This file is part of PHP Copy/Paste Detector (PHPCPD).
|
|||
|
*
|
|||
|
* (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\PHPCPD;
|
|||
|
|
|||
|
use SebastianBergmann\CliParser\Exception as CliParserException;
|
|||
|
use SebastianBergmann\CliParser\Parser as CliParser;
|
|||
|
|
|||
|
final class ArgumentsBuilder
|
|||
|
{
|
|||
|
/**
|
|||
|
* @throws ArgumentsBuilderException
|
|||
|
*/
|
|||
|
public function build(array $argv): Arguments
|
|||
|
{
|
|||
|
try {
|
|||
|
$options = (new CliParser)->parse(
|
|||
|
$argv,
|
|||
|
'hv',
|
|||
|
[
|
|||
|
'suffix=',
|
|||
|
'exclude=',
|
|||
|
'log-pmd=',
|
|||
|
'fuzzy',
|
|||
|
'min-lines=',
|
|||
|
'min-tokens=',
|
|||
|
'verbose',
|
|||
|
'help',
|
|||
|
'version',
|
|||
|
]
|
|||
|
);
|
|||
|
} catch (CliParserException $e) {
|
|||
|
throw new ArgumentsBuilderException(
|
|||
|
$e->getMessage(),
|
|||
|
(int) $e->getCode(),
|
|||
|
$e
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
$directories = $options[1];
|
|||
|
$exclude = [];
|
|||
|
$suffixes = ['.php'];
|
|||
|
$pmdCpdXmlLogfile = null;
|
|||
|
$linesThreshold = 5;
|
|||
|
$tokensThreshold = 70;
|
|||
|
$fuzzy = false;
|
|||
|
$verbose = false;
|
|||
|
$help = false;
|
|||
|
$version = false;
|
|||
|
|
|||
|
foreach ($options[0] as $option) {
|
|||
|
switch ($option[0]) {
|
|||
|
case '--suffix':
|
|||
|
$suffixes[] = $option[1];
|
|||
|
|
|||
|
break;
|
|||
|
|
|||
|
case '--exclude':
|
|||
|
$exclude[] = $option[1];
|
|||
|
|
|||
|
break;
|
|||
|
|
|||
|
case '--log-pmd':
|
|||
|
$pmdCpdXmlLogfile = $option[1];
|
|||
|
|
|||
|
break;
|
|||
|
|
|||
|
case '--fuzzy':
|
|||
|
$fuzzy = true;
|
|||
|
|
|||
|
break;
|
|||
|
|
|||
|
case '--min-lines':
|
|||
|
$linesThreshold = (int) $option[1];
|
|||
|
|
|||
|
break;
|
|||
|
|
|||
|
case '--min-tokens':
|
|||
|
$tokensThreshold = (int) $option[1];
|
|||
|
|
|||
|
break;
|
|||
|
|
|||
|
case '--verbose':
|
|||
|
$verbose = true;
|
|||
|
|
|||
|
break;
|
|||
|
|
|||
|
case 'h':
|
|||
|
case '--help':
|
|||
|
$help = true;
|
|||
|
|
|||
|
break;
|
|||
|
|
|||
|
case 'v':
|
|||
|
case '--version':
|
|||
|
$version = true;
|
|||
|
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (empty($options[1]) && !$help && !$version) {
|
|||
|
throw new ArgumentsBuilderException(
|
|||
|
'No directory specified'
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
return new Arguments(
|
|||
|
$directories,
|
|||
|
$suffixes,
|
|||
|
$exclude,
|
|||
|
$pmdCpdXmlLogfile,
|
|||
|
$linesThreshold,
|
|||
|
$tokensThreshold,
|
|||
|
$fuzzy,
|
|||
|
$verbose,
|
|||
|
$help,
|
|||
|
$version,
|
|||
|
);
|
|||
|
}
|
|||
|
}
|
|||
|
<?php declare(strict_types=1);
|
|||
|
/*
|
|||
|
* This file is part of PHP Copy/Paste Detector (PHPCPD).
|
|||
|
*
|
|||
|
* (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\PHPCPD;
|
|||
|
|
|||
|
use const PHP_EOL;
|
|||
|
use function count;
|
|||
|
use function printf;
|
|||
|
use SebastianBergmann\FileIterator\Facade;
|
|||
|
use SebastianBergmann\PHPCPD\Detector\Detector;
|
|||
|
use SebastianBergmann\PHPCPD\Detector\Strategy\DefaultStrategy;
|
|||
|
use SebastianBergmann\PHPCPD\Log\PMD;
|
|||
|
use SebastianBergmann\PHPCPD\Log\Text;
|
|||
|
use SebastianBergmann\Timer\ResourceUsageFormatter;
|
|||
|
use SebastianBergmann\Timer\Timer;
|
|||
|
use SebastianBergmann\Version;
|
|||
|
|
|||
|
final class Application
|
|||
|
{
|
|||
|
private const VERSION = '6.0.3';
|
|||
|
|
|||
|
public function run(array $argv): int
|
|||
|
{
|
|||
|
$this->printVersion();
|
|||
|
|
|||
|
try {
|
|||
|
$arguments = (new ArgumentsBuilder)->build($argv);
|
|||
|
} catch (Exception $e) {
|
|||
|
print PHP_EOL . $e->getMessage() . PHP_EOL;
|
|||
|
|
|||
|
return 1;
|
|||
|
}
|
|||
|
|
|||
|
if ($arguments->version()) {
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
print PHP_EOL;
|
|||
|
|
|||
|
if ($arguments->help()) {
|
|||
|
$this->help();
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
$files = (new Facade)->getFilesAsArray(
|
|||
|
$arguments->directories(),
|
|||
|
$arguments->suffixes(),
|
|||
|
'',
|
|||
|
$arguments->exclude()
|
|||
|
);
|
|||
|
|
|||
|
if (empty($files)) {
|
|||
|
print 'No files found to scan' . PHP_EOL;
|
|||
|
|
|||
|
return 1;
|
|||
|
}
|
|||
|
|
|||
|
$strategy = new DefaultStrategy;
|
|||
|
|
|||
|
$timer = new Timer;
|
|||
|
$timer->start();
|
|||
|
|
|||
|
$clones = (new Detector($strategy))->copyPasteDetection(
|
|||
|
$files,
|
|||
|
$arguments->linesThreshold(),
|
|||
|
$arguments->tokensThreshold(),
|
|||
|
$arguments->fuzzy()
|
|||
|
);
|
|||
|
|
|||
|
(new Text)->printResult($clones, $arguments->verbose());
|
|||
|
|
|||
|
if ($arguments->pmdCpdXmlLogfile()) {
|
|||
|
(new PMD($arguments->pmdCpdXmlLogfile()))->processClones($clones);
|
|||
|
}
|
|||
|
|
|||
|
print (new ResourceUsageFormatter)->resourceUsage($timer->stop()) . PHP_EOL;
|
|||
|
|
|||
|
return count($clones) > 0 ? 1 : 0;
|
|||
|
}
|
|||
|
|
|||
|
private function printVersion(): void
|
|||
|
{
|
|||
|
printf(
|
|||
|
'phpcpd %s by Sebastian Bergmann.' . PHP_EOL,
|
|||
|
(new Version(self::VERSION, dirname(__DIR__)))->getVersion()
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
private function help(): void
|
|||
|
{
|
|||
|
print <<<'EOT'
|
|||
|
Usage:
|
|||
|
phpcpd [options] <directory>
|
|||
|
|
|||
|
Options for selecting files:
|
|||
|
|
|||
|
--suffix <suffix> Include files with names ending in <suffix> in the analysis
|
|||
|
(default: .php; can be given multiple times)
|
|||
|
--exclude <path> Exclude files with <path> in their path from the analysis
|
|||
|
(can be given multiple times)
|
|||
|
|
|||
|
Options for analysing files:
|
|||
|
|
|||
|
--fuzzy Fuzz variable names
|
|||
|
--min-lines <N> Minimum number of identical lines (default: 5)
|
|||
|
--min-tokens <N> Minimum number of identical tokens (default: 70)
|
|||
|
|
|||
|
Options for report generation:
|
|||
|
|
|||
|
--log-pmd <file> Write log in PMD-CPD XML format to <file>
|
|||
|
|
|||
|
EOT;
|
|||
|
}
|
|||
|
}
|
|||
|
<?php declare(strict_types=1);
|
|||
|
/*
|
|||
|
* This file is part of PHP Copy/Paste Detector (PHPCPD).
|
|||
|
*
|
|||
|
* (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\PHPCPD;
|
|||
|
|
|||
|
use function count;
|
|||
|
use function max;
|
|||
|
use function sprintf;
|
|||
|
use Countable;
|
|||
|
use IteratorAggregate;
|
|||
|
|
|||
|
final class CodeCloneMap implements Countable, IteratorAggregate
|
|||
|
{
|
|||
|
/**
|
|||
|
* @var CodeClone[]
|
|||
|
*/
|
|||
|
private $clones = [];
|
|||
|
|
|||
|
/**
|
|||
|
* @var CodeClone[]
|
|||
|
*/
|
|||
|
private $clonesById = [];
|
|||
|
|
|||
|
/**
|
|||
|
* @var int
|
|||
|
*/
|
|||
|
private $numberOfDuplicatedLines = 0;
|
|||
|
|
|||
|
/**
|
|||
|
* @var int
|
|||
|
*/
|
|||
|
private $numberOfLines = 0;
|
|||
|
|
|||
|
/**
|
|||
|
* @var int
|
|||
|
*/
|
|||
|
private $largestCloneSize = 0;
|
|||
|
|
|||
|
/**
|
|||
|
* @var array
|
|||
|
*/
|
|||
|
private $filesWithClones = [];
|
|||
|
|
|||
|
public function add(CodeClone $clone): void
|
|||
|
{
|
|||
|
$id = $clone->id();
|
|||
|
|
|||
|
if (!isset($this->clonesById[$id])) {
|
|||
|
$this->clones[] = $clone;
|
|||
|
$this->clonesById[$id] = $clone;
|
|||
|
} else {
|
|||
|
$existClone = $this->clonesById[$id];
|
|||
|
|
|||
|
foreach ($clone->files() as $file) {
|
|||
|
$existClone->add($file);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
$this->numberOfDuplicatedLines += $clone->numberOfLines() * (count($clone->files()) - 1);
|
|||
|
|
|||
|
foreach ($clone->files() as $file) {
|
|||
|
if (!isset($this->filesWithClones[$file->name()])) {
|
|||
|
$this->filesWithClones[$file->name()] = true;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
$this->largestCloneSize = max($this->largestCloneSize, $clone->numberOfLines());
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* @return CodeClone[]
|
|||
|
*/
|
|||
|
public function clones(): array
|
|||
|
{
|
|||
|
return $this->clones;
|
|||
|
}
|
|||
|
|
|||
|
public function percentage(): string
|
|||
|
{
|
|||
|
if ($this->numberOfLines > 0) {
|
|||
|
$percent = ($this->numberOfDuplicatedLines / $this->numberOfLines) * 100;
|
|||
|
} else {
|
|||
|
$percent = 100;
|
|||
|
}
|
|||
|
|
|||
|
return sprintf('%01.2F%%', $percent);
|
|||
|
}
|
|||
|
|
|||
|
public function numberOfLines(): int
|
|||
|
{
|
|||
|
return $this->numberOfLines;
|
|||
|
}
|
|||
|
|
|||
|
public function addToNumberOfLines(int $numberOfLines): void
|
|||
|
{
|
|||
|
$this->numberOfLines += $numberOfLines;
|
|||
|
}
|
|||
|
|
|||
|
public function count(): int
|
|||
|
{
|
|||
|
return count($this->clones);
|
|||
|
}
|
|||
|
|
|||
|
public function numberOfFilesWithClones(): int
|
|||
|
{
|
|||
|
return count($this->filesWithClones);
|
|||
|
}
|
|||
|
|
|||
|
public function numberOfDuplicatedLines(): int
|
|||
|
{
|
|||
|
return $this->numberOfDuplicatedLines;
|
|||
|
}
|
|||
|
|
|||
|
public function getIterator(): CodeCloneMapIterator
|
|||
|
{
|
|||
|
return new CodeCloneMapIterator($this);
|
|||
|
}
|
|||
|
|
|||
|
public function isEmpty(): bool
|
|||
|
{
|
|||
|
return empty($this->clones);
|
|||
|
}
|
|||
|
|
|||
|
public function averageSize(): float
|
|||
|
{
|
|||
|
return $this->numberOfDuplicatedLines() / $this->count();
|
|||
|
}
|
|||
|
|
|||
|
public function largestSize(): int
|
|||
|
{
|
|||
|
return $this->largestCloneSize;
|
|||
|
}
|
|||
|
}
|
|||
|
<?php declare(strict_types=1);
|
|||
|
/*
|
|||
|
* This file is part of PHP Copy/Paste Detector (PHPCPD).
|
|||
|
*
|
|||
|
* (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\PHPCPD;
|
|||
|
|
|||
|
final class CodeCloneFile
|
|||
|
{
|
|||
|
/**
|
|||
|
* @var string
|
|||
|
*/
|
|||
|
private $id;
|
|||
|
|
|||
|
/**
|
|||
|
* @var string
|
|||
|
*/
|
|||
|
private $name;
|
|||
|
|
|||
|
/**
|
|||
|
* @var int
|
|||
|
*/
|
|||
|
private $startLine;
|
|||
|
|
|||
|
public function __construct(string $name, int $startLine)
|
|||
|
{
|
|||
|
$this->name = $name;
|
|||
|
$this->startLine = $startLine;
|
|||
|
$this->id = $this->name . ':' . $this->startLine;
|
|||
|
}
|
|||
|
|
|||
|
public function id(): string
|
|||
|
{
|
|||
|
return $this->id;
|
|||
|
}
|
|||
|
|
|||
|
public function name(): string
|
|||
|
{
|
|||
|
return $this->name;
|
|||
|
}
|
|||
|
|
|||
|
public function startLine(): int
|
|||
|
{
|
|||
|
return $this->startLine;
|
|||
|
}
|
|||
|
}
|
|||
|
<?php declare(strict_types=1);
|
|||
|
/*
|
|||
|
* This file is part of PHP Copy/Paste Detector (PHPCPD).
|
|||
|
*
|
|||
|
* (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\PHPCPD\Log;
|
|||
|
|
|||
|
use const ENT_COMPAT;
|
|||
|
use function file_put_contents;
|
|||
|
use function function_exists;
|
|||
|
use function htmlspecialchars;
|
|||
|
use function mb_convert_encoding;
|
|||
|
use function ord;
|
|||
|
use function preg_replace;
|
|||
|
use function strlen;
|
|||
|
use function utf8_encode;
|
|||
|
use DOMDocument;
|
|||
|
use SebastianBergmann\PHPCPD\CodeCloneMap;
|
|||
|
|
|||
|
abstract class AbstractXmlLogger
|
|||
|
{
|
|||
|
/**
|
|||
|
* @var DOMDocument
|
|||
|
*/
|
|||
|
protected $document;
|
|||
|
|
|||
|
/**
|
|||
|
* @var string
|
|||
|
*/
|
|||
|
private $filename;
|
|||
|
|
|||
|
public function __construct(string $filename)
|
|||
|
{
|
|||
|
$this->document = new DOMDocument('1.0', 'UTF-8');
|
|||
|
$this->document->formatOutput = true;
|
|||
|
|
|||
|
$this->filename = $filename;
|
|||
|
}
|
|||
|
|
|||
|
abstract public function processClones(CodeCloneMap $clones);
|
|||
|
|
|||
|
protected function flush(): void
|
|||
|
{
|
|||
|
file_put_contents($this->filename, $this->document->saveXML());
|
|||
|
}
|
|||
|
|
|||
|
protected function convertToUtf8(string $string): string
|
|||
|
{
|
|||
|
if (!$this->isUtf8($string)) {
|
|||
|
if (function_exists('mb_convert_encoding')) {
|
|||
|
$string = mb_convert_encoding($string, 'UTF-8');
|
|||
|
} else {
|
|||
|
$string = utf8_encode($string);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return $string;
|
|||
|
}
|
|||
|
|
|||
|
protected function isUtf8(string $string): bool
|
|||
|
{
|
|||
|
$length = strlen($string);
|
|||
|
|
|||
|
for ($i = 0; $i < $length; $i++) {
|
|||
|
if (ord($string[$i]) < 0x80) {
|
|||
|
$n = 0;
|
|||
|
} elseif ((ord($string[$i]) & 0xE0) === 0xC0) {
|
|||
|
$n = 1;
|
|||
|
} elseif ((ord($string[$i]) & 0xF0) === 0xE0) {
|
|||
|
$n = 2;
|
|||
|
} elseif ((ord($string[$i]) & 0xF0) === 0xF0) {
|
|||
|
$n = 3;
|
|||
|
} else {
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
for ($j = 0; $j < $n; $j++) {
|
|||
|
if ((++$i === $length) || ((ord($string[$i]) & 0xC0) !== 0x80)) {
|
|||
|
return false;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
protected function escapeForXml(string $string): string
|
|||
|
{
|
|||
|
$string = $this->convertToUtf8($string);
|
|||
|
|
|||
|
$string = preg_replace(
|
|||
|
'/[^\x09\x0A\x0D\x{0020}-\x{D7FF}\x{E000}-\x{FFFD}]/u',
|
|||
|
"\xEF\xBF\xBD",
|
|||
|
$string
|
|||
|
);
|
|||
|
|
|||
|
return htmlspecialchars($string, ENT_COMPAT, 'UTF-8');
|
|||
|
}
|
|||
|
}
|
|||
|
<?php declare(strict_types=1);
|
|||
|
/*
|
|||
|
* This file is part of PHP Copy/Paste Detector (PHPCPD).
|
|||
|
*
|
|||
|
* (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\PHPCPD\Log;
|
|||
|
|
|||
|
use SebastianBergmann\PHPCPD\CodeCloneMap;
|
|||
|
|
|||
|
final class PMD extends AbstractXmlLogger
|
|||
|
{
|
|||
|
/** @noinspection UnusedFunctionResultInspection */
|
|||
|
public function processClones(CodeCloneMap $clones): void
|
|||
|
{
|
|||
|
$cpd = $this->document->createElement('pmd-cpd');
|
|||
|
|
|||
|
$this->document->appendChild($cpd);
|
|||
|
|
|||
|
foreach ($clones as $clone) {
|
|||
|
$duplication = $cpd->appendChild(
|
|||
|
$this->document->createElement('duplication')
|
|||
|
);
|
|||
|
|
|||
|
$duplication->setAttribute('lines', (string) $clone->numberOfLines());
|
|||
|
$duplication->setAttribute('tokens', (string) $clone->numberOfTokens());
|
|||
|
|
|||
|
foreach ($clone->files() as $codeCloneFile) {
|
|||
|
$file = $duplication->appendChild(
|
|||
|
$this->document->createElement('file')
|
|||
|
);
|
|||
|
|
|||
|
$file->setAttribute('path', $codeCloneFile->name());
|
|||
|
$file->setAttribute('line', (string) $codeCloneFile->startLine());
|
|||
|
}
|
|||
|
|
|||
|
$duplication->appendChild(
|
|||
|
$this->document->createElement(
|
|||
|
'codefragment',
|
|||
|
$this->escapeForXml($clone->lines())
|
|||
|
)
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
$this->flush();
|
|||
|
}
|
|||
|
}
|
|||
|
<?php declare(strict_types=1);
|
|||
|
/*
|
|||
|
* This file is part of PHP Copy/Paste Detector (PHPCPD).
|
|||
|
*
|
|||
|
* (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\PHPCPD\Log;
|
|||
|
|
|||
|
use const PHP_EOL;
|
|||
|
use function count;
|
|||
|
use function printf;
|
|||
|
use SebastianBergmann\PHPCPD\CodeCloneMap;
|
|||
|
|
|||
|
final class Text
|
|||
|
{
|
|||
|
public function printResult(CodeCloneMap $clones, bool $verbose): void
|
|||
|
{
|
|||
|
if (count($clones) > 0) {
|
|||
|
printf(
|
|||
|
'Found %d clones with %d duplicated lines in %d files:' . PHP_EOL . PHP_EOL,
|
|||
|
count($clones),
|
|||
|
$clones->numberOfDuplicatedLines(),
|
|||
|
$clones->numberOfFilesWithClones()
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
foreach ($clones as $clone) {
|
|||
|
$firstOccurrence = true;
|
|||
|
|
|||
|
foreach ($clone->files() as $file) {
|
|||
|
printf(
|
|||
|
' %s%s:%d-%d%s' . PHP_EOL,
|
|||
|
$firstOccurrence ? '- ' : ' ',
|
|||
|
$file->name(),
|
|||
|
$file->startLine(),
|
|||
|
$file->startLine() + $clone->numberOfLines(),
|
|||
|
$firstOccurrence ? ' (' . $clone->numberOfLines() . ' lines)' : ''
|
|||
|
);
|
|||
|
|
|||
|
$firstOccurrence = false;
|
|||
|
}
|
|||
|
|
|||
|
if ($verbose) {
|
|||
|
print PHP_EOL . $clone->lines(' ');
|
|||
|
}
|
|||
|
|
|||
|
print PHP_EOL;
|
|||
|
}
|
|||
|
|
|||
|
if ($clones->isEmpty()) {
|
|||
|
print 'No clones found.' . PHP_EOL . PHP_EOL;
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
printf(
|
|||
|
'%s duplicated lines out of %d total lines of code.' . PHP_EOL .
|
|||
|
'Average size of duplication is %d lines, largest clone has %d of lines' . PHP_EOL . PHP_EOL,
|
|||
|
$clones->percentage(),
|
|||
|
$clones->numberOfLines(),
|
|||
|
$clones->averageSize(),
|
|||
|
$clones->largestSize()
|
|||
|
);
|
|||
|
}
|
|||
|
}
|
|||
|
<?php declare(strict_types=1);
|
|||
|
/*
|
|||
|
* This file is part of PHP Copy/Paste Detector (PHPCPD).
|
|||
|
*
|
|||
|
* (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\PHPCPD;
|
|||
|
|
|||
|
use RuntimeException;
|
|||
|
|
|||
|
final class ArgumentsBuilderException extends RuntimeException implements Exception
|
|||
|
{
|
|||
|
}
|
|||
|
<?php declare(strict_types=1);
|
|||
|
/*
|
|||
|
* This file is part of PHP Copy/Paste Detector (PHPCPD).
|
|||
|
*
|
|||
|
* (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\PHPCPD;
|
|||
|
|
|||
|
use Throwable;
|
|||
|
|
|||
|
interface Exception extends Throwable
|
|||
|
{
|
|||
|
}
|
|||
|
<?php declare(strict_types=1);
|
|||
|
/*
|
|||
|
* This file is part of PHP Copy/Paste Detector (PHPCPD).
|
|||
|
*
|
|||
|
* (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\PHPCPD\Detector\Strategy;
|
|||
|
|
|||
|
use const T_CLOSE_TAG;
|
|||
|
use const T_COMMENT;
|
|||
|
use const T_DOC_COMMENT;
|
|||
|
use const T_INLINE_HTML;
|
|||
|
use const T_NS_SEPARATOR;
|
|||
|
use const T_OPEN_TAG;
|
|||
|
use const T_OPEN_TAG_WITH_ECHO;
|
|||
|
use const T_USE;
|
|||
|
use const T_WHITESPACE;
|
|||
|
use SebastianBergmann\PHPCPD\CodeCloneMap;
|
|||
|
|
|||
|
abstract class AbstractStrategy
|
|||
|
{
|
|||
|
/**
|
|||
|
* @psalm-var array<int,true>
|
|||
|
*/
|
|||
|
protected $tokensIgnoreList = [
|
|||
|
T_INLINE_HTML => true,
|
|||
|
T_COMMENT => true,
|
|||
|
T_DOC_COMMENT => true,
|
|||
|
T_OPEN_TAG => true,
|
|||
|
T_OPEN_TAG_WITH_ECHO => true,
|
|||
|
T_CLOSE_TAG => true,
|
|||
|
T_WHITESPACE => true,
|
|||
|
T_USE => true,
|
|||
|
T_NS_SEPARATOR => true,
|
|||
|
];
|
|||
|
|
|||
|
/**
|
|||
|
* @psalm-var array<string,array{0: string, 1: int}>
|
|||
|
*/
|
|||
|
protected $hashes = [];
|
|||
|
|
|||
|
abstract public function processFile(string $file, int $minLines, int $minTokens, CodeCloneMap $result, bool $fuzzy = false): void;
|
|||
|
}
|
|||
|
<?php declare(strict_types=1);
|
|||
|
/*
|
|||
|
* This file is part of PHP Copy/Paste Detector (PHPCPD).
|
|||
|
*
|
|||
|
* (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\PHPCPD\Detector\Strategy;
|
|||
|
|
|||
|
use const T_VARIABLE;
|
|||
|
use function array_keys;
|
|||
|
use function chr;
|
|||
|
use function count;
|
|||
|
use function crc32;
|
|||
|
use function file_get_contents;
|
|||
|
use function is_array;
|
|||
|
use function md5;
|
|||
|
use function pack;
|
|||
|
use function substr;
|
|||
|
use function substr_count;
|
|||
|
use function token_get_all;
|
|||
|
use SebastianBergmann\PHPCPD\CodeClone;
|
|||
|
use SebastianBergmann\PHPCPD\CodeCloneFile;
|
|||
|
use SebastianBergmann\PHPCPD\CodeCloneMap;
|
|||
|
|
|||
|
final class DefaultStrategy extends AbstractStrategy
|
|||
|
{
|
|||
|
public function processFile(string $file, int $minLines, int $minTokens, CodeCloneMap $result, bool $fuzzy = false): void
|
|||
|
{
|
|||
|
$buffer = file_get_contents($file);
|
|||
|
$currentTokenPositions = [];
|
|||
|
$currentTokenRealPositions = [];
|
|||
|
$currentSignature = '';
|
|||
|
$tokens = token_get_all($buffer);
|
|||
|
$tokenNr = 0;
|
|||
|
$lastTokenLine = 0;
|
|||
|
|
|||
|
$result->addToNumberOfLines(substr_count($buffer, "\n"));
|
|||
|
|
|||
|
unset($buffer);
|
|||
|
|
|||
|
foreach (array_keys($tokens) as $key) {
|
|||
|
$token = $tokens[$key];
|
|||
|
|
|||
|
if (is_array($token)) {
|
|||
|
if (!isset($this->tokensIgnoreList[$token[0]])) {
|
|||
|
if ($tokenNr === 0) {
|
|||
|
$currentTokenPositions[$tokenNr] = $token[2] - $lastTokenLine;
|
|||
|
} else {
|
|||
|
$currentTokenPositions[$tokenNr] = $currentTokenPositions[$tokenNr - 1] +
|
|||
|
$token[2] - $lastTokenLine;
|
|||
|
}
|
|||
|
|
|||
|
$currentTokenRealPositions[$tokenNr++] = $token[2];
|
|||
|
|
|||
|
if ($fuzzy && $token[0] === T_VARIABLE) {
|
|||
|
$token[1] = 'variable';
|
|||
|
}
|
|||
|
|
|||
|
$currentSignature .= chr($token[0] & 255) .
|
|||
|
pack('N*', crc32($token[1]));
|
|||
|
}
|
|||
|
|
|||
|
$lastTokenLine = $token[2];
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
$count = count($currentTokenPositions);
|
|||
|
$firstLine = 0;
|
|||
|
$firstRealLine = 0;
|
|||
|
$found = false;
|
|||
|
$tokenNr = 0;
|
|||
|
|
|||
|
while ($tokenNr <= $count - $minTokens) {
|
|||
|
$line = $currentTokenPositions[$tokenNr];
|
|||
|
$realLine = $currentTokenRealPositions[$tokenNr];
|
|||
|
|
|||
|
$hash = substr(
|
|||
|
md5(
|
|||
|
substr(
|
|||
|
$currentSignature,
|
|||
|
$tokenNr * 5,
|
|||
|
$minTokens * 5
|
|||
|
),
|
|||
|
true
|
|||
|
),
|
|||
|
0,
|
|||
|
8
|
|||
|
);
|
|||
|
|
|||
|
if (isset($this->hashes[$hash])) {
|
|||
|
$found = true;
|
|||
|
|
|||
|
if ($firstLine === 0) {
|
|||
|
$firstLine = $line;
|
|||
|
$firstRealLine = $realLine;
|
|||
|
$firstHash = $hash;
|
|||
|
$firstToken = $tokenNr;
|
|||
|
}
|
|||
|
} else {
|
|||
|
if ($found) {
|
|||
|
$fileA = $this->hashes[$firstHash][0];
|
|||
|
$firstLineA = $this->hashes[$firstHash][1];
|
|||
|
$lastToken = ($tokenNr - 1) + $minTokens - 1;
|
|||
|
$lastLine = $currentTokenPositions[$lastToken];
|
|||
|
$lastRealLine = $currentTokenRealPositions[$lastToken];
|
|||
|
$numLines = $lastLine + 1 - $firstLine;
|
|||
|
$realNumLines = $lastRealLine + 1 - $firstRealLine;
|
|||
|
|
|||
|
if ($numLines >= $minLines &&
|
|||
|
($fileA !== $file ||
|
|||
|
$firstLineA !== $firstRealLine)) {
|
|||
|
$result->add(
|
|||
|
new CodeClone(
|
|||
|
new CodeCloneFile($fileA, $firstLineA),
|
|||
|
new CodeCloneFile($file, $firstRealLine),
|
|||
|
$realNumLines,
|
|||
|
$lastToken + 1 - $firstToken
|
|||
|
)
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
$found = false;
|
|||
|
$firstLine = 0;
|
|||
|
}
|
|||
|
|
|||
|
$this->hashes[$hash] = [$file, $realLine];
|
|||
|
}
|
|||
|
|
|||
|
$tokenNr++;
|
|||
|
}
|
|||
|
|
|||
|
if ($found) {
|
|||
|
$fileA = $this->hashes[$firstHash][0];
|
|||
|
$firstLineA = $this->hashes[$firstHash][1];
|
|||
|
$lastToken = ($tokenNr - 1) + $minTokens - 1;
|
|||
|
$lastLine = $currentTokenPositions[$lastToken];
|
|||
|
$lastRealLine = $currentTokenRealPositions[$lastToken];
|
|||
|
$numLines = $lastLine + 1 - $firstLine;
|
|||
|
$realNumLines = $lastRealLine + 1 - $firstRealLine;
|
|||
|
|
|||
|
if ($numLines >= $minLines &&
|
|||
|
($fileA !== $file || $firstLineA !== $firstRealLine)) {
|
|||
|
$result->add(
|
|||
|
new CodeClone(
|
|||
|
new CodeCloneFile($fileA, $firstLineA),
|
|||
|
new CodeCloneFile($file, $firstRealLine),
|
|||
|
$realNumLines,
|
|||
|
$lastToken + 1 - $firstToken
|
|||
|
)
|
|||
|
);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
<?php declare(strict_types=1);
|
|||
|
/*
|
|||
|
* This file is part of PHP Copy/Paste Detector (PHPCPD).
|
|||
|
*
|
|||
|
* (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\PHPCPD\Detector;
|
|||
|
|
|||
|
use SebastianBergmann\PHPCPD\CodeCloneMap;
|
|||
|
use SebastianBergmann\PHPCPD\Detector\Strategy\AbstractStrategy;
|
|||
|
|
|||
|
final class Detector
|
|||
|
{
|
|||
|
/**
|
|||
|
* @var AbstractStrategy
|
|||
|
*/
|
|||
|
private $strategy;
|
|||
|
|
|||
|
public function __construct(AbstractStrategy $strategy)
|
|||
|
{
|
|||
|
$this->strategy = $strategy;
|
|||
|
}
|
|||
|
|
|||
|
public function copyPasteDetection(iterable $files, int $minLines = 5, int $minTokens = 70, bool $fuzzy = false): CodeCloneMap
|
|||
|
{
|
|||
|
$result = new CodeCloneMap;
|
|||
|
|
|||
|
foreach ($files as $file) {
|
|||
|
if (empty($file)) {
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
$this->strategy->processFile(
|
|||
|
$file,
|
|||
|
$minLines,
|
|||
|
$minTokens,
|
|||
|
$result,
|
|||
|
$fuzzy
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
return $result;
|
|||
|
}
|
|||
|
}
|
|||
|
<?php declare(strict_types=1);
|
|||
|
/*
|
|||
|
* This file is part of PHP Copy/Paste Detector (PHPCPD).
|
|||
|
*
|
|||
|
* (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\PHPCPD;
|
|||
|
|
|||
|
use function array_map;
|
|||
|
use function array_slice;
|
|||
|
use function current;
|
|||
|
use function file;
|
|||
|
use function implode;
|
|||
|
use function md5;
|
|||
|
|
|||
|
final class CodeClone
|
|||
|
{
|
|||
|
/**
|
|||
|
* @var int
|
|||
|
*/
|
|||
|
private $numberOfLines;
|
|||
|
|
|||
|
/**
|
|||
|
* @var int
|
|||
|
*/
|
|||
|
private $numberOfTokens;
|
|||
|
|
|||
|
/**
|
|||
|
* @var CodeCloneFile[]
|
|||
|
*/
|
|||
|
private $files = [];
|
|||
|
|
|||
|
/**
|
|||
|
* @var string
|
|||
|
*/
|
|||
|
private $id;
|
|||
|
|
|||
|
/**
|
|||
|
* @var string
|
|||
|
*/
|
|||
|
private $lines = '';
|
|||
|
|
|||
|
public function __construct(CodeCloneFile $fileA, CodeCloneFile $fileB, int $numberOfLines, int $numberOfTokens)
|
|||
|
{
|
|||
|
$this->add($fileA);
|
|||
|
$this->add($fileB);
|
|||
|
|
|||
|
$this->numberOfLines = $numberOfLines;
|
|||
|
$this->numberOfTokens = $numberOfTokens;
|
|||
|
$this->id = md5($this->lines());
|
|||
|
}
|
|||
|
|
|||
|
public function add(CodeCloneFile $file): void
|
|||
|
{
|
|||
|
$id = $file->id();
|
|||
|
|
|||
|
if (!isset($this->files[$id])) {
|
|||
|
$this->files[$id] = $file;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* @return CodeCloneFile[]
|
|||
|
*/
|
|||
|
public function files(): array
|
|||
|
{
|
|||
|
return $this->files;
|
|||
|
}
|
|||
|
|
|||
|
public function lines($indent = ''): string
|
|||
|
{
|
|||
|
if (empty($this->lines)) {
|
|||
|
$file = current($this->files);
|
|||
|
|
|||
|
$this->lines = implode(
|
|||
|
'',
|
|||
|
array_map(
|
|||
|
function ($line) use ($indent) {
|
|||
|
return $indent . $line;
|
|||
|
},
|
|||
|
array_slice(
|
|||
|
file($file->name()),
|
|||
|
$file->startLine() - 1,
|
|||
|
$this->numberOfLines
|
|||
|
)
|
|||
|
)
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
return $this->lines;
|
|||
|
}
|
|||
|
|
|||
|
public function id(): string
|
|||
|
{
|
|||
|
return $this->id;
|
|||
|
}
|
|||
|
|
|||
|
public function numberOfLines(): int
|
|||
|
{
|
|||
|
return $this->numberOfLines;
|
|||
|
}
|
|||
|
|
|||
|
public function numberOfTokens(): int
|
|||
|
{
|
|||
|
return $this->numberOfTokens;
|
|||
|
}
|
|||
|
}
|
|||
|
<?php declare(strict_types=1);
|
|||
|
/*
|
|||
|
* This file is part of PHP Copy/Paste Detector (PHPCPD).
|
|||
|
*
|
|||
|
* (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\PHPCPD;
|
|||
|
|
|||
|
use function array_reverse;
|
|||
|
use function count;
|
|||
|
use function usort;
|
|||
|
use Iterator;
|
|||
|
|
|||
|
final class CodeCloneMapIterator implements Iterator
|
|||
|
{
|
|||
|
/**
|
|||
|
* @var CodeClone[]
|
|||
|
*/
|
|||
|
private $clones = [];
|
|||
|
|
|||
|
/**
|
|||
|
* @var int
|
|||
|
*/
|
|||
|
private $position = 0;
|
|||
|
|
|||
|
public function __construct(CodeCloneMap $clones)
|
|||
|
{
|
|||
|
$this->clones = $clones->clones();
|
|||
|
|
|||
|
usort(
|
|||
|
$this->clones,
|
|||
|
static function (CodeClone $a, CodeClone $b): int {
|
|||
|
return $a->numberOfLines() <=> $b->numberOfLines();
|
|||
|
}
|
|||
|
);
|
|||
|
|
|||
|
$this->clones = array_reverse($this->clones);
|
|||
|
}
|
|||
|
|
|||
|
public function rewind(): void
|
|||
|
{
|
|||
|
$this->position = 0;
|
|||
|
}
|
|||
|
|
|||
|
public function valid(): bool
|
|||
|
{
|
|||
|
return $this->position < count($this->clones);
|
|||
|
}
|
|||
|
|
|||
|
public function key(): int
|
|||
|
{
|
|||
|
return $this->position;
|
|||
|
}
|
|||
|
|
|||
|
public function current(): CodeClone
|
|||
|
{
|
|||
|
return $this->clones[$this->position];
|
|||
|
}
|
|||
|
|
|||
|
public function next(): void
|
|||
|
{
|
|||
|
$this->position++;
|
|||
|
}
|
|||
|
}
|
|||
|
sebastian/phpcpd: 6.0.3
|
|||
|
phpunit/php-file-iterator: 3.0.5
|
|||
|
phpunit/php-timer: 5.0.3
|
|||
|
sebastian/cli-parser: 1.0.1
|
|||
|
sebastian/version: 3.0.2
|
|||
|
<?php declare(strict_types=1);
|
|||
|
/*
|
|||
|
* This file is part of sebastian/cli-parser.
|
|||
|
*
|
|||
|
* (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\CliParser;
|
|||
|
|
|||
|
use Throwable;
|
|||
|
|
|||
|
interface Exception extends Throwable
|
|||
|
{
|
|||
|
}
|
|||
|
<?php declare(strict_types=1);
|
|||
|
/*
|
|||
|
* This file is part of sebastian/cli-parser.
|
|||
|
*
|
|||
|
* (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\CliParser;
|
|||
|
|
|||
|
use function sprintf;
|
|||
|
use RuntimeException;
|
|||
|
|
|||
|
final class OptionDoesNotAllowArgumentException extends RuntimeException implements Exception
|
|||
|
{
|
|||
|
public function __construct(string $option)
|
|||
|
{
|
|||
|
parent::__construct(
|
|||
|
sprintf(
|
|||
|
'Option "%s" does not allow an argument',
|
|||
|
$option
|
|||
|
)
|
|||
|
);
|
|||
|
}
|
|||
|
}
|
|||
|
<?php declare(strict_types=1);
|
|||
|
/*
|
|||
|
* This file is part of sebastian/cli-parser.
|
|||
|
*
|
|||
|
* (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\CliParser;
|
|||
|
|
|||
|
use function sprintf;
|
|||
|
use RuntimeException;
|
|||
|
|
|||
|
final class UnknownOptionException extends RuntimeException implements Exception
|
|||
|
{
|
|||
|
public function __construct(string $option)
|
|||
|
{
|
|||
|
parent::__construct(
|
|||
|
sprintf(
|
|||
|
'Unknown option "%s"',
|
|||
|
$option
|
|||
|
)
|
|||
|
);
|
|||
|
}
|
|||
|
}
|
|||
|
<?php declare(strict_types=1);
|
|||
|
/*
|
|||
|
* This file is part of sebastian/cli-parser.
|
|||
|
*
|
|||
|
* (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\CliParser;
|
|||
|
|
|||
|
use function sprintf;
|
|||
|
use RuntimeException;
|
|||
|
|
|||
|
final class RequiredOptionArgumentMissingException extends RuntimeException implements Exception
|
|||
|
{
|
|||
|
public function __construct(string $option)
|
|||
|
{
|
|||
|
parent::__construct(
|
|||
|
sprintf(
|
|||
|
'Required argument for option "%s" is missing',
|
|||
|
$option
|
|||
|
)
|
|||
|
);
|
|||
|
}
|
|||
|
}
|
|||
|
<?php declare(strict_types=1);
|
|||
|
/*
|
|||
|
* This file is part of sebastian/cli-parser.
|
|||
|
*
|
|||
|
* (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\CliParser;
|
|||
|
|
|||
|
use function sprintf;
|
|||
|
use RuntimeException;
|
|||
|
|
|||
|
final class AmbiguousOptionException extends RuntimeException implements Exception
|
|||
|
{
|
|||
|
public function __construct(string $option)
|
|||
|
{
|
|||
|
parent::__construct(
|
|||
|
sprintf(
|
|||
|
'Option "%s" is ambiguous',
|
|||
|
$option
|
|||
|
)
|
|||
|
);
|
|||
|
}
|
|||
|
}
|
|||
|
<?php declare(strict_types=1);
|
|||
|
/*
|
|||
|
* This file is part of sebastian/cli-parser.
|
|||
|
*
|
|||
|
* (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\CliParser;
|
|||
|
|
|||
|
use function array_map;
|
|||
|
use function array_merge;
|
|||
|
use function array_shift;
|
|||
|
use function array_slice;
|
|||
|
use function assert;
|
|||
|
use function count;
|
|||
|
use function current;
|
|||
|
use function explode;
|
|||
|
use function is_array;
|
|||
|
use function is_int;
|
|||
|
use function is_string;
|
|||
|
use function key;
|
|||
|
use function next;
|
|||
|
use function preg_replace;
|
|||
|
use function reset;
|
|||
|
use function sort;
|
|||
|
use function strlen;
|
|||
|
use function strpos;
|
|||
|
use function strstr;
|
|||
|
use function substr;
|
|||
|
|
|||
|
final class Parser
|
|||
|
{
|
|||
|
/**
|
|||
|
* @psalm-param list<string> $argv
|
|||
|
* @psalm-param list<string> $longOptions
|
|||
|
*
|
|||
|
* @throws AmbiguousOptionException
|
|||
|
* @throws RequiredOptionArgumentMissingException
|
|||
|
* @throws OptionDoesNotAllowArgumentException
|
|||
|
* @throws UnknownOptionException
|
|||
|
*/
|
|||
|
public function parse(array $argv, string $shortOptions, array $longOptions = null): array
|
|||
|
{
|
|||
|
if (empty($argv)) {
|
|||
|
return [[], []];
|
|||
|
}
|
|||
|
|
|||
|
$options = [];
|
|||
|
$nonOptions = [];
|
|||
|
|
|||
|
if ($longOptions) {
|
|||
|
sort($longOptions);
|
|||
|
}
|
|||
|
|
|||
|
if (isset($argv[0][0]) && $argv[0][0] !== '-') {
|
|||
|
array_shift($argv);
|
|||
|
}
|
|||
|
|
|||
|
reset($argv);
|
|||
|
|
|||
|
$argv = array_map('trim', $argv);
|
|||
|
|
|||
|
while (false !== $arg = current($argv)) {
|
|||
|
$i = key($argv);
|
|||
|
|
|||
|
assert(is_int($i));
|
|||
|
|
|||
|
next($argv);
|
|||
|
|
|||
|
if ($arg === '') {
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
if ($arg === '--') {
|
|||
|
$nonOptions = array_merge($nonOptions, array_slice($argv, $i + 1));
|
|||
|
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
if ($arg[0] !== '-' || (strlen($arg) > 1 && $arg[1] === '-' && !$longOptions)) {
|
|||
|
$nonOptions[] = $arg;
|
|||
|
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
if (strlen($arg) > 1 && $arg[1] === '-' && is_array($longOptions)) {
|
|||
|
$this->parseLongOption(
|
|||
|
substr($arg, 2),
|
|||
|
$longOptions,
|
|||
|
$options,
|
|||
|
$argv
|
|||
|
);
|
|||
|
} else {
|
|||
|
$this->parseShortOption(
|
|||
|
substr($arg, 1),
|
|||
|
$shortOptions,
|
|||
|
$options,
|
|||
|
$argv
|
|||
|
);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return [$options, $nonOptions];
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* @throws RequiredOptionArgumentMissingException
|
|||
|
*/
|
|||
|
private function parseShortOption(string $arg, string $shortOptions, array &$opts, array &$args): void
|
|||
|
{
|
|||
|
$argLength = strlen($arg);
|
|||
|
|
|||
|
for ($i = 0; $i < $argLength; $i++) {
|
|||
|
$option = $arg[$i];
|
|||
|
$optionArgument = null;
|
|||
|
|
|||
|
if ($arg[$i] === ':' || ($spec = strstr($shortOptions, $option)) === false) {
|
|||
|
throw new UnknownOptionException('-' . $option);
|
|||
|
}
|
|||
|
|
|||
|
assert(is_string($spec));
|
|||
|
|
|||
|
if (strlen($spec) > 1 && $spec[1] === ':') {
|
|||
|
if ($i + 1 < $argLength) {
|
|||
|
$opts[] = [$option, substr($arg, $i + 1)];
|
|||
|
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
if (!(strlen($spec) > 2 && $spec[2] === ':')) {
|
|||
|
$optionArgument = current($args);
|
|||
|
|
|||
|
if (!$optionArgument) {
|
|||
|
throw new RequiredOptionArgumentMissingException('-' . $option);
|
|||
|
}
|
|||
|
|
|||
|
assert(is_string($optionArgument));
|
|||
|
|
|||
|
next($args);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
$opts[] = [$option, $optionArgument];
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* @psalm-param list<string> $longOptions
|
|||
|
*
|
|||
|
* @throws AmbiguousOptionException
|
|||
|
* @throws RequiredOptionArgumentMissingException
|
|||
|
* @throws OptionDoesNotAllowArgumentException
|
|||
|
* @throws UnknownOptionException
|
|||
|
*/
|
|||
|
private function parseLongOption(string $arg, array $longOptions, array &$opts, array &$args): void
|
|||
|
{
|
|||
|
$count = count($longOptions);
|
|||
|
$list = explode('=', $arg);
|
|||
|
$option = $list[0];
|
|||
|
$optionArgument = null;
|
|||
|
|
|||
|
if (count($list) > 1) {
|
|||
|
$optionArgument = $list[1];
|
|||
|
}
|
|||
|
|
|||
|
$optionLength = strlen($option);
|
|||
|
|
|||
|
foreach ($longOptions as $i => $longOption) {
|
|||
|
$opt_start = substr($longOption, 0, $optionLength);
|
|||
|
|
|||
|
if ($opt_start !== $option) {
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
$opt_rest = substr($longOption, $optionLength);
|
|||
|
|
|||
|
if ($opt_rest !== '' && $i + 1 < $count && $option[0] !== '=' && strpos($longOptions[$i + 1], $option) === 0) {
|
|||
|
throw new AmbiguousOptionException('--' . $option);
|
|||
|
}
|
|||
|
|
|||
|
if (substr($longOption, -1) === '=') {
|
|||
|
/* @noinspection StrlenInEmptyStringCheckContextInspection */
|
|||
|
if (substr($longOption, -2) !== '==' && !strlen((string) $optionArgument)) {
|
|||
|
if (false === $optionArgument = current($args)) {
|
|||
|
throw new RequiredOptionArgumentMissingException('--' . $option);
|
|||
|
}
|
|||
|
|
|||
|
next($args);
|
|||
|
}
|
|||
|
} elseif ($optionArgument) {
|
|||
|
throw new OptionDoesNotAllowArgumentException('--' . $option);
|
|||
|
}
|
|||
|
|
|||
|
$fullOption = '--' . preg_replace('/={1,2}$/', '', $longOption);
|
|||
|
$opts[] = [$fullOption, $optionArgument];
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
throw new UnknownOptionException('--' . $option);
|
|||
|
}
|
|||
|
}
|
|||
|
sebastian/cli-parser
|
|||
|
|
|||
|
Copyright (c) 2020, Sebastian Bergmann <sebastian@phpunit.de>.
|
|||
|
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.
|
|||
|
<?php
|
|||
|
/*
|
|||
|
* This file is part of sebastian/version.
|
|||
|
*
|
|||
|
* (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;
|
|||
|
|
|||
|
final class Version
|
|||
|
{
|
|||
|
/**
|
|||
|
* @var string
|
|||
|
*/
|
|||
|
private $path;
|
|||
|
|
|||
|
/**
|
|||
|
* @var string
|
|||
|
*/
|
|||
|
private $release;
|
|||
|
|
|||
|
/**
|
|||
|
* @var string
|
|||
|
*/
|
|||
|
private $version;
|
|||
|
|
|||
|
public function __construct(string $release, string $path)
|
|||
|
{
|
|||
|
$this->release = $release;
|
|||
|
$this->path = $path;
|
|||
|
}
|
|||
|
|
|||
|
public function getVersion(): string
|
|||
|
{
|
|||
|
if ($this->version === null) {
|
|||
|
if (\substr_count($this->release, '.') + 1 === 3) {
|
|||
|
$this->version = $this->release;
|
|||
|
} else {
|
|||
|
$this->version = $this->release . '-dev';
|
|||
|
}
|
|||
|
|
|||
|
$git = $this->getGitInformation($this->path);
|
|||
|
|
|||
|
if ($git) {
|
|||
|
if (\substr_count($this->release, '.') + 1 === 3) {
|
|||
|
$this->version = $git;
|
|||
|
} else {
|
|||
|
$git = \explode('-', $git);
|
|||
|
|
|||
|
$this->version = $this->release . '-' . \end($git);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return $this->version;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* @return bool|string
|
|||
|
*/
|
|||
|
private function getGitInformation(string $path)
|
|||
|
{
|
|||
|
if (!\is_dir($path . DIRECTORY_SEPARATOR . '.git')) {
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
$process = \proc_open(
|
|||
|
'git describe --tags',
|
|||
|
[
|
|||
|
1 => ['pipe', 'w'],
|
|||
|
2 => ['pipe', 'w'],
|
|||
|
],
|
|||
|
$pipes,
|
|||
|
$path
|
|||
|
);
|
|||
|
|
|||
|
if (!\is_resource($process)) {
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
$result = \trim(\stream_get_contents($pipes[1]));
|
|||
|
|
|||
|
\fclose($pipes[1]);
|
|||
|
\fclose($pipes[2]);
|
|||
|
|
|||
|
$returnCode = \proc_close($process);
|
|||
|
|
|||
|
if ($returnCode !== 0) {
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
return $result;
|
|||
|
}
|
|||
|
}
|
|||
|
Version
|
|||
|
|
|||
|
Copyright (c) 2013-2020, Sebastian Bergmann <sebastian@phpunit.de>.
|
|||
|
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.
|
|||
|
<?php declare(strict_types=1);
|
|||
|
/*
|
|||
|
* This file is part of phpunit/php-file-iterator.
|
|||
|
*
|
|||
|
* (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\FileIterator;
|
|||
|
|
|||
|
use const GLOB_ONLYDIR;
|
|||
|
use function array_filter;
|
|||
|
use function array_map;
|
|||
|
use function array_merge;
|
|||
|
use function glob;
|
|||
|
use function is_dir;
|
|||
|
use function is_string;
|
|||
|
use function realpath;
|
|||
|
use AppendIterator;
|
|||
|
use RecursiveDirectoryIterator;
|
|||
|
use RecursiveIteratorIterator;
|
|||
|
|
|||
|
class Factory
|
|||
|
{
|
|||
|
/**
|
|||
|
* @param array|string $paths
|
|||
|
* @param array|string $suffixes
|
|||
|
* @param array|string $prefixes
|
|||
|
*/
|
|||
|
public function getFileIterator($paths, $suffixes = '', $prefixes = '', array $exclude = []): AppendIterator
|
|||
|
{
|
|||
|
if (is_string($paths)) {
|
|||
|
$paths = [$paths];
|
|||
|
}
|
|||
|
|
|||
|
$paths = $this->getPathsAfterResolvingWildcards($paths);
|
|||
|
$exclude = $this->getPathsAfterResolvingWildcards($exclude);
|
|||
|
|
|||
|
if (is_string($prefixes)) {
|
|||
|
if ($prefixes !== '') {
|
|||
|
$prefixes = [$prefixes];
|
|||
|
} else {
|
|||
|
$prefixes = [];
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (is_string($suffixes)) {
|
|||
|
if ($suffixes !== '') {
|
|||
|
$suffixes = [$suffixes];
|
|||
|
} else {
|
|||
|
$suffixes = [];
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
$iterator = new AppendIterator;
|
|||
|
|
|||
|
foreach ($paths as $path) {
|
|||
|
if (is_dir($path)) {
|
|||
|
$iterator->append(
|
|||
|
new Iterator(
|
|||
|
$path,
|
|||
|
new RecursiveIteratorIterator(
|
|||
|
new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::FOLLOW_SYMLINKS | RecursiveDirectoryIterator::SKIP_DOTS)
|
|||
|
),
|
|||
|
$suffixes,
|
|||
|
$prefixes,
|
|||
|
$exclude
|
|||
|
)
|
|||
|
);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return $iterator;
|
|||
|
}
|
|||
|
|
|||
|
protected function getPathsAfterResolvingWildcards(array $paths): array
|
|||
|
{
|
|||
|
$_paths = [];
|
|||
|
|
|||
|
foreach ($paths as $path) {
|
|||
|
if ($locals = glob($path, GLOB_ONLYDIR)) {
|
|||
|
$_paths = array_merge($_paths, array_map('\realpath', $locals));
|
|||
|
} else {
|
|||
|
$_paths[] = realpath($path);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return array_filter($_paths);
|
|||
|
}
|
|||
|
}
|
|||
|
<?php declare(strict_types=1);
|
|||
|
/*
|
|||
|
* This file is part of phpunit/php-file-iterator.
|
|||
|
*
|
|||
|
* (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\FileIterator;
|
|||
|
|
|||
|
use function array_filter;
|
|||
|
use function array_map;
|
|||
|
use function preg_match;
|
|||
|
use function realpath;
|
|||
|
use function str_replace;
|
|||
|
use function strlen;
|
|||
|
use function strpos;
|
|||
|
use function substr;
|
|||
|
use FilterIterator;
|
|||
|
|
|||
|
class Iterator extends FilterIterator
|
|||
|
{
|
|||
|
public const PREFIX = 0;
|
|||
|
|
|||
|
public const SUFFIX = 1;
|
|||
|
|
|||
|
/**
|
|||
|
* @var string
|
|||
|
*/
|
|||
|
private $basePath;
|
|||
|
|
|||
|
/**
|
|||
|
* @var array
|
|||
|
*/
|
|||
|
private $suffixes = [];
|
|||
|
|
|||
|
/**
|
|||
|
* @var array
|
|||
|
*/
|
|||
|
private $prefixes = [];
|
|||
|
|
|||
|
/**
|
|||
|
* @var array
|
|||
|
*/
|
|||
|
private $exclude = [];
|
|||
|
|
|||
|
public function __construct(string $basePath, \Iterator $iterator, array $suffixes = [], array $prefixes = [], array $exclude = [])
|
|||
|
{
|
|||
|
$this->basePath = realpath($basePath);
|
|||
|
$this->prefixes = $prefixes;
|
|||
|
$this->suffixes = $suffixes;
|
|||
|
$this->exclude = array_filter(array_map('realpath', $exclude));
|
|||
|
|
|||
|
parent::__construct($iterator);
|
|||
|
}
|
|||
|
|
|||
|
public function accept(): bool
|
|||
|
{
|
|||
|
$current = $this->getInnerIterator()->current();
|
|||
|
$filename = $current->getFilename();
|
|||
|
$realPath = $current->getRealPath();
|
|||
|
|
|||
|
if ($realPath === false) {
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
return $this->acceptPath($realPath) &&
|
|||
|
$this->acceptPrefix($filename) &&
|
|||
|
$this->acceptSuffix($filename);
|
|||
|
}
|
|||
|
|
|||
|
private function acceptPath(string $path): bool
|
|||
|
{
|
|||
|
// Filter files in hidden directories by checking path that is relative to the base path.
|
|||
|
if (preg_match('=/\.[^/]*/=', str_replace($this->basePath, '', $path))) {
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
foreach ($this->exclude as $exclude) {
|
|||
|
if (strpos($path, $exclude) === 0) {
|
|||
|
return false;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
private function acceptPrefix(string $filename): bool
|
|||
|
{
|
|||
|
return $this->acceptSubString($filename, $this->prefixes, self::PREFIX);
|
|||
|
}
|
|||
|
|
|||
|
private function acceptSuffix(string $filename): bool
|
|||
|
{
|
|||
|
return $this->acceptSubString($filename, $this->suffixes, self::SUFFIX);
|
|||
|
}
|
|||
|
|
|||
|
private function acceptSubString(string $filename, array $subStrings, int $type): bool
|
|||
|
{
|
|||
|
if (empty($subStrings)) {
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
$matched = false;
|
|||
|
|
|||
|
foreach ($subStrings as $string) {
|
|||
|
if (($type === self::PREFIX && strpos($filename, $string) === 0) ||
|
|||
|
($type === self::SUFFIX &&
|
|||
|
substr($filename, -1 * strlen($string)) === $string)) {
|
|||
|
$matched = true;
|
|||
|
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return $matched;
|
|||
|
}
|
|||
|
}
|
|||
|
<?php declare(strict_types=1);
|
|||
|
/*
|
|||
|
* This file is part of phpunit/php-file-iterator.
|
|||
|
*
|
|||
|
* (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\FileIterator;
|
|||
|
|
|||
|
use const DIRECTORY_SEPARATOR;
|
|||
|
use function array_unique;
|
|||
|
use function count;
|
|||
|
use function dirname;
|
|||
|
use function explode;
|
|||
|
use function is_file;
|
|||
|
use function is_string;
|
|||
|
use function realpath;
|
|||
|
use function sort;
|
|||
|
|
|||
|
class Facade
|
|||
|
{
|
|||
|
/**
|
|||
|
* @param array|string $paths
|
|||
|
* @param array|string $suffixes
|
|||
|
* @param array|string $prefixes
|
|||
|
*/
|
|||
|
public function getFilesAsArray($paths, $suffixes = '', $prefixes = '', array $exclude = [], bool $commonPath = false): array
|
|||
|
{
|
|||
|
if (is_string($paths)) {
|
|||
|
$paths = [$paths];
|
|||
|
}
|
|||
|
|
|||
|
$iterator = (new Factory)->getFileIterator($paths, $suffixes, $prefixes, $exclude);
|
|||
|
|
|||
|
$files = [];
|
|||
|
|
|||
|
foreach ($iterator as $file) {
|
|||
|
$file = $file->getRealPath();
|
|||
|
|
|||
|
if ($file) {
|
|||
|
$files[] = $file;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
foreach ($paths as $path) {
|
|||
|
if (is_file($path)) {
|
|||
|
$files[] = realpath($path);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
$files = array_unique($files);
|
|||
|
sort($files);
|
|||
|
|
|||
|
if ($commonPath) {
|
|||
|
return [
|
|||
|
'commonPath' => $this->getCommonPath($files),
|
|||
|
'files' => $files,
|
|||
|
];
|
|||
|
}
|
|||
|
|
|||
|
return $files;
|
|||
|
}
|
|||
|
|
|||
|
protected function getCommonPath(array $files): string
|
|||
|
{
|
|||
|
$count = count($files);
|
|||
|
|
|||
|
if ($count === 0) {
|
|||
|
return '';
|
|||
|
}
|
|||
|
|
|||
|
if ($count === 1) {
|
|||
|
return dirname($files[0]) . DIRECTORY_SEPARATOR;
|
|||
|
}
|
|||
|
|
|||
|
$_files = [];
|
|||
|
|
|||
|
foreach ($files as $file) {
|
|||
|
$_files[] = $_fileParts = explode(DIRECTORY_SEPARATOR, $file);
|
|||
|
|
|||
|
if (empty($_fileParts[0])) {
|
|||
|
$_fileParts[0] = DIRECTORY_SEPARATOR;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
$common = '';
|
|||
|
$done = false;
|
|||
|
$j = 0;
|
|||
|
$count--;
|
|||
|
|
|||
|
while (!$done) {
|
|||
|
for ($i = 0; $i < $count; $i++) {
|
|||
|
if ($_files[$i][$j] != $_files[$i + 1][$j]) {
|
|||
|
$done = true;
|
|||
|
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (!$done) {
|
|||
|
$common .= $_files[0][$j];
|
|||
|
|
|||
|
if ($j > 0) {
|
|||
|
$common .= DIRECTORY_SEPARATOR;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
$j++;
|
|||
|
}
|
|||
|
|
|||
|
return DIRECTORY_SEPARATOR . $common;
|
|||
|
}
|
|||
|
}
|
|||
|
php-file-iterator
|
|||
|
|
|||
|
Copyright (c) 2009-2020, Sebastian Bergmann <sebastian@phpunit.de>.
|
|||
|
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.
|
|||
|
b<><62><EFBFBD><EFBFBD>xл0><3E><><EFBFBD><17><>H<EFBFBD><07><12><><EFBFBD><EFBFBD>9<EFBFBD>MhlG0`<60>gbP<62><50>J <20>e<EFBFBD>-|<7C>x;Cs<43>쭵<EFBFBD><ECADB5><EFBFBD>$GBMB
|