mirror of https://github.com/FriendsOfTYPO3/tea.git synced 2024-11-25 19:36:13 +01:00
tea/tools/phpcpd
Oliver Klee ef38d7d84b
[TASK] Drop the .phar suffix from the tools (#204)
PhpStorm by default indexes `*.phar` files. For our current set of
tools, we do not want this. (This keeps PhpStorm from complaining
about multiple versions of the same class.)

Also mark the tools as binary for git.
2021-02-24 12:02:56 +01:00

2502 lines
No EOL
69 KiB
Text
Executable file
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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><>_G S45<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<>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><>!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