first commit
This commit is contained in:
1
vendor/nunomaduro/collision/.temp/.gitkeep
vendored
Normal file
1
vendor/nunomaduro/collision/.temp/.gitkeep
vendored
Normal file
@ -0,0 +1 @@
|
||||
.gitkeep
|
21
vendor/nunomaduro/collision/LICENSE.md
vendored
Normal file
21
vendor/nunomaduro/collision/LICENSE.md
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) Nuno Maduro <enunomaduro@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
67
vendor/nunomaduro/collision/README.md
vendored
Normal file
67
vendor/nunomaduro/collision/README.md
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/nunomaduro/collision/v7.x/docs/logo.png" alt="Collision logo" width="480">
|
||||
<br>
|
||||
<img src="https://raw.githubusercontent.com/nunomaduro/collision/v7.x/docs/example.png" alt="Collision code example" height="300">
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/nunomaduro/collision/actions"><img src="https://img.shields.io/github/actions/workflow/status/nunomaduro/collision/tests.yml?branch=v7.x&label=tests&style=round-square" alt="Build Status"></img></a>
|
||||
<a href="https://scrutinizer-ci.com/g/nunomaduro/collision"><img src="https://img.shields.io/scrutinizer/g/nunomaduro/collision.svg" alt="Quality Score"></img></a>
|
||||
<a href="https://packagist.org/packages/nunomaduro/collision"><img src="https://poser.pugx.org/nunomaduro/collision/d/total.svg" alt="Total Downloads"></a>
|
||||
<a href="https://packagist.org/packages/nunomaduro/collision"><img src="https://poser.pugx.org/nunomaduro/collision/license.svg" alt="License"></a>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
Collision was created by, and is maintained by **[Nuno Maduro](https://github.com/nunomaduro)**, and is a package designed to give you beautiful error reporting when interacting with your app through the command line.
|
||||
|
||||
* It's included on **[Laravel](https://laravel.com)**, the most popular free, open-source PHP framework in the world.
|
||||
* Built on top of the **[Whoops](https://github.com/filp/whoops)** error handler.
|
||||
* Supports [Laravel](https://github.com/laravel/laravel), [Symfony](https://symfony.com), [PHPUnit](https://github.com/sebastianbergmann/phpunit), and many other frameworks.
|
||||
|
||||
## Installation & Usage
|
||||
|
||||
> **Requires [PHP 8.1+](https://php.net/releases/)**
|
||||
|
||||
Require Collision using [Composer](https://getcomposer.org):
|
||||
|
||||
```bash
|
||||
composer require nunomaduro/collision --dev
|
||||
```
|
||||
|
||||
## Version Compatibility
|
||||
|
||||
Laravel | Collision | PHPUnit | Pest
|
||||
:---------|:----------|:----------|:----------
|
||||
6.x | 3.x | |
|
||||
7.x | 4.x | |
|
||||
8.x | 5.x | |
|
||||
9.x | 6.x | |
|
||||
10.x | 6.x | 9.x | 1.x
|
||||
10.x | 7.x | 10.x | 2.x
|
||||
|
||||
As an example, here is how to require Collision on Laravel 8.x:
|
||||
|
||||
```bash
|
||||
composer require nunomaduro/collision:^5.0 --dev
|
||||
```
|
||||
|
||||
## No adapter
|
||||
|
||||
You need to register the handler in your code:
|
||||
|
||||
```php
|
||||
(new \NunoMaduro\Collision\Provider)->register();
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Thank you for considering to contribute to Collision. All the contribution guidelines are mentioned [here](CONTRIBUTING.md).
|
||||
|
||||
You can have a look at the [CHANGELOG](CHANGELOG.md) for constant updates & detailed information about the changes. You can also follow the twitter account for latest announcements or just come say hi!: [@enunomaduro](https://twitter.com/enunomaduro)
|
||||
|
||||
## License
|
||||
|
||||
Collision is an open-sourced software licensed under the [MIT license](LICENSE.md).
|
||||
|
||||
Logo by [Caneco](https://twitter.com/caneco).
|
91
vendor/nunomaduro/collision/composer.json
vendored
Normal file
91
vendor/nunomaduro/collision/composer.json
vendored
Normal file
@ -0,0 +1,91 @@
|
||||
{
|
||||
"name": "nunomaduro/collision",
|
||||
"description": "Cli error handling for console/command-line PHP applications.",
|
||||
"keywords": ["console", "command-line", "php", "cli", "error", "handling", "laravel-zero", "laravel", "artisan", "symfony"],
|
||||
"license": "MIT",
|
||||
"support": {
|
||||
"issues": "https://github.com/nunomaduro/collision/issues",
|
||||
"source": "https://github.com/nunomaduro/collision"
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nuno Maduro",
|
||||
"email": "enunomaduro@gmail.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^8.1.0",
|
||||
"filp/whoops": "^2.15.3",
|
||||
"nunomaduro/termwind": "^1.15.1",
|
||||
"symfony/console": "^6.3.4"
|
||||
},
|
||||
"conflict": {
|
||||
"laravel/framework": ">=11.0.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"brianium/paratest": "^7.3.0",
|
||||
"laravel/framework": "^10.28.0",
|
||||
"laravel/pint": "^1.13.3",
|
||||
"laravel/sail": "^1.25.0",
|
||||
"laravel/sanctum": "^3.3.1",
|
||||
"laravel/tinker": "^2.8.2",
|
||||
"nunomaduro/larastan": "^2.6.4",
|
||||
"orchestra/testbench-core": "^8.13.0",
|
||||
"pestphp/pest": "^2.23.2",
|
||||
"phpunit/phpunit": "^10.4.1",
|
||||
"sebastian/environment": "^6.0.1",
|
||||
"spatie/laravel-ignition": "^2.3.1"
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Tests\\Printer\\": "tests/Printer",
|
||||
"Tests\\Unit\\": "tests/Unit",
|
||||
"Tests\\FakeProgram\\": "tests/FakeProgram",
|
||||
"Tests\\": "tests/LaravelApp/tests",
|
||||
"App\\": "tests/LaravelApp/app/"
|
||||
}
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true,
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"NunoMaduro\\Collision\\": "src/"
|
||||
},
|
||||
"files": [
|
||||
"./src/Adapters/Phpunit/Autoload.php"
|
||||
]
|
||||
},
|
||||
"config": {
|
||||
"preferred-install": "dist",
|
||||
"sort-packages": true,
|
||||
"allow-plugins": {
|
||||
"pestphp/pest-plugin": true
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "pint -v",
|
||||
"test:lint": "pint --test -v",
|
||||
"test:types": "phpstan analyse --ansi",
|
||||
"test:unit:phpunit": [
|
||||
"@putenv XDEBUG_MODE=coverage",
|
||||
"phpunit --colors=always"
|
||||
],
|
||||
"test:unit:pest": [
|
||||
"@putenv XDEBUG_MODE=coverage",
|
||||
"pest --colors=always -v"
|
||||
],
|
||||
"test": [
|
||||
"@test:lint",
|
||||
"@test:types",
|
||||
"@test:unit:phpunit",
|
||||
"@test:unit:pest"
|
||||
]
|
||||
}
|
||||
}
|
79
vendor/nunomaduro/collision/src/Adapters/Laravel/CollisionServiceProvider.php
vendored
Normal file
79
vendor/nunomaduro/collision/src/Adapters/Laravel/CollisionServiceProvider.php
vendored
Normal file
@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace NunoMaduro\Collision\Adapters\Laravel;
|
||||
|
||||
use Illuminate\Contracts\Debug\ExceptionHandler as ExceptionHandlerContract;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use NunoMaduro\Collision\Adapters\Laravel\Commands\TestCommand;
|
||||
use NunoMaduro\Collision\Handler;
|
||||
use NunoMaduro\Collision\Provider;
|
||||
use NunoMaduro\Collision\SolutionsRepositories\NullSolutionsRepository;
|
||||
use NunoMaduro\Collision\Writer;
|
||||
use Spatie\Ignition\Contracts\SolutionProviderRepository;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class CollisionServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected bool $defer = true;
|
||||
|
||||
/**
|
||||
* Boots application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
$this->commands([
|
||||
TestCommand::class,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
if ($this->app->runningInConsole() && ! $this->app->runningUnitTests()) {
|
||||
$this->app->bind(Provider::class, function () {
|
||||
if ($this->app->has(SolutionProviderRepository::class)) {
|
||||
/** @var SolutionProviderRepository $solutionProviderRepository */
|
||||
$solutionProviderRepository = $this->app->get(SolutionProviderRepository::class);
|
||||
|
||||
$solutionsRepository = new IgnitionSolutionsRepository($solutionProviderRepository);
|
||||
} else {
|
||||
$solutionsRepository = new NullSolutionsRepository();
|
||||
}
|
||||
|
||||
$writer = new Writer($solutionsRepository);
|
||||
$handler = new Handler($writer);
|
||||
|
||||
return new Provider(null, $handler);
|
||||
});
|
||||
|
||||
/** @var \Illuminate\Contracts\Debug\ExceptionHandler $appExceptionHandler */
|
||||
$appExceptionHandler = $this->app->make(ExceptionHandlerContract::class);
|
||||
|
||||
$this->app->singleton(
|
||||
ExceptionHandlerContract::class,
|
||||
function ($app) use ($appExceptionHandler) {
|
||||
return new ExceptionHandler($app, $appExceptionHandler);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function provides()
|
||||
{
|
||||
return [Provider::class];
|
||||
}
|
||||
}
|
398
vendor/nunomaduro/collision/src/Adapters/Laravel/Commands/TestCommand.php
vendored
Normal file
398
vendor/nunomaduro/collision/src/Adapters/Laravel/Commands/TestCommand.php
vendored
Normal file
@ -0,0 +1,398 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace NunoMaduro\Collision\Adapters\Laravel\Commands;
|
||||
|
||||
use Dotenv\Exception\InvalidPathException;
|
||||
use Dotenv\Parser\Parser;
|
||||
use Dotenv\Store\StoreBuilder;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Env;
|
||||
use Illuminate\Support\Str;
|
||||
use NunoMaduro\Collision\Adapters\Laravel\Exceptions\RequirementsException;
|
||||
use NunoMaduro\Collision\Coverage;
|
||||
use ParaTest\Options;
|
||||
use PHPUnit\Runner\Version;
|
||||
use RuntimeException;
|
||||
use SebastianBergmann\Environment\Console;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
use Symfony\Component\Console\Input\InputDefinition;
|
||||
use Symfony\Component\Process\Exception\ProcessSignaledException;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class TestCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'test
|
||||
{--without-tty : Disable output to TTY}
|
||||
{--compact : Indicates whether the compact printer should be used}
|
||||
{--coverage : Indicates whether code coverage information should be collected}
|
||||
{--min= : Indicates the minimum threshold enforcement for code coverage}
|
||||
{--p|parallel : Indicates if the tests should run in parallel}
|
||||
{--profile : Lists top 10 slowest tests}
|
||||
{--recreate-databases : Indicates if the test databases should be re-created}
|
||||
{--drop-databases : Indicates if the test databases should be dropped}
|
||||
{--without-databases : Indicates if database configuration should be performed}
|
||||
';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Run the application tests';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->ignoreValidationErrors();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$phpunitVersion = Version::id();
|
||||
|
||||
if ($phpunitVersion[0].$phpunitVersion[1] !== '10') {
|
||||
throw new RequirementsException('Running Collision 7.x artisan test command requires at least PHPUnit 10.x.');
|
||||
}
|
||||
|
||||
$laravelVersion = \Illuminate\Foundation\Application::VERSION;
|
||||
|
||||
if ($laravelVersion[0].$laravelVersion[1] !== '10') { // @phpstan-ignore-line
|
||||
throw new RequirementsException('Running Collision 7.x artisan test command requires at least Laravel 10.x.');
|
||||
}
|
||||
|
||||
if ($this->option('coverage') && ! Coverage::isAvailable()) {
|
||||
$this->output->writeln(sprintf(
|
||||
"\n <fg=white;bg=red;options=bold> ERROR </> Code coverage driver not available.%s</>",
|
||||
Coverage::usingXdebug()
|
||||
? " Did you set <href=https://xdebug.org/docs/code_coverage#mode>Xdebug's coverage mode</>?"
|
||||
: ' Did you install <href=https://xdebug.org/>Xdebug</> or <href=https://github.com/krakjoe/pcov>PCOV</>?'
|
||||
));
|
||||
|
||||
$this->newLine();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/** @var bool $usesParallel */
|
||||
$usesParallel = $this->option('parallel');
|
||||
|
||||
if ($usesParallel && ! $this->isParallelDependenciesInstalled()) {
|
||||
throw new RequirementsException('Running Collision 7.x artisan test command in parallel requires at least ParaTest (brianium/paratest) 7.x.');
|
||||
}
|
||||
|
||||
$options = array_slice($_SERVER['argv'], $this->option('without-tty') ? 3 : 2);
|
||||
|
||||
$this->clearEnv();
|
||||
|
||||
$parallel = $this->option('parallel');
|
||||
|
||||
$process = (new Process(array_merge(
|
||||
// Binary ...
|
||||
$this->binary(),
|
||||
// Arguments ...
|
||||
$parallel ? $this->paratestArguments($options) : $this->phpunitArguments($options)
|
||||
),
|
||||
null,
|
||||
// Envs ...
|
||||
$parallel ? $this->paratestEnvironmentVariables() : $this->phpunitEnvironmentVariables(),
|
||||
))->setTimeout(null);
|
||||
|
||||
try {
|
||||
$process->setTty(! $this->option('without-tty'));
|
||||
} catch (RuntimeException $e) {
|
||||
// $this->output->writeln('Warning: '.$e->getMessage());
|
||||
}
|
||||
|
||||
$exitCode = 1;
|
||||
|
||||
try {
|
||||
$exitCode = $process->run(function ($type, $line) {
|
||||
$this->output->write($line);
|
||||
});
|
||||
} catch (ProcessSignaledException $e) {
|
||||
if (extension_loaded('pcntl') && $e->getSignal() !== SIGINT) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
if ($exitCode === 0 && $this->option('coverage')) {
|
||||
if (! $this->usingPest() && $this->option('parallel')) {
|
||||
$this->newLine();
|
||||
}
|
||||
|
||||
$coverage = Coverage::report($this->output);
|
||||
|
||||
$exitCode = (int) ($coverage < $this->option('min'));
|
||||
|
||||
if ($exitCode === 1) {
|
||||
$this->output->writeln(sprintf(
|
||||
"\n <fg=white;bg=red;options=bold> FAIL </> Code coverage below expected:<fg=red;options=bold> %s %%</>. Minimum:<fg=white;options=bold> %s %%</>.",
|
||||
number_format($coverage, 1),
|
||||
number_format((float) $this->option('min'), 1)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return $exitCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the PHP binary to execute.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function binary()
|
||||
{
|
||||
if ($this->usingPest()) {
|
||||
$command = $this->option('parallel') ? ['vendor/pestphp/pest/bin/pest', '--parallel'] : ['vendor/pestphp/pest/bin/pest'];
|
||||
} else {
|
||||
$command = $this->option('parallel') ? ['vendor/brianium/paratest/bin/paratest'] : ['vendor/phpunit/phpunit/phpunit'];
|
||||
}
|
||||
|
||||
if ('phpdbg' === PHP_SAPI) {
|
||||
return array_merge([PHP_BINARY, '-qrr'], $command);
|
||||
}
|
||||
|
||||
return array_merge([PHP_BINARY], $command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the common arguments of PHPUnit and Pest.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function commonArguments()
|
||||
{
|
||||
$arguments = [];
|
||||
|
||||
if ($this->option('coverage')) {
|
||||
$arguments[] = '--coverage-php';
|
||||
$arguments[] = Coverage::getPath();
|
||||
}
|
||||
|
||||
if ($this->option('ansi')) {
|
||||
$arguments[] = '--colors=always';
|
||||
} elseif ($this->option('no-ansi')) { // @phpstan-ignore-line
|
||||
$arguments[] = '--colors=never';
|
||||
} elseif ((new Console)->hasColorSupport()) {
|
||||
$arguments[] = '--colors=always';
|
||||
}
|
||||
|
||||
return $arguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if Pest is being used.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function usingPest()
|
||||
{
|
||||
return function_exists('\Pest\\version');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array of arguments for running PHPUnit.
|
||||
*
|
||||
* @param array $options
|
||||
* @return array
|
||||
*/
|
||||
protected function phpunitArguments($options)
|
||||
{
|
||||
$options = array_merge(['--no-output'], $options);
|
||||
|
||||
$options = array_values(array_filter($options, function ($option) {
|
||||
return ! Str::startsWith($option, '--env=')
|
||||
&& $option != '-q'
|
||||
&& $option != '--quiet'
|
||||
&& $option != '--coverage'
|
||||
&& $option != '--compact'
|
||||
&& $option != '--profile'
|
||||
&& $option != '--ansi'
|
||||
&& $option != '--no-ansi'
|
||||
&& ! Str::startsWith($option, '--min');
|
||||
}));
|
||||
|
||||
return array_merge($this->commonArguments(), ['--configuration='.$this->getConfigurationFile()], $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the configuration file.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getConfigurationFile()
|
||||
{
|
||||
if (! file_exists($file = base_path('phpunit.xml'))) {
|
||||
$file = base_path('phpunit.xml.dist');
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array of arguments for running Paratest.
|
||||
*
|
||||
* @param array $options
|
||||
* @return array
|
||||
*/
|
||||
protected function paratestArguments($options)
|
||||
{
|
||||
$options = array_values(array_filter($options, function ($option) {
|
||||
return ! Str::startsWith($option, '--env=')
|
||||
&& $option != '--coverage'
|
||||
&& $option != '-q'
|
||||
&& $option != '--quiet'
|
||||
&& $option != '--ansi'
|
||||
&& $option != '--no-ansi'
|
||||
&& ! Str::startsWith($option, '--min')
|
||||
&& ! Str::startsWith($option, '-p')
|
||||
&& ! Str::startsWith($option, '--parallel')
|
||||
&& ! Str::startsWith($option, '--recreate-databases')
|
||||
&& ! Str::startsWith($option, '--drop-databases')
|
||||
&& ! Str::startsWith($option, '--without-databases');
|
||||
}));
|
||||
|
||||
$options = array_merge($this->commonArguments(), [
|
||||
'--configuration='.$this->getConfigurationFile(),
|
||||
"--runner=\Illuminate\Testing\ParallelRunner",
|
||||
], $options);
|
||||
|
||||
$inputDefinition = new InputDefinition();
|
||||
Options::setInputDefinition($inputDefinition);
|
||||
$input = new ArgvInput($options, $inputDefinition);
|
||||
|
||||
/** @var non-empty-string $basePath */
|
||||
$basePath = base_path();
|
||||
|
||||
$paraTestOptions = Options::fromConsoleInput(
|
||||
$input,
|
||||
$basePath,
|
||||
);
|
||||
|
||||
if (! $paraTestOptions->configuration->hasCoverageCacheDirectory()) {
|
||||
$cacheDirectory = sys_get_temp_dir().DIRECTORY_SEPARATOR.'__laravel_test_cache_directory';
|
||||
$options[] = '--cache-directory';
|
||||
$options[] = $cacheDirectory;
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array of environment variables for running PHPUnit.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function phpunitEnvironmentVariables()
|
||||
{
|
||||
$variables = [
|
||||
'COLLISION_PRINTER' => 'DefaultPrinter',
|
||||
];
|
||||
|
||||
if ($this->option('compact')) {
|
||||
$variables['COLLISION_PRINTER_COMPACT'] = 'true';
|
||||
}
|
||||
|
||||
if ($this->option('profile')) {
|
||||
$variables['COLLISION_PRINTER_PROFILE'] = 'true';
|
||||
}
|
||||
|
||||
return $variables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array of environment variables for running Paratest.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function paratestEnvironmentVariables()
|
||||
{
|
||||
return [
|
||||
'LARAVEL_PARALLEL_TESTING' => 1,
|
||||
'LARAVEL_PARALLEL_TESTING_RECREATE_DATABASES' => $this->option('recreate-databases'),
|
||||
'LARAVEL_PARALLEL_TESTING_DROP_DATABASES' => $this->option('drop-databases'),
|
||||
'LARAVEL_PARALLEL_TESTING_WITHOUT_DATABASES' => $this->option('without-databases'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears any set Environment variables set by Laravel if the --env option is empty.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function clearEnv()
|
||||
{
|
||||
if (! $this->option('env')) {
|
||||
$vars = self::getEnvironmentVariables(
|
||||
$this->laravel->environmentPath(),
|
||||
$this->laravel->environmentFile()
|
||||
);
|
||||
|
||||
$repository = Env::getRepository();
|
||||
|
||||
foreach ($vars as $name) {
|
||||
$repository->clear($name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param string $file
|
||||
* @return array
|
||||
*/
|
||||
protected static function getEnvironmentVariables($path, $file)
|
||||
{
|
||||
try {
|
||||
$content = StoreBuilder::createWithNoNames()
|
||||
->addPath($path)
|
||||
->addName($file)
|
||||
->make()
|
||||
->read();
|
||||
} catch (InvalidPathException $e) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$vars = [];
|
||||
|
||||
foreach ((new Parser())->parse($content) as $entry) {
|
||||
$vars[] = $entry->getName();
|
||||
}
|
||||
|
||||
return $vars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the parallel dependencies are installed.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isParallelDependenciesInstalled()
|
||||
{
|
||||
return class_exists(\ParaTest\ParaTestCommand::class);
|
||||
}
|
||||
}
|
121
vendor/nunomaduro/collision/src/Adapters/Laravel/ExceptionHandler.php
vendored
Normal file
121
vendor/nunomaduro/collision/src/Adapters/Laravel/ExceptionHandler.php
vendored
Normal file
@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace NunoMaduro\Collision\Adapters\Laravel;
|
||||
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Illuminate\Contracts\Debug\ExceptionHandler as ExceptionHandlerContract;
|
||||
use NunoMaduro\Collision\Provider;
|
||||
use Symfony\Component\Console\Exception\ExceptionInterface as SymfonyConsoleExceptionInterface;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class ExceptionHandler implements ExceptionHandlerContract
|
||||
{
|
||||
/**
|
||||
* Holds an instance of the application exception handler.
|
||||
*
|
||||
* @var \Illuminate\Contracts\Debug\ExceptionHandler
|
||||
*/
|
||||
protected $appExceptionHandler;
|
||||
|
||||
/**
|
||||
* Holds an instance of the container.
|
||||
*
|
||||
* @var \Illuminate\Contracts\Container\Container
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
/**
|
||||
* Creates a new instance of the ExceptionHandler.
|
||||
*/
|
||||
public function __construct(Container $container, ExceptionHandlerContract $appExceptionHandler)
|
||||
{
|
||||
$this->container = $container;
|
||||
$this->appExceptionHandler = $appExceptionHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function report(Throwable $e)
|
||||
{
|
||||
$this->appExceptionHandler->report($e);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render($request, Throwable $e)
|
||||
{
|
||||
return $this->appExceptionHandler->render($request, $e);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function renderForConsole($output, Throwable $e)
|
||||
{
|
||||
if ($e instanceof SymfonyConsoleExceptionInterface) {
|
||||
$this->appExceptionHandler->renderForConsole($output, $e);
|
||||
} else {
|
||||
/** @var Provider $provider */
|
||||
$provider = $this->container->make(Provider::class);
|
||||
|
||||
$handler = $provider->register()
|
||||
->getHandler()
|
||||
->setOutput($output);
|
||||
|
||||
$handler->setInspector((new Inspector($e)));
|
||||
|
||||
$handler->handle();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the exception should be reported.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function shouldReport(Throwable $e)
|
||||
{
|
||||
return $this->appExceptionHandler->shouldReport($e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a reportable callback.
|
||||
*
|
||||
* @return \Illuminate\Foundation\Exceptions\ReportableHandler
|
||||
*/
|
||||
public function reportable(callable $reportUsing)
|
||||
{
|
||||
return $this->appExceptionHandler->reportable($reportUsing);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a renderable callback.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function renderable(callable $renderUsing)
|
||||
{
|
||||
$this->appExceptionHandler->renderable($renderUsing);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not report duplicate exceptions.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function dontReportDuplicates()
|
||||
{
|
||||
$this->appExceptionHandler->dontReportDuplicates();
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
16
vendor/nunomaduro/collision/src/Adapters/Laravel/Exceptions/NotSupportedYetException.php
vendored
Normal file
16
vendor/nunomaduro/collision/src/Adapters/Laravel/Exceptions/NotSupportedYetException.php
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace NunoMaduro\Collision\Adapters\Laravel\Exceptions;
|
||||
|
||||
use NunoMaduro\Collision\Contracts\RenderlessEditor;
|
||||
use NunoMaduro\Collision\Contracts\RenderlessTrace;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class NotSupportedYetException extends RuntimeException implements RenderlessEditor, RenderlessTrace
|
||||
{
|
||||
}
|
16
vendor/nunomaduro/collision/src/Adapters/Laravel/Exceptions/RequirementsException.php
vendored
Normal file
16
vendor/nunomaduro/collision/src/Adapters/Laravel/Exceptions/RequirementsException.php
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace NunoMaduro\Collision\Adapters\Laravel\Exceptions;
|
||||
|
||||
use NunoMaduro\Collision\Contracts\RenderlessEditor;
|
||||
use NunoMaduro\Collision\Contracts\RenderlessTrace;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class RequirementsException extends RuntimeException implements RenderlessEditor, RenderlessTrace
|
||||
{
|
||||
}
|
38
vendor/nunomaduro/collision/src/Adapters/Laravel/IgnitionSolutionsRepository.php
vendored
Normal file
38
vendor/nunomaduro/collision/src/Adapters/Laravel/IgnitionSolutionsRepository.php
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace NunoMaduro\Collision\Adapters\Laravel;
|
||||
|
||||
use NunoMaduro\Collision\Contracts\SolutionsRepository;
|
||||
use Spatie\Ignition\Contracts\SolutionProviderRepository;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class IgnitionSolutionsRepository implements SolutionsRepository
|
||||
{
|
||||
/**
|
||||
* Holds an instance of ignition solutions provider repository.
|
||||
*
|
||||
* @var \Spatie\Ignition\Contracts\SolutionProviderRepository
|
||||
*/
|
||||
protected $solutionProviderRepository;
|
||||
|
||||
/**
|
||||
* IgnitionSolutionsRepository constructor.
|
||||
*/
|
||||
public function __construct(SolutionProviderRepository $solutionProviderRepository)
|
||||
{
|
||||
$this->solutionProviderRepository = $solutionProviderRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFromThrowable(Throwable $throwable): array
|
||||
{
|
||||
return $this->solutionProviderRepository->getSolutionsForThrowable($throwable);
|
||||
}
|
||||
}
|
30
vendor/nunomaduro/collision/src/Adapters/Laravel/Inspector.php
vendored
Normal file
30
vendor/nunomaduro/collision/src/Adapters/Laravel/Inspector.php
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of Collision.
|
||||
*
|
||||
* (c) Nuno Maduro <enunomaduro@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace NunoMaduro\Collision\Adapters\Laravel;
|
||||
|
||||
use Whoops\Exception\Inspector as BaseInspector;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Inspector extends BaseInspector
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getTrace($e)
|
||||
{
|
||||
return $e->getTrace();
|
||||
}
|
||||
}
|
12
vendor/nunomaduro/collision/src/Adapters/Phpunit/Autoload.php
vendored
Normal file
12
vendor/nunomaduro/collision/src/Adapters/Phpunit/Autoload.php
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace NunoMaduro\Collision\Adapters\Phpunit;
|
||||
|
||||
use NunoMaduro\Collision\Adapters\Phpunit\Subscribers\EnsurePrinterIsRegisteredSubscriber;
|
||||
use PHPUnit\Runner\Version;
|
||||
|
||||
if (class_exists(Version::class) && (int) Version::series() >= 10) {
|
||||
EnsurePrinterIsRegisteredSubscriber::register();
|
||||
}
|
40
vendor/nunomaduro/collision/src/Adapters/Phpunit/ConfigureIO.php
vendored
Normal file
40
vendor/nunomaduro/collision/src/Adapters/Phpunit/ConfigureIO.php
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of Collision.
|
||||
*
|
||||
* (c) Nuno Maduro <enunomaduro@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace NunoMaduro\Collision\Adapters\Phpunit;
|
||||
|
||||
use ReflectionObject;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\Output;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class ConfigureIO
|
||||
{
|
||||
/**
|
||||
* Configures both given input and output with
|
||||
* options from the environment.
|
||||
*
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public static function of(InputInterface $input, Output $output): void
|
||||
{
|
||||
$application = new Application();
|
||||
$reflector = new ReflectionObject($application);
|
||||
$method = $reflector->getMethod('configureIO');
|
||||
$method->setAccessible(true);
|
||||
$method->invoke($application, $input, $output);
|
||||
}
|
||||
}
|
432
vendor/nunomaduro/collision/src/Adapters/Phpunit/Printers/DefaultPrinter.php
vendored
Normal file
432
vendor/nunomaduro/collision/src/Adapters/Phpunit/Printers/DefaultPrinter.php
vendored
Normal file
@ -0,0 +1,432 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace NunoMaduro\Collision\Adapters\Phpunit\Printers;
|
||||
|
||||
use NunoMaduro\Collision\Adapters\Phpunit\ConfigureIO;
|
||||
use NunoMaduro\Collision\Adapters\Phpunit\State;
|
||||
use NunoMaduro\Collision\Adapters\Phpunit\Style;
|
||||
use NunoMaduro\Collision\Adapters\Phpunit\Support\ResultReflection;
|
||||
use NunoMaduro\Collision\Adapters\Phpunit\TestResult;
|
||||
use NunoMaduro\Collision\Exceptions\ShouldNotHappen;
|
||||
use NunoMaduro\Collision\Exceptions\TestOutcome;
|
||||
use Pest\Result;
|
||||
use PHPUnit\Event\Code\TestMethod;
|
||||
use PHPUnit\Event\Code\ThrowableBuilder;
|
||||
use PHPUnit\Event\Test\BeforeFirstTestMethodErrored;
|
||||
use PHPUnit\Event\Test\ConsideredRisky;
|
||||
use PHPUnit\Event\Test\DeprecationTriggered;
|
||||
use PHPUnit\Event\Test\Errored;
|
||||
use PHPUnit\Event\Test\Failed;
|
||||
use PHPUnit\Event\Test\Finished;
|
||||
use PHPUnit\Event\Test\MarkedIncomplete;
|
||||
use PHPUnit\Event\Test\NoticeTriggered;
|
||||
use PHPUnit\Event\Test\Passed;
|
||||
use PHPUnit\Event\Test\PhpDeprecationTriggered;
|
||||
use PHPUnit\Event\Test\PhpNoticeTriggered;
|
||||
use PHPUnit\Event\Test\PhpunitDeprecationTriggered;
|
||||
use PHPUnit\Event\Test\PhpunitErrorTriggered;
|
||||
use PHPUnit\Event\Test\PhpunitWarningTriggered;
|
||||
use PHPUnit\Event\Test\PhpWarningTriggered;
|
||||
use PHPUnit\Event\Test\PreparationStarted;
|
||||
use PHPUnit\Event\Test\PrintedUnexpectedOutput;
|
||||
use PHPUnit\Event\Test\Skipped;
|
||||
use PHPUnit\Event\Test\WarningTriggered;
|
||||
use PHPUnit\Event\TestRunner\DeprecationTriggered as TestRunnerDeprecationTriggered;
|
||||
use PHPUnit\Event\TestRunner\ExecutionFinished;
|
||||
use PHPUnit\Event\TestRunner\ExecutionStarted;
|
||||
use PHPUnit\Event\TestRunner\WarningTriggered as TestRunnerWarningTriggered;
|
||||
use PHPUnit\Framework\IncompleteTestError;
|
||||
use PHPUnit\Framework\SkippedWithMessageException;
|
||||
use PHPUnit\TestRunner\TestResult\Facade;
|
||||
use PHPUnit\TextUI\Configuration\Registry;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
use Symfony\Component\Console\Output\ConsoleOutput;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class DefaultPrinter
|
||||
{
|
||||
/**
|
||||
* The output instance.
|
||||
*/
|
||||
private ConsoleOutput $output;
|
||||
|
||||
/**
|
||||
* The state instance.
|
||||
*/
|
||||
private State $state;
|
||||
|
||||
/**
|
||||
* The style instance.
|
||||
*/
|
||||
private Style $style;
|
||||
|
||||
/**
|
||||
* If the printer should be compact.
|
||||
*/
|
||||
private static bool $compact = false;
|
||||
|
||||
/**
|
||||
* If the printer should profile.
|
||||
*/
|
||||
private static bool $profile = false;
|
||||
|
||||
/**
|
||||
* When profiling, holds a list of slow tests.
|
||||
*/
|
||||
private array $profileSlowTests = [];
|
||||
|
||||
/**
|
||||
* The test started at in microseconds.
|
||||
*/
|
||||
private float $testStartedAt = 0.0;
|
||||
|
||||
/**
|
||||
* If the printer should be verbose.
|
||||
*/
|
||||
private static bool $verbose = false;
|
||||
|
||||
/**
|
||||
* Creates a new Printer instance.
|
||||
*/
|
||||
public function __construct(bool $colors)
|
||||
{
|
||||
$this->output = new ConsoleOutput(OutputInterface::VERBOSITY_NORMAL, $colors);
|
||||
|
||||
ConfigureIO::of(new ArgvInput(), $this->output);
|
||||
|
||||
self::$verbose = $this->output->isVerbose();
|
||||
|
||||
$this->style = new Style($this->output);
|
||||
|
||||
$this->state = new State();
|
||||
}
|
||||
|
||||
/**
|
||||
* If the printer instances should be compact.
|
||||
*/
|
||||
public static function compact(bool $value = null): bool
|
||||
{
|
||||
if (! is_null($value)) {
|
||||
self::$compact = $value;
|
||||
}
|
||||
|
||||
return ! self::$verbose && self::$compact;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the printer instances should profile.
|
||||
*/
|
||||
public static function profile(bool $value = null): bool
|
||||
{
|
||||
if (! is_null($value)) {
|
||||
self::$profile = $value;
|
||||
}
|
||||
|
||||
return self::$profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines if the output should be decorated or not.
|
||||
*/
|
||||
public function setDecorated(bool $decorated): void
|
||||
{
|
||||
$this->output->setDecorated($decorated);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the runner execution started event.
|
||||
*/
|
||||
public function testPrintedUnexpectedOutput(PrintedUnexpectedOutput $printedUnexpectedOutput): void
|
||||
{
|
||||
$this->output->write($printedUnexpectedOutput->output());
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the runner execution started event.
|
||||
*/
|
||||
public function testRunnerExecutionStarted(ExecutionStarted $executionStarted): void
|
||||
{
|
||||
// ..
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the test finished event.
|
||||
*/
|
||||
public function testFinished(Finished $event): void
|
||||
{
|
||||
$duration = (hrtime(true) - $this->testStartedAt) / 1_000_000;
|
||||
|
||||
$test = $event->test();
|
||||
|
||||
if (! $test instanceof TestMethod) {
|
||||
throw new ShouldNotHappen();
|
||||
}
|
||||
|
||||
if (! $this->state->existsInTestCase($event->test())) {
|
||||
$this->state->add(TestResult::fromTestCase($event->test(), TestResult::PASS));
|
||||
}
|
||||
|
||||
$result = $this->state->setDuration($test, $duration);
|
||||
|
||||
if (self::$profile) {
|
||||
$this->profileSlowTests[$event->test()->id()] = $result;
|
||||
|
||||
// Sort the slow tests by time, and keep only 10 of them.
|
||||
uasort($this->profileSlowTests, static function (TestResult $a, TestResult $b) {
|
||||
return $b->duration <=> $a->duration;
|
||||
});
|
||||
|
||||
$this->profileSlowTests = array_slice($this->profileSlowTests, 0, 10);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the test prepared event.
|
||||
*/
|
||||
public function testPreparationStarted(PreparationStarted $event): void
|
||||
{
|
||||
$this->testStartedAt = hrtime(true);
|
||||
|
||||
$test = $event->test();
|
||||
|
||||
if (! $test instanceof TestMethod) {
|
||||
throw new ShouldNotHappen();
|
||||
}
|
||||
|
||||
if ($this->state->testCaseHasChanged($test)) {
|
||||
$this->style->writeCurrentTestCaseSummary($this->state);
|
||||
|
||||
$this->state->moveTo($test);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the test errored event.
|
||||
*/
|
||||
public function testBeforeFirstTestMethodErrored(BeforeFirstTestMethodErrored $event): void
|
||||
{
|
||||
$this->state->add(TestResult::fromBeforeFirstTestMethodErrored($event));
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the test errored event.
|
||||
*/
|
||||
public function testErrored(Errored $event): void
|
||||
{
|
||||
$this->state->add(TestResult::fromTestCase($event->test(), TestResult::FAIL, $event->throwable()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the test failed event.
|
||||
*/
|
||||
public function testFailed(Failed $event): void
|
||||
{
|
||||
$throwable = $event->throwable();
|
||||
|
||||
$this->state->add(TestResult::fromTestCase($event->test(), TestResult::FAIL, $throwable));
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the test marked incomplete event.
|
||||
*/
|
||||
public function testMarkedIncomplete(MarkedIncomplete $event): void
|
||||
{
|
||||
$this->state->add(TestResult::fromTestCase($event->test(), TestResult::INCOMPLETE, $event->throwable()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the test considered risky event.
|
||||
*/
|
||||
public function testConsideredRisky(ConsideredRisky $event): void
|
||||
{
|
||||
$throwable = ThrowableBuilder::from(new IncompleteTestError($event->message()));
|
||||
|
||||
$this->state->add(TestResult::fromTestCase($event->test(), TestResult::RISKY, $throwable));
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the test runner deprecation triggered.
|
||||
*/
|
||||
public function testRunnerDeprecationTriggered(TestRunnerDeprecationTriggered $event): void
|
||||
{
|
||||
$this->style->writeWarning($event->message());
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the test runner warning triggered.
|
||||
*/
|
||||
public function testRunnerWarningTriggered(TestRunnerWarningTriggered $event): void
|
||||
{
|
||||
if (! str_starts_with($event->message(), 'No tests found in class')) {
|
||||
$this->style->writeWarning($event->message());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the test runner warning triggered.
|
||||
*/
|
||||
public function testPhpDeprecationTriggered(PhpDeprecationTriggered $event): void
|
||||
{
|
||||
$throwable = ThrowableBuilder::from(new TestOutcome($event->message()));
|
||||
|
||||
$this->state->add(TestResult::fromTestCase($event->test(), TestResult::DEPRECATED, $throwable));
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the test runner notice triggered.
|
||||
*/
|
||||
public function testPhpNoticeTriggered(PhpNoticeTriggered $event): void
|
||||
{
|
||||
$throwable = ThrowableBuilder::from(new TestOutcome($event->message()));
|
||||
|
||||
$this->state->add(TestResult::fromTestCase($event->test(), TestResult::NOTICE, $throwable));
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the test php warning triggered event.
|
||||
*/
|
||||
public function testPhpWarningTriggered(PhpWarningTriggered $event): void
|
||||
{
|
||||
$throwable = ThrowableBuilder::from(new TestOutcome($event->message()));
|
||||
|
||||
$this->state->add(TestResult::fromTestCase($event->test(), TestResult::WARN, $throwable));
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the test runner warning triggered.
|
||||
*/
|
||||
public function testPhpunitWarningTriggered(PhpunitWarningTriggered $event): void
|
||||
{
|
||||
$throwable = ThrowableBuilder::from(new TestOutcome($event->message()));
|
||||
|
||||
$this->state->add(TestResult::fromTestCase($event->test(), TestResult::WARN, $throwable));
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the test deprecation triggered event.
|
||||
*/
|
||||
public function testDeprecationTriggered(DeprecationTriggered $event): void
|
||||
{
|
||||
$throwable = ThrowableBuilder::from(new TestOutcome($event->message()));
|
||||
|
||||
$this->state->add(TestResult::fromTestCase($event->test(), TestResult::DEPRECATED, $throwable));
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the test phpunit deprecation triggered event.
|
||||
*/
|
||||
public function testPhpunitDeprecationTriggered(PhpunitDeprecationTriggered $event): void
|
||||
{
|
||||
$throwable = ThrowableBuilder::from(new TestOutcome($event->message()));
|
||||
|
||||
$this->state->add(TestResult::fromTestCase($event->test(), TestResult::DEPRECATED, $throwable));
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the test phpunit error triggered event.
|
||||
*/
|
||||
public function testPhpunitErrorTriggered(PhpunitErrorTriggered $event): void
|
||||
{
|
||||
$throwable = ThrowableBuilder::from(new TestOutcome($event->message()));
|
||||
|
||||
$this->state->add(TestResult::fromTestCase($event->test(), TestResult::FAIL, $throwable));
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the test warning triggered event.
|
||||
*/
|
||||
public function testNoticeTriggered(NoticeTriggered $event): void
|
||||
{
|
||||
$throwable = ThrowableBuilder::from(new TestOutcome($event->message()));
|
||||
|
||||
$this->state->add(TestResult::fromTestCase($event->test(), TestResult::NOTICE, $throwable));
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the test warning triggered event.
|
||||
*/
|
||||
public function testWarningTriggered(WarningTriggered $event): void
|
||||
{
|
||||
$throwable = ThrowableBuilder::from(new TestOutcome($event->message()));
|
||||
|
||||
$this->state->add(TestResult::fromTestCase($event->test(), TestResult::WARN, $throwable));
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the test skipped event.
|
||||
*/
|
||||
public function testSkipped(Skipped $event): void
|
||||
{
|
||||
if ($event->message() === '__TODO__') {
|
||||
$this->state->add(TestResult::fromTestCase($event->test(), TestResult::TODO));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$throwable = ThrowableBuilder::from(new SkippedWithMessageException($event->message()));
|
||||
|
||||
$this->state->add(TestResult::fromTestCase($event->test(), TestResult::SKIPPED, $throwable));
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the test finished event.
|
||||
*/
|
||||
public function testPassed(Passed $event): void
|
||||
{
|
||||
if (! $this->state->existsInTestCase($event->test())) {
|
||||
$this->state->add(TestResult::fromTestCase($event->test(), TestResult::PASS));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the runner execution finished event.
|
||||
*/
|
||||
public function testRunnerExecutionFinished(ExecutionFinished $event): void
|
||||
{
|
||||
$result = Facade::result();
|
||||
|
||||
if (ResultReflection::numberOfTests(Facade::result()) === 0) {
|
||||
$this->output->writeln([
|
||||
'',
|
||||
' <fg=white;options=bold;bg=blue> INFO </> No tests found.',
|
||||
'',
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->style->writeCurrentTestCaseSummary($this->state);
|
||||
|
||||
if (self::$compact) {
|
||||
$this->output->writeln(['']);
|
||||
}
|
||||
|
||||
if (class_exists(Result::class)) {
|
||||
$failed = Result::failed(Registry::get(), Facade::result());
|
||||
} else {
|
||||
$failed = ! Facade::result()->wasSuccessful();
|
||||
}
|
||||
|
||||
$this->style->writeErrorsSummary($this->state);
|
||||
|
||||
$this->style->writeRecap($this->state, $event->telemetryInfo(), $result);
|
||||
|
||||
if (! $failed && count($this->profileSlowTests) > 0) {
|
||||
$this->style->writeSlowTests($this->profileSlowTests, $event->telemetryInfo());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports the given throwable.
|
||||
*/
|
||||
public function report(Throwable $throwable): void
|
||||
{
|
||||
$this->style->writeError(ThrowableBuilder::from($throwable));
|
||||
}
|
||||
}
|
37
vendor/nunomaduro/collision/src/Adapters/Phpunit/Printers/ReportablePrinter.php
vendored
Normal file
37
vendor/nunomaduro/collision/src/Adapters/Phpunit/Printers/ReportablePrinter.php
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace NunoMaduro\Collision\Adapters\Phpunit\Printers;
|
||||
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @mixin DefaultPrinter
|
||||
*/
|
||||
final class ReportablePrinter
|
||||
{
|
||||
/**
|
||||
* Creates a new Printer instance.
|
||||
*/
|
||||
public function __construct(private readonly DefaultPrinter $printer)
|
||||
{
|
||||
// ..
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the original method, but reports any errors to the reporter.
|
||||
*/
|
||||
public function __call(string $name, array $arguments): mixed
|
||||
{
|
||||
try {
|
||||
return $this->printer->$name(...$arguments);
|
||||
} catch (Throwable $throwable) {
|
||||
$this->printer->report($throwable);
|
||||
}
|
||||
|
||||
exit(1);
|
||||
}
|
||||
}
|
267
vendor/nunomaduro/collision/src/Adapters/Phpunit/State.php
vendored
Normal file
267
vendor/nunomaduro/collision/src/Adapters/Phpunit/State.php
vendored
Normal file
@ -0,0 +1,267 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace NunoMaduro\Collision\Adapters\Phpunit;
|
||||
|
||||
use NunoMaduro\Collision\Contracts\Adapters\Phpunit\HasPrintableTestCaseName;
|
||||
use PHPUnit\Event\Code\Test;
|
||||
use PHPUnit\Event\Code\TestMethod;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class State
|
||||
{
|
||||
/**
|
||||
* The complete test suite tests.
|
||||
*
|
||||
* @var array<string, TestResult>
|
||||
*/
|
||||
public array $suiteTests = [];
|
||||
|
||||
/**
|
||||
* The current test case class.
|
||||
*/
|
||||
public ?string $testCaseName;
|
||||
|
||||
/**
|
||||
* The current test case tests.
|
||||
*
|
||||
* @var array<string, TestResult>
|
||||
*/
|
||||
public array $testCaseTests = [];
|
||||
|
||||
/**
|
||||
* The current test case tests.
|
||||
*
|
||||
* @var array<string, TestResult>
|
||||
*/
|
||||
public array $toBePrintedCaseTests = [];
|
||||
|
||||
/**
|
||||
* Header printed.
|
||||
*/
|
||||
public bool $headerPrinted = false;
|
||||
|
||||
/**
|
||||
* The state constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->testCaseName = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given test already contains a result.
|
||||
*/
|
||||
public function existsInTestCase(Test $test): bool
|
||||
{
|
||||
return isset($this->testCaseTests[$test->id()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given test to the State.
|
||||
*/
|
||||
public function add(TestResult $test): void
|
||||
{
|
||||
$this->testCaseName = $test->testCaseName;
|
||||
|
||||
$levels = array_flip([
|
||||
TestResult::PASS,
|
||||
TestResult::RUNS,
|
||||
TestResult::TODO,
|
||||
TestResult::SKIPPED,
|
||||
TestResult::WARN,
|
||||
TestResult::NOTICE,
|
||||
TestResult::DEPRECATED,
|
||||
TestResult::RISKY,
|
||||
TestResult::INCOMPLETE,
|
||||
TestResult::FAIL,
|
||||
]);
|
||||
|
||||
if (isset($this->testCaseTests[$test->id])) {
|
||||
$existing = $this->testCaseTests[$test->id];
|
||||
|
||||
if ($levels[$existing->type] >= $levels[$test->type]) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$this->testCaseTests[$test->id] = $test;
|
||||
$this->toBePrintedCaseTests[$test->id] = $test;
|
||||
|
||||
$this->suiteTests[$test->id] = $test;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the duration of the given test, and returns the test result.
|
||||
*/
|
||||
public function setDuration(Test $test, float $duration): TestResult
|
||||
{
|
||||
$result = $this->testCaseTests[$test->id()];
|
||||
|
||||
$result->setDuration($duration);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the test case title.
|
||||
*/
|
||||
public function getTestCaseTitle(): string
|
||||
{
|
||||
foreach ($this->testCaseTests as $test) {
|
||||
if ($test->type === TestResult::FAIL) {
|
||||
return 'FAIL';
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->testCaseTests as $test) {
|
||||
if ($test->type !== TestResult::PASS && $test->type !== TestResult::TODO && $test->type !== TestResult::DEPRECATED && $test->type !== TestResult::NOTICE) {
|
||||
return 'WARN';
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->testCaseTests as $test) {
|
||||
if ($test->type === TestResult::NOTICE) {
|
||||
return 'NOTI';
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->testCaseTests as $test) {
|
||||
if ($test->type === TestResult::DEPRECATED) {
|
||||
return 'DEPR';
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->todosCount() > 0 && (count($this->testCaseTests) === $this->todosCount())) {
|
||||
return 'TODO';
|
||||
}
|
||||
|
||||
return 'PASS';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of tests that are todos.
|
||||
*/
|
||||
public function todosCount(): int
|
||||
{
|
||||
return count(array_values(array_filter($this->testCaseTests, function (TestResult $test): bool {
|
||||
return $test->type === TestResult::TODO;
|
||||
})));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the test case title color.
|
||||
*/
|
||||
public function getTestCaseFontColor(): string
|
||||
{
|
||||
if ($this->getTestCaseTitleColor() === 'blue') {
|
||||
return 'white';
|
||||
}
|
||||
|
||||
return $this->getTestCaseTitle() === 'FAIL' ? 'default' : 'black';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the test case title color.
|
||||
*/
|
||||
public function getTestCaseTitleColor(): string
|
||||
{
|
||||
foreach ($this->testCaseTests as $test) {
|
||||
if ($test->type === TestResult::FAIL) {
|
||||
return 'red';
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->testCaseTests as $test) {
|
||||
if ($test->type !== TestResult::PASS && $test->type !== TestResult::TODO && $test->type !== TestResult::DEPRECATED) {
|
||||
return 'yellow';
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->testCaseTests as $test) {
|
||||
if ($test->type === TestResult::DEPRECATED) {
|
||||
return 'yellow';
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->testCaseTests as $test) {
|
||||
if ($test->type === TestResult::TODO) {
|
||||
return 'blue';
|
||||
}
|
||||
}
|
||||
|
||||
return 'green';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of tests on the current test case.
|
||||
*/
|
||||
public function testCaseTestsCount(): int
|
||||
{
|
||||
return count($this->testCaseTests);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of tests on the complete test suite.
|
||||
*/
|
||||
public function testSuiteTestsCount(): int
|
||||
{
|
||||
return count($this->suiteTests);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given test case is different from the current one.
|
||||
*/
|
||||
public function testCaseHasChanged(TestMethod $test): bool
|
||||
{
|
||||
return self::getPrintableTestCaseName($test) !== $this->testCaseName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the an new test case.
|
||||
*/
|
||||
public function moveTo(TestMethod $test): void
|
||||
{
|
||||
$this->testCaseName = self::getPrintableTestCaseName($test);
|
||||
|
||||
$this->testCaseTests = [];
|
||||
|
||||
$this->headerPrinted = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Foreach test in the test case.
|
||||
*/
|
||||
public function eachTestCaseTests(callable $callback): void
|
||||
{
|
||||
foreach ($this->toBePrintedCaseTests as $test) {
|
||||
$callback($test);
|
||||
}
|
||||
|
||||
$this->toBePrintedCaseTests = [];
|
||||
}
|
||||
|
||||
public function countTestsInTestSuiteBy(string $type): int
|
||||
{
|
||||
return count(array_filter($this->suiteTests, function (TestResult $testResult) use ($type) {
|
||||
return $testResult->type === $type;
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the printable test case name from the given `TestCase`.
|
||||
*/
|
||||
public static function getPrintableTestCaseName(TestMethod $test): string
|
||||
{
|
||||
$className = explode('::', $test->id())[0];
|
||||
|
||||
if (is_subclass_of($className, HasPrintableTestCaseName::class)) {
|
||||
return $className::getPrintableTestCaseName();
|
||||
}
|
||||
|
||||
return $className;
|
||||
}
|
||||
}
|
555
vendor/nunomaduro/collision/src/Adapters/Phpunit/Style.php
vendored
Normal file
555
vendor/nunomaduro/collision/src/Adapters/Phpunit/Style.php
vendored
Normal file
@ -0,0 +1,555 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace NunoMaduro\Collision\Adapters\Phpunit;
|
||||
|
||||
use Closure;
|
||||
use NunoMaduro\Collision\Adapters\Phpunit\Printers\DefaultPrinter;
|
||||
use NunoMaduro\Collision\Adapters\Phpunit\Support\ResultReflection;
|
||||
use NunoMaduro\Collision\Exceptions\ShouldNotHappen;
|
||||
use NunoMaduro\Collision\Exceptions\TestException;
|
||||
use NunoMaduro\Collision\Exceptions\TestOutcome;
|
||||
use NunoMaduro\Collision\Writer;
|
||||
use Pest\Expectation;
|
||||
use PHPUnit\Event\Code\Throwable;
|
||||
use PHPUnit\Event\Telemetry\Info;
|
||||
use PHPUnit\Framework\ExpectationFailedException;
|
||||
use PHPUnit\Framework\IncompleteTestError;
|
||||
use PHPUnit\Framework\SkippedWithMessageException;
|
||||
use PHPUnit\TestRunner\TestResult\TestResult as PHPUnitTestResult;
|
||||
use PHPUnit\TextUI\Configuration\Registry;
|
||||
use ReflectionClass;
|
||||
use ReflectionFunction;
|
||||
use Symfony\Component\Console\Output\ConsoleOutput;
|
||||
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
||||
use Termwind\Terminal;
|
||||
use Whoops\Exception\Frame;
|
||||
use Whoops\Exception\Inspector;
|
||||
|
||||
use function Termwind\render;
|
||||
use function Termwind\renderUsing;
|
||||
use function Termwind\terminal;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Style
|
||||
{
|
||||
private int $compactProcessed = 0;
|
||||
|
||||
private int $compactSymbolsPerLine = 0;
|
||||
|
||||
private readonly Terminal $terminal;
|
||||
|
||||
private readonly ConsoleOutput $output;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private const TYPES = [TestResult::DEPRECATED, TestResult::FAIL, TestResult::WARN, TestResult::RISKY, TestResult::INCOMPLETE, TestResult::NOTICE, TestResult::TODO, TestResult::SKIPPED, TestResult::PASS];
|
||||
|
||||
/**
|
||||
* Style constructor.
|
||||
*/
|
||||
public function __construct(ConsoleOutputInterface $output)
|
||||
{
|
||||
if (! $output instanceof ConsoleOutput) {
|
||||
throw new ShouldNotHappen();
|
||||
}
|
||||
|
||||
$this->terminal = terminal();
|
||||
$this->output = $output;
|
||||
|
||||
$this->compactSymbolsPerLine = $this->terminal->width() - 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints the content similar too:.
|
||||
*
|
||||
* ```
|
||||
* WARN Your XML configuration validates against a deprecated schema...
|
||||
* ```
|
||||
*/
|
||||
public function writeWarning(string $message): void
|
||||
{
|
||||
$this->output->writeln(['', ' <fg=black;bg=yellow;options=bold> WARN </> '.$message]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints the content similar too:.
|
||||
*
|
||||
* ```
|
||||
* WARN Your XML configuration validates against a deprecated schema...
|
||||
* ```
|
||||
*/
|
||||
public function writeThrowable(\Throwable $throwable): void
|
||||
{
|
||||
$this->output->writeln(['', ' <fg=white;bg=red;options=bold> ERROR </> '.$throwable->getMessage()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints the content similar too:.
|
||||
*
|
||||
* ```
|
||||
* PASS Unit\ExampleTest
|
||||
* ✓ basic test
|
||||
* ```
|
||||
*/
|
||||
public function writeCurrentTestCaseSummary(State $state): void
|
||||
{
|
||||
if ($state->testCaseTestsCount() === 0 || is_null($state->testCaseName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $state->headerPrinted && ! DefaultPrinter::compact()) {
|
||||
$this->output->writeln($this->titleLineFrom(
|
||||
$state->getTestCaseFontColor(),
|
||||
$state->getTestCaseTitleColor(),
|
||||
$state->getTestCaseTitle(),
|
||||
$state->testCaseName,
|
||||
$state->todosCount(),
|
||||
));
|
||||
$state->headerPrinted = true;
|
||||
}
|
||||
|
||||
$state->eachTestCaseTests(function (TestResult $testResult): void {
|
||||
if ($testResult->description !== '') {
|
||||
if (DefaultPrinter::compact()) {
|
||||
$this->writeCompactDescriptionLine($testResult);
|
||||
} else {
|
||||
$this->writeDescriptionLine($testResult);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints the content similar too:.
|
||||
*
|
||||
* ```
|
||||
* PASS Unit\ExampleTest
|
||||
* ✓ basic test
|
||||
* ```
|
||||
*/
|
||||
public function writeErrorsSummary(State $state): void
|
||||
{
|
||||
$configuration = Registry::get();
|
||||
$failTypes = [
|
||||
TestResult::FAIL,
|
||||
];
|
||||
|
||||
if ($configuration->displayDetailsOnTestsThatTriggerNotices()) {
|
||||
$failTypes[] = TestResult::NOTICE;
|
||||
}
|
||||
|
||||
if ($configuration->displayDetailsOnTestsThatTriggerDeprecations()) {
|
||||
$failTypes[] = TestResult::DEPRECATED;
|
||||
}
|
||||
|
||||
if ($configuration->failOnWarning() || $configuration->displayDetailsOnTestsThatTriggerWarnings()) {
|
||||
$failTypes[] = TestResult::WARN;
|
||||
}
|
||||
|
||||
if ($configuration->failOnRisky()) {
|
||||
$failTypes[] = TestResult::RISKY;
|
||||
}
|
||||
|
||||
if ($configuration->failOnIncomplete() || $configuration->displayDetailsOnIncompleteTests()) {
|
||||
$failTypes[] = TestResult::INCOMPLETE;
|
||||
}
|
||||
|
||||
if ($configuration->failOnSkipped() || $configuration->displayDetailsOnSkippedTests()) {
|
||||
$failTypes[] = TestResult::SKIPPED;
|
||||
}
|
||||
|
||||
$failTypes = array_unique($failTypes);
|
||||
|
||||
$errors = array_values(array_filter($state->suiteTests, fn (TestResult $testResult) => in_array(
|
||||
$testResult->type,
|
||||
$failTypes,
|
||||
true
|
||||
)));
|
||||
|
||||
array_map(function (TestResult $testResult): void {
|
||||
if (! $testResult->throwable instanceof Throwable) {
|
||||
throw new ShouldNotHappen();
|
||||
}
|
||||
|
||||
renderUsing($this->output);
|
||||
render(<<<'HTML'
|
||||
<div class="mx-2 text-red">
|
||||
<hr/>
|
||||
</div>
|
||||
HTML
|
||||
);
|
||||
|
||||
$testCaseName = $testResult->testCaseName;
|
||||
$description = $testResult->description;
|
||||
|
||||
/** @var class-string $throwableClassName */
|
||||
$throwableClassName = $testResult->throwable->className();
|
||||
|
||||
$throwableClassName = ! in_array($throwableClassName, [
|
||||
ExpectationFailedException::class,
|
||||
IncompleteTestError::class,
|
||||
SkippedWithMessageException::class,
|
||||
TestOutcome::class,
|
||||
], true) ? sprintf('<span class="px-1 bg-red font-bold">%s</span>', (new ReflectionClass($throwableClassName))->getShortName())
|
||||
: '';
|
||||
|
||||
$truncateClasses = $this->output->isVerbose() ? '' : 'flex-1 truncate';
|
||||
|
||||
renderUsing($this->output);
|
||||
render(sprintf(<<<'HTML'
|
||||
<div class="flex justify-between mx-2">
|
||||
<span class="%s">
|
||||
<span class="px-1 bg-%s %s font-bold uppercase">%s</span> <span class="font-bold">%s</span><span class="text-gray mx-1">></span><span>%s</span>
|
||||
</span>
|
||||
<span class="ml-1">
|
||||
%s
|
||||
</span>
|
||||
</div>
|
||||
HTML, $truncateClasses, $testResult->color === 'yellow' ? 'yellow-400' : $testResult->color, $testResult->color === 'yellow' ? 'text-black' : '', $testResult->type, $testCaseName, $description, $throwableClassName));
|
||||
|
||||
$this->writeError($testResult->throwable);
|
||||
}, $errors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the final recap.
|
||||
*/
|
||||
public function writeRecap(State $state, Info $telemetry, PHPUnitTestResult $result): void
|
||||
{
|
||||
$tests = [];
|
||||
foreach (self::TYPES as $type) {
|
||||
if (($countTests = $state->countTestsInTestSuiteBy($type)) !== 0) {
|
||||
$color = TestResult::makeColor($type);
|
||||
|
||||
if ($type === TestResult::WARN && $countTests < 2) {
|
||||
$type = 'warning';
|
||||
}
|
||||
|
||||
if ($type === TestResult::NOTICE && $countTests > 1) {
|
||||
$type = 'notices';
|
||||
}
|
||||
|
||||
if ($type === TestResult::TODO && $countTests > 1) {
|
||||
$type = 'todos';
|
||||
}
|
||||
|
||||
$tests[] = "<fg=$color;options=bold>$countTests $type</>";
|
||||
}
|
||||
}
|
||||
|
||||
$pending = ResultReflection::numberOfTests($result) - $result->numberOfTestsRun();
|
||||
if ($pending > 0) {
|
||||
$tests[] = "\e[2m$pending pending\e[22m";
|
||||
}
|
||||
|
||||
$timeElapsed = number_format($telemetry->durationSinceStart()->asFloat(), 2, '.', '');
|
||||
|
||||
$this->output->writeln(['']);
|
||||
|
||||
if (! empty($tests)) {
|
||||
$this->output->writeln([
|
||||
sprintf(
|
||||
' <fg=gray>Tests:</> <fg=default>%s</><fg=gray> (%s assertions)</>',
|
||||
implode('<fg=gray>,</> ', $tests),
|
||||
$result->numberOfAssertions()
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
$this->output->writeln([
|
||||
sprintf(
|
||||
' <fg=gray>Duration:</> <fg=default>%ss</>',
|
||||
$timeElapsed
|
||||
),
|
||||
]);
|
||||
|
||||
$this->output->writeln('');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, TestResult> $slowTests
|
||||
*/
|
||||
public function writeSlowTests(array $slowTests, Info $telemetry): void
|
||||
{
|
||||
$this->output->writeln(' <fg=gray>Top 10 slowest tests:</>');
|
||||
|
||||
$timeElapsed = $telemetry->durationSinceStart()->asFloat();
|
||||
|
||||
foreach ($slowTests as $testResult) {
|
||||
$seconds = number_format($testResult->duration / 1000, 2, '.', '');
|
||||
|
||||
$color = ($testResult->duration / 1000) > $timeElapsed * 0.25 ? 'red' : ($testResult->duration > $timeElapsed * 0.1 ? 'yellow' : 'gray');
|
||||
|
||||
renderUsing($this->output);
|
||||
render(sprintf(<<<'HTML'
|
||||
<div class="flex justify-between space-x-1 mx-2">
|
||||
<span class="flex-1">
|
||||
<span class="font-bold">%s</span><span class="text-gray mx-1">></span><span class="text-gray">%s</span>
|
||||
</span>
|
||||
<span class="ml-1 font-bold text-%s">
|
||||
%ss
|
||||
</span>
|
||||
</div>
|
||||
HTML, $testResult->testCaseName, $testResult->description, $color, $seconds));
|
||||
}
|
||||
|
||||
$timeElapsedInSlowTests = array_sum(array_map(fn (TestResult $testResult) => $testResult->duration / 1000, $slowTests));
|
||||
|
||||
$timeElapsedAsString = number_format($timeElapsed, 2, '.', '');
|
||||
$percentageInSlowTestsAsString = number_format($timeElapsedInSlowTests * 100 / $timeElapsed, 2, '.', '');
|
||||
$timeElapsedInSlowTestsAsString = number_format($timeElapsedInSlowTests, 2, '.', '');
|
||||
|
||||
renderUsing($this->output);
|
||||
render(sprintf(<<<'HTML'
|
||||
<div class="mx-2 mb-1 flex">
|
||||
<div class="text-gray">
|
||||
<hr/>
|
||||
</div>
|
||||
<div class="flex space-x-1 justify-between">
|
||||
<span>
|
||||
</span>
|
||||
<span>
|
||||
<span class="text-gray">(%s%% of %ss)</span>
|
||||
<span class="ml-1 font-bold">%ss</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
HTML, $percentageInSlowTestsAsString, $timeElapsedAsString, $timeElapsedInSlowTestsAsString));
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the error using Collision's writer and terminates with exit code === 1.
|
||||
*/
|
||||
public function writeError(Throwable $throwable): void
|
||||
{
|
||||
$writer = (new Writer())->setOutput($this->output);
|
||||
|
||||
$throwable = new TestException($throwable, $this->output->isVerbose());
|
||||
|
||||
$writer->showTitle(false);
|
||||
|
||||
$writer->ignoreFilesIn([
|
||||
'/vendor\/nunomaduro\/collision/',
|
||||
'/vendor\/bin\/pest/',
|
||||
'/bin\/pest/',
|
||||
'/vendor\/pestphp\/pest/',
|
||||
'/vendor\/pestphp\/pest-plugin-arch/',
|
||||
'/vendor\/phpspec\/prophecy-phpunit/',
|
||||
'/vendor\/phpspec\/prophecy/',
|
||||
'/vendor\/phpunit\/phpunit\/src/',
|
||||
'/vendor\/mockery\/mockery/',
|
||||
'/vendor\/laravel\/dusk/',
|
||||
'/Illuminate\/Testing/',
|
||||
'/Illuminate\/Foundation\/Testing/',
|
||||
'/Illuminate\/Foundation\/Bootstrap\/HandleExceptions/',
|
||||
'/vendor\/symfony\/framework-bundle\/Test/',
|
||||
'/vendor\/symfony\/phpunit-bridge/',
|
||||
'/vendor\/symfony\/dom-crawler/',
|
||||
'/vendor\/symfony\/browser-kit/',
|
||||
'/vendor\/symfony\/css-selector/',
|
||||
'/vendor\/bin\/.phpunit/',
|
||||
'/bin\/.phpunit/',
|
||||
'/vendor\/bin\/simple-phpunit/',
|
||||
'/bin\/phpunit/',
|
||||
'/vendor\/coduo\/php-matcher\/src\/PHPUnit/',
|
||||
'/vendor\/sulu\/sulu\/src\/Sulu\/Bundle\/TestBundle\/Testing/',
|
||||
'/vendor\/webmozart\/assert/',
|
||||
|
||||
$this->ignorePestPipes(...),
|
||||
$this->ignorePestExtends(...),
|
||||
$this->ignorePestInterceptors(...),
|
||||
|
||||
]);
|
||||
|
||||
/** @var \Throwable $throwable */
|
||||
$inspector = new Inspector($throwable);
|
||||
|
||||
$writer->write($inspector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the title contents.
|
||||
*/
|
||||
private function titleLineFrom(string $fg, string $bg, string $title, string $testCaseName, int $todos): string
|
||||
{
|
||||
return sprintf(
|
||||
"\n <fg=%s;bg=%s;options=bold> %s </><fg=default> %s</>%s",
|
||||
$fg,
|
||||
$bg,
|
||||
$title,
|
||||
$testCaseName,
|
||||
$todos > 0 ? sprintf('<fg=gray> - %s todo%s</>', $todos, $todos > 1 ? 's' : '') : '',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a description line.
|
||||
*/
|
||||
private function writeCompactDescriptionLine(TestResult $result): void
|
||||
{
|
||||
$symbolsOnCurrentLine = $this->compactProcessed % $this->compactSymbolsPerLine;
|
||||
|
||||
if ($symbolsOnCurrentLine >= $this->terminal->width() - 4) {
|
||||
$symbolsOnCurrentLine = 0;
|
||||
}
|
||||
|
||||
if ($symbolsOnCurrentLine === 0) {
|
||||
$this->output->writeln('');
|
||||
$this->output->write(' ');
|
||||
}
|
||||
|
||||
$this->output->write(sprintf('<fg=%s;options=bold>%s</>', $result->compactColor, $result->compactIcon));
|
||||
|
||||
$this->compactProcessed++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a description line.
|
||||
*/
|
||||
private function writeDescriptionLine(TestResult $result): void
|
||||
{
|
||||
if (! empty($warning = $result->warning)) {
|
||||
if (! str_contains($warning, "\n")) {
|
||||
$warning = sprintf(
|
||||
' → %s',
|
||||
$warning
|
||||
);
|
||||
} else {
|
||||
$warningLines = explode("\n", $warning);
|
||||
$warning = '';
|
||||
|
||||
foreach ($warningLines as $w) {
|
||||
$warning .= sprintf(
|
||||
"\n <fg=yellow;options=bold>⇂ %s</>",
|
||||
trim($w)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$seconds = '';
|
||||
|
||||
if (($result->duration / 1000) > 0.0) {
|
||||
$seconds = number_format($result->duration / 1000, 2, '.', '');
|
||||
$seconds = $seconds !== '0.00' ? sprintf('<span class="text-gray mr-2">%ss</span>', $seconds) : '';
|
||||
}
|
||||
|
||||
if (isset($_SERVER['REBUILD_SNAPSHOTS']) || (isset($_SERVER['COLLISION_IGNORE_DURATION']) && $_SERVER['COLLISION_IGNORE_DURATION'] === 'true')) {
|
||||
$seconds = '';
|
||||
}
|
||||
|
||||
$truncateClasses = $this->output->isVerbose() ? '' : 'flex-1 truncate';
|
||||
|
||||
if ($warning !== '') {
|
||||
$warning = sprintf('<span class="ml-1 text-yellow">%s</span>', $warning);
|
||||
|
||||
if (! empty($result->warningSource)) {
|
||||
$warning .= ' // '.$result->warningSource;
|
||||
}
|
||||
}
|
||||
|
||||
$description = preg_replace('/`([^`]+)`/', '<span class="text-white">$1</span>', $result->description);
|
||||
|
||||
renderUsing($this->output);
|
||||
render(sprintf(<<<'HTML'
|
||||
<div class="%s ml-2">
|
||||
<span class="%s text-gray">
|
||||
<span class="text-%s font-bold">%s</span><span class="ml-1 text-gray">%s</span>%s
|
||||
</span>%s
|
||||
</div>
|
||||
HTML, $seconds === '' ? '' : 'flex space-x-1 justify-between', $truncateClasses, $result->color, $result->icon, $description, $warning, $seconds));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Frame $frame
|
||||
*/
|
||||
private function ignorePestPipes($frame): bool
|
||||
{
|
||||
if (class_exists(Expectation::class)) {
|
||||
$reflection = new ReflectionClass(Expectation::class);
|
||||
|
||||
/** @var array<string, array<Closure(Closure, mixed ...$arguments): void>> $expectationPipes */
|
||||
$expectationPipes = $reflection->getStaticPropertyValue('pipes', []);
|
||||
|
||||
foreach ($expectationPipes as $pipes) {
|
||||
foreach ($pipes as $pipeClosure) {
|
||||
if ($this->isFrameInClosure($frame, $pipeClosure)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Frame $frame
|
||||
*/
|
||||
private function ignorePestExtends($frame): bool
|
||||
{
|
||||
if (class_exists(Expectation::class)) {
|
||||
$reflection = new ReflectionClass(Expectation::class);
|
||||
|
||||
/** @var array<string, Closure> $extends */
|
||||
$extends = $reflection->getStaticPropertyValue('extends', []);
|
||||
|
||||
foreach ($extends as $extendClosure) {
|
||||
if ($this->isFrameInClosure($frame, $extendClosure)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Frame $frame
|
||||
*/
|
||||
private function ignorePestInterceptors($frame): bool
|
||||
{
|
||||
if (class_exists(Expectation::class)) {
|
||||
$reflection = new ReflectionClass(Expectation::class);
|
||||
|
||||
/** @var array<string, array<Closure(Closure, mixed ...$arguments): void>> $expectationInterceptors */
|
||||
$expectationInterceptors = $reflection->getStaticPropertyValue('interceptors', []);
|
||||
|
||||
foreach ($expectationInterceptors as $pipes) {
|
||||
foreach ($pipes as $pipeClosure) {
|
||||
if ($this->isFrameInClosure($frame, $pipeClosure)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Frame $frame
|
||||
*/
|
||||
private function isFrameInClosure($frame, Closure $closure): bool
|
||||
{
|
||||
$reflection = new ReflectionFunction($closure);
|
||||
|
||||
$sanitizedPath = (string) str_replace('\\', '/', (string) $frame->getFile());
|
||||
|
||||
/** @phpstan-ignore-next-line */
|
||||
$sanitizedClosurePath = (string) str_replace('\\', '/', $reflection->getFileName());
|
||||
|
||||
if ($sanitizedPath === $sanitizedClosurePath) {
|
||||
if ($reflection->getStartLine() <= $frame->getLine() && $frame->getLine() <= $reflection->getEndLine()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
310
vendor/nunomaduro/collision/src/Adapters/Phpunit/Subscribers/EnsurePrinterIsRegisteredSubscriber.php
vendored
Normal file
310
vendor/nunomaduro/collision/src/Adapters/Phpunit/Subscribers/EnsurePrinterIsRegisteredSubscriber.php
vendored
Normal file
@ -0,0 +1,310 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace NunoMaduro\Collision\Adapters\Phpunit\Subscribers;
|
||||
|
||||
use NunoMaduro\Collision\Adapters\Phpunit\Printers\DefaultPrinter;
|
||||
use NunoMaduro\Collision\Adapters\Phpunit\Printers\ReportablePrinter;
|
||||
use PHPUnit\Event\Application\Started;
|
||||
use PHPUnit\Event\Application\StartedSubscriber;
|
||||
use PHPUnit\Event\Facade;
|
||||
use PHPUnit\Event\Test\BeforeFirstTestMethodErrored;
|
||||
use PHPUnit\Event\Test\BeforeFirstTestMethodErroredSubscriber;
|
||||
use PHPUnit\Event\Test\ConsideredRisky;
|
||||
use PHPUnit\Event\Test\ConsideredRiskySubscriber;
|
||||
use PHPUnit\Event\Test\DeprecationTriggered;
|
||||
use PHPUnit\Event\Test\DeprecationTriggeredSubscriber;
|
||||
use PHPUnit\Event\Test\Errored;
|
||||
use PHPUnit\Event\Test\ErroredSubscriber;
|
||||
use PHPUnit\Event\Test\Failed;
|
||||
use PHPUnit\Event\Test\FailedSubscriber;
|
||||
use PHPUnit\Event\Test\Finished;
|
||||
use PHPUnit\Event\Test\FinishedSubscriber;
|
||||
use PHPUnit\Event\Test\MarkedIncomplete;
|
||||
use PHPUnit\Event\Test\MarkedIncompleteSubscriber;
|
||||
use PHPUnit\Event\Test\NoticeTriggered;
|
||||
use PHPUnit\Event\Test\NoticeTriggeredSubscriber;
|
||||
use PHPUnit\Event\Test\Passed;
|
||||
use PHPUnit\Event\Test\PassedSubscriber;
|
||||
use PHPUnit\Event\Test\PhpDeprecationTriggered;
|
||||
use PHPUnit\Event\Test\PhpDeprecationTriggeredSubscriber;
|
||||
use PHPUnit\Event\Test\PhpNoticeTriggered;
|
||||
use PHPUnit\Event\Test\PhpNoticeTriggeredSubscriber;
|
||||
use PHPUnit\Event\Test\PhpunitDeprecationTriggered;
|
||||
use PHPUnit\Event\Test\PhpunitDeprecationTriggeredSubscriber;
|
||||
use PHPUnit\Event\Test\PhpunitErrorTriggered;
|
||||
use PHPUnit\Event\Test\PhpunitErrorTriggeredSubscriber;
|
||||
use PHPUnit\Event\Test\PhpunitWarningTriggered;
|
||||
use PHPUnit\Event\Test\PhpunitWarningTriggeredSubscriber;
|
||||
use PHPUnit\Event\Test\PhpWarningTriggered;
|
||||
use PHPUnit\Event\Test\PhpWarningTriggeredSubscriber;
|
||||
use PHPUnit\Event\Test\PreparationStarted;
|
||||
use PHPUnit\Event\Test\PreparationStartedSubscriber;
|
||||
use PHPUnit\Event\Test\PrintedUnexpectedOutput;
|
||||
use PHPUnit\Event\Test\PrintedUnexpectedOutputSubscriber;
|
||||
use PHPUnit\Event\Test\Skipped;
|
||||
use PHPUnit\Event\Test\SkippedSubscriber;
|
||||
use PHPUnit\Event\Test\WarningTriggered;
|
||||
use PHPUnit\Event\Test\WarningTriggeredSubscriber;
|
||||
use PHPUnit\Event\TestRunner\Configured;
|
||||
use PHPUnit\Event\TestRunner\ConfiguredSubscriber;
|
||||
use PHPUnit\Event\TestRunner\DeprecationTriggered as TestRunnerDeprecationTriggered;
|
||||
use PHPUnit\Event\TestRunner\DeprecationTriggeredSubscriber as TestRunnerDeprecationTriggeredSubscriber;
|
||||
use PHPUnit\Event\TestRunner\ExecutionFinished;
|
||||
use PHPUnit\Event\TestRunner\ExecutionFinishedSubscriber;
|
||||
use PHPUnit\Event\TestRunner\ExecutionStarted;
|
||||
use PHPUnit\Event\TestRunner\ExecutionStartedSubscriber;
|
||||
use PHPUnit\Event\TestRunner\WarningTriggered as TestRunnerWarningTriggered;
|
||||
use PHPUnit\Event\TestRunner\WarningTriggeredSubscriber as TestRunnerWarningTriggeredSubscriber;
|
||||
use PHPUnit\Runner\Version;
|
||||
|
||||
if (class_exists(Version::class) && (int) Version::series() >= 10) {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class EnsurePrinterIsRegisteredSubscriber implements StartedSubscriber
|
||||
{
|
||||
/**
|
||||
* If this subscriber has been registered on PHPUnit's facade.
|
||||
*/
|
||||
private static bool $registered = false;
|
||||
|
||||
/**
|
||||
* Runs the subscriber.
|
||||
*/
|
||||
public function notify(Started $event): void
|
||||
{
|
||||
$printer = new ReportablePrinter(new DefaultPrinter(true));
|
||||
|
||||
if (isset($_SERVER['COLLISION_PRINTER_COMPACT'])) {
|
||||
DefaultPrinter::compact(true);
|
||||
}
|
||||
|
||||
if (isset($_SERVER['COLLISION_PRINTER_PROFILE'])) {
|
||||
DefaultPrinter::profile(true);
|
||||
}
|
||||
|
||||
$subscribers = [
|
||||
// Configured
|
||||
new class($printer) extends Subscriber implements ConfiguredSubscriber
|
||||
{
|
||||
public function notify(Configured $event): void
|
||||
{
|
||||
$this->printer()->setDecorated(
|
||||
$event->configuration()->colors()
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
// Test
|
||||
new class($printer) extends Subscriber implements PrintedUnexpectedOutputSubscriber
|
||||
{
|
||||
public function notify(PrintedUnexpectedOutput $event): void
|
||||
{
|
||||
$this->printer()->testPrintedUnexpectedOutput($event);
|
||||
}
|
||||
},
|
||||
|
||||
// Test Runner
|
||||
new class($printer) extends Subscriber implements ExecutionStartedSubscriber
|
||||
{
|
||||
public function notify(ExecutionStarted $event): void
|
||||
{
|
||||
$this->printer()->testRunnerExecutionStarted($event);
|
||||
}
|
||||
},
|
||||
|
||||
new class($printer) extends Subscriber implements ExecutionFinishedSubscriber
|
||||
{
|
||||
public function notify(ExecutionFinished $event): void
|
||||
{
|
||||
$this->printer()->testRunnerExecutionFinished($event);
|
||||
}
|
||||
},
|
||||
|
||||
// Test > Hook Methods
|
||||
|
||||
new class($printer) extends Subscriber implements BeforeFirstTestMethodErroredSubscriber
|
||||
{
|
||||
public function notify(BeforeFirstTestMethodErrored $event): void
|
||||
{
|
||||
$this->printer()->testBeforeFirstTestMethodErrored($event);
|
||||
}
|
||||
},
|
||||
|
||||
// Test > Lifecycle ...
|
||||
|
||||
new class($printer) extends Subscriber implements FinishedSubscriber
|
||||
{
|
||||
public function notify(Finished $event): void
|
||||
{
|
||||
$this->printer()->testFinished($event);
|
||||
}
|
||||
},
|
||||
|
||||
new class($printer) extends Subscriber implements PreparationStartedSubscriber
|
||||
{
|
||||
public function notify(PreparationStarted $event): void
|
||||
{
|
||||
$this->printer()->testPreparationStarted($event);
|
||||
}
|
||||
},
|
||||
|
||||
// Test > Issues ...
|
||||
|
||||
new class($printer) extends Subscriber implements ConsideredRiskySubscriber
|
||||
{
|
||||
public function notify(ConsideredRisky $event): void
|
||||
{
|
||||
$this->printer()->testConsideredRisky($event);
|
||||
}
|
||||
},
|
||||
|
||||
new class($printer) extends Subscriber implements DeprecationTriggeredSubscriber
|
||||
{
|
||||
public function notify(DeprecationTriggered $event): void
|
||||
{
|
||||
$this->printer()->testDeprecationTriggered($event);
|
||||
}
|
||||
},
|
||||
|
||||
new class($printer) extends Subscriber implements TestRunnerDeprecationTriggeredSubscriber
|
||||
{
|
||||
public function notify(TestRunnerDeprecationTriggered $event): void
|
||||
{
|
||||
$this->printer()->testRunnerDeprecationTriggered($event);
|
||||
}
|
||||
},
|
||||
|
||||
new class($printer) extends Subscriber implements TestRunnerWarningTriggeredSubscriber
|
||||
{
|
||||
public function notify(TestRunnerWarningTriggered $event): void
|
||||
{
|
||||
$this->printer()->testRunnerWarningTriggered($event);
|
||||
}
|
||||
},
|
||||
|
||||
new class($printer) extends Subscriber implements PhpDeprecationTriggeredSubscriber
|
||||
{
|
||||
public function notify(PhpDeprecationTriggered $event): void
|
||||
{
|
||||
$this->printer()->testPhpDeprecationTriggered($event);
|
||||
}
|
||||
},
|
||||
|
||||
new class($printer) extends Subscriber implements PhpunitDeprecationTriggeredSubscriber
|
||||
{
|
||||
public function notify(PhpunitDeprecationTriggered $event): void
|
||||
{
|
||||
$this->printer()->testPhpunitDeprecationTriggered($event);
|
||||
}
|
||||
},
|
||||
|
||||
new class($printer) extends Subscriber implements PhpNoticeTriggeredSubscriber
|
||||
{
|
||||
public function notify(PhpNoticeTriggered $event): void
|
||||
{
|
||||
$this->printer()->testPhpNoticeTriggered($event);
|
||||
}
|
||||
},
|
||||
|
||||
new class($printer) extends Subscriber implements PhpWarningTriggeredSubscriber
|
||||
{
|
||||
public function notify(PhpWarningTriggered $event): void
|
||||
{
|
||||
$this->printer()->testPhpWarningTriggered($event);
|
||||
}
|
||||
},
|
||||
|
||||
new class($printer) extends Subscriber implements PhpunitWarningTriggeredSubscriber
|
||||
{
|
||||
public function notify(PhpunitWarningTriggered $event): void
|
||||
{
|
||||
$this->printer()->testPhpunitWarningTriggered($event);
|
||||
}
|
||||
},
|
||||
|
||||
new class($printer) extends Subscriber implements PhpunitErrorTriggeredSubscriber
|
||||
{
|
||||
public function notify(PhpunitErrorTriggered $event): void
|
||||
{
|
||||
$this->printer()->testPhpunitErrorTriggered($event);
|
||||
}
|
||||
},
|
||||
|
||||
// Test > Outcome ...
|
||||
|
||||
new class($printer) extends Subscriber implements ErroredSubscriber
|
||||
{
|
||||
public function notify(Errored $event): void
|
||||
{
|
||||
$this->printer()->testErrored($event);
|
||||
}
|
||||
},
|
||||
new class($printer) extends Subscriber implements FailedSubscriber
|
||||
{
|
||||
public function notify(Failed $event): void
|
||||
{
|
||||
$this->printer()->testFailed($event);
|
||||
}
|
||||
},
|
||||
new class($printer) extends Subscriber implements MarkedIncompleteSubscriber
|
||||
{
|
||||
public function notify(MarkedIncomplete $event): void
|
||||
{
|
||||
$this->printer()->testMarkedIncomplete($event);
|
||||
}
|
||||
},
|
||||
|
||||
new class($printer) extends Subscriber implements NoticeTriggeredSubscriber
|
||||
{
|
||||
public function notify(NoticeTriggered $event): void
|
||||
{
|
||||
$this->printer()->testNoticeTriggered($event);
|
||||
}
|
||||
},
|
||||
|
||||
new class($printer) extends Subscriber implements PassedSubscriber
|
||||
{
|
||||
public function notify(Passed $event): void
|
||||
{
|
||||
$this->printer()->testPassed($event);
|
||||
}
|
||||
},
|
||||
new class($printer) extends Subscriber implements SkippedSubscriber
|
||||
{
|
||||
public function notify(Skipped $event): void
|
||||
{
|
||||
$this->printer()->testSkipped($event);
|
||||
}
|
||||
},
|
||||
|
||||
new class($printer) extends Subscriber implements WarningTriggeredSubscriber
|
||||
{
|
||||
public function notify(WarningTriggered $event): void
|
||||
{
|
||||
$this->printer()->testWarningTriggered($event);
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
Facade::instance()->registerSubscribers(...$subscribers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the subscriber on PHPUnit's facade.
|
||||
*/
|
||||
public static function register(): void
|
||||
{
|
||||
$shouldRegister = self::$registered === false
|
||||
&& isset($_SERVER['COLLISION_PRINTER']);
|
||||
|
||||
if ($shouldRegister) {
|
||||
self::$registered = true;
|
||||
|
||||
Facade::instance()->registerSubscriber(new self());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
43
vendor/nunomaduro/collision/src/Adapters/Phpunit/Subscribers/Subscriber.php
vendored
Normal file
43
vendor/nunomaduro/collision/src/Adapters/Phpunit/Subscribers/Subscriber.php
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of Collision.
|
||||
*
|
||||
* (c) Nuno Maduro <enunomaduro@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace NunoMaduro\Collision\Adapters\Phpunit\Subscribers;
|
||||
|
||||
use NunoMaduro\Collision\Adapters\Phpunit\Printers\ReportablePrinter;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
abstract class Subscriber
|
||||
{
|
||||
/**
|
||||
* The printer instance.
|
||||
*/
|
||||
private ReportablePrinter $printer;
|
||||
|
||||
/**
|
||||
* Creates a new subscriber.
|
||||
*/
|
||||
public function __construct(ReportablePrinter $printer)
|
||||
{
|
||||
$this->printer = $printer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the printer instance.
|
||||
*/
|
||||
protected function printer(): ReportablePrinter
|
||||
{
|
||||
return $this->printer;
|
||||
}
|
||||
}
|
21
vendor/nunomaduro/collision/src/Adapters/Phpunit/Support/ResultReflection.php
vendored
Normal file
21
vendor/nunomaduro/collision/src/Adapters/Phpunit/Support/ResultReflection.php
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace NunoMaduro\Collision\Adapters\Phpunit\Support;
|
||||
|
||||
use PHPUnit\TestRunner\TestResult\TestResult;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class ResultReflection
|
||||
{
|
||||
/**
|
||||
* The number of processed tests.
|
||||
*/
|
||||
public static function numberOfTests(TestResult $testResult): int
|
||||
{
|
||||
return (fn () => $this->numberOfTests)->call($testResult);
|
||||
}
|
||||
}
|
324
vendor/nunomaduro/collision/src/Adapters/Phpunit/TestResult.php
vendored
Normal file
324
vendor/nunomaduro/collision/src/Adapters/Phpunit/TestResult.php
vendored
Normal file
@ -0,0 +1,324 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace NunoMaduro\Collision\Adapters\Phpunit;
|
||||
|
||||
use NunoMaduro\Collision\Contracts\Adapters\Phpunit\HasPrintableTestCaseName;
|
||||
use NunoMaduro\Collision\Exceptions\ShouldNotHappen;
|
||||
use PHPUnit\Event\Code\Test;
|
||||
use PHPUnit\Event\Code\TestMethod;
|
||||
use PHPUnit\Event\Code\Throwable;
|
||||
use PHPUnit\Event\Test\BeforeFirstTestMethodErrored;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class TestResult
|
||||
{
|
||||
public const FAIL = 'failed';
|
||||
|
||||
public const SKIPPED = 'skipped';
|
||||
|
||||
public const INCOMPLETE = 'incomplete';
|
||||
|
||||
public const TODO = 'todo';
|
||||
|
||||
public const RISKY = 'risky';
|
||||
|
||||
public const DEPRECATED = 'deprecated';
|
||||
|
||||
public const NOTICE = 'notice';
|
||||
|
||||
public const WARN = 'warnings';
|
||||
|
||||
public const RUNS = 'pending';
|
||||
|
||||
public const PASS = 'passed';
|
||||
|
||||
public string $id;
|
||||
|
||||
public string $testCaseName;
|
||||
|
||||
public string $description;
|
||||
|
||||
public string $type;
|
||||
|
||||
public string $compactIcon;
|
||||
|
||||
public string $icon;
|
||||
|
||||
public string $compactColor;
|
||||
|
||||
public string $color;
|
||||
|
||||
public float $duration;
|
||||
|
||||
public ?Throwable $throwable;
|
||||
|
||||
public string $warning = '';
|
||||
|
||||
public string $warningSource = '';
|
||||
|
||||
/**
|
||||
* Creates a new TestResult instance.
|
||||
*/
|
||||
private function __construct(string $id, string $testCaseName, string $description, string $type, string $icon, string $compactIcon, string $color, string $compactColor, Throwable $throwable = null)
|
||||
{
|
||||
$this->id = $id;
|
||||
$this->testCaseName = $testCaseName;
|
||||
$this->description = $description;
|
||||
$this->type = $type;
|
||||
$this->icon = $icon;
|
||||
$this->compactIcon = $compactIcon;
|
||||
$this->color = $color;
|
||||
$this->compactColor = $compactColor;
|
||||
$this->throwable = $throwable;
|
||||
|
||||
$this->duration = 0.0;
|
||||
|
||||
$asWarning = $this->type === TestResult::WARN
|
||||
|| $this->type === TestResult::RISKY
|
||||
|| $this->type === TestResult::SKIPPED
|
||||
|| $this->type === TestResult::DEPRECATED
|
||||
|| $this->type === TestResult::NOTICE
|
||||
|| $this->type === TestResult::INCOMPLETE;
|
||||
|
||||
if ($throwable instanceof Throwable && $asWarning) {
|
||||
if (in_array($this->type, [TestResult::DEPRECATED, TestResult::NOTICE])) {
|
||||
foreach (explode("\n", $throwable->stackTrace()) as $line) {
|
||||
if (strpos($line, 'vendor/nunomaduro/collision') === false) {
|
||||
$this->warningSource = str_replace(getcwd().'/', '', $line);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->warning .= trim((string) preg_replace("/\r|\n/", ' ', $throwable->message()));
|
||||
|
||||
// pest specific
|
||||
$this->warning = str_replace('__pest_evaluable_', '', $this->warning);
|
||||
$this->warning = str_replace('This test depends on "P\\', 'This test depends on "', $this->warning);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the telemetry information.
|
||||
*/
|
||||
public function setDuration(float $duration): void
|
||||
{
|
||||
$this->duration = $duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new test from the given test case.
|
||||
*/
|
||||
public static function fromTestCase(Test $test, string $type, Throwable $throwable = null): self
|
||||
{
|
||||
if (! $test instanceof TestMethod) {
|
||||
throw new ShouldNotHappen();
|
||||
}
|
||||
|
||||
if (is_subclass_of($test->className(), HasPrintableTestCaseName::class)) {
|
||||
$testCaseName = $test->className()::getPrintableTestCaseName();
|
||||
} else {
|
||||
$testCaseName = $test->className();
|
||||
}
|
||||
|
||||
$description = self::makeDescription($test);
|
||||
|
||||
$icon = self::makeIcon($type);
|
||||
|
||||
$compactIcon = self::makeCompactIcon($type);
|
||||
|
||||
$color = self::makeColor($type);
|
||||
|
||||
$compactColor = self::makeCompactColor($type);
|
||||
|
||||
return new self($test->id(), $testCaseName, $description, $type, $icon, $compactIcon, $color, $compactColor, $throwable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new test from the given Pest Parallel Test Case.
|
||||
*/
|
||||
public static function fromPestParallelTestCase(Test $test, string $type, Throwable $throwable = null): self
|
||||
{
|
||||
if (! $test instanceof TestMethod) {
|
||||
throw new ShouldNotHappen();
|
||||
}
|
||||
|
||||
if (is_subclass_of($test->className(), HasPrintableTestCaseName::class)) {
|
||||
$testCaseName = $test->className()::getPrintableTestCaseName();
|
||||
} else {
|
||||
$testCaseName = $test->className();
|
||||
}
|
||||
|
||||
if (is_subclass_of($test->className(), HasPrintableTestCaseName::class)) {
|
||||
$description = $test->testDox()->prettifiedMethodName();
|
||||
} else {
|
||||
$description = self::makeDescription($test);
|
||||
}
|
||||
|
||||
$icon = self::makeIcon($type);
|
||||
|
||||
$compactIcon = self::makeCompactIcon($type);
|
||||
|
||||
$color = self::makeColor($type);
|
||||
|
||||
$compactColor = self::makeCompactColor($type);
|
||||
|
||||
return new self($test->id(), $testCaseName, $description, $type, $icon, $compactIcon, $color, $compactColor, $throwable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new test from the given test case.
|
||||
*/
|
||||
public static function fromBeforeFirstTestMethodErrored(BeforeFirstTestMethodErrored $event): self
|
||||
{
|
||||
if (is_subclass_of($event->testClassName(), HasPrintableTestCaseName::class)) {
|
||||
$testCaseName = $event->testClassName()::getPrintableTestCaseName();
|
||||
} else {
|
||||
$testCaseName = $event->testClassName();
|
||||
}
|
||||
|
||||
$description = '';
|
||||
|
||||
$icon = self::makeIcon(self::FAIL);
|
||||
|
||||
$compactIcon = self::makeCompactIcon(self::FAIL);
|
||||
|
||||
$color = self::makeColor(self::FAIL);
|
||||
|
||||
$compactColor = self::makeCompactColor(self::FAIL);
|
||||
|
||||
return new self($testCaseName, $testCaseName, $description, self::FAIL, $icon, $compactIcon, $color, $compactColor, $event->throwable());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the test case description.
|
||||
*/
|
||||
public static function makeDescription(TestMethod $test): string
|
||||
{
|
||||
if (is_subclass_of($test->className(), HasPrintableTestCaseName::class)) {
|
||||
return $test->className()::getLatestPrintableTestCaseMethodName();
|
||||
}
|
||||
|
||||
$name = $test->name();
|
||||
|
||||
// First, lets replace underscore by spaces.
|
||||
$name = str_replace('_', ' ', $name);
|
||||
|
||||
// Then, replace upper cases by spaces.
|
||||
$name = (string) preg_replace('/([A-Z])/', ' $1', $name);
|
||||
|
||||
// Finally, if it starts with `test`, we remove it.
|
||||
$name = (string) preg_replace('/^test/', '', $name);
|
||||
|
||||
// Removes spaces
|
||||
$name = trim($name);
|
||||
|
||||
// Lower case everything
|
||||
$name = mb_strtolower($name);
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the test case icon.
|
||||
*/
|
||||
public static function makeIcon(string $type): string
|
||||
{
|
||||
switch ($type) {
|
||||
case self::FAIL:
|
||||
return '⨯';
|
||||
case self::SKIPPED:
|
||||
return '-';
|
||||
case self::DEPRECATED:
|
||||
case self::WARN:
|
||||
case self::RISKY:
|
||||
case self::NOTICE:
|
||||
return '!';
|
||||
case self::INCOMPLETE:
|
||||
return '…';
|
||||
case self::TODO:
|
||||
return '↓';
|
||||
case self::RUNS:
|
||||
return '•';
|
||||
default:
|
||||
return '✓';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the test case compact icon.
|
||||
*/
|
||||
public static function makeCompactIcon(string $type): string
|
||||
{
|
||||
switch ($type) {
|
||||
case self::FAIL:
|
||||
return '⨯';
|
||||
case self::SKIPPED:
|
||||
return 's';
|
||||
case self::DEPRECATED:
|
||||
case self::NOTICE:
|
||||
case self::WARN:
|
||||
case self::RISKY:
|
||||
return '!';
|
||||
case self::INCOMPLETE:
|
||||
return 'i';
|
||||
case self::TODO:
|
||||
return 't';
|
||||
case self::RUNS:
|
||||
return '•';
|
||||
default:
|
||||
return '.';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the test case compact color.
|
||||
*/
|
||||
public static function makeCompactColor(string $type): string
|
||||
{
|
||||
switch ($type) {
|
||||
case self::FAIL:
|
||||
return 'red';
|
||||
case self::DEPRECATED:
|
||||
case self::NOTICE:
|
||||
case self::SKIPPED:
|
||||
case self::INCOMPLETE:
|
||||
case self::RISKY:
|
||||
case self::WARN:
|
||||
case self::RUNS:
|
||||
return 'yellow';
|
||||
case self::TODO:
|
||||
return 'cyan';
|
||||
default:
|
||||
return 'gray';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the test case color.
|
||||
*/
|
||||
public static function makeColor(string $type): string
|
||||
{
|
||||
switch ($type) {
|
||||
case self::TODO:
|
||||
return 'cyan';
|
||||
case self::FAIL:
|
||||
return 'red';
|
||||
case self::DEPRECATED:
|
||||
case self::NOTICE:
|
||||
case self::SKIPPED:
|
||||
case self::INCOMPLETE:
|
||||
case self::RISKY:
|
||||
case self::WARN:
|
||||
case self::RUNS:
|
||||
return 'yellow';
|
||||
default:
|
||||
return 'green';
|
||||
}
|
||||
}
|
||||
}
|
40
vendor/nunomaduro/collision/src/ArgumentFormatter.php
vendored
Normal file
40
vendor/nunomaduro/collision/src/ArgumentFormatter.php
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace NunoMaduro\Collision;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @see \Tests\Unit\ArgumentFormatterTest
|
||||
*/
|
||||
final class ArgumentFormatter
|
||||
{
|
||||
private const MAX_STRING_LENGTH = 1000;
|
||||
|
||||
public function format(array $arguments, bool $recursive = true): string
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($arguments as $argument) {
|
||||
switch (true) {
|
||||
case is_string($argument):
|
||||
$result[] = '"'.(mb_strlen($argument) > self::MAX_STRING_LENGTH ? mb_substr($argument, 0, self::MAX_STRING_LENGTH).'...' : $argument).'"';
|
||||
break;
|
||||
case is_array($argument):
|
||||
$associative = array_keys($argument) !== range(0, count($argument) - 1);
|
||||
if ($recursive && $associative && count($argument) <= 5) {
|
||||
$result[] = '['.$this->format($argument, false).']';
|
||||
}
|
||||
break;
|
||||
case is_object($argument):
|
||||
$class = get_class($argument);
|
||||
$result[] = "Object($class)";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return implode(', ', $result);
|
||||
}
|
||||
}
|
236
vendor/nunomaduro/collision/src/ConsoleColor.php
vendored
Normal file
236
vendor/nunomaduro/collision/src/ConsoleColor.php
vendored
Normal file
@ -0,0 +1,236 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace NunoMaduro\Collision;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use NunoMaduro\Collision\Exceptions\InvalidStyleException;
|
||||
use NunoMaduro\Collision\Exceptions\ShouldNotHappen;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class ConsoleColor
|
||||
{
|
||||
public const FOREGROUND = 38;
|
||||
|
||||
public const BACKGROUND = 48;
|
||||
|
||||
public const COLOR256_REGEXP = '~^(bg_)?color_(\d{1,3})$~';
|
||||
|
||||
public const RESET_STYLE = 0;
|
||||
|
||||
private bool $forceStyle = false;
|
||||
|
||||
/** @var array */
|
||||
private const STYLES = [
|
||||
'none' => null,
|
||||
'bold' => '1',
|
||||
'dark' => '2',
|
||||
'italic' => '3',
|
||||
'underline' => '4',
|
||||
'blink' => '5',
|
||||
'reverse' => '7',
|
||||
'concealed' => '8',
|
||||
|
||||
'default' => '39',
|
||||
'black' => '30',
|
||||
'red' => '31',
|
||||
'green' => '32',
|
||||
'yellow' => '33',
|
||||
'blue' => '34',
|
||||
'magenta' => '35',
|
||||
'cyan' => '36',
|
||||
'light_gray' => '37',
|
||||
|
||||
'dark_gray' => '90',
|
||||
'light_red' => '91',
|
||||
'light_green' => '92',
|
||||
'light_yellow' => '93',
|
||||
'light_blue' => '94',
|
||||
'light_magenta' => '95',
|
||||
'light_cyan' => '96',
|
||||
'white' => '97',
|
||||
|
||||
'bg_default' => '49',
|
||||
'bg_black' => '40',
|
||||
'bg_red' => '41',
|
||||
'bg_green' => '42',
|
||||
'bg_yellow' => '43',
|
||||
'bg_blue' => '44',
|
||||
'bg_magenta' => '45',
|
||||
'bg_cyan' => '46',
|
||||
'bg_light_gray' => '47',
|
||||
|
||||
'bg_dark_gray' => '100',
|
||||
'bg_light_red' => '101',
|
||||
'bg_light_green' => '102',
|
||||
'bg_light_yellow' => '103',
|
||||
'bg_light_blue' => '104',
|
||||
'bg_light_magenta' => '105',
|
||||
'bg_light_cyan' => '106',
|
||||
'bg_white' => '107',
|
||||
];
|
||||
|
||||
private array $themes = [];
|
||||
|
||||
/**
|
||||
* @throws InvalidStyleException
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function apply(array|string $style, string $text): string
|
||||
{
|
||||
if (! $this->isStyleForced() && ! $this->isSupported()) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
if (is_string($style)) {
|
||||
$style = [$style];
|
||||
}
|
||||
if (! is_array($style)) {
|
||||
throw new InvalidArgumentException('Style must be string or array.');
|
||||
}
|
||||
|
||||
$sequences = [];
|
||||
|
||||
foreach ($style as $s) {
|
||||
if (isset($this->themes[$s])) {
|
||||
$sequences = array_merge($sequences, $this->themeSequence($s));
|
||||
} elseif ($this->isValidStyle($s)) {
|
||||
$sequences[] = $this->styleSequence($s);
|
||||
} else {
|
||||
throw new ShouldNotHappen();
|
||||
}
|
||||
}
|
||||
|
||||
$sequences = array_filter($sequences, function ($val) {
|
||||
return $val !== null;
|
||||
});
|
||||
|
||||
if (empty($sequences)) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
return $this->escSequence(implode(';', $sequences)).$text.$this->escSequence(self::RESET_STYLE);
|
||||
}
|
||||
|
||||
public function setForceStyle(bool $forceStyle): void
|
||||
{
|
||||
$this->forceStyle = $forceStyle;
|
||||
}
|
||||
|
||||
public function isStyleForced(): bool
|
||||
{
|
||||
return $this->forceStyle;
|
||||
}
|
||||
|
||||
public function setThemes(array $themes): void
|
||||
{
|
||||
$this->themes = [];
|
||||
foreach ($themes as $name => $styles) {
|
||||
$this->addTheme($name, $styles);
|
||||
}
|
||||
}
|
||||
|
||||
public function addTheme(string $name, array|string $styles): void
|
||||
{
|
||||
if (is_string($styles)) {
|
||||
$styles = [$styles];
|
||||
}
|
||||
if (! is_array($styles)) {
|
||||
throw new InvalidArgumentException('Style must be string or array.');
|
||||
}
|
||||
|
||||
foreach ($styles as $style) {
|
||||
if (! $this->isValidStyle($style)) {
|
||||
throw new InvalidStyleException($style);
|
||||
}
|
||||
}
|
||||
|
||||
$this->themes[$name] = $styles;
|
||||
}
|
||||
|
||||
public function getThemes(): array
|
||||
{
|
||||
return $this->themes;
|
||||
}
|
||||
|
||||
public function hasTheme(string $name): bool
|
||||
{
|
||||
return isset($this->themes[$name]);
|
||||
}
|
||||
|
||||
public function removeTheme(string $name): void
|
||||
{
|
||||
unset($this->themes[$name]);
|
||||
}
|
||||
|
||||
public function isSupported(): bool
|
||||
{
|
||||
// The COLLISION_FORCE_COLORS variable is for internal purposes only
|
||||
if (getenv('COLLISION_FORCE_COLORS') !== false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (DIRECTORY_SEPARATOR === '\\') {
|
||||
return getenv('ANSICON') !== false || getenv('ConEmuANSI') === 'ON';
|
||||
}
|
||||
|
||||
return function_exists('posix_isatty') && @posix_isatty(STDOUT);
|
||||
}
|
||||
|
||||
public function are256ColorsSupported(): bool
|
||||
{
|
||||
if (DIRECTORY_SEPARATOR === '\\') {
|
||||
return function_exists('sapi_windows_vt100_support') && @sapi_windows_vt100_support(STDOUT);
|
||||
}
|
||||
|
||||
return strpos((string) getenv('TERM'), '256color') !== false;
|
||||
}
|
||||
|
||||
public function getPossibleStyles(): array
|
||||
{
|
||||
return array_keys(self::STYLES);
|
||||
}
|
||||
|
||||
private function themeSequence(string $name): array
|
||||
{
|
||||
$sequences = [];
|
||||
foreach ($this->themes[$name] as $style) {
|
||||
$sequences[] = $this->styleSequence($style);
|
||||
}
|
||||
|
||||
return $sequences;
|
||||
}
|
||||
|
||||
private function styleSequence(string $style): ?string
|
||||
{
|
||||
if (array_key_exists($style, self::STYLES)) {
|
||||
return self::STYLES[$style];
|
||||
}
|
||||
|
||||
if (! $this->are256ColorsSupported()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
preg_match(self::COLOR256_REGEXP, $style, $matches);
|
||||
|
||||
$type = $matches[1] === 'bg_' ? self::BACKGROUND : self::FOREGROUND;
|
||||
$value = $matches[2];
|
||||
|
||||
return "$type;5;$value";
|
||||
}
|
||||
|
||||
private function isValidStyle(string $style): bool
|
||||
{
|
||||
return array_key_exists($style, self::STYLES) || preg_match(self::COLOR256_REGEXP, $style);
|
||||
}
|
||||
|
||||
private function escSequence(string|int $value): string
|
||||
{
|
||||
return "\033[{$value}m";
|
||||
}
|
||||
}
|
26
vendor/nunomaduro/collision/src/Contracts/Adapters/Phpunit/HasPrintableTestCaseName.php
vendored
Normal file
26
vendor/nunomaduro/collision/src/Contracts/Adapters/Phpunit/HasPrintableTestCaseName.php
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace NunoMaduro\Collision\Contracts\Adapters\Phpunit;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
interface HasPrintableTestCaseName
|
||||
{
|
||||
/**
|
||||
* The printable test case name.
|
||||
*/
|
||||
public static function getPrintableTestCaseName(): string;
|
||||
|
||||
/**
|
||||
* The printable test case method name.
|
||||
*/
|
||||
public function getPrintableTestCaseMethodName(): string;
|
||||
|
||||
/**
|
||||
* The "latest" printable test case method name.
|
||||
*/
|
||||
public static function getLatestPrintableTestCaseMethodName(): string;
|
||||
}
|
13
vendor/nunomaduro/collision/src/Contracts/RenderableOnCollisionEditor.php
vendored
Normal file
13
vendor/nunomaduro/collision/src/Contracts/RenderableOnCollisionEditor.php
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace NunoMaduro\Collision\Contracts;
|
||||
|
||||
use Whoops\Exception\Frame;
|
||||
|
||||
interface RenderableOnCollisionEditor
|
||||
{
|
||||
/**
|
||||
* Returns the frame to be used on the Collision Editor.
|
||||
*/
|
||||
public function toCollisionEditor(): Frame;
|
||||
}
|
12
vendor/nunomaduro/collision/src/Contracts/RenderlessEditor.php
vendored
Normal file
12
vendor/nunomaduro/collision/src/Contracts/RenderlessEditor.php
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace NunoMaduro\Collision\Contracts;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
interface RenderlessEditor
|
||||
{
|
||||
}
|
12
vendor/nunomaduro/collision/src/Contracts/RenderlessTrace.php
vendored
Normal file
12
vendor/nunomaduro/collision/src/Contracts/RenderlessTrace.php
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace NunoMaduro\Collision\Contracts;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
interface RenderlessTrace
|
||||
{
|
||||
}
|
21
vendor/nunomaduro/collision/src/Contracts/SolutionsRepository.php
vendored
Normal file
21
vendor/nunomaduro/collision/src/Contracts/SolutionsRepository.php
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace NunoMaduro\Collision\Contracts;
|
||||
|
||||
use Spatie\Ignition\Contracts\Solution;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
interface SolutionsRepository
|
||||
{
|
||||
/**
|
||||
* Gets the solutions from the given `$throwable`.
|
||||
*
|
||||
* @return array<int, Solution>
|
||||
*/
|
||||
public function getFromThrowable(Throwable $throwable): array;
|
||||
}
|
202
vendor/nunomaduro/collision/src/Coverage.php
vendored
Normal file
202
vendor/nunomaduro/collision/src/Coverage.php
vendored
Normal file
@ -0,0 +1,202 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace NunoMaduro\Collision;
|
||||
|
||||
use SebastianBergmann\CodeCoverage\CodeCoverage;
|
||||
use SebastianBergmann\CodeCoverage\Node\Directory;
|
||||
use SebastianBergmann\CodeCoverage\Node\File;
|
||||
use SebastianBergmann\Environment\Runtime;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
use function Termwind\render;
|
||||
use function Termwind\renderUsing;
|
||||
use function Termwind\terminal;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Coverage
|
||||
{
|
||||
/**
|
||||
* Returns the coverage path.
|
||||
*/
|
||||
public static function getPath(): string
|
||||
{
|
||||
return implode(DIRECTORY_SEPARATOR, [
|
||||
dirname(__DIR__),
|
||||
'.temp',
|
||||
'coverage',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs true there is any code coverage driver available.
|
||||
*/
|
||||
public static function isAvailable(): bool
|
||||
{
|
||||
$runtime = new Runtime();
|
||||
|
||||
if (! $runtime->canCollectCodeCoverage()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($runtime->hasPCOV() || $runtime->hasPHPDBGCodeCoverage()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (self::usingXdebug()) {
|
||||
$mode = getenv('XDEBUG_MODE') ?: ini_get('xdebug.mode');
|
||||
|
||||
return $mode && in_array('coverage', explode(',', $mode), true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the user is using Xdebug.
|
||||
*/
|
||||
public static function usingXdebug(): bool
|
||||
{
|
||||
return (new Runtime())->hasXdebug();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports the code coverage report to the
|
||||
* console and returns the result in float.
|
||||
*/
|
||||
public static function report(OutputInterface $output): float
|
||||
{
|
||||
if (! file_exists($reportPath = self::getPath())) {
|
||||
if (self::usingXdebug()) {
|
||||
$output->writeln(
|
||||
" <fg=black;bg=yellow;options=bold> WARN </> Unable to get coverage using Xdebug. Did you set <href=https://xdebug.org/docs/code_coverage#mode>Xdebug's coverage mode</>?</>",
|
||||
);
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
$output->writeln(
|
||||
' <fg=black;bg=yellow;options=bold> WARN </> No coverage driver detected.</> Did you install <href=https://xdebug.org/>Xdebug</> or <href=https://github.com/krakjoe/pcov>PCOV</>?',
|
||||
);
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
/** @var CodeCoverage $codeCoverage */
|
||||
$codeCoverage = require $reportPath;
|
||||
unlink($reportPath);
|
||||
|
||||
$totalCoverage = $codeCoverage->getReport()->percentageOfExecutedLines();
|
||||
|
||||
/** @var Directory<File|Directory> $report */
|
||||
$report = $codeCoverage->getReport();
|
||||
|
||||
foreach ($report->getIterator() as $file) {
|
||||
if (! $file instanceof File) {
|
||||
continue;
|
||||
}
|
||||
$dirname = dirname($file->id());
|
||||
$basename = basename($file->id(), '.php');
|
||||
|
||||
$name = $dirname === '.' ? $basename : implode(DIRECTORY_SEPARATOR, [
|
||||
$dirname,
|
||||
$basename,
|
||||
]);
|
||||
|
||||
$percentage = $file->numberOfExecutableLines() === 0
|
||||
? '100.0'
|
||||
: number_format($file->percentageOfExecutedLines()->asFloat(), 1, '.', '');
|
||||
|
||||
$uncoveredLines = '';
|
||||
|
||||
$percentageOfExecutedLinesAsString = $file->percentageOfExecutedLines()->asString();
|
||||
|
||||
if (! in_array($percentageOfExecutedLinesAsString, ['0.00%', '100.00%', '100.0%', ''], true)) {
|
||||
$uncoveredLines = trim(implode(', ', self::getMissingCoverage($file)));
|
||||
$uncoveredLines = sprintf('<span>%s</span>', $uncoveredLines).' <span class="text-gray"> / </span>';
|
||||
}
|
||||
|
||||
$color = $percentage === '100.0' ? 'green' : ($percentage === '0.0' ? 'red' : 'yellow');
|
||||
|
||||
$truncateAt = max(1, terminal()->width() - 12);
|
||||
|
||||
renderUsing($output);
|
||||
render(<<<HTML
|
||||
<div class="flex mx-2">
|
||||
<span class="truncate-{$truncateAt}">{$name}</span>
|
||||
<span class="flex-1 content-repeat-[.] text-gray mx-1"></span>
|
||||
<span class="text-{$color}">$uncoveredLines {$percentage}%</span>
|
||||
</div>
|
||||
HTML);
|
||||
}
|
||||
|
||||
$totalCoverageAsString = $totalCoverage->asFloat() === 0.0
|
||||
? '0.0'
|
||||
: number_format($totalCoverage->asFloat(), 1, '.', '');
|
||||
|
||||
renderUsing($output);
|
||||
render(<<<HTML
|
||||
<div class="mx-2">
|
||||
<hr class="text-gray" />
|
||||
<div class="w-full text-right">
|
||||
<span class="ml-1 font-bold">Total: {$totalCoverageAsString} %</span>
|
||||
</div>
|
||||
</div>
|
||||
HTML);
|
||||
|
||||
return $totalCoverage->asFloat();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an array of missing coverage on the following format:.
|
||||
*
|
||||
* ```
|
||||
* ['11', '20..25', '50', '60..80'];
|
||||
* ```
|
||||
*
|
||||
* @param File $file
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public static function getMissingCoverage($file): array
|
||||
{
|
||||
$shouldBeNewLine = true;
|
||||
|
||||
$eachLine = function (array $array, array $tests, int $line) use (&$shouldBeNewLine): array {
|
||||
if ($tests !== []) {
|
||||
$shouldBeNewLine = true;
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
if ($shouldBeNewLine) {
|
||||
$array[] = (string) $line;
|
||||
$shouldBeNewLine = false;
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
$lastKey = count($array) - 1;
|
||||
|
||||
if (array_key_exists($lastKey, $array) && str_contains((string) $array[$lastKey], '..')) {
|
||||
[$from] = explode('..', (string) $array[$lastKey]);
|
||||
$array[$lastKey] = $line > $from ? sprintf('%s..%s', $from, $line) : sprintf('%s..%s', $line, $from);
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
$array[$lastKey] = sprintf('%s..%s', $array[$lastKey], $line);
|
||||
|
||||
return $array;
|
||||
};
|
||||
|
||||
$array = [];
|
||||
foreach (array_filter($file->lineCoverageData(), 'is_array') as $line => $tests) {
|
||||
$array = $eachLine($array, $tests, $line);
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
}
|
15
vendor/nunomaduro/collision/src/Exceptions/InvalidStyleException.php
vendored
Normal file
15
vendor/nunomaduro/collision/src/Exceptions/InvalidStyleException.php
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace NunoMaduro\Collision\Exceptions;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class InvalidStyleException extends RuntimeException
|
||||
{
|
||||
// ...
|
||||
}
|
26
vendor/nunomaduro/collision/src/Exceptions/ShouldNotHappen.php
vendored
Normal file
26
vendor/nunomaduro/collision/src/Exceptions/ShouldNotHappen.php
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace NunoMaduro\Collision\Exceptions;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class ShouldNotHappen extends RuntimeException
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private const MESSAGE = 'This should not happen, please open an issue on collision repository: %s';
|
||||
|
||||
/**
|
||||
* Creates a new Exception instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(sprintf(self::MESSAGE, 'https://github.com/nunomaduro/collision/issues/new'));
|
||||
}
|
||||
}
|
193
vendor/nunomaduro/collision/src/Exceptions/TestException.php
vendored
Normal file
193
vendor/nunomaduro/collision/src/Exceptions/TestException.php
vendored
Normal file
@ -0,0 +1,193 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace NunoMaduro\Collision\Exceptions;
|
||||
|
||||
use PHPUnit\Event\Code\Throwable;
|
||||
use PHPUnit\Framework\ExpectationFailedException;
|
||||
use ReflectionClass;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class TestException
|
||||
{
|
||||
private const DIFF_SEPARATOR = '--- Expected'.PHP_EOL.'+++ Actual'.PHP_EOL.'@@ @@'.PHP_EOL;
|
||||
|
||||
/**
|
||||
* Creates a new Exception instance.
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly Throwable $throwable,
|
||||
private readonly bool $isVerbose
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
||||
public function getThrowable(): Throwable
|
||||
{
|
||||
return $this->throwable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return class-string
|
||||
*/
|
||||
public function getClassName(): string
|
||||
{
|
||||
return $this->throwable->className();
|
||||
}
|
||||
|
||||
public function getMessage(): string
|
||||
{
|
||||
if ($this->throwable->className() === ExpectationFailedException::class) {
|
||||
$message = $this->throwable->description();
|
||||
} else {
|
||||
$message = $this->throwable->message();
|
||||
}
|
||||
|
||||
$regexes = [
|
||||
'To contain' => '/Failed asserting that \'(.*)\' contains "(.*)"\./s',
|
||||
'Not to contain' => '/Failed asserting that \'(.*)\' does not contain "(.*)"\./s',
|
||||
];
|
||||
|
||||
foreach ($regexes as $key => $pattern) {
|
||||
preg_match($pattern, $message, $matches, PREG_OFFSET_CAPTURE, 0);
|
||||
|
||||
if (count($matches) === 3) {
|
||||
$message = $this->shortenMessage($matches, $key);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Diffs...
|
||||
if (str_contains($message, self::DIFF_SEPARATOR)) {
|
||||
$diff = '';
|
||||
$lines = explode(PHP_EOL, explode(self::DIFF_SEPARATOR, $message)[1]);
|
||||
|
||||
foreach ($lines as $line) {
|
||||
$diff .= $this->colorizeLine($line, str_starts_with($line, '-') ? 'red' : 'green').PHP_EOL;
|
||||
}
|
||||
|
||||
$message = str_replace(explode(self::DIFF_SEPARATOR, $message)[1], $diff, $message);
|
||||
$message = str_replace(self::DIFF_SEPARATOR, '', $message);
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
private function shortenMessage(array $matches, string $key): string
|
||||
{
|
||||
$actual = $matches[1][0];
|
||||
$expected = $matches[2][0];
|
||||
|
||||
$actualExploded = explode(PHP_EOL, $actual);
|
||||
$expectedExploded = explode(PHP_EOL, $expected);
|
||||
|
||||
if (($countActual = count($actualExploded)) > 4 && ! $this->isVerbose) {
|
||||
$actualExploded = array_slice($actualExploded, 0, 3);
|
||||
}
|
||||
|
||||
if (($countExpected = count($expectedExploded)) > 4 && ! $this->isVerbose) {
|
||||
$expectedExploded = array_slice($expectedExploded, 0, 3);
|
||||
}
|
||||
|
||||
$actualAsString = '';
|
||||
$expectedAsString = '';
|
||||
foreach ($actualExploded as $line) {
|
||||
$actualAsString .= PHP_EOL.$this->colorizeLine($line, 'red');
|
||||
}
|
||||
|
||||
foreach ($expectedExploded as $line) {
|
||||
$expectedAsString .= PHP_EOL.$this->colorizeLine($line, 'green');
|
||||
}
|
||||
|
||||
if ($countActual > 4 && ! $this->isVerbose) {
|
||||
$actualAsString .= PHP_EOL.$this->colorizeLine(sprintf('... (%s more lines)', $countActual - 3), 'gray');
|
||||
}
|
||||
|
||||
if ($countExpected > 4 && ! $this->isVerbose) {
|
||||
$expectedAsString .= PHP_EOL.$this->colorizeLine(sprintf('... (%s more lines)', $countExpected - 3), 'gray');
|
||||
}
|
||||
|
||||
return implode(PHP_EOL, [
|
||||
'Expected: '.ltrim($actualAsString, PHP_EOL.' '),
|
||||
'',
|
||||
' '.$key.': '.ltrim($expectedAsString, PHP_EOL.' '),
|
||||
'',
|
||||
]);
|
||||
}
|
||||
|
||||
public function getCode(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public function getFile(): string
|
||||
{
|
||||
if (! isset($this->getTrace()[0])) {
|
||||
return (string) (new ReflectionClass($this->getClassName()))->getFileName();
|
||||
}
|
||||
|
||||
return $this->getTrace()[0]['file'];
|
||||
}
|
||||
|
||||
public function getLine(): int
|
||||
{
|
||||
if (! isset($this->getTrace()[0])) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (int) $this->getTrace()[0]['line'];
|
||||
}
|
||||
|
||||
public function getTrace(): array
|
||||
{
|
||||
$frames = explode("\n", $this->getTraceAsString());
|
||||
|
||||
$frames = array_filter($frames, fn ($trace) => $trace !== '');
|
||||
|
||||
return array_map(function ($trace) {
|
||||
if (trim($trace) === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$parts = explode(':', $trace);
|
||||
$line = array_pop($parts);
|
||||
$file = implode(':', $parts);
|
||||
|
||||
return [
|
||||
'file' => $file,
|
||||
'line' => $line,
|
||||
];
|
||||
}, $frames);
|
||||
}
|
||||
|
||||
public function getTraceAsString(): string
|
||||
{
|
||||
return $this->throwable->stackTrace();
|
||||
}
|
||||
|
||||
public function getPrevious(): ?self
|
||||
{
|
||||
if ($this->throwable->hasPrevious()) {
|
||||
return new self($this->throwable->previous(), $this->isVerbose);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return $this->getMessage();
|
||||
}
|
||||
|
||||
private function colorizeLine(string $line, string $color): string
|
||||
{
|
||||
return sprintf(' <fg=%s>%s</>', $color, $line);
|
||||
}
|
||||
}
|
15
vendor/nunomaduro/collision/src/Exceptions/TestOutcome.php
vendored
Normal file
15
vendor/nunomaduro/collision/src/Exceptions/TestOutcome.php
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace NunoMaduro\Collision\Exceptions;
|
||||
|
||||
use PHPUnit\Framework\Exception;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class TestOutcome extends Exception
|
||||
{
|
||||
// ...
|
||||
}
|
57
vendor/nunomaduro/collision/src/Handler.php
vendored
Normal file
57
vendor/nunomaduro/collision/src/Handler.php
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace NunoMaduro\Collision;
|
||||
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Whoops\Handler\Handler as AbstractHandler;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @see \Tests\Unit\HandlerTest
|
||||
*/
|
||||
final class Handler extends AbstractHandler
|
||||
{
|
||||
/**
|
||||
* Holds an instance of the writer.
|
||||
*/
|
||||
private Writer $writer;
|
||||
|
||||
/**
|
||||
* Creates an instance of the Handler.
|
||||
*/
|
||||
public function __construct(Writer $writer = null)
|
||||
{
|
||||
$this->writer = $writer ?: new Writer();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$this->writer->write($this->getInspector()); // @phpstan-ignore-line
|
||||
|
||||
return self::QUIT;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setOutput(OutputInterface $output): self
|
||||
{
|
||||
$this->writer->setOutput($output);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getWriter(): Writer
|
||||
{
|
||||
return $this->writer;
|
||||
}
|
||||
}
|
289
vendor/nunomaduro/collision/src/Highlighter.php
vendored
Normal file
289
vendor/nunomaduro/collision/src/Highlighter.php
vendored
Normal file
@ -0,0 +1,289 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace NunoMaduro\Collision;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Highlighter
|
||||
{
|
||||
public const TOKEN_DEFAULT = 'token_default';
|
||||
|
||||
public const TOKEN_COMMENT = 'token_comment';
|
||||
|
||||
public const TOKEN_STRING = 'token_string';
|
||||
|
||||
public const TOKEN_HTML = 'token_html';
|
||||
|
||||
public const TOKEN_KEYWORD = 'token_keyword';
|
||||
|
||||
public const ACTUAL_LINE_MARK = 'actual_line_mark';
|
||||
|
||||
public const LINE_NUMBER = 'line_number';
|
||||
|
||||
private const ARROW_SYMBOL = '>';
|
||||
|
||||
private const DELIMITER = '|';
|
||||
|
||||
private const ARROW_SYMBOL_UTF8 = '➜';
|
||||
|
||||
private const DELIMITER_UTF8 = '▕'; // '▶';
|
||||
|
||||
private const LINE_NUMBER_DIVIDER = 'line_divider';
|
||||
|
||||
private const MARKED_LINE_NUMBER = 'marked_line';
|
||||
|
||||
private const WIDTH = 3;
|
||||
|
||||
/**
|
||||
* Holds the theme.
|
||||
*/
|
||||
private const THEME = [
|
||||
self::TOKEN_STRING => ['light_gray'],
|
||||
self::TOKEN_COMMENT => ['dark_gray', 'italic'],
|
||||
self::TOKEN_KEYWORD => ['magenta', 'bold'],
|
||||
self::TOKEN_DEFAULT => ['default', 'bold'],
|
||||
self::TOKEN_HTML => ['blue', 'bold'],
|
||||
|
||||
self::ACTUAL_LINE_MARK => ['red', 'bold'],
|
||||
self::LINE_NUMBER => ['dark_gray'],
|
||||
self::MARKED_LINE_NUMBER => ['italic', 'bold'],
|
||||
self::LINE_NUMBER_DIVIDER => ['dark_gray'],
|
||||
];
|
||||
|
||||
private ConsoleColor $color;
|
||||
|
||||
private const DEFAULT_THEME = [
|
||||
self::TOKEN_STRING => 'red',
|
||||
self::TOKEN_COMMENT => 'yellow',
|
||||
self::TOKEN_KEYWORD => 'green',
|
||||
self::TOKEN_DEFAULT => 'default',
|
||||
self::TOKEN_HTML => 'cyan',
|
||||
|
||||
self::ACTUAL_LINE_MARK => 'dark_gray',
|
||||
self::LINE_NUMBER => 'dark_gray',
|
||||
self::MARKED_LINE_NUMBER => 'dark_gray',
|
||||
self::LINE_NUMBER_DIVIDER => 'dark_gray',
|
||||
];
|
||||
|
||||
private string $delimiter = self::DELIMITER_UTF8;
|
||||
|
||||
private string $arrow = self::ARROW_SYMBOL_UTF8;
|
||||
|
||||
private const NO_MARK = ' ';
|
||||
|
||||
/**
|
||||
* Creates an instance of the Highlighter.
|
||||
*/
|
||||
public function __construct(ConsoleColor $color = null, bool $UTF8 = true)
|
||||
{
|
||||
$this->color = $color ?: new ConsoleColor();
|
||||
|
||||
foreach (self::DEFAULT_THEME as $name => $styles) {
|
||||
if (! $this->color->hasTheme($name)) {
|
||||
$this->color->addTheme($name, $styles);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (self::THEME as $name => $styles) {
|
||||
$this->color->addTheme($name, $styles);
|
||||
}
|
||||
if (! $UTF8) {
|
||||
$this->delimiter = self::DELIMITER;
|
||||
$this->arrow = self::ARROW_SYMBOL;
|
||||
}
|
||||
$this->delimiter .= ' ';
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlights the provided content.
|
||||
*/
|
||||
public function highlight(string $content, int $line): string
|
||||
{
|
||||
return rtrim($this->getCodeSnippet($content, $line, 4, 4));
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlights the provided content.
|
||||
*/
|
||||
public function getCodeSnippet(string $source, int $lineNumber, int $linesBefore = 2, int $linesAfter = 2): string
|
||||
{
|
||||
$tokenLines = $this->getHighlightedLines($source);
|
||||
|
||||
$offset = $lineNumber - $linesBefore - 1;
|
||||
$offset = max($offset, 0);
|
||||
$length = $linesAfter + $linesBefore + 1;
|
||||
$tokenLines = array_slice($tokenLines, $offset, $length, $preserveKeys = true);
|
||||
|
||||
$lines = $this->colorLines($tokenLines);
|
||||
|
||||
return $this->lineNumbers($lines, $lineNumber);
|
||||
}
|
||||
|
||||
private function getHighlightedLines(string $source): array
|
||||
{
|
||||
$source = str_replace(["\r\n", "\r"], "\n", $source);
|
||||
$tokens = $this->tokenize($source);
|
||||
|
||||
return $this->splitToLines($tokens);
|
||||
}
|
||||
|
||||
private function tokenize(string $source): array
|
||||
{
|
||||
$tokens = token_get_all($source);
|
||||
|
||||
$output = [];
|
||||
$currentType = null;
|
||||
$buffer = '';
|
||||
$newType = null;
|
||||
|
||||
foreach ($tokens as $token) {
|
||||
if (is_array($token)) {
|
||||
switch ($token[0]) {
|
||||
case T_WHITESPACE:
|
||||
break;
|
||||
|
||||
case T_OPEN_TAG:
|
||||
case T_OPEN_TAG_WITH_ECHO:
|
||||
case T_CLOSE_TAG:
|
||||
case T_STRING:
|
||||
case T_VARIABLE:
|
||||
// Constants
|
||||
case T_DIR:
|
||||
case T_FILE:
|
||||
case T_METHOD_C:
|
||||
case T_DNUMBER:
|
||||
case T_LNUMBER:
|
||||
case T_NS_C:
|
||||
case T_LINE:
|
||||
case T_CLASS_C:
|
||||
case T_FUNC_C:
|
||||
case T_TRAIT_C:
|
||||
$newType = self::TOKEN_DEFAULT;
|
||||
break;
|
||||
|
||||
case T_COMMENT:
|
||||
case T_DOC_COMMENT:
|
||||
$newType = self::TOKEN_COMMENT;
|
||||
break;
|
||||
|
||||
case T_ENCAPSED_AND_WHITESPACE:
|
||||
case T_CONSTANT_ENCAPSED_STRING:
|
||||
$newType = self::TOKEN_STRING;
|
||||
break;
|
||||
|
||||
case T_INLINE_HTML:
|
||||
$newType = self::TOKEN_HTML;
|
||||
break;
|
||||
|
||||
default:
|
||||
$newType = self::TOKEN_KEYWORD;
|
||||
}
|
||||
} else {
|
||||
$newType = $token === '"' ? self::TOKEN_STRING : self::TOKEN_KEYWORD;
|
||||
}
|
||||
|
||||
if ($currentType === null) {
|
||||
$currentType = $newType;
|
||||
}
|
||||
|
||||
if ($currentType !== $newType) {
|
||||
$output[] = [$currentType, $buffer];
|
||||
$buffer = '';
|
||||
$currentType = $newType;
|
||||
}
|
||||
|
||||
$buffer .= is_array($token) ? $token[1] : $token;
|
||||
}
|
||||
|
||||
if (isset($newType)) {
|
||||
$output[] = [$newType, $buffer];
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
private function splitToLines(array $tokens): array
|
||||
{
|
||||
$lines = [];
|
||||
|
||||
$line = [];
|
||||
foreach ($tokens as $token) {
|
||||
foreach (explode("\n", $token[1]) as $count => $tokenLine) {
|
||||
if ($count > 0) {
|
||||
$lines[] = $line;
|
||||
$line = [];
|
||||
}
|
||||
|
||||
if ($tokenLine === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$line[] = [$token[0], $tokenLine];
|
||||
}
|
||||
}
|
||||
|
||||
$lines[] = $line;
|
||||
|
||||
return $lines;
|
||||
}
|
||||
|
||||
private function colorLines(array $tokenLines): array
|
||||
{
|
||||
$lines = [];
|
||||
foreach ($tokenLines as $lineCount => $tokenLine) {
|
||||
$line = '';
|
||||
foreach ($tokenLine as $token) {
|
||||
[$tokenType, $tokenValue] = $token;
|
||||
if ($this->color->hasTheme($tokenType)) {
|
||||
$line .= $this->color->apply($tokenType, $tokenValue);
|
||||
} else {
|
||||
$line .= $tokenValue;
|
||||
}
|
||||
}
|
||||
$lines[$lineCount] = $line;
|
||||
}
|
||||
|
||||
return $lines;
|
||||
}
|
||||
|
||||
private function lineNumbers(array $lines, int $markLine = null): string
|
||||
{
|
||||
$lineStrlen = strlen((string) ((int) array_key_last($lines) + 1));
|
||||
$lineStrlen = $lineStrlen < self::WIDTH ? self::WIDTH : $lineStrlen;
|
||||
$snippet = '';
|
||||
$mark = ' '.$this->arrow.' ';
|
||||
foreach ($lines as $i => $line) {
|
||||
$coloredLineNumber = $this->coloredLineNumber(self::LINE_NUMBER, $i, $lineStrlen);
|
||||
|
||||
if ($markLine !== null) {
|
||||
$snippet .=
|
||||
($markLine === $i + 1
|
||||
? $this->color->apply(self::ACTUAL_LINE_MARK, $mark)
|
||||
: self::NO_MARK
|
||||
);
|
||||
|
||||
$coloredLineNumber =
|
||||
($markLine === $i + 1 ?
|
||||
$this->coloredLineNumber(self::MARKED_LINE_NUMBER, $i, $lineStrlen) :
|
||||
$coloredLineNumber
|
||||
);
|
||||
}
|
||||
$snippet .= $coloredLineNumber;
|
||||
|
||||
$snippet .=
|
||||
$this->color->apply(self::LINE_NUMBER_DIVIDER, $this->delimiter);
|
||||
|
||||
$snippet .= $line.PHP_EOL;
|
||||
}
|
||||
|
||||
return $snippet;
|
||||
}
|
||||
|
||||
private function coloredLineNumber(string $style, int $i, int $length): string
|
||||
{
|
||||
return $this->color->apply($style, str_pad((string) ($i + 1), $length, ' ', STR_PAD_LEFT));
|
||||
}
|
||||
}
|
54
vendor/nunomaduro/collision/src/Provider.php
vendored
Normal file
54
vendor/nunomaduro/collision/src/Provider.php
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace NunoMaduro\Collision;
|
||||
|
||||
use Whoops\Run;
|
||||
use Whoops\RunInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @see \Tests\Unit\ProviderTest
|
||||
*/
|
||||
final class Provider
|
||||
{
|
||||
/**
|
||||
* Holds an instance of the Run.
|
||||
*/
|
||||
private RunInterface $run;
|
||||
|
||||
/**
|
||||
* Holds an instance of the handler.
|
||||
*/
|
||||
private Handler $handler;
|
||||
|
||||
/**
|
||||
* Creates a new instance of the Provider.
|
||||
*/
|
||||
public function __construct(RunInterface $run = null, Handler $handler = null)
|
||||
{
|
||||
$this->run = $run ?: new Run();
|
||||
$this->handler = $handler ?: new Handler();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the current Handler as Error Handler.
|
||||
*/
|
||||
public function register(): self
|
||||
{
|
||||
$this->run->pushHandler($this->handler)
|
||||
->register();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the handler.
|
||||
*/
|
||||
public function getHandler(): Handler
|
||||
{
|
||||
return $this->handler;
|
||||
}
|
||||
}
|
22
vendor/nunomaduro/collision/src/SolutionsRepositories/NullSolutionsRepository.php
vendored
Normal file
22
vendor/nunomaduro/collision/src/SolutionsRepositories/NullSolutionsRepository.php
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace NunoMaduro\Collision\SolutionsRepositories;
|
||||
|
||||
use NunoMaduro\Collision\Contracts\SolutionsRepository;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class NullSolutionsRepository implements SolutionsRepository
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFromThrowable(Throwable $throwable): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
352
vendor/nunomaduro/collision/src/Writer.php
vendored
Normal file
352
vendor/nunomaduro/collision/src/Writer.php
vendored
Normal file
@ -0,0 +1,352 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace NunoMaduro\Collision;
|
||||
|
||||
use Closure;
|
||||
use NunoMaduro\Collision\Contracts\RenderableOnCollisionEditor;
|
||||
use NunoMaduro\Collision\Contracts\RenderlessEditor;
|
||||
use NunoMaduro\Collision\Contracts\RenderlessTrace;
|
||||
use NunoMaduro\Collision\Contracts\SolutionsRepository;
|
||||
use NunoMaduro\Collision\Exceptions\TestException;
|
||||
use NunoMaduro\Collision\SolutionsRepositories\NullSolutionsRepository;
|
||||
use Symfony\Component\Console\Output\ConsoleOutput;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Throwable;
|
||||
use Whoops\Exception\Frame;
|
||||
use Whoops\Exception\Inspector;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @see \Tests\Unit\WriterTest
|
||||
*/
|
||||
final class Writer
|
||||
{
|
||||
/**
|
||||
* The number of frames if no verbosity is specified.
|
||||
*/
|
||||
public const VERBOSITY_NORMAL_FRAMES = 1;
|
||||
|
||||
/**
|
||||
* Holds an instance of the solutions repository.
|
||||
*/
|
||||
private SolutionsRepository $solutionsRepository;
|
||||
|
||||
/**
|
||||
* Holds an instance of the Output.
|
||||
*/
|
||||
private OutputInterface $output;
|
||||
|
||||
/**
|
||||
* Holds an instance of the Argument Formatter.
|
||||
*/
|
||||
private ArgumentFormatter $argumentFormatter;
|
||||
|
||||
/**
|
||||
* Holds an instance of the Highlighter.
|
||||
*/
|
||||
private Highlighter $highlighter;
|
||||
|
||||
/**
|
||||
* Ignores traces where the file string matches one
|
||||
* of the provided regex expressions.
|
||||
*
|
||||
* @var array<int, string|Closure>
|
||||
*/
|
||||
private array $ignore = [];
|
||||
|
||||
/**
|
||||
* Declares whether or not the trace should appear.
|
||||
*/
|
||||
private bool $showTrace = true;
|
||||
|
||||
/**
|
||||
* Declares whether or not the title should appear.
|
||||
*/
|
||||
private bool $showTitle = true;
|
||||
|
||||
/**
|
||||
* Declares whether the editor should appear.
|
||||
*/
|
||||
private bool $showEditor = true;
|
||||
|
||||
/**
|
||||
* Creates an instance of the writer.
|
||||
*/
|
||||
public function __construct(
|
||||
SolutionsRepository $solutionsRepository = null,
|
||||
OutputInterface $output = null,
|
||||
ArgumentFormatter $argumentFormatter = null,
|
||||
Highlighter $highlighter = null
|
||||
) {
|
||||
$this->solutionsRepository = $solutionsRepository ?: new NullSolutionsRepository();
|
||||
$this->output = $output ?: new ConsoleOutput();
|
||||
$this->argumentFormatter = $argumentFormatter ?: new ArgumentFormatter();
|
||||
$this->highlighter = $highlighter ?: new Highlighter();
|
||||
}
|
||||
|
||||
public function write(Inspector $inspector): void
|
||||
{
|
||||
$this->renderTitleAndDescription($inspector);
|
||||
|
||||
$frames = $this->getFrames($inspector);
|
||||
|
||||
$exception = $inspector->getException();
|
||||
|
||||
if ($exception instanceof RenderableOnCollisionEditor) {
|
||||
$editorFrame = $exception->toCollisionEditor();
|
||||
} else {
|
||||
$editorFrame = array_shift($frames);
|
||||
}
|
||||
|
||||
if ($this->showEditor
|
||||
&& $editorFrame !== null
|
||||
&& ! $exception instanceof RenderlessEditor
|
||||
) {
|
||||
$this->renderEditor($editorFrame);
|
||||
}
|
||||
|
||||
$this->renderSolution($inspector);
|
||||
|
||||
if ($this->showTrace && ! empty($frames) && ! $exception instanceof RenderlessTrace) {
|
||||
$this->renderTrace($frames);
|
||||
} elseif (! $exception instanceof RenderlessEditor) {
|
||||
$this->output->writeln('');
|
||||
}
|
||||
}
|
||||
|
||||
public function ignoreFilesIn(array $ignore): self
|
||||
{
|
||||
$this->ignore = $ignore;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function showTrace(bool $show): self
|
||||
{
|
||||
$this->showTrace = $show;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function showTitle(bool $show): self
|
||||
{
|
||||
$this->showTitle = $show;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function showEditor(bool $show): self
|
||||
{
|
||||
$this->showEditor = $show;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setOutput(OutputInterface $output): self
|
||||
{
|
||||
$this->output = $output;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getOutput(): OutputInterface
|
||||
{
|
||||
return $this->output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns pertinent frames.
|
||||
*
|
||||
* @return array<int, Frame>
|
||||
*/
|
||||
private function getFrames(Inspector $inspector): array
|
||||
{
|
||||
return $inspector->getFrames()
|
||||
->filter(
|
||||
function ($frame) {
|
||||
// If we are in verbose mode, we always
|
||||
// display the full stack trace.
|
||||
if ($this->output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ($this->ignore as $ignore) {
|
||||
if (is_string($ignore)) {
|
||||
// Ensure paths are linux-style (like the ones on $this->ignore)
|
||||
$sanitizedPath = (string) str_replace('\\', '/', $frame->getFile());
|
||||
if (preg_match($ignore, $sanitizedPath)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($ignore instanceof Closure) {
|
||||
if ($ignore($frame)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
)
|
||||
->getArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the title of the exception.
|
||||
*/
|
||||
private function renderTitleAndDescription(Inspector $inspector): self
|
||||
{
|
||||
/** @var Throwable|TestException $exception */
|
||||
$exception = $inspector->getException();
|
||||
$message = rtrim($exception->getMessage());
|
||||
$class = $exception instanceof TestException
|
||||
? $exception->getClassName()
|
||||
: $inspector->getExceptionName();
|
||||
|
||||
if ($this->showTitle) {
|
||||
$this->render("<bg=red;options=bold> $class </>");
|
||||
$this->output->writeln('');
|
||||
}
|
||||
|
||||
$this->output->writeln("<fg=default;options=bold> $message</>");
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the solution of the exception, if any.
|
||||
*/
|
||||
private function renderSolution(Inspector $inspector): self
|
||||
{
|
||||
$throwable = $inspector->getException();
|
||||
|
||||
$solutions = $throwable instanceof Throwable
|
||||
? $this->solutionsRepository->getFromThrowable($throwable)
|
||||
: []; // @phpstan-ignore-line
|
||||
|
||||
foreach ($solutions as $solution) {
|
||||
/** @var \Spatie\Ignition\Contracts\Solution $solution */
|
||||
$title = $solution->getSolutionTitle();
|
||||
$description = $solution->getSolutionDescription();
|
||||
$links = $solution->getDocumentationLinks();
|
||||
|
||||
$description = trim((string) preg_replace("/\n/", "\n ", $description));
|
||||
|
||||
$this->render(sprintf(
|
||||
'<fg=cyan;options=bold>i</> <fg=default;options=bold>%s</>: %s %s',
|
||||
rtrim($title, '.'),
|
||||
$description,
|
||||
implode(', ', array_map(function (string $link) {
|
||||
return sprintf("\n <fg=gray>%s</>", $link);
|
||||
}, $links))
|
||||
));
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the editor containing the code that was the
|
||||
* origin of the exception.
|
||||
*/
|
||||
private function renderEditor(Frame $frame): self
|
||||
{
|
||||
if ($frame->getFile() !== 'Unknown') {
|
||||
$file = $this->getFileRelativePath((string) $frame->getFile());
|
||||
|
||||
// getLine() might return null so cast to int to get 0 instead
|
||||
$line = (int) $frame->getLine();
|
||||
$this->render('at <fg=green>'.$file.'</>'.':<fg=green>'.$line.'</>');
|
||||
|
||||
$content = $this->highlighter->highlight((string) $frame->getFileContents(), (int) $frame->getLine());
|
||||
|
||||
$this->output->writeln($content);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the trace of the exception.
|
||||
*/
|
||||
private function renderTrace(array $frames): self
|
||||
{
|
||||
$vendorFrames = 0;
|
||||
$userFrames = 0;
|
||||
|
||||
if (! empty($frames)) {
|
||||
$this->output->writeln(['']);
|
||||
}
|
||||
|
||||
foreach ($frames as $i => $frame) {
|
||||
if ($this->output->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE && strpos($frame->getFile(), '/vendor/') !== false) {
|
||||
$vendorFrames++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($userFrames > self::VERBOSITY_NORMAL_FRAMES && $this->output->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE) {
|
||||
break;
|
||||
}
|
||||
|
||||
$userFrames++;
|
||||
|
||||
$file = $this->getFileRelativePath($frame->getFile());
|
||||
$line = $frame->getLine();
|
||||
$class = empty($frame->getClass()) ? '' : $frame->getClass().'::';
|
||||
$function = $frame->getFunction();
|
||||
$args = $this->argumentFormatter->format($frame->getArgs());
|
||||
$pos = str_pad((string) ((int) $i + 1), 4, ' ');
|
||||
|
||||
if ($vendorFrames > 0) {
|
||||
$this->output->writeln(
|
||||
sprintf(" \e[2m+%s vendor frames \e[22m", $vendorFrames)
|
||||
);
|
||||
$vendorFrames = 0;
|
||||
}
|
||||
|
||||
$this->render("<fg=yellow>$pos</><fg=default;options=bold>$file</>:<fg=default;options=bold>$line</>", (bool) $class && $i > 0);
|
||||
if ($class) {
|
||||
$this->render("<fg=gray> $class$function($args)</>", false);
|
||||
}
|
||||
}
|
||||
|
||||
if (! empty($frames)) {
|
||||
$this->output->writeln(['']);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a message into the console.
|
||||
*/
|
||||
private function render(string $message, bool $break = true): self
|
||||
{
|
||||
if ($break) {
|
||||
$this->output->writeln('');
|
||||
}
|
||||
|
||||
$this->output->writeln(" $message");
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the relative path of the given file path.
|
||||
*/
|
||||
private function getFileRelativePath(string $filePath): string
|
||||
{
|
||||
$cwd = (string) getcwd();
|
||||
|
||||
if (! empty($cwd)) {
|
||||
return str_replace("$cwd".DIRECTORY_SEPARATOR, '', $filePath);
|
||||
}
|
||||
|
||||
return $filePath;
|
||||
}
|
||||
}
|
21
vendor/nunomaduro/termwind/LICENSE.md
vendored
Normal file
21
vendor/nunomaduro/termwind/LICENSE.md
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) Nuno Maduro <enunomaduro@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
33
vendor/nunomaduro/termwind/Makefile
vendored
Normal file
33
vendor/nunomaduro/termwind/Makefile
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
# Well documented Makefiles
|
||||
DEFAULT_GOAL := help
|
||||
help:
|
||||
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z0-9_-]+:.*?##/ { printf " \033[36m%-40s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
|
||||
|
||||
##@ [Docker]
|
||||
start: ## Spin up the container
|
||||
docker-compose up -d
|
||||
|
||||
stop: ## Shut down the containers
|
||||
docker-compose down
|
||||
|
||||
build: ## Build all docker images
|
||||
docker-compose build
|
||||
|
||||
##@ [Application]
|
||||
composer: ## Run composer commands. Specify the command e.g. via "make composer ARGS="install|update|require <dependency>"
|
||||
docker-compose run --rm app composer $(ARGS)
|
||||
|
||||
lint: ## Run the Linter
|
||||
docker-compose run --rm app ./vendor/bin/pint -v
|
||||
|
||||
test-lint: ## Run the Linter Test
|
||||
docker-compose run --rm app ./vendor/bin/pint --test -v
|
||||
|
||||
test-types: ## Run the PHPStan analysis
|
||||
docker-compose run --rm app ./vendor/bin/phpstan analyse --ansi
|
||||
|
||||
test-unit: ## Run the Pest Test Suite
|
||||
docker-compose run --rm app ./vendor/bin/pest --colors=always
|
||||
|
||||
test: ## Run the tests. Apply arguments via make test ARGS="--init"
|
||||
make test-lint && make test-types && make test-unit
|
69
vendor/nunomaduro/termwind/composer.json
vendored
Normal file
69
vendor/nunomaduro/termwind/composer.json
vendored
Normal file
@ -0,0 +1,69 @@
|
||||
{
|
||||
"name": "nunomaduro/termwind",
|
||||
"description": "Its like Tailwind CSS, but for the console.",
|
||||
"keywords": ["php", "cli", "package", "console", "css", "style"],
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nuno Maduro",
|
||||
"email": "enunomaduro@gmail.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^8.0",
|
||||
"ext-mbstring": "*",
|
||||
"symfony/console": "^5.3.0|^6.0.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"ergebnis/phpstan-rules": "^1.0.",
|
||||
"illuminate/console": "^8.0|^9.0",
|
||||
"illuminate/support": "^8.0|^9.0",
|
||||
"laravel/pint": "^1.0.0",
|
||||
"pestphp/pest": "^1.21.0",
|
||||
"pestphp/pest-plugin-mock": "^1.0",
|
||||
"phpstan/phpstan": "^1.4.6",
|
||||
"phpstan/phpstan-strict-rules": "^1.1.0",
|
||||
"symfony/var-dumper": "^5.2.7|^6.0.0",
|
||||
"thecodingmachine/phpstan-strict-rules": "^1.0.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Termwind\\": "src/"
|
||||
},
|
||||
"files": [
|
||||
"src/Functions.php"
|
||||
]
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true,
|
||||
"config": {
|
||||
"sort-packages": true,
|
||||
"preferred-install": "dist",
|
||||
"allow-plugins": {
|
||||
"pestphp/pest-plugin": true
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "pint -v",
|
||||
"test:lint": "pint --test -v",
|
||||
"test:types": "phpstan analyse --ansi",
|
||||
"test:unit": "pest --colors=always",
|
||||
"test": [
|
||||
"@test:lint",
|
||||
"@test:types",
|
||||
"@test:unit"
|
||||
]
|
||||
},
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Termwind\\Laravel\\TermwindServiceProvider"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
13
vendor/nunomaduro/termwind/docker-compose.yml
vendored
Normal file
13
vendor/nunomaduro/termwind/docker-compose.yml
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
app:
|
||||
image: termwind-docker
|
||||
container_name: termwind-docker
|
||||
stdin_open: true
|
||||
tty: true
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/Dockerfile
|
||||
volumes:
|
||||
- .:/usr/src/app
|
11
vendor/nunomaduro/termwind/docker/Dockerfile
vendored
Normal file
11
vendor/nunomaduro/termwind/docker/Dockerfile
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
FROM php:8.2-cli-alpine
|
||||
|
||||
# INSTALL AND UPDATE COMPOSER
|
||||
COPY --from=composer /usr/bin/composer /usr/bin/composer
|
||||
RUN composer self-update
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
COPY . .
|
||||
|
||||
# INSTALL YOUR DEPENDENCIES
|
||||
RUN composer install --prefer-dist
|
14
vendor/nunomaduro/termwind/playground.php
vendored
Normal file
14
vendor/nunomaduro/termwind/playground.php
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__.'/vendor/autoload.php';
|
||||
|
||||
use function Termwind\render;
|
||||
|
||||
render(<<<'HTML'
|
||||
<div class="mx-2 my-1">
|
||||
<div class="flex space-x-1">
|
||||
<span class="flex-1 truncate">Lorem ipsum dolor, sit amet consectetur adipisicing elit. Sunt illo et nisi omnis porro at, mollitia harum quas esse, aperiam dolorem ab recusandae fugiat nesciunt doloribus rem eaque nostrum itaque.</span>
|
||||
<span class="text-green">DONE</span>
|
||||
</div>
|
||||
</div>
|
||||
HTML);
|
154
vendor/nunomaduro/termwind/src/Actions/StyleToMethod.php
vendored
Normal file
154
vendor/nunomaduro/termwind/src/Actions/StyleToMethod.php
vendored
Normal file
@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Termwind\Actions;
|
||||
|
||||
use Termwind\Exceptions\StyleNotFound;
|
||||
use Termwind\Repositories\Styles as StyleRepository;
|
||||
use Termwind\Terminal;
|
||||
use Termwind\ValueObjects\Styles;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class StyleToMethod
|
||||
{
|
||||
/**
|
||||
* Finds if there is any media query on the style class.
|
||||
*/
|
||||
private const MEDIA_QUERIES_REGEX = "/^(sm|md|lg|xl|2xl)\:(.*)/";
|
||||
|
||||
/**
|
||||
* Defines the Media Query Breakpoints.
|
||||
*/
|
||||
public const MEDIA_QUERY_BREAKPOINTS = [
|
||||
'sm' => 64,
|
||||
'md' => 76,
|
||||
'lg' => 102,
|
||||
'xl' => 128,
|
||||
'2xl' => 153,
|
||||
];
|
||||
|
||||
/**
|
||||
* Creates a new action instance.
|
||||
*/
|
||||
public function __construct(
|
||||
private Styles $styles,
|
||||
private string $style,
|
||||
) {
|
||||
// ..
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies multiple styles to the given styles.
|
||||
*/
|
||||
public static function multiple(Styles $styles, string $stylesString): Styles
|
||||
{
|
||||
$stylesString = self::sortStyles(array_merge(
|
||||
$styles->defaultStyles(),
|
||||
array_filter((array) preg_split('/(?![^\[]*\])\s/', $stylesString))
|
||||
));
|
||||
|
||||
foreach ($stylesString as $style) {
|
||||
$styles = (new self($styles, $style))->__invoke();
|
||||
}
|
||||
|
||||
return $styles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given style to a method name.
|
||||
*
|
||||
* @return Styles
|
||||
*/
|
||||
public function __invoke(string|int ...$arguments): Styles
|
||||
{
|
||||
if (StyleRepository::has($this->style)) {
|
||||
return StyleRepository::get($this->style)($this->styles, ...$arguments);
|
||||
}
|
||||
|
||||
$method = $this->applyMediaQuery($this->style);
|
||||
|
||||
if ($method === '') {
|
||||
return $this->styles;
|
||||
}
|
||||
|
||||
$method = array_filter(
|
||||
(array) preg_split('/(?![^\[]*\])-/', $method),
|
||||
fn ($item) => $item !== false
|
||||
);
|
||||
|
||||
$method = array_slice($method, 0, count($method) - count($arguments));
|
||||
|
||||
$methodName = implode(' ', $method);
|
||||
$methodName = ucwords($methodName);
|
||||
$methodName = lcfirst($methodName);
|
||||
$methodName = str_replace(' ', '', $methodName);
|
||||
|
||||
if ($methodName === '') {
|
||||
throw StyleNotFound::fromStyle($this->style);
|
||||
}
|
||||
|
||||
if (! method_exists($this->styles, $methodName)) {
|
||||
$argument = array_pop($method);
|
||||
|
||||
$arguments[] = is_numeric($argument) ? (int) $argument : (string) $argument;
|
||||
|
||||
return $this->__invoke(...$arguments);
|
||||
}
|
||||
|
||||
return $this->styles
|
||||
->setStyle($this->style)
|
||||
->$methodName(...array_reverse($arguments));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts all the styles based on the correct render order.
|
||||
*
|
||||
* @param string[] $styles
|
||||
* @return string[]
|
||||
*/
|
||||
private static function sortStyles(array $styles): array
|
||||
{
|
||||
$keys = array_keys(self::MEDIA_QUERY_BREAKPOINTS);
|
||||
|
||||
usort($styles, function ($a, $b) use ($keys) {
|
||||
$existsA = (bool) preg_match(self::MEDIA_QUERIES_REGEX, $a, $matchesA);
|
||||
$existsB = (bool) preg_match(self::MEDIA_QUERIES_REGEX, $b, $matchesB);
|
||||
|
||||
if ($existsA && ! $existsB) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ($existsA && array_search($matchesA[1], $keys, true) > array_search($matchesB[1], $keys, true)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return -1;
|
||||
});
|
||||
|
||||
return $styles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the media query if exists.
|
||||
*/
|
||||
private function applyMediaQuery(string $method): string
|
||||
{
|
||||
$matches = [];
|
||||
preg_match(self::MEDIA_QUERIES_REGEX, $method, $matches);
|
||||
|
||||
if (count($matches) < 1) {
|
||||
return $method;
|
||||
}
|
||||
|
||||
[, $size, $method] = $matches;
|
||||
|
||||
if ((new Terminal)->width() >= self::MEDIA_QUERY_BREAKPOINTS[$size]) {
|
||||
return $method;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
9
vendor/nunomaduro/termwind/src/Components/Anchor.php
vendored
Normal file
9
vendor/nunomaduro/termwind/src/Components/Anchor.php
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Termwind\Components;
|
||||
|
||||
final class Anchor extends Element
|
||||
{
|
||||
}
|
26
vendor/nunomaduro/termwind/src/Components/BreakLine.php
vendored
Normal file
26
vendor/nunomaduro/termwind/src/Components/BreakLine.php
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Termwind\Components;
|
||||
|
||||
final class BreakLine extends Element
|
||||
{
|
||||
/**
|
||||
* Get the string representation of the element.
|
||||
*/
|
||||
public function toString(): string
|
||||
{
|
||||
$display = $this->styles->getProperties()['styles']['display'] ?? 'inline';
|
||||
|
||||
if ($display === 'hidden') {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ($display === 'block') {
|
||||
return parent::toString();
|
||||
}
|
||||
|
||||
return parent::toString()."\r";
|
||||
}
|
||||
}
|
10
vendor/nunomaduro/termwind/src/Components/Dd.php
vendored
Normal file
10
vendor/nunomaduro/termwind/src/Components/Dd.php
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Termwind\Components;
|
||||
|
||||
final class Dd extends Element
|
||||
{
|
||||
protected static array $defaultStyles = ['block', 'ml-4'];
|
||||
}
|
10
vendor/nunomaduro/termwind/src/Components/Div.php
vendored
Normal file
10
vendor/nunomaduro/termwind/src/Components/Div.php
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Termwind\Components;
|
||||
|
||||
final class Div extends Element
|
||||
{
|
||||
protected static array $defaultStyles = ['block'];
|
||||
}
|
10
vendor/nunomaduro/termwind/src/Components/Dl.php
vendored
Normal file
10
vendor/nunomaduro/termwind/src/Components/Dl.php
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Termwind\Components;
|
||||
|
||||
final class Dl extends Element
|
||||
{
|
||||
protected static array $defaultStyles = ['block'];
|
||||
}
|
10
vendor/nunomaduro/termwind/src/Components/Dt.php
vendored
Normal file
10
vendor/nunomaduro/termwind/src/Components/Dt.php
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Termwind\Components;
|
||||
|
||||
final class Dt extends Element
|
||||
{
|
||||
protected static array $defaultStyles = ['block', 'font-bold'];
|
||||
}
|
121
vendor/nunomaduro/termwind/src/Components/Element.php
vendored
Normal file
121
vendor/nunomaduro/termwind/src/Components/Element.php
vendored
Normal file
@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Termwind\Components;
|
||||
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Termwind\Actions\StyleToMethod;
|
||||
use Termwind\Html\InheritStyles;
|
||||
use Termwind\ValueObjects\Styles;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @method Element inheritFromStyles(Styles $styles)
|
||||
* @method Element fontBold()
|
||||
* @method Element strong()
|
||||
* @method Element italic()
|
||||
* @method Element underline()
|
||||
* @method Element lineThrough()
|
||||
* @method int getLength()
|
||||
* @method int getInnerWidth()
|
||||
* @method array getProperties()
|
||||
* @method Element href(string $href)
|
||||
* @method bool hasStyle(string $style)
|
||||
* @method Element addStyle(string $style)
|
||||
*/
|
||||
abstract class Element
|
||||
{
|
||||
/** @var string[] */
|
||||
protected static array $defaultStyles = [];
|
||||
|
||||
protected Styles $styles;
|
||||
|
||||
/**
|
||||
* Creates an element instance.
|
||||
*
|
||||
* @param array<int, Element|string>|string $content
|
||||
*/
|
||||
final public function __construct(
|
||||
protected OutputInterface $output,
|
||||
protected array|string $content,
|
||||
Styles|null $styles = null
|
||||
) {
|
||||
$this->styles = $styles ?? new Styles(defaultStyles: static::$defaultStyles);
|
||||
$this->styles->setElement($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an element instance with the given styles.
|
||||
*
|
||||
* @param array<int, Element|string>|string $content
|
||||
* @param array<string, mixed> $properties
|
||||
*/
|
||||
final public static function fromStyles(OutputInterface $output, array|string $content, string $styles = '', array $properties = []): static
|
||||
{
|
||||
$element = new static($output, $content);
|
||||
if ($properties !== []) {
|
||||
$element->styles->setProperties($properties);
|
||||
}
|
||||
|
||||
$elementStyles = StyleToMethod::multiple($element->styles, $styles);
|
||||
|
||||
return new static($output, $content, $elementStyles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the string representation of the element.
|
||||
*/
|
||||
public function toString(): string
|
||||
{
|
||||
if (is_array($this->content)) {
|
||||
$inheritance = new InheritStyles();
|
||||
$this->content = implode('', $inheritance($this->content, $this->styles));
|
||||
}
|
||||
|
||||
return $this->styles->format($this->content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, mixed> $arguments
|
||||
*/
|
||||
public function __call(string $name, array $arguments): mixed
|
||||
{
|
||||
if (method_exists($this->styles, $name)) {
|
||||
$result = $this->styles->{$name}(...$arguments);
|
||||
|
||||
if (str_starts_with($name, 'get') || str_starts_with($name, 'has')) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the content of the element.
|
||||
*
|
||||
* @param array<int, Element|string>|string $content
|
||||
*/
|
||||
final public function setContent(array|string $content): static
|
||||
{
|
||||
return new static($this->output, $content, $this->styles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the string representation of the element on the output.
|
||||
*/
|
||||
final public function render(int $options): void
|
||||
{
|
||||
$this->output->writeln($this->toString(), $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the string representation of the element.
|
||||
*/
|
||||
final public function __toString(): string
|
||||
{
|
||||
return $this->toString();
|
||||
}
|
||||
}
|
10
vendor/nunomaduro/termwind/src/Components/Hr.php
vendored
Normal file
10
vendor/nunomaduro/termwind/src/Components/Hr.php
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Termwind\Components;
|
||||
|
||||
final class Hr extends Element
|
||||
{
|
||||
protected static array $defaultStyles = ['block', 'border-t'];
|
||||
}
|
10
vendor/nunomaduro/termwind/src/Components/Li.php
vendored
Normal file
10
vendor/nunomaduro/termwind/src/Components/Li.php
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Termwind\Components;
|
||||
|
||||
final class Li extends Element
|
||||
{
|
||||
protected static array $defaultStyles = ['block'];
|
||||
}
|
10
vendor/nunomaduro/termwind/src/Components/Ol.php
vendored
Normal file
10
vendor/nunomaduro/termwind/src/Components/Ol.php
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Termwind\Components;
|
||||
|
||||
final class Ol extends Element
|
||||
{
|
||||
protected static array $defaultStyles = ['block', 'list-decimal'];
|
||||
}
|
10
vendor/nunomaduro/termwind/src/Components/Paragraph.php
vendored
Normal file
10
vendor/nunomaduro/termwind/src/Components/Paragraph.php
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Termwind\Components;
|
||||
|
||||
final class Paragraph extends Element
|
||||
{
|
||||
protected static array $defaultStyles = ['block', 'my-1'];
|
||||
}
|
19
vendor/nunomaduro/termwind/src/Components/Raw.php
vendored
Normal file
19
vendor/nunomaduro/termwind/src/Components/Raw.php
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Termwind\Components;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Raw extends Element
|
||||
{
|
||||
/**
|
||||
* Get the string representation of the element.
|
||||
*/
|
||||
public function toString(): string
|
||||
{
|
||||
return is_array($this->content) ? implode('', $this->content) : $this->content;
|
||||
}
|
||||
}
|
10
vendor/nunomaduro/termwind/src/Components/Span.php
vendored
Normal file
10
vendor/nunomaduro/termwind/src/Components/Span.php
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Termwind\Components;
|
||||
|
||||
final class Span extends Element
|
||||
{
|
||||
// ..
|
||||
}
|
10
vendor/nunomaduro/termwind/src/Components/Ul.php
vendored
Normal file
10
vendor/nunomaduro/termwind/src/Components/Ul.php
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Termwind\Components;
|
||||
|
||||
final class Ul extends Element
|
||||
{
|
||||
protected static array $defaultStyles = ['block', 'list-disc'];
|
||||
}
|
482
vendor/nunomaduro/termwind/src/Enums/Color.php
vendored
Normal file
482
vendor/nunomaduro/termwind/src/Enums/Color.php
vendored
Normal file
@ -0,0 +1,482 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Termwind\Enums;
|
||||
|
||||
final class Color
|
||||
{
|
||||
public const BLACK = 'black';
|
||||
|
||||
public const WHITE = 'white';
|
||||
|
||||
public const BRIGHTWHITE = 'bright-white';
|
||||
|
||||
public const SLATE_50 = '#f8fafc';
|
||||
|
||||
public const SLATE_100 = '#f1f5f9';
|
||||
|
||||
public const SLATE_200 = '#e2e8f0';
|
||||
|
||||
public const SLATE_300 = '#cbd5e1';
|
||||
|
||||
public const SLATE_400 = '#94a3b8';
|
||||
|
||||
public const SLATE_500 = '#64748b';
|
||||
|
||||
public const SLATE_600 = '#475569';
|
||||
|
||||
public const SLATE_700 = '#334155';
|
||||
|
||||
public const SLATE_800 = '#1e293b';
|
||||
|
||||
public const SLATE_900 = '#0f172a';
|
||||
|
||||
public const GRAY = 'gray';
|
||||
|
||||
public const GRAY_50 = '#f9fafb';
|
||||
|
||||
public const GRAY_100 = '#f3f4f6';
|
||||
|
||||
public const GRAY_200 = '#e5e7eb';
|
||||
|
||||
public const GRAY_300 = '#d1d5db';
|
||||
|
||||
public const GRAY_400 = '#9ca3af';
|
||||
|
||||
public const GRAY_500 = '#6b7280';
|
||||
|
||||
public const GRAY_600 = '#4b5563';
|
||||
|
||||
public const GRAY_700 = '#374151';
|
||||
|
||||
public const GRAY_800 = '#1f2937';
|
||||
|
||||
public const GRAY_900 = '#111827';
|
||||
|
||||
public const ZINC_50 = '#fafafa';
|
||||
|
||||
public const ZINC_100 = '#f4f4f5';
|
||||
|
||||
public const ZINC_200 = '#e4e4e7';
|
||||
|
||||
public const ZINC_300 = '#d4d4d8';
|
||||
|
||||
public const ZINC_400 = '#a1a1aa';
|
||||
|
||||
public const ZINC_500 = '#71717a';
|
||||
|
||||
public const ZINC_600 = '#52525b';
|
||||
|
||||
public const ZINC_700 = '#3f3f46';
|
||||
|
||||
public const ZINC_800 = '#27272a';
|
||||
|
||||
public const ZINC_900 = '#18181b';
|
||||
|
||||
public const NEUTRAL_50 = '#fafafa';
|
||||
|
||||
public const NEUTRAL_100 = '#f5f5f5';
|
||||
|
||||
public const NEUTRAL_200 = '#e5e5e5';
|
||||
|
||||
public const NEUTRAL_300 = '#d4d4d4';
|
||||
|
||||
public const NEUTRAL_400 = '#a3a3a3';
|
||||
|
||||
public const NEUTRAL_500 = '#737373';
|
||||
|
||||
public const NEUTRAL_600 = '#525252';
|
||||
|
||||
public const NEUTRAL_700 = '#404040';
|
||||
|
||||
public const NEUTRAL_800 = '#262626';
|
||||
|
||||
public const NEUTRAL_900 = '#171717';
|
||||
|
||||
public const STONE_50 = '#fafaf9';
|
||||
|
||||
public const STONE_100 = '#f5f5f4';
|
||||
|
||||
public const STONE_200 = '#e7e5e4';
|
||||
|
||||
public const STONE_300 = '#d6d3d1';
|
||||
|
||||
public const STONE_400 = '#a8a29e';
|
||||
|
||||
public const STONE_500 = '#78716c';
|
||||
|
||||
public const STONE_600 = '#57534e';
|
||||
|
||||
public const STONE_700 = '#44403c';
|
||||
|
||||
public const STONE_800 = '#292524';
|
||||
|
||||
public const STONE_900 = '#1c1917';
|
||||
|
||||
public const RED = 'red';
|
||||
|
||||
public const BRIGHTRED = 'bright-red';
|
||||
|
||||
public const RED_50 = '#fef2f2';
|
||||
|
||||
public const RED_100 = '#fee2e2';
|
||||
|
||||
public const RED_200 = '#fecaca';
|
||||
|
||||
public const RED_300 = '#fca5a5';
|
||||
|
||||
public const RED_400 = '#f87171';
|
||||
|
||||
public const RED_500 = '#ef4444';
|
||||
|
||||
public const RED_600 = '#dc2626';
|
||||
|
||||
public const RED_700 = '#b91c1c';
|
||||
|
||||
public const RED_800 = '#991b1b';
|
||||
|
||||
public const RED_900 = '#7f1d1d';
|
||||
|
||||
public const ORANGE = '#f97316';
|
||||
|
||||
public const ORANGE_50 = '#fff7ed';
|
||||
|
||||
public const ORANGE_100 = '#ffedd5';
|
||||
|
||||
public const ORANGE_200 = '#fed7aa';
|
||||
|
||||
public const ORANGE_300 = '#fdba74';
|
||||
|
||||
public const ORANGE_400 = '#fb923c';
|
||||
|
||||
public const ORANGE_500 = '#f97316';
|
||||
|
||||
public const ORANGE_600 = '#ea580c';
|
||||
|
||||
public const ORANGE_700 = '#c2410c';
|
||||
|
||||
public const ORANGE_800 = '#9a3412';
|
||||
|
||||
public const ORANGE_900 = '#7c2d12';
|
||||
|
||||
public const AMBER_50 = '#fffbeb';
|
||||
|
||||
public const AMBER_100 = '#fef3c7';
|
||||
|
||||
public const AMBER_200 = '#fde68a';
|
||||
|
||||
public const AMBER_300 = '#fcd34d';
|
||||
|
||||
public const AMBER_400 = '#fbbf24';
|
||||
|
||||
public const AMBER_500 = '#f59e0b';
|
||||
|
||||
public const AMBER_600 = '#d97706';
|
||||
|
||||
public const AMBER_700 = '#b45309';
|
||||
|
||||
public const AMBER_800 = '#92400e';
|
||||
|
||||
public const AMBER_900 = '#78350f';
|
||||
|
||||
public const YELLOW = 'yellow';
|
||||
|
||||
public const BRIGHTYELLOW = 'bright-yellow';
|
||||
|
||||
public const YELLOW_50 = '#fefce8';
|
||||
|
||||
public const YELLOW_100 = '#fef9c3';
|
||||
|
||||
public const YELLOW_200 = '#fef08a';
|
||||
|
||||
public const YELLOW_300 = '#fde047';
|
||||
|
||||
public const YELLOW_400 = '#facc15';
|
||||
|
||||
public const YELLOW_500 = '#eab308';
|
||||
|
||||
public const YELLOW_600 = '#ca8a04';
|
||||
|
||||
public const YELLOW_700 = '#a16207';
|
||||
|
||||
public const YELLOW_800 = '#854d0e';
|
||||
|
||||
public const YELLOW_900 = '#713f12';
|
||||
|
||||
public const LIME_50 = '#f7fee7';
|
||||
|
||||
public const LIME_100 = '#ecfccb';
|
||||
|
||||
public const LIME_200 = '#d9f99d';
|
||||
|
||||
public const LIME_300 = '#bef264';
|
||||
|
||||
public const LIME_400 = '#a3e635';
|
||||
|
||||
public const LIME_500 = '#84cc16';
|
||||
|
||||
public const LIME_600 = '#65a30d';
|
||||
|
||||
public const LIME_700 = '#4d7c0f';
|
||||
|
||||
public const LIME_800 = '#3f6212';
|
||||
|
||||
public const LIME_900 = '#365314';
|
||||
|
||||
public const GREEN = 'green';
|
||||
|
||||
public const BRIGHTGREEN = 'bright-green';
|
||||
|
||||
public const GREEN_50 = '#f0fdf4';
|
||||
|
||||
public const GREEN_100 = '#dcfce7';
|
||||
|
||||
public const GREEN_200 = '#bbf7d0';
|
||||
|
||||
public const GREEN_300 = '#86efac';
|
||||
|
||||
public const GREEN_400 = '#4ade80';
|
||||
|
||||
public const GREEN_500 = '#22c55e';
|
||||
|
||||
public const GREEN_600 = '#16a34a';
|
||||
|
||||
public const GREEN_700 = '#15803d';
|
||||
|
||||
public const GREEN_800 = '#166534';
|
||||
|
||||
public const GREEN_900 = '#14532d';
|
||||
|
||||
public const EMERALD_50 = '#ecfdf5';
|
||||
|
||||
public const EMERALD_100 = '#d1fae5';
|
||||
|
||||
public const EMERALD_200 = '#a7f3d0';
|
||||
|
||||
public const EMERALD_300 = '#6ee7b7';
|
||||
|
||||
public const EMERALD_400 = '#34d399';
|
||||
|
||||
public const EMERALD_500 = '#10b981';
|
||||
|
||||
public const EMERALD_600 = '#059669';
|
||||
|
||||
public const EMERALD_700 = '#047857';
|
||||
|
||||
public const EMERALD_800 = '#065f46';
|
||||
|
||||
public const EMERALD_900 = '#064e3b';
|
||||
|
||||
public const TEAL_50 = '#f0fdfa';
|
||||
|
||||
public const TEAL_100 = '#ccfbf1';
|
||||
|
||||
public const TEAL_200 = '#99f6e4';
|
||||
|
||||
public const TEAL_300 = '#5eead4';
|
||||
|
||||
public const TEAL_400 = '#2dd4bf';
|
||||
|
||||
public const TEAL_500 = '#14b8a6';
|
||||
|
||||
public const TEAL_600 = '#0d9488';
|
||||
|
||||
public const TEAL_700 = '#0f766e';
|
||||
|
||||
public const TEAL_800 = '#115e59';
|
||||
|
||||
public const TEAL_900 = '#134e4a';
|
||||
|
||||
public const CYAN = 'cyan';
|
||||
|
||||
public const BRIGHTCYAN = 'bright-cyan';
|
||||
|
||||
public const CYAN_50 = '#ecfeff';
|
||||
|
||||
public const CYAN_100 = '#cffafe';
|
||||
|
||||
public const CYAN_200 = '#a5f3fc';
|
||||
|
||||
public const CYAN_300 = '#67e8f9';
|
||||
|
||||
public const CYAN_400 = '#22d3ee';
|
||||
|
||||
public const CYAN_500 = '#06b6d4';
|
||||
|
||||
public const CYAN_600 = '#0891b2';
|
||||
|
||||
public const CYAN_700 = '#0e7490';
|
||||
|
||||
public const CYAN_800 = '#155e75';
|
||||
|
||||
public const CYAN_900 = '#164e63';
|
||||
|
||||
public const SKY_50 = '#f0f9ff';
|
||||
|
||||
public const SKY_100 = '#e0f2fe';
|
||||
|
||||
public const SKY_200 = '#bae6fd';
|
||||
|
||||
public const SKY_300 = '#7dd3fc';
|
||||
|
||||
public const SKY_400 = '#38bdf8';
|
||||
|
||||
public const SKY_500 = '#0ea5e9';
|
||||
|
||||
public const SKY_600 = '#0284c7';
|
||||
|
||||
public const SKY_700 = '#0369a1';
|
||||
|
||||
public const SKY_800 = '#075985';
|
||||
|
||||
public const SKY_900 = '#0c4a6e';
|
||||
|
||||
public const BLUE = 'blue';
|
||||
|
||||
public const BRIGHTBLUE = 'bright-blue';
|
||||
|
||||
public const BLUE_50 = '#eff6ff';
|
||||
|
||||
public const BLUE_100 = '#dbeafe';
|
||||
|
||||
public const BLUE_200 = '#bfdbfe';
|
||||
|
||||
public const BLUE_300 = '#93c5fd';
|
||||
|
||||
public const BLUE_400 = '#60a5fa';
|
||||
|
||||
public const BLUE_500 = '#3b82f6';
|
||||
|
||||
public const BLUE_600 = '#2563eb';
|
||||
|
||||
public const BLUE_700 = '#1d4ed8';
|
||||
|
||||
public const BLUE_800 = '#1e40af';
|
||||
|
||||
public const BLUE_900 = '#1e3a8a';
|
||||
|
||||
public const INDIGO_50 = '#eef2ff';
|
||||
|
||||
public const INDIGO_100 = '#e0e7ff';
|
||||
|
||||
public const INDIGO_200 = '#c7d2fe';
|
||||
|
||||
public const INDIGO_300 = '#a5b4fc';
|
||||
|
||||
public const INDIGO_400 = '#818cf8';
|
||||
|
||||
public const INDIGO_500 = '#6366f1';
|
||||
|
||||
public const INDIGO_600 = '#4f46e5';
|
||||
|
||||
public const INDIGO_700 = '#4338ca';
|
||||
|
||||
public const INDIGO_800 = '#3730a3';
|
||||
|
||||
public const INDIGO_900 = '#312e81';
|
||||
|
||||
public const VIOLET_50 = '#f5f3ff';
|
||||
|
||||
public const VIOLET_100 = '#ede9fe';
|
||||
|
||||
public const VIOLET_200 = '#ddd6fe';
|
||||
|
||||
public const VIOLET_300 = '#c4b5fd';
|
||||
|
||||
public const VIOLET_400 = '#a78bfa';
|
||||
|
||||
public const VIOLET_500 = '#8b5cf6';
|
||||
|
||||
public const VIOLET_600 = '#7c3aed';
|
||||
|
||||
public const VIOLET_700 = '#6d28d9';
|
||||
|
||||
public const VIOLET_800 = '#5b21b6';
|
||||
|
||||
public const VIOLET_900 = '#4c1d95';
|
||||
|
||||
public const PURPLE_50 = '#faf5ff';
|
||||
|
||||
public const PURPLE_100 = '#f3e8ff';
|
||||
|
||||
public const PURPLE_200 = '#e9d5ff';
|
||||
|
||||
public const PURPLE_300 = '#d8b4fe';
|
||||
|
||||
public const PURPLE_400 = '#c084fc';
|
||||
|
||||
public const PURPLE_500 = '#a855f7';
|
||||
|
||||
public const PURPLE_600 = '#9333ea';
|
||||
|
||||
public const PURPLE_700 = '#7e22ce';
|
||||
|
||||
public const PURPLE_800 = '#6b21a8';
|
||||
|
||||
public const PURPLE_900 = '#581c87';
|
||||
|
||||
public const FUCHSIA_50 = '#fdf4ff';
|
||||
|
||||
public const FUCHSIA_100 = '#fae8ff';
|
||||
|
||||
public const FUCHSIA_200 = '#f5d0fe';
|
||||
|
||||
public const FUCHSIA_300 = '#f0abfc';
|
||||
|
||||
public const FUCHSIA_400 = '#e879f9';
|
||||
|
||||
public const FUCHSIA_500 = '#d946ef';
|
||||
|
||||
public const FUCHSIA_600 = '#c026d3';
|
||||
|
||||
public const FUCHSIA_700 = '#a21caf';
|
||||
|
||||
public const FUCHSIA_800 = '#86198f';
|
||||
|
||||
public const FUCHSIA_900 = '#701a75';
|
||||
|
||||
public const PINK_50 = '#fdf2f8';
|
||||
|
||||
public const PINK_100 = '#fce7f3';
|
||||
|
||||
public const PINK_200 = '#fbcfe8';
|
||||
|
||||
public const PINK_300 = '#f9a8d4';
|
||||
|
||||
public const PINK_400 = '#f472b6';
|
||||
|
||||
public const PINK_500 = '#ec4899';
|
||||
|
||||
public const PINK_600 = '#db2777';
|
||||
|
||||
public const PINK_700 = '#be185d';
|
||||
|
||||
public const PINK_800 = '#9d174d';
|
||||
|
||||
public const PINK_900 = '#831843';
|
||||
|
||||
public const ROSE_50 = '#fff1f2';
|
||||
|
||||
public const ROSE_100 = '#ffe4e6';
|
||||
|
||||
public const ROSE_200 = '#fecdd3';
|
||||
|
||||
public const ROSE_300 = '#fda4af';
|
||||
|
||||
public const ROSE_400 = '#fb7185';
|
||||
|
||||
public const ROSE_500 = '#f43f5e';
|
||||
|
||||
public const ROSE_600 = '#e11d48';
|
||||
|
||||
public const ROSE_700 = '#be123c';
|
||||
|
||||
public const ROSE_800 = '#9f1239';
|
||||
|
||||
public const ROSE_900 = '#881337';
|
||||
|
||||
public const MAGENTA = 'magenta';
|
||||
|
||||
public const BRIGHTMAGENTA = 'bright-magenta';
|
||||
}
|
14
vendor/nunomaduro/termwind/src/Exceptions/ColorNotFound.php
vendored
Normal file
14
vendor/nunomaduro/termwind/src/Exceptions/ColorNotFound.php
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Termwind\Exceptions;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class ColorNotFound extends InvalidArgumentException
|
||||
{
|
||||
}
|
14
vendor/nunomaduro/termwind/src/Exceptions/InvalidChild.php
vendored
Normal file
14
vendor/nunomaduro/termwind/src/Exceptions/InvalidChild.php
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Termwind\Exceptions;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class InvalidChild extends InvalidArgumentException
|
||||
{
|
||||
}
|
14
vendor/nunomaduro/termwind/src/Exceptions/InvalidColor.php
vendored
Normal file
14
vendor/nunomaduro/termwind/src/Exceptions/InvalidColor.php
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Termwind\Exceptions;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class InvalidColor extends InvalidArgumentException
|
||||
{
|
||||
}
|
14
vendor/nunomaduro/termwind/src/Exceptions/InvalidStyle.php
vendored
Normal file
14
vendor/nunomaduro/termwind/src/Exceptions/InvalidStyle.php
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Termwind\Exceptions;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class InvalidStyle extends InvalidArgumentException
|
||||
{
|
||||
}
|
29
vendor/nunomaduro/termwind/src/Exceptions/StyleNotFound.php
vendored
Normal file
29
vendor/nunomaduro/termwind/src/Exceptions/StyleNotFound.php
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Termwind\Exceptions;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class StyleNotFound extends InvalidArgumentException
|
||||
{
|
||||
/**
|
||||
* Creates a new style not found instance.
|
||||
*/
|
||||
private function __construct(string $message)
|
||||
{
|
||||
parent::__construct($message, 0, $this->getPrevious());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new style not found instance from the given style.
|
||||
*/
|
||||
public static function fromStyle(string $style): self
|
||||
{
|
||||
return new self(sprintf('Style [%s] not found.', $style));
|
||||
}
|
||||
}
|
65
vendor/nunomaduro/termwind/src/Functions.php
vendored
Normal file
65
vendor/nunomaduro/termwind/src/Functions.php
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Termwind;
|
||||
|
||||
use Closure;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Termwind\Repositories\Styles as StyleRepository;
|
||||
use Termwind\ValueObjects\Style;
|
||||
use Termwind\ValueObjects\Styles;
|
||||
|
||||
if (! function_exists('Termwind\renderUsing')) {
|
||||
/**
|
||||
* Sets the renderer implementation.
|
||||
*/
|
||||
function renderUsing(OutputInterface|null $renderer): void
|
||||
{
|
||||
Termwind::renderUsing($renderer);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('Termwind\style')) {
|
||||
/**
|
||||
* Creates a new style.
|
||||
*
|
||||
* @param (Closure(Styles $renderable, string|int ...$arguments): Styles)|null $callback
|
||||
*/
|
||||
function style(string $name, Closure $callback = null): Style
|
||||
{
|
||||
return StyleRepository::create($name, $callback);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('Termwind\render')) {
|
||||
/**
|
||||
* Render HTML to a string.
|
||||
*/
|
||||
function render(string $html, int $options = OutputInterface::OUTPUT_NORMAL): void
|
||||
{
|
||||
(new HtmlRenderer)->render($html, $options);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('Termwind\terminal')) {
|
||||
/**
|
||||
* Returns a Terminal instance.
|
||||
*/
|
||||
function terminal(): Terminal
|
||||
{
|
||||
return new Terminal;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('Termwind\ask')) {
|
||||
/**
|
||||
* Renders a prompt to the user.
|
||||
*
|
||||
* @param iterable<array-key, string>|null $autocomplete
|
||||
*/
|
||||
function ask(string $question, iterable $autocomplete = null): mixed
|
||||
{
|
||||
return (new Question)->ask($question, $autocomplete);
|
||||
}
|
||||
}
|
25
vendor/nunomaduro/termwind/src/Helpers/QuestionHelper.php
vendored
Normal file
25
vendor/nunomaduro/termwind/src/Helpers/QuestionHelper.php
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Termwind\Helpers;
|
||||
|
||||
use Symfony\Component\Console\Formatter\OutputFormatter;
|
||||
use Symfony\Component\Console\Helper\SymfonyQuestionHelper;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\Question;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class QuestionHelper extends SymfonyQuestionHelper
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function writePrompt(OutputInterface $output, Question $question): void
|
||||
{
|
||||
$text = OutputFormatter::escapeTrailingBackslash($question->getQuestion());
|
||||
$output->write($text);
|
||||
}
|
||||
}
|
282
vendor/nunomaduro/termwind/src/Html/CodeRenderer.php
vendored
Normal file
282
vendor/nunomaduro/termwind/src/Html/CodeRenderer.php
vendored
Normal file
@ -0,0 +1,282 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Termwind\Html;
|
||||
|
||||
use Termwind\Components\Element;
|
||||
use Termwind\Termwind;
|
||||
use Termwind\ValueObjects\Node;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class CodeRenderer
|
||||
{
|
||||
public const TOKEN_DEFAULT = 'token_default';
|
||||
|
||||
public const TOKEN_COMMENT = 'token_comment';
|
||||
|
||||
public const TOKEN_STRING = 'token_string';
|
||||
|
||||
public const TOKEN_HTML = 'token_html';
|
||||
|
||||
public const TOKEN_KEYWORD = 'token_keyword';
|
||||
|
||||
public const ACTUAL_LINE_MARK = 'actual_line_mark';
|
||||
|
||||
public const LINE_NUMBER = 'line_number';
|
||||
|
||||
private const ARROW_SYMBOL_UTF8 = '➜';
|
||||
|
||||
private const DELIMITER_UTF8 = '▕ '; // '▶';
|
||||
|
||||
private const LINE_NUMBER_DIVIDER = 'line_divider';
|
||||
|
||||
private const MARKED_LINE_NUMBER = 'marked_line';
|
||||
|
||||
private const WIDTH = 3;
|
||||
|
||||
/**
|
||||
* Holds the theme.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
private const THEME = [
|
||||
self::TOKEN_STRING => 'text-gray',
|
||||
self::TOKEN_COMMENT => 'text-gray italic',
|
||||
self::TOKEN_KEYWORD => 'text-magenta strong',
|
||||
self::TOKEN_DEFAULT => 'strong',
|
||||
self::TOKEN_HTML => 'text-blue strong',
|
||||
|
||||
self::ACTUAL_LINE_MARK => 'text-red strong',
|
||||
self::LINE_NUMBER => 'text-gray',
|
||||
self::MARKED_LINE_NUMBER => 'italic strong',
|
||||
self::LINE_NUMBER_DIVIDER => 'text-gray',
|
||||
];
|
||||
|
||||
private string $delimiter = self::DELIMITER_UTF8;
|
||||
|
||||
private string $arrow = self::ARROW_SYMBOL_UTF8;
|
||||
|
||||
private const NO_MARK = ' ';
|
||||
|
||||
/**
|
||||
* Highlights HTML content from a given node and converts to the content element.
|
||||
*/
|
||||
public function toElement(Node $node): Element
|
||||
{
|
||||
$line = max((int) $node->getAttribute('line'), 0);
|
||||
$startLine = max((int) $node->getAttribute('start-line'), 1);
|
||||
|
||||
$html = $node->getHtml();
|
||||
$lines = explode("\n", $html);
|
||||
$extraSpaces = $this->findExtraSpaces($lines);
|
||||
|
||||
if ($extraSpaces !== '') {
|
||||
$lines = array_map(static function (string $line) use ($extraSpaces): string {
|
||||
return str_starts_with($line, $extraSpaces) ? substr($line, strlen($extraSpaces)) : $line;
|
||||
}, $lines);
|
||||
$html = implode("\n", $lines);
|
||||
}
|
||||
|
||||
$tokenLines = $this->getHighlightedLines(trim($html, "\n"), $startLine);
|
||||
$lines = $this->colorLines($tokenLines);
|
||||
$lines = $this->lineNumbers($lines, $line);
|
||||
|
||||
return Termwind::div(trim($lines, "\n"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds extra spaces which should be removed from HTML.
|
||||
*
|
||||
* @param array<int, string> $lines
|
||||
*/
|
||||
private function findExtraSpaces(array $lines): string
|
||||
{
|
||||
foreach ($lines as $line) {
|
||||
if ($line === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (preg_replace('/\s+/', '', $line) === '') {
|
||||
return $line;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns content split into lines with numbers.
|
||||
*
|
||||
* @return array<int, array<int, array{0: string, 1: non-empty-string}>>
|
||||
*/
|
||||
private function getHighlightedLines(string $source, int $startLine): array
|
||||
{
|
||||
$source = str_replace(["\r\n", "\r"], "\n", $source);
|
||||
$tokens = $this->tokenize($source);
|
||||
|
||||
return $this->splitToLines($tokens, $startLine - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits content into tokens.
|
||||
*
|
||||
* @return array<int, array{0: string, 1: string}>
|
||||
*/
|
||||
private function tokenize(string $source): array
|
||||
{
|
||||
$tokens = token_get_all($source);
|
||||
|
||||
$output = [];
|
||||
$currentType = null;
|
||||
$newType = self::TOKEN_KEYWORD;
|
||||
$buffer = '';
|
||||
|
||||
foreach ($tokens as $token) {
|
||||
if (is_array($token)) {
|
||||
if ($token[0] !== T_WHITESPACE) {
|
||||
$newType = match ($token[0]) {
|
||||
T_OPEN_TAG, T_OPEN_TAG_WITH_ECHO, T_CLOSE_TAG, T_STRING, T_VARIABLE,
|
||||
T_DIR, T_FILE, T_METHOD_C, T_DNUMBER, T_LNUMBER, T_NS_C,
|
||||
T_LINE, T_CLASS_C, T_FUNC_C, T_TRAIT_C => self::TOKEN_DEFAULT,
|
||||
T_COMMENT, T_DOC_COMMENT => self::TOKEN_COMMENT,
|
||||
T_ENCAPSED_AND_WHITESPACE, T_CONSTANT_ENCAPSED_STRING => self::TOKEN_STRING,
|
||||
T_INLINE_HTML => self::TOKEN_HTML,
|
||||
default => self::TOKEN_KEYWORD
|
||||
};
|
||||
}
|
||||
} else {
|
||||
$newType = $token === '"' ? self::TOKEN_STRING : self::TOKEN_KEYWORD;
|
||||
}
|
||||
|
||||
if ($currentType === null) {
|
||||
$currentType = $newType;
|
||||
}
|
||||
|
||||
if ($currentType !== $newType) {
|
||||
$output[] = [$currentType, $buffer];
|
||||
$buffer = '';
|
||||
$currentType = $newType;
|
||||
}
|
||||
|
||||
$buffer .= is_array($token) ? $token[1] : $token;
|
||||
}
|
||||
|
||||
$output[] = [$newType, $buffer];
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits tokens into lines.
|
||||
*
|
||||
* @param array<int, array{0: string, 1: string}> $tokens
|
||||
* @param int $startLine
|
||||
* @return array<int, array<int, array{0: string, 1: non-empty-string}>>
|
||||
*/
|
||||
private function splitToLines(array $tokens, int $startLine): array
|
||||
{
|
||||
$lines = [];
|
||||
|
||||
$line = [];
|
||||
foreach ($tokens as $token) {
|
||||
foreach (explode("\n", $token[1]) as $count => $tokenLine) {
|
||||
if ($count > 0) {
|
||||
$lines[$startLine++] = $line;
|
||||
$line = [];
|
||||
}
|
||||
|
||||
if ($tokenLine === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$line[] = [$token[0], $tokenLine];
|
||||
}
|
||||
}
|
||||
|
||||
$lines[$startLine++] = $line;
|
||||
|
||||
return $lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies colors to tokens according to a color schema.
|
||||
*
|
||||
* @param array<int, array<int, array{0: string, 1: non-empty-string}>> $tokenLines
|
||||
* @return array<int, string>
|
||||
*/
|
||||
private function colorLines(array $tokenLines): array
|
||||
{
|
||||
$lines = [];
|
||||
|
||||
foreach ($tokenLines as $lineCount => $tokenLine) {
|
||||
$line = '';
|
||||
foreach ($tokenLine as $token) {
|
||||
[$tokenType, $tokenValue] = $token;
|
||||
$line .= $this->styleToken($tokenType, $tokenValue);
|
||||
}
|
||||
|
||||
$lines[$lineCount] = $line;
|
||||
}
|
||||
|
||||
return $lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepends line numbers into lines.
|
||||
*
|
||||
* @param array<int, string> $lines
|
||||
* @param int $markLine
|
||||
* @return string
|
||||
*/
|
||||
private function lineNumbers(array $lines, int $markLine): string
|
||||
{
|
||||
$lastLine = (int) array_key_last($lines);
|
||||
$lineLength = strlen((string) ($lastLine + 1));
|
||||
$lineLength = $lineLength < self::WIDTH ? self::WIDTH : $lineLength;
|
||||
|
||||
$snippet = '';
|
||||
$mark = ' '.$this->arrow.' ';
|
||||
foreach ($lines as $i => $line) {
|
||||
$coloredLineNumber = $this->coloredLineNumber(self::LINE_NUMBER, $i, $lineLength);
|
||||
|
||||
if (0 !== $markLine) {
|
||||
$snippet .= ($markLine === $i + 1
|
||||
? $this->styleToken(self::ACTUAL_LINE_MARK, $mark)
|
||||
: self::NO_MARK
|
||||
);
|
||||
|
||||
$coloredLineNumber = ($markLine === $i + 1 ?
|
||||
$this->coloredLineNumber(self::MARKED_LINE_NUMBER, $i, $lineLength) :
|
||||
$coloredLineNumber
|
||||
);
|
||||
}
|
||||
|
||||
$snippet .= $coloredLineNumber;
|
||||
$snippet .= $this->styleToken(self::LINE_NUMBER_DIVIDER, $this->delimiter);
|
||||
$snippet .= $line.PHP_EOL;
|
||||
}
|
||||
|
||||
return $snippet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats line number and applies color according to a color schema.
|
||||
*/
|
||||
private function coloredLineNumber(string $token, int $lineNumber, int $length): string
|
||||
{
|
||||
return $this->styleToken(
|
||||
$token, str_pad((string) ($lineNumber + 1), $length, ' ', STR_PAD_LEFT)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats string and applies color according to a color schema.
|
||||
*/
|
||||
private function styleToken(string $token, string $string): string
|
||||
{
|
||||
return (string) Termwind::span($string, self::THEME[$token]);
|
||||
}
|
||||
}
|
218
vendor/nunomaduro/termwind/src/Html/InheritStyles.php
vendored
Normal file
218
vendor/nunomaduro/termwind/src/Html/InheritStyles.php
vendored
Normal file
@ -0,0 +1,218 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Termwind\Html;
|
||||
|
||||
use Termwind\Components\Element;
|
||||
use Termwind\Termwind;
|
||||
use Termwind\ValueObjects\Styles;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class InheritStyles
|
||||
{
|
||||
/**
|
||||
* Applies styles from parent element to child elements.
|
||||
*
|
||||
* @param array<int, Element|string> $elements
|
||||
* @return array<int, Element|string>
|
||||
*/
|
||||
public function __invoke(array $elements, Styles $styles): array
|
||||
{
|
||||
$elements = array_values($elements);
|
||||
|
||||
foreach ($elements as &$element) {
|
||||
if (is_string($element)) {
|
||||
$element = Termwind::raw($element);
|
||||
}
|
||||
|
||||
$element->inheritFromStyles($styles);
|
||||
}
|
||||
|
||||
/** @var Element[] $elements */
|
||||
if (($styles->getProperties()['styles']['display'] ?? 'inline') === 'flex') {
|
||||
$elements = $this->applyFlex($elements);
|
||||
}
|
||||
|
||||
return match ($styles->getProperties()['styles']['justifyContent'] ?? false) {
|
||||
'between' => $this->applyJustifyBetween($elements),
|
||||
'evenly' => $this->applyJustifyEvenly($elements),
|
||||
'around' => $this->applyJustifyAround($elements),
|
||||
'center' => $this->applyJustifyCenter($elements),
|
||||
default => $elements,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies flex-1 to child elements with the class.
|
||||
*
|
||||
* @param array<int, Element> $elements
|
||||
* @return array<int, Element>
|
||||
*/
|
||||
private function applyFlex(array $elements): array
|
||||
{
|
||||
[$totalWidth, $parentWidth] = $this->getWidthFromElements($elements);
|
||||
|
||||
$width = max(0, array_reduce($elements, function ($carry, $element) {
|
||||
return $carry += $element->hasStyle('flex-1') ? $element->getInnerWidth() : 0;
|
||||
}, $parentWidth - $totalWidth));
|
||||
|
||||
$flexed = array_values(array_filter(
|
||||
$elements, fn ($element) => $element->hasStyle('flex-1')
|
||||
));
|
||||
|
||||
foreach ($flexed as $index => &$element) {
|
||||
if ($width === 0 && ! ($element->getProperties()['styles']['contentRepeat'] ?? false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$float = $width / count($flexed);
|
||||
$elementWidth = floor($float);
|
||||
|
||||
if ($index === count($flexed) - 1) {
|
||||
$elementWidth += ($float - floor($float)) * count($flexed);
|
||||
}
|
||||
|
||||
$element->addStyle("w-{$elementWidth}");
|
||||
}
|
||||
|
||||
return $elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the space between the elements.
|
||||
*
|
||||
* @param array<int, Element> $elements
|
||||
* @return array<int, Element|string>
|
||||
*/
|
||||
private function applyJustifyBetween(array $elements): array
|
||||
{
|
||||
if (count($elements) <= 1) {
|
||||
return $elements;
|
||||
}
|
||||
|
||||
[$totalWidth, $parentWidth] = $this->getWidthFromElements($elements);
|
||||
$space = ($parentWidth - $totalWidth) / (count($elements) - 1);
|
||||
|
||||
if ($space < 1) {
|
||||
return $elements;
|
||||
}
|
||||
|
||||
$arr = [];
|
||||
|
||||
foreach ($elements as $index => &$element) {
|
||||
if ($index !== 0) {
|
||||
// Since there is no float pixel, on the last one it should round up...
|
||||
$length = $index === count($elements) - 1 ? ceil($space) : floor($space);
|
||||
$arr[] = str_repeat(' ', (int) $length);
|
||||
}
|
||||
|
||||
$arr[] = $element;
|
||||
}
|
||||
|
||||
return $arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the space between and around the elements.
|
||||
*
|
||||
* @param array<int, Element> $elements
|
||||
* @return array<int, Element|string>
|
||||
*/
|
||||
private function applyJustifyEvenly(array $elements): array
|
||||
{
|
||||
[$totalWidth, $parentWidth] = $this->getWidthFromElements($elements);
|
||||
$space = ($parentWidth - $totalWidth) / (count($elements) + 1);
|
||||
|
||||
if ($space < 1) {
|
||||
return $elements;
|
||||
}
|
||||
|
||||
$arr = [];
|
||||
foreach ($elements as &$element) {
|
||||
$arr[] = str_repeat(' ', (int) floor($space));
|
||||
$arr[] = $element;
|
||||
}
|
||||
|
||||
$decimals = ceil(($space - floor($space)) * (count($elements) + 1));
|
||||
$arr[] = str_repeat(' ', (int) (floor($space) + $decimals));
|
||||
|
||||
return $arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the space around the elements.
|
||||
*
|
||||
* @param array<int, Element> $elements
|
||||
* @return array<int, Element|string>
|
||||
*/
|
||||
private function applyJustifyAround(array $elements): array
|
||||
{
|
||||
if (count($elements) === 0) {
|
||||
return $elements;
|
||||
}
|
||||
|
||||
[$totalWidth, $parentWidth] = $this->getWidthFromElements($elements);
|
||||
$space = ($parentWidth - $totalWidth) / count($elements);
|
||||
|
||||
if ($space < 1) {
|
||||
return $elements;
|
||||
}
|
||||
|
||||
$contentSize = $totalWidth;
|
||||
$arr = [];
|
||||
|
||||
foreach ($elements as $index => &$element) {
|
||||
if ($index !== 0) {
|
||||
$arr[] = str_repeat(' ', (int) ceil($space));
|
||||
$contentSize += ceil($space);
|
||||
}
|
||||
|
||||
$arr[] = $element;
|
||||
}
|
||||
|
||||
return [
|
||||
str_repeat(' ', (int) floor(($parentWidth - $contentSize) / 2)),
|
||||
...$arr,
|
||||
str_repeat(' ', (int) ceil(($parentWidth - $contentSize) / 2)),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the space on before first element and after last element.
|
||||
*
|
||||
* @param array<int, Element> $elements
|
||||
* @return array<int, Element|string>
|
||||
*/
|
||||
private function applyJustifyCenter(array $elements): array
|
||||
{
|
||||
[$totalWidth, $parentWidth] = $this->getWidthFromElements($elements);
|
||||
$space = $parentWidth - $totalWidth;
|
||||
|
||||
if ($space < 1) {
|
||||
return $elements;
|
||||
}
|
||||
|
||||
return [
|
||||
str_repeat(' ', (int) floor($space / 2)),
|
||||
...$elements,
|
||||
str_repeat(' ', (int) ceil($space / 2)),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the total width for the elements and their parent width.
|
||||
*
|
||||
* @param array<int, Element> $elements
|
||||
* @return int[]
|
||||
*/
|
||||
private function getWidthFromElements(array $elements)
|
||||
{
|
||||
$totalWidth = (int) array_reduce($elements, fn ($carry, $element) => $carry += $element->getLength(), 0);
|
||||
$parentWidth = Styles::getParentWidth($elements[0]->getProperties()['parentStyles'] ?? []);
|
||||
|
||||
return [$totalWidth, $parentWidth];
|
||||
}
|
||||
}
|
46
vendor/nunomaduro/termwind/src/Html/PreRenderer.php
vendored
Normal file
46
vendor/nunomaduro/termwind/src/Html/PreRenderer.php
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Termwind\Html;
|
||||
|
||||
use Termwind\Components\Element;
|
||||
use Termwind\Termwind;
|
||||
use Termwind\ValueObjects\Node;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class PreRenderer
|
||||
{
|
||||
/**
|
||||
* Gets HTML content from a given node and converts to the content element.
|
||||
*/
|
||||
public function toElement(Node $node): Element
|
||||
{
|
||||
$lines = explode("\n", $node->getHtml());
|
||||
if (reset($lines) === '') {
|
||||
array_shift($lines);
|
||||
}
|
||||
|
||||
if (end($lines) === '') {
|
||||
array_pop($lines);
|
||||
}
|
||||
|
||||
$maxStrLen = array_reduce(
|
||||
$lines,
|
||||
static fn (int $max, string $line) => ($max < strlen($line)) ? strlen($line) : $max,
|
||||
0
|
||||
);
|
||||
|
||||
$styles = $node->getClassAttribute();
|
||||
$html = array_map(
|
||||
static fn (string $line) => (string) Termwind::div(str_pad($line, $maxStrLen + 3), $styles),
|
||||
$lines
|
||||
);
|
||||
|
||||
return Termwind::raw(
|
||||
implode('', $html)
|
||||
);
|
||||
}
|
||||
}
|
251
vendor/nunomaduro/termwind/src/Html/TableRenderer.php
vendored
Normal file
251
vendor/nunomaduro/termwind/src/Html/TableRenderer.php
vendored
Normal file
@ -0,0 +1,251 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Termwind\Html;
|
||||
|
||||
use Iterator;
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
use Symfony\Component\Console\Helper\TableCell;
|
||||
use Symfony\Component\Console\Helper\TableCellStyle;
|
||||
use Symfony\Component\Console\Helper\TableSeparator;
|
||||
use Symfony\Component\Console\Output\BufferedOutput;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Termwind\Components\Element;
|
||||
use Termwind\HtmlRenderer;
|
||||
use Termwind\Termwind;
|
||||
use Termwind\ValueObjects\Node;
|
||||
use Termwind\ValueObjects\Styles;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class TableRenderer
|
||||
{
|
||||
/**
|
||||
* Symfony table object uses for table generation.
|
||||
*/
|
||||
private Table $table;
|
||||
|
||||
/**
|
||||
* This object is used for accumulating output data from Symfony table object and return it as a string.
|
||||
*/
|
||||
private BufferedOutput $output;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->output = new BufferedOutput(
|
||||
// Content should output as is, without changes
|
||||
OutputInterface::VERBOSITY_NORMAL | OutputInterface::OUTPUT_RAW,
|
||||
true
|
||||
);
|
||||
|
||||
$this->table = new Table($this->output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts table output to the content element.
|
||||
*/
|
||||
public function toElement(Node $node): Element
|
||||
{
|
||||
$this->parseTable($node);
|
||||
$this->table->render();
|
||||
|
||||
$content = preg_replace('/\n$/', '', $this->output->fetch()) ?? '';
|
||||
|
||||
return Termwind::div($content, '', [
|
||||
'isFirstChild' => $node->isFirstChild(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks for thead, tfoot, tbody, tr elements in a given DOM and appends rows from them to the Symfony table object.
|
||||
*/
|
||||
private function parseTable(Node $node): void
|
||||
{
|
||||
$style = $node->getAttribute('style');
|
||||
if ($style !== '') {
|
||||
$this->table->setStyle($style);
|
||||
}
|
||||
|
||||
foreach ($node->getChildNodes() as $child) {
|
||||
match ($child->getName()) {
|
||||
'thead' => $this->parseHeader($child),
|
||||
'tfoot' => $this->parseFoot($child),
|
||||
'tbody' => $this->parseBody($child),
|
||||
default => $this->parseRows($child)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks for table header title and tr elements in a given thead DOM node and adds them to the Symfony table object.
|
||||
*/
|
||||
private function parseHeader(Node $node): void
|
||||
{
|
||||
$title = $node->getAttribute('title');
|
||||
|
||||
if ($title !== '') {
|
||||
$this->table->getStyle()->setHeaderTitleFormat(
|
||||
$this->parseTitleStyle($node)
|
||||
);
|
||||
$this->table->setHeaderTitle($title);
|
||||
}
|
||||
|
||||
foreach ($node->getChildNodes() as $child) {
|
||||
if ($child->isName('tr')) {
|
||||
foreach ($this->parseRow($child) as $row) {
|
||||
if (! is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
$this->table->setHeaders($row);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks for table footer and tr elements in a given tfoot DOM node and adds them to the Symfony table object.
|
||||
*/
|
||||
private function parseFoot(Node $node): void
|
||||
{
|
||||
$title = $node->getAttribute('title');
|
||||
|
||||
if ($title !== '') {
|
||||
$this->table->getStyle()->setFooterTitleFormat(
|
||||
$this->parseTitleStyle($node)
|
||||
);
|
||||
$this->table->setFooterTitle($title);
|
||||
}
|
||||
|
||||
foreach ($node->getChildNodes() as $child) {
|
||||
if ($child->isName('tr')) {
|
||||
$rows = iterator_to_array($this->parseRow($child));
|
||||
if (count($rows) > 0) {
|
||||
$this->table->addRow(new TableSeparator());
|
||||
$this->table->addRows($rows);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks for tr elements in a given DOM node and adds them to the Symfony table object.
|
||||
*/
|
||||
private function parseBody(Node $node): void
|
||||
{
|
||||
foreach ($node->getChildNodes() as $child) {
|
||||
if ($child->isName('tr')) {
|
||||
$this->parseRows($child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses table tr elements.
|
||||
*/
|
||||
private function parseRows(Node $node): void
|
||||
{
|
||||
foreach ($this->parseRow($node) as $row) {
|
||||
$this->table->addRow($row);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks for th, td elements in a given DOM node and converts them to a table cells.
|
||||
*
|
||||
* @return Iterator<array<int, TableCell>|TableSeparator>
|
||||
*/
|
||||
private function parseRow(Node $node): Iterator
|
||||
{
|
||||
$row = [];
|
||||
|
||||
foreach ($node->getChildNodes() as $child) {
|
||||
if ($child->isName('th') || $child->isName('td')) {
|
||||
$align = $child->getAttribute('align');
|
||||
|
||||
$class = $child->getClassAttribute();
|
||||
|
||||
if ($child->isName('th')) {
|
||||
$class .= ' strong';
|
||||
}
|
||||
|
||||
$text = (string) (new HtmlRenderer)->parse(
|
||||
trim(preg_replace('/<br\s?+\/?>/', "\n", $child->getHtml()) ?? '')
|
||||
);
|
||||
|
||||
if ((bool) preg_match(Styles::STYLING_REGEX, $text)) {
|
||||
$class .= ' font-normal';
|
||||
}
|
||||
|
||||
$row[] = new TableCell(
|
||||
// I need only spaces after applying margin, padding and width except tags.
|
||||
// There is no place for tags, they broke cell formatting.
|
||||
(string) Termwind::span($text, $class),
|
||||
[
|
||||
// Gets rowspan and colspan from tr and td tag attributes
|
||||
'colspan' => max((int) $child->getAttribute('colspan'), 1),
|
||||
'rowspan' => max((int) $child->getAttribute('rowspan'), 1),
|
||||
|
||||
// There are background and foreground and options
|
||||
'style' => $this->parseCellStyle(
|
||||
$class,
|
||||
$align === '' ? TableCellStyle::DEFAULT_ALIGN : $align
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($row !== []) {
|
||||
yield $row;
|
||||
}
|
||||
|
||||
$border = (int) $node->getAttribute('border');
|
||||
for ($i = $border; $i--; $i > 0) {
|
||||
yield new TableSeparator();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses tr, td tag class attribute and passes bg, fg and options to a table cell style.
|
||||
*/
|
||||
private function parseCellStyle(string $styles, string $align = TableCellStyle::DEFAULT_ALIGN): TableCellStyle
|
||||
{
|
||||
// I use this empty span for getting styles for bg, fg and options
|
||||
// It will be a good idea to get properties without element object and then pass them to an element object
|
||||
$element = Termwind::span('%s', $styles);
|
||||
|
||||
$styles = [];
|
||||
|
||||
$colors = $element->getProperties()['colors'] ?? [];
|
||||
|
||||
foreach ($colors as $option => $content) {
|
||||
if (in_array($option, ['fg', 'bg'], true)) {
|
||||
$content = is_array($content) ? array_pop($content) : $content;
|
||||
|
||||
$styles[] = "$option=$content";
|
||||
}
|
||||
}
|
||||
|
||||
// If there are no styles we don't need extra tags
|
||||
if ($styles === []) {
|
||||
$cellFormat = '%s';
|
||||
} else {
|
||||
$cellFormat = '<'.implode(';', $styles).'>%s</>';
|
||||
}
|
||||
|
||||
return new TableCellStyle([
|
||||
'align' => $align,
|
||||
'cellFormat' => $cellFormat,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get styled representation of title.
|
||||
*/
|
||||
private function parseTitleStyle(Node $node): string
|
||||
{
|
||||
return (string) Termwind::span(' %s ', $node->getClassAttribute());
|
||||
}
|
||||
}
|
116
vendor/nunomaduro/termwind/src/HtmlRenderer.php
vendored
Normal file
116
vendor/nunomaduro/termwind/src/HtmlRenderer.php
vendored
Normal file
@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Termwind;
|
||||
|
||||
use DOMDocument;
|
||||
use DOMNode;
|
||||
use Termwind\Html\CodeRenderer;
|
||||
use Termwind\Html\PreRenderer;
|
||||
use Termwind\Html\TableRenderer;
|
||||
use Termwind\ValueObjects\Node;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class HtmlRenderer
|
||||
{
|
||||
/**
|
||||
* Renders the given html.
|
||||
*/
|
||||
public function render(string $html, int $options): void
|
||||
{
|
||||
$this->parse($html)->render($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the given html.
|
||||
*/
|
||||
public function parse(string $html): Components\Element
|
||||
{
|
||||
$dom = new DOMDocument();
|
||||
|
||||
if (strip_tags($html) === $html) {
|
||||
return Termwind::span($html);
|
||||
}
|
||||
|
||||
$html = '<?xml encoding="UTF-8">'.trim($html);
|
||||
$dom->loadHTML($html, LIBXML_NOERROR | LIBXML_COMPACT | LIBXML_HTML_NODEFDTD | LIBXML_NOBLANKS | LIBXML_NOXMLDECL);
|
||||
|
||||
/** @var DOMNode $body */
|
||||
$body = $dom->getElementsByTagName('body')->item(0);
|
||||
$el = $this->convert(new Node($body));
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
return is_string($el)
|
||||
? Termwind::span($el)
|
||||
: $el;
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a tree of DOM nodes to a tree of termwind elements.
|
||||
*/
|
||||
private function convert(Node $node): Components\Element|string
|
||||
{
|
||||
$children = [];
|
||||
|
||||
if ($node->isName('table')) {
|
||||
return (new TableRenderer)->toElement($node);
|
||||
} elseif ($node->isName('code')) {
|
||||
return (new CodeRenderer)->toElement($node);
|
||||
} elseif ($node->isName('pre')) {
|
||||
return (new PreRenderer)->toElement($node);
|
||||
}
|
||||
|
||||
foreach ($node->getChildNodes() as $child) {
|
||||
$children[] = $this->convert($child);
|
||||
}
|
||||
|
||||
$children = array_filter($children, fn ($child) => $child !== '');
|
||||
|
||||
return $this->toElement($node, $children);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a given DOM node to it's termwind element equivalent.
|
||||
*
|
||||
* @param array<int, Components\Element|string> $children
|
||||
*/
|
||||
private function toElement(Node $node, array $children): Components\Element|string
|
||||
{
|
||||
if ($node->isText() || $node->isComment()) {
|
||||
return (string) $node;
|
||||
}
|
||||
|
||||
/** @var array<string, mixed> $properties */
|
||||
$properties = [
|
||||
'isFirstChild' => $node->isFirstChild(),
|
||||
];
|
||||
|
||||
$styles = $node->getClassAttribute();
|
||||
|
||||
return match ($node->getName()) {
|
||||
'body' => $children[0], // Pick only the first element from the body node
|
||||
'div' => Termwind::div($children, $styles, $properties),
|
||||
'p' => Termwind::paragraph($children, $styles, $properties),
|
||||
'ul' => Termwind::ul($children, $styles, $properties),
|
||||
'ol' => Termwind::ol($children, $styles, $properties),
|
||||
'li' => Termwind::li($children, $styles, $properties),
|
||||
'dl' => Termwind::dl($children, $styles, $properties),
|
||||
'dt' => Termwind::dt($children, $styles, $properties),
|
||||
'dd' => Termwind::dd($children, $styles, $properties),
|
||||
'span' => Termwind::span($children, $styles, $properties),
|
||||
'br' => Termwind::breakLine($styles, $properties),
|
||||
'strong' => Termwind::span($children, $styles, $properties)->strong(),
|
||||
'b' => Termwind::span($children, $styles, $properties)->fontBold(),
|
||||
'em', 'i' => Termwind::span($children, $styles, $properties)->italic(),
|
||||
'u' => Termwind::span($children, $styles, $properties)->underline(),
|
||||
's' => Termwind::span($children, $styles, $properties)->lineThrough(),
|
||||
'a' => Termwind::anchor($children, $styles, $properties)->href($node->getAttribute('href')),
|
||||
'hr' => Termwind::hr($styles, $properties),
|
||||
default => Termwind::div($children, $styles, $properties),
|
||||
};
|
||||
}
|
||||
}
|
22
vendor/nunomaduro/termwind/src/Laravel/TermwindServiceProvider.php
vendored
Normal file
22
vendor/nunomaduro/termwind/src/Laravel/TermwindServiceProvider.php
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Termwind\Laravel;
|
||||
|
||||
use Illuminate\Console\OutputStyle;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Termwind\Termwind;
|
||||
|
||||
final class TermwindServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Sets the correct renderer to be used.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
$this->app->resolving(OutputStyle::class, function ($style): void {
|
||||
Termwind::renderUsing($style->getOutput());
|
||||
});
|
||||
}
|
||||
}
|
93
vendor/nunomaduro/termwind/src/Question.php
vendored
Normal file
93
vendor/nunomaduro/termwind/src/Question.php
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Termwind;
|
||||
|
||||
use ReflectionClass;
|
||||
use Symfony\Component\Console\Helper\SymfonyQuestionHelper;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
use Symfony\Component\Console\Input\StreamableInputInterface;
|
||||
use Symfony\Component\Console\Question\Question as SymfonyQuestion;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Termwind\Helpers\QuestionHelper;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Question
|
||||
{
|
||||
/**
|
||||
* The streamable input to receive the input from the user.
|
||||
*/
|
||||
private static StreamableInputInterface|null $streamableInput;
|
||||
|
||||
/**
|
||||
* An instance of Symfony's question helper.
|
||||
*/
|
||||
private SymfonyQuestionHelper $helper;
|
||||
|
||||
public function __construct(SymfonyQuestionHelper $helper = null)
|
||||
{
|
||||
$this->helper = $helper ?? new QuestionHelper();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the streamable input implementation.
|
||||
*/
|
||||
public static function setStreamableInput(StreamableInputInterface|null $streamableInput): void
|
||||
{
|
||||
self::$streamableInput = $streamableInput ?? new ArgvInput();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the streamable input implementation.
|
||||
*/
|
||||
public static function getStreamableInput(): StreamableInputInterface
|
||||
{
|
||||
return self::$streamableInput ??= new ArgvInput();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a prompt to the user.
|
||||
*
|
||||
* @param iterable<array-key, string>|null $autocomplete
|
||||
*/
|
||||
public function ask(string $question, iterable $autocomplete = null): mixed
|
||||
{
|
||||
$html = (new HtmlRenderer)->parse($question)->toString();
|
||||
|
||||
$question = new SymfonyQuestion($html);
|
||||
|
||||
if ($autocomplete !== null) {
|
||||
$question->setAutocompleterValues($autocomplete);
|
||||
}
|
||||
|
||||
$output = Termwind::getRenderer();
|
||||
|
||||
if ($output instanceof SymfonyStyle) {
|
||||
$property = (new ReflectionClass(SymfonyStyle::class))
|
||||
->getProperty('questionHelper');
|
||||
|
||||
$property->setAccessible(true);
|
||||
|
||||
$currentHelper = $property->isInitialized($output)
|
||||
? $property->getValue($output)
|
||||
: new SymfonyQuestionHelper();
|
||||
|
||||
$property->setValue($output, new QuestionHelper);
|
||||
|
||||
try {
|
||||
return $output->askQuestion($question);
|
||||
} finally {
|
||||
$property->setValue($output, $currentHelper);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->helper->ask(
|
||||
self::getStreamableInput(),
|
||||
Termwind::getRenderer(),
|
||||
$question,
|
||||
);
|
||||
}
|
||||
}
|
59
vendor/nunomaduro/termwind/src/Repositories/Styles.php
vendored
Normal file
59
vendor/nunomaduro/termwind/src/Repositories/Styles.php
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Termwind\Repositories;
|
||||
|
||||
use Closure;
|
||||
use Termwind\ValueObjects\Style;
|
||||
use Termwind\ValueObjects\Styles as StylesValueObject;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Styles
|
||||
{
|
||||
/**
|
||||
* @var array<string, Style>
|
||||
*/
|
||||
private static array $storage = [];
|
||||
|
||||
/**
|
||||
* Creates a new style from the given arguments.
|
||||
*
|
||||
* @param (Closure(StylesValueObject $element, string|int ...$arguments): StylesValueObject)|null $callback
|
||||
* @return Style
|
||||
*/
|
||||
public static function create(string $name, Closure $callback = null): Style
|
||||
{
|
||||
self::$storage[$name] = $style = new Style(
|
||||
$callback ?? static fn (StylesValueObject $styles) => $styles
|
||||
);
|
||||
|
||||
return $style;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all existing styles.
|
||||
*/
|
||||
public static function flush(): void
|
||||
{
|
||||
self::$storage = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a style with the given name exists.
|
||||
*/
|
||||
public static function has(string $name): bool
|
||||
{
|
||||
return array_key_exists($name, self::$storage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the style with the given name.
|
||||
*/
|
||||
public static function get(string $name): Style
|
||||
{
|
||||
return self::$storage[$name];
|
||||
}
|
||||
}
|
50
vendor/nunomaduro/termwind/src/Terminal.php
vendored
Normal file
50
vendor/nunomaduro/termwind/src/Terminal.php
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Termwind;
|
||||
|
||||
use Symfony\Component\Console\Terminal as ConsoleTerminal;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Terminal
|
||||
{
|
||||
/**
|
||||
* An instance of Symfony's console terminal.
|
||||
*/
|
||||
private ConsoleTerminal $terminal;
|
||||
|
||||
/**
|
||||
* Creates a new terminal instance.
|
||||
*/
|
||||
public function __construct(ConsoleTerminal $terminal = null)
|
||||
{
|
||||
$this->terminal = $terminal ?? new ConsoleTerminal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the terminal width.
|
||||
*/
|
||||
public function width(): int
|
||||
{
|
||||
return $this->terminal->getWidth();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the terminal height.
|
||||
*/
|
||||
public function height(): int
|
||||
{
|
||||
return $this->terminal->getHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the terminal screen.
|
||||
*/
|
||||
public function clear(): void
|
||||
{
|
||||
Termwind::getRenderer()->write("\ec");
|
||||
}
|
||||
}
|
300
vendor/nunomaduro/termwind/src/Termwind.php
vendored
Normal file
300
vendor/nunomaduro/termwind/src/Termwind.php
vendored
Normal file
@ -0,0 +1,300 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Termwind;
|
||||
|
||||
use Closure;
|
||||
use Symfony\Component\Console\Output\ConsoleOutput;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Termwind\Components\Element;
|
||||
use Termwind\Exceptions\InvalidChild;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Termwind
|
||||
{
|
||||
/**
|
||||
* The implementation of the output.
|
||||
*/
|
||||
private static OutputInterface|null $renderer;
|
||||
|
||||
/**
|
||||
* Sets the renderer implementation.
|
||||
*/
|
||||
public static function renderUsing(OutputInterface|null $renderer): void
|
||||
{
|
||||
self::$renderer = $renderer ?? new ConsoleOutput();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a div element instance.
|
||||
*
|
||||
* @param array<int, Element|string>|string $content
|
||||
* @param array<string, mixed> $properties
|
||||
*/
|
||||
public static function div(array|string $content = '', string $styles = '', array $properties = []): Components\Div
|
||||
{
|
||||
$content = self::prepareElements($content, $styles);
|
||||
|
||||
return Components\Div::fromStyles(
|
||||
self::getRenderer(), $content, $styles, $properties
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a paragraph element instance.
|
||||
*
|
||||
* @param array<int, Element|string>|string $content
|
||||
* @param array<string, mixed> $properties
|
||||
*/
|
||||
public static function paragraph(array|string $content = '', string $styles = '', array $properties = []): Components\Paragraph
|
||||
{
|
||||
$content = self::prepareElements($content, $styles);
|
||||
|
||||
return Components\Paragraph::fromStyles(
|
||||
self::getRenderer(), $content, $styles, $properties
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a span element instance with the given style.
|
||||
*
|
||||
* @param array<int, Element|string>|string $content
|
||||
* @param array<string, mixed> $properties
|
||||
*/
|
||||
public static function span(array|string $content = '', string $styles = '', array $properties = []): Components\Span
|
||||
{
|
||||
$content = self::prepareElements($content, $styles);
|
||||
|
||||
return Components\Span::fromStyles(
|
||||
self::getRenderer(), $content, $styles, $properties
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an element instance with raw content.
|
||||
*
|
||||
* @param array<int, Element|string>|string $content
|
||||
*/
|
||||
public static function raw(array|string $content = ''): Components\Raw
|
||||
{
|
||||
return Components\Raw::fromStyles(
|
||||
self::getRenderer(), $content
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an anchor element instance with the given style.
|
||||
*
|
||||
* @param array<int, Element|string>|string $content
|
||||
* @param array<string, mixed> $properties
|
||||
*/
|
||||
public static function anchor(array|string $content = '', string $styles = '', array $properties = []): Components\Anchor
|
||||
{
|
||||
$content = self::prepareElements($content, $styles);
|
||||
|
||||
return Components\Anchor::fromStyles(
|
||||
self::getRenderer(), $content, $styles, $properties
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an unordered list instance.
|
||||
*
|
||||
* @param array<int, string|Element> $content
|
||||
* @param array<string, mixed> $properties
|
||||
*/
|
||||
public static function ul(array $content = [], string $styles = '', array $properties = []): Components\Ul
|
||||
{
|
||||
$ul = Components\Ul::fromStyles(
|
||||
self::getRenderer(), '', $styles, $properties
|
||||
);
|
||||
|
||||
$content = self::prepareElements(
|
||||
$content,
|
||||
$styles,
|
||||
static function ($li) use ($ul): string|Element {
|
||||
if (is_string($li)) {
|
||||
return $li;
|
||||
}
|
||||
|
||||
if (! $li instanceof Components\Li) {
|
||||
throw new InvalidChild('Unordered lists only accept `li` as child');
|
||||
}
|
||||
|
||||
return match (true) {
|
||||
$li->hasStyle('list-none') => $li,
|
||||
$ul->hasStyle('list-none') => $li->addStyle('list-none'),
|
||||
$ul->hasStyle('list-square') => $li->addStyle('list-square'),
|
||||
$ul->hasStyle('list-disc') => $li->addStyle('list-disc'),
|
||||
default => $li->addStyle('list-none'),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
return $ul->setContent($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an ordered list instance.
|
||||
*
|
||||
* @param array<int, string|Element> $content
|
||||
* @param array<string, mixed> $properties
|
||||
*/
|
||||
public static function ol(array $content = [], string $styles = '', array $properties = []): Components\Ol
|
||||
{
|
||||
$ol = Components\Ol::fromStyles(
|
||||
self::getRenderer(), '', $styles, $properties
|
||||
);
|
||||
|
||||
$index = 0;
|
||||
|
||||
$content = self::prepareElements(
|
||||
$content,
|
||||
$styles,
|
||||
static function ($li) use ($ol, &$index): string|Element {
|
||||
if (is_string($li)) {
|
||||
return $li;
|
||||
}
|
||||
|
||||
if (! $li instanceof Components\Li) {
|
||||
throw new InvalidChild('Ordered lists only accept `li` as child');
|
||||
}
|
||||
|
||||
return match (true) {
|
||||
$li->hasStyle('list-none') => $li->addStyle('list-none'),
|
||||
$ol->hasStyle('list-none') => $li->addStyle('list-none'),
|
||||
$ol->hasStyle('list-decimal') => $li->addStyle('list-decimal-'.(++$index)),
|
||||
default => $li->addStyle('list-none'),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
return $ol->setContent($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a list item instance.
|
||||
*
|
||||
* @param array<int, Element|string>|string $content
|
||||
* @param array<string, mixed> $properties
|
||||
*/
|
||||
public static function li(array|string $content = '', string $styles = '', array $properties = []): Components\Li
|
||||
{
|
||||
$content = self::prepareElements($content, $styles);
|
||||
|
||||
return Components\Li::fromStyles(
|
||||
self::getRenderer(), $content, $styles, $properties
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a description list instance.
|
||||
*
|
||||
* @param array<int, string|Element> $content
|
||||
* @param array<string, mixed> $properties
|
||||
*/
|
||||
public static function dl(array $content = [], string $styles = '', array $properties = []): Components\Dl
|
||||
{
|
||||
$content = self::prepareElements(
|
||||
$content,
|
||||
$styles,
|
||||
static function ($element): string|Element {
|
||||
if (is_string($element)) {
|
||||
return $element;
|
||||
}
|
||||
|
||||
if (! $element instanceof Components\Dt && ! $element instanceof Components\Dd) {
|
||||
throw new InvalidChild('Description lists only accept `dt` and `dd` as children');
|
||||
}
|
||||
|
||||
return $element;
|
||||
}
|
||||
);
|
||||
|
||||
return Components\Dl::fromStyles(
|
||||
self::getRenderer(), $content, $styles, $properties
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a description term instance.
|
||||
*
|
||||
* @param array<int, Element|string>|string $content
|
||||
* @param array<string, mixed> $properties
|
||||
*/
|
||||
public static function dt(array|string $content = '', string $styles = '', array $properties = []): Components\Dt
|
||||
{
|
||||
$content = self::prepareElements($content, $styles);
|
||||
|
||||
return Components\Dt::fromStyles(
|
||||
self::getRenderer(), $content, $styles, $properties
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a description details instance.
|
||||
*
|
||||
* @param array<int, Element|string>|string $content
|
||||
* @param array<string, mixed> $properties
|
||||
*/
|
||||
public static function dd(array|string $content = '', string $styles = '', array $properties = []): Components\Dd
|
||||
{
|
||||
$content = self::prepareElements($content, $styles);
|
||||
|
||||
return Components\Dd::fromStyles(
|
||||
self::getRenderer(), $content, $styles, $properties
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a horizontal rule instance.
|
||||
*
|
||||
* @param array<string, mixed> $properties
|
||||
*/
|
||||
public static function hr(string $styles = '', array $properties = []): Components\Hr
|
||||
{
|
||||
return Components\Hr::fromStyles(
|
||||
self::getRenderer(), '', $styles, $properties
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an break line element instance.
|
||||
*
|
||||
* @param array<string, mixed> $properties
|
||||
*/
|
||||
public static function breakLine(string $styles = '', array $properties = []): Components\BreakLine
|
||||
{
|
||||
return Components\BreakLine::fromStyles(
|
||||
self::getRenderer(), '', $styles, $properties
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current renderer instance.
|
||||
*/
|
||||
public static function getRenderer(): OutputInterface
|
||||
{
|
||||
return self::$renderer ??= new ConsoleOutput();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert child elements to a string.
|
||||
*
|
||||
* @param array<int, string|Element>|string $elements
|
||||
* @return array<int, string|Element>
|
||||
*/
|
||||
private static function prepareElements($elements, string $styles = '', Closure|null $callback = null): array
|
||||
{
|
||||
if ($callback === null) {
|
||||
$callback = static fn ($element): string|Element => $element;
|
||||
}
|
||||
|
||||
$elements = is_array($elements) ? $elements : [$elements];
|
||||
|
||||
return array_map($callback, $elements);
|
||||
}
|
||||
}
|
205
vendor/nunomaduro/termwind/src/ValueObjects/Node.php
vendored
Normal file
205
vendor/nunomaduro/termwind/src/ValueObjects/Node.php
vendored
Normal file
@ -0,0 +1,205 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Termwind\ValueObjects;
|
||||
|
||||
use Generator;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Node
|
||||
{
|
||||
/**
|
||||
* A value object with helper methods for working with DOM node.
|
||||
*/
|
||||
public function __construct(private \DOMNode $node)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the node.
|
||||
*/
|
||||
public function getValue(): string
|
||||
{
|
||||
return $this->node->nodeValue ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets child nodes of the node.
|
||||
*
|
||||
* @return Generator<Node>
|
||||
*/
|
||||
public function getChildNodes(): Generator
|
||||
{
|
||||
foreach ($this->node->childNodes as $node) {
|
||||
yield new static($node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the node is a text.
|
||||
*/
|
||||
public function isText(): bool
|
||||
{
|
||||
return $this->node instanceof \DOMText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the node is a comment.
|
||||
*/
|
||||
public function isComment(): bool
|
||||
{
|
||||
return $this->node instanceof \DOMComment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the current node name with a given name.
|
||||
*/
|
||||
public function isName(string $name): bool
|
||||
{
|
||||
return $this->getName() === $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current node type name.
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->node->nodeName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns value of [class] attribute.
|
||||
*/
|
||||
public function getClassAttribute(): string
|
||||
{
|
||||
return $this->getAttribute('class');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns value of attribute with a given name.
|
||||
*/
|
||||
public function getAttribute(string $name): string
|
||||
{
|
||||
if ($this->node instanceof \DOMElement) {
|
||||
return $this->node->getAttribute($name);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the node is empty.
|
||||
*/
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return $this->isText() && preg_replace('/\s+/', '', $this->getValue()) === '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the previous sibling from the node.
|
||||
*/
|
||||
public function getPreviousSibling(): static|null
|
||||
{
|
||||
$node = $this->node;
|
||||
|
||||
while ($node = $node->previousSibling) {
|
||||
$node = new static($node);
|
||||
|
||||
if ($node->isEmpty()) {
|
||||
$node = $node->node;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! $node->isComment()) {
|
||||
return $node;
|
||||
}
|
||||
|
||||
$node = $node->node;
|
||||
}
|
||||
|
||||
return is_null($node) ? null : new static($node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next sibling from the node.
|
||||
*/
|
||||
public function getNextSibling(): static|null
|
||||
{
|
||||
$node = $this->node;
|
||||
|
||||
while ($node = $node->nextSibling) {
|
||||
$node = new static($node);
|
||||
|
||||
if ($node->isEmpty()) {
|
||||
$node = $node->node;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! $node->isComment()) {
|
||||
return $node;
|
||||
}
|
||||
|
||||
$node = $node->node;
|
||||
}
|
||||
|
||||
return is_null($node) ? null : new static($node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the node is the first child.
|
||||
*/
|
||||
public function isFirstChild(): bool
|
||||
{
|
||||
return is_null($this->getPreviousSibling());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the inner HTML representation of the node including child nodes.
|
||||
*/
|
||||
public function getHtml(): string
|
||||
{
|
||||
$html = '';
|
||||
foreach ($this->node->childNodes as $child) {
|
||||
if ($child->ownerDocument instanceof \DOMDocument) {
|
||||
$html .= $child->ownerDocument->saveXML($child);
|
||||
}
|
||||
}
|
||||
|
||||
return html_entity_decode($html);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the node to a string.
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
if ($this->isComment()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ($this->getValue() === ' ') {
|
||||
return ' ';
|
||||
}
|
||||
|
||||
if ($this->isEmpty()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$text = preg_replace('/\s+/', ' ', $this->getValue()) ?? '';
|
||||
|
||||
if (is_null($this->getPreviousSibling())) {
|
||||
$text = ltrim($text);
|
||||
}
|
||||
|
||||
if (is_null($this->getNextSibling())) {
|
||||
$text = rtrim($text);
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
}
|
70
vendor/nunomaduro/termwind/src/ValueObjects/Style.php
vendored
Normal file
70
vendor/nunomaduro/termwind/src/ValueObjects/Style.php
vendored
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Termwind\ValueObjects;
|
||||
|
||||
use Closure;
|
||||
use Termwind\Actions\StyleToMethod;
|
||||
use Termwind\Exceptions\InvalidColor;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Style
|
||||
{
|
||||
/**
|
||||
* Creates a new value object instance.
|
||||
*
|
||||
* @param Closure(Styles $styles, string|int ...$argument): Styles $callback
|
||||
*/
|
||||
public function __construct(private Closure $callback, private string $color = '')
|
||||
{
|
||||
// ..
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the given set of styles to the styles.
|
||||
*/
|
||||
public function apply(string $styles): void
|
||||
{
|
||||
$callback = clone $this->callback;
|
||||
|
||||
$this->callback = static function (
|
||||
Styles $formatter,
|
||||
string|int ...$arguments
|
||||
) use ($callback, $styles): Styles {
|
||||
$formatter = $callback($formatter, ...$arguments);
|
||||
|
||||
return StyleToMethod::multiple($formatter, $styles);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color to the style.
|
||||
*/
|
||||
public function color(string $color): void
|
||||
{
|
||||
if (preg_match('/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/', $color) < 1) {
|
||||
throw new InvalidColor(sprintf('The color %s is invalid.', $color));
|
||||
}
|
||||
|
||||
$this->color = $color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the color.
|
||||
*/
|
||||
public function getColor(): string
|
||||
{
|
||||
return $this->color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Styles the given formatter with this style.
|
||||
*/
|
||||
public function __invoke(Styles $styles, string|int ...$arguments): Styles
|
||||
{
|
||||
return ($this->callback)($styles, ...$arguments);
|
||||
}
|
||||
}
|
1061
vendor/nunomaduro/termwind/src/ValueObjects/Styles.php
vendored
Normal file
1061
vendor/nunomaduro/termwind/src/ValueObjects/Styles.php
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user