first commit
This commit is contained in:
21
vendor/laravel/prompts/LICENSE.md
vendored
Normal file
21
vendor/laravel/prompts/LICENSE.md
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) Taylor Otwell
|
||||
|
||||
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.
|
34
vendor/laravel/prompts/README.md
vendored
Normal file
34
vendor/laravel/prompts/README.md
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
<p align="center"><img width="386" height="68" src="/art/logo.svg" alt="Laravel Prompts"></p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/laravel/prompts/actions"><img src="https://github.com/laravel/prompts/workflows/tests/badge.svg" alt="Build Status"></a>
|
||||
<a href="https://packagist.org/packages/laravel/prompts"><img src="https://img.shields.io/packagist/dt/laravel/prompts" alt="Total Downloads"></a>
|
||||
<a href="https://packagist.org/packages/laravel/prompts"><img src="https://img.shields.io/packagist/v/laravel/prompts" alt="Latest Stable Version"></a>
|
||||
<a href="https://packagist.org/packages/laravel/prompts"><img src="https://img.shields.io/packagist/l/laravel/prompts" alt="License"></a>
|
||||
</p>
|
||||
|
||||
## Introduction
|
||||
|
||||
Laravel Prompts is a PHP package for adding beautiful and user-friendly forms to your command-line applications, with browser-like features including placeholder text and validation.
|
||||
|
||||
Laravel Prompts is perfect for accepting user input in your [Artisan console commands](https://laravel.com/docs/artisan#writing-commands), but it may also be used in any command-line PHP project.
|
||||
|
||||
## Official Documentation
|
||||
|
||||
Documentation for Laravel Prompts can be found on the [Laravel website](https://laravel.com/docs/prompts).
|
||||
|
||||
## Contributing
|
||||
|
||||
Thank you for considering contributing to Laravel Prompts! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
|
||||
|
||||
## Security Vulnerabilities
|
||||
|
||||
Please review [our security policy](https://github.com/laravel/prompts/security/policy) on how to report security vulnerabilities.
|
||||
|
||||
## License
|
||||
|
||||
Laravel Prompts is open-sourced software licensed under the [MIT license](LICENSE.md).
|
49
vendor/laravel/prompts/composer.json
vendored
Normal file
49
vendor/laravel/prompts/composer.json
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "laravel/prompts",
|
||||
"type": "library",
|
||||
"license": "MIT",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Laravel\\Prompts\\": "src/"
|
||||
},
|
||||
"files": [
|
||||
"src/helpers.php"
|
||||
]
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.1",
|
||||
"ext-mbstring": "*",
|
||||
"illuminate/collections": "^10.0|^11.0",
|
||||
"symfony/console": "^6.2|^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^1.11",
|
||||
"pestphp/pest": "^2.3",
|
||||
"mockery/mockery": "^1.5",
|
||||
"phpstan/phpstan-mockery": "^1.1"
|
||||
},
|
||||
"conflict": {
|
||||
"illuminate/console": ">=10.17.0 <10.25.0",
|
||||
"laravel/framework": ">=10.17.0 <10.25.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-pcntl": "Required for the spinner to be animated."
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"pestphp/pest-plugin": true
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "0.1.x-dev"
|
||||
}
|
||||
},
|
||||
"prefer-stable": true,
|
||||
"minimum-stability": "dev"
|
||||
}
|
14
vendor/laravel/prompts/phpunit.xml
vendored
Normal file
14
vendor/laravel/prompts/phpunit.xml
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.2/phpunit.xsd" bootstrap="vendor/autoload.php" colors="true" backupStaticProperties="true">
|
||||
<testsuites>
|
||||
<testsuite name="Test Suite">
|
||||
<directory suffix="Test.php">./tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<source>
|
||||
<include>
|
||||
<directory suffix=".php">./app</directory>
|
||||
<directory suffix=".php">./src</directory>
|
||||
</include>
|
||||
</source>
|
||||
</phpunit>
|
206
vendor/laravel/prompts/src/Concerns/Colors.php
vendored
Normal file
206
vendor/laravel/prompts/src/Concerns/Colors.php
vendored
Normal file
@ -0,0 +1,206 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts\Concerns;
|
||||
|
||||
trait Colors
|
||||
{
|
||||
/**
|
||||
* Reset all colors and styles.
|
||||
*/
|
||||
public function reset(string $text): string
|
||||
{
|
||||
return "\e[0m{$text}\e[0m";
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the text bold.
|
||||
*/
|
||||
public function bold(string $text): string
|
||||
{
|
||||
return "\e[1m{$text}\e[22m";
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the text dim.
|
||||
*/
|
||||
public function dim(string $text): string
|
||||
{
|
||||
return "\e[2m{$text}\e[22m";
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the text italic.
|
||||
*/
|
||||
public function italic(string $text): string
|
||||
{
|
||||
return "\e[3m{$text}\e[23m";
|
||||
}
|
||||
|
||||
/**
|
||||
* Underline the text.
|
||||
*/
|
||||
public function underline(string $text): string
|
||||
{
|
||||
return "\e[4m{$text}\e[24m";
|
||||
}
|
||||
|
||||
/**
|
||||
* Invert the text and background colors.
|
||||
*/
|
||||
public function inverse(string $text): string
|
||||
{
|
||||
return "\e[7m{$text}\e[27m";
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the text.
|
||||
*/
|
||||
public function hidden(string $text): string
|
||||
{
|
||||
return "\e[8m{$text}\e[28m";
|
||||
}
|
||||
|
||||
/**
|
||||
* Strike through the text.
|
||||
*/
|
||||
public function strikethrough(string $text): string
|
||||
{
|
||||
return "\e[9m{$text}\e[29m";
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the text color to black.
|
||||
*/
|
||||
public function black(string $text): string
|
||||
{
|
||||
return "\e[30m{$text}\e[39m";
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the text color to red.
|
||||
*/
|
||||
public function red(string $text): string
|
||||
{
|
||||
return "\e[31m{$text}\e[39m";
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the text color to green.
|
||||
*/
|
||||
public function green(string $text): string
|
||||
{
|
||||
return "\e[32m{$text}\e[39m";
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the text color to yellow.
|
||||
*/
|
||||
public function yellow(string $text): string
|
||||
{
|
||||
return "\e[33m{$text}\e[39m";
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the text color to blue.
|
||||
*/
|
||||
public function blue(string $text): string
|
||||
{
|
||||
return "\e[34m{$text}\e[39m";
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the text color to magenta.
|
||||
*/
|
||||
public function magenta(string $text): string
|
||||
{
|
||||
return "\e[35m{$text}\e[39m";
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the text color to cyan.
|
||||
*/
|
||||
public function cyan(string $text): string
|
||||
{
|
||||
return "\e[36m{$text}\e[39m";
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the text color to white.
|
||||
*/
|
||||
public function white(string $text): string
|
||||
{
|
||||
return "\e[37m{$text}\e[39m";
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the text background to black.
|
||||
*/
|
||||
public function bgBlack(string $text): string
|
||||
{
|
||||
return "\e[40m{$text}\e[49m";
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the text background to red.
|
||||
*/
|
||||
public function bgRed(string $text): string
|
||||
{
|
||||
return "\e[41m{$text}\e[49m";
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the text background to green.
|
||||
*/
|
||||
public function bgGreen(string $text): string
|
||||
{
|
||||
return "\e[42m{$text}\e[49m";
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the text background to yellow.
|
||||
*/
|
||||
public function bgYellow(string $text): string
|
||||
{
|
||||
return "\e[43m{$text}\e[49m";
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the text background to blue.
|
||||
*/
|
||||
public function bgBlue(string $text): string
|
||||
{
|
||||
return "\e[44m{$text}\e[49m";
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the text background to magenta.
|
||||
*/
|
||||
public function bgMagenta(string $text): string
|
||||
{
|
||||
return "\e[45m{$text}\e[49m";
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the text background to cyan.
|
||||
*/
|
||||
public function bgCyan(string $text): string
|
||||
{
|
||||
return "\e[46m{$text}\e[49m";
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the text background to white.
|
||||
*/
|
||||
public function bgWhite(string $text): string
|
||||
{
|
||||
return "\e[47m{$text}\e[49m";
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the text color to gray.
|
||||
*/
|
||||
public function gray(string $text): string
|
||||
{
|
||||
return "\e[90m{$text}\e[39m";
|
||||
}
|
||||
}
|
79
vendor/laravel/prompts/src/Concerns/Cursor.php
vendored
Normal file
79
vendor/laravel/prompts/src/Concerns/Cursor.php
vendored
Normal file
@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts\Concerns;
|
||||
|
||||
trait Cursor
|
||||
{
|
||||
/**
|
||||
* Indicates if the cursor has been hidden.
|
||||
*/
|
||||
protected static bool $cursorHidden = false;
|
||||
|
||||
/**
|
||||
* Hide the cursor.
|
||||
*/
|
||||
public function hideCursor(): void
|
||||
{
|
||||
static::writeDirectly("\e[?25l");
|
||||
|
||||
static::$cursorHidden = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the cursor.
|
||||
*/
|
||||
public function showCursor(): void
|
||||
{
|
||||
static::writeDirectly("\e[?25h");
|
||||
|
||||
static::$cursorHidden = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the cursor if it was hidden.
|
||||
*/
|
||||
public function restoreCursor(): void
|
||||
{
|
||||
if (static::$cursorHidden) {
|
||||
$this->showCursor();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the cursor.
|
||||
*/
|
||||
public function moveCursor(int $x, int $y = 0): void
|
||||
{
|
||||
$sequence = '';
|
||||
|
||||
if ($x < 0) {
|
||||
$sequence .= "\e[".abs($x).'D'; // Left
|
||||
} elseif ($x > 0) {
|
||||
$sequence .= "\e[{$x}C"; // Right
|
||||
}
|
||||
|
||||
if ($y < 0) {
|
||||
$sequence .= "\e[".abs($y).'A'; // Up
|
||||
} elseif ($y > 0) {
|
||||
$sequence .= "\e[{$y}B"; // Down
|
||||
}
|
||||
|
||||
static::writeDirectly($sequence);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the cursor to the given column.
|
||||
*/
|
||||
public function moveCursorToColumn(int $column): void
|
||||
{
|
||||
static::writeDirectly("\e[{$column}G");
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the cursor up by the given number of lines.
|
||||
*/
|
||||
public function moveCursorUp(int $lines): void
|
||||
{
|
||||
static::writeDirectly("\e[{$lines}A");
|
||||
}
|
||||
}
|
31
vendor/laravel/prompts/src/Concerns/Erase.php
vendored
Normal file
31
vendor/laravel/prompts/src/Concerns/Erase.php
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts\Concerns;
|
||||
|
||||
trait Erase
|
||||
{
|
||||
/**
|
||||
* Erase the given number of lines downwards from the cursor position.
|
||||
*/
|
||||
public function eraseLines(int $count): void
|
||||
{
|
||||
$clear = '';
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$clear .= "\e[2K".($i < $count - 1 ? "\e[{$count}A" : '');
|
||||
}
|
||||
|
||||
if ($count) {
|
||||
$clear .= "\e[G";
|
||||
}
|
||||
|
||||
static::writeDirectly($clear);
|
||||
}
|
||||
|
||||
/**
|
||||
* Erase from cursor until end of screen.
|
||||
*/
|
||||
public function eraseDown(): void
|
||||
{
|
||||
static::writeDirectly("\e[J");
|
||||
}
|
||||
}
|
41
vendor/laravel/prompts/src/Concerns/Events.php
vendored
Normal file
41
vendor/laravel/prompts/src/Concerns/Events.php
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts\Concerns;
|
||||
|
||||
use Closure;
|
||||
|
||||
trait Events
|
||||
{
|
||||
/**
|
||||
* The registered event listeners.
|
||||
*
|
||||
* @var array<string, array<int, Closure>>
|
||||
*/
|
||||
protected array $listeners = [];
|
||||
|
||||
/**
|
||||
* Register an event listener.
|
||||
*/
|
||||
public function on(string $event, Closure $callback): void
|
||||
{
|
||||
$this->listeners[$event][] = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit an event.
|
||||
*/
|
||||
public function emit(string $event, mixed ...$data): void
|
||||
{
|
||||
foreach ($this->listeners[$event] ?? [] as $listener) {
|
||||
$listener(...$data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean the event listeners.
|
||||
*/
|
||||
public function clearListeners(): void
|
||||
{
|
||||
$this->listeners = [];
|
||||
}
|
||||
}
|
92
vendor/laravel/prompts/src/Concerns/FakesInputOutput.php
vendored
Normal file
92
vendor/laravel/prompts/src/Concerns/FakesInputOutput.php
vendored
Normal file
@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts\Concerns;
|
||||
|
||||
use Laravel\Prompts\Output\BufferedConsoleOutput;
|
||||
use Laravel\Prompts\Terminal;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use RuntimeException;
|
||||
|
||||
trait FakesInputOutput
|
||||
{
|
||||
/**
|
||||
* Fake the terminal and queue key presses to be simulated.
|
||||
*
|
||||
* @param array<string> $keys
|
||||
*/
|
||||
public static function fake(array $keys = []): void
|
||||
{
|
||||
// Force interactive mode when testing because we will be mocking the terminal.
|
||||
static::interactive();
|
||||
|
||||
$mock = \Mockery::mock(Terminal::class);
|
||||
|
||||
$mock->shouldReceive('write')->byDefault();
|
||||
$mock->shouldReceive('exit')->byDefault();
|
||||
$mock->shouldReceive('setTty')->byDefault();
|
||||
$mock->shouldReceive('restoreTty')->byDefault();
|
||||
$mock->shouldReceive('cols')->byDefault()->andReturn(80);
|
||||
$mock->shouldReceive('lines')->byDefault()->andReturn(24);
|
||||
$mock->shouldReceive('initDimensions')->byDefault();
|
||||
|
||||
foreach ($keys as $key) {
|
||||
$mock->shouldReceive('read')->once()->andReturn($key);
|
||||
}
|
||||
|
||||
static::$terminal = $mock;
|
||||
|
||||
self::setOutput(new BufferedConsoleOutput());
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the output contains the given string.
|
||||
*/
|
||||
public static function assertOutputContains(string $string): void
|
||||
{
|
||||
Assert::assertStringContainsString($string, static::content());
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the output doesn't contain the given string.
|
||||
*/
|
||||
public static function assertOutputDoesntContain(string $string): void
|
||||
{
|
||||
Assert::assertStringNotContainsString($string, static::content());
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the stripped output contains the given string.
|
||||
*/
|
||||
public static function assertStrippedOutputContains(string $string): void
|
||||
{
|
||||
Assert::assertStringContainsString($string, static::strippedContent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the stripped output doesn't contain the given string.
|
||||
*/
|
||||
public static function assertStrippedOutputDoesntContain(string $string): void
|
||||
{
|
||||
Assert::assertStringNotContainsString($string, static::strippedContent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the buffered console output.
|
||||
*/
|
||||
public static function content(): string
|
||||
{
|
||||
if (! static::output() instanceof BufferedConsoleOutput) {
|
||||
throw new RuntimeException('Prompt must be faked before accessing content.');
|
||||
}
|
||||
|
||||
return static::output()->content();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the buffered console output, stripped of escape sequences.
|
||||
*/
|
||||
public static function strippedContent(): string
|
||||
{
|
||||
return preg_replace("/\e\[[0-9;?]*[A-Za-z]/", '', static::content());
|
||||
}
|
||||
}
|
61
vendor/laravel/prompts/src/Concerns/Fallback.php
vendored
Normal file
61
vendor/laravel/prompts/src/Concerns/Fallback.php
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts\Concerns;
|
||||
|
||||
use Closure;
|
||||
use RuntimeException;
|
||||
|
||||
trait Fallback
|
||||
{
|
||||
/**
|
||||
* Whether to fallback to a custom implementation
|
||||
*/
|
||||
protected static bool $shouldFallback = false;
|
||||
|
||||
/**
|
||||
* The fallback implementations.
|
||||
*
|
||||
* @var array<class-string, Closure($this): mixed>
|
||||
*/
|
||||
protected static array $fallbacks = [];
|
||||
|
||||
/**
|
||||
* Enable the fallback implementation.
|
||||
*/
|
||||
public static function fallbackWhen(bool $condition): void
|
||||
{
|
||||
static::$shouldFallback = $condition || static::$shouldFallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the prompt should fallback to a custom implementation.
|
||||
*/
|
||||
public static function shouldFallback(): bool
|
||||
{
|
||||
return static::$shouldFallback && isset(static::$fallbacks[static::class]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the fallback implementation.
|
||||
*
|
||||
* @param Closure($this): mixed $fallback
|
||||
*/
|
||||
public static function fallbackUsing(Closure $fallback): void
|
||||
{
|
||||
static::$fallbacks[static::class] = $fallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the registered fallback implementation.
|
||||
*/
|
||||
public function fallback(): mixed
|
||||
{
|
||||
$fallback = static::$fallbacks[static::class] ?? null;
|
||||
|
||||
if ($fallback === null) {
|
||||
throw new RuntimeException('No fallback implementation registered for ['.static::class.']');
|
||||
}
|
||||
|
||||
return $fallback($this);
|
||||
}
|
||||
}
|
37
vendor/laravel/prompts/src/Concerns/Interactivity.php
vendored
Normal file
37
vendor/laravel/prompts/src/Concerns/Interactivity.php
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts\Concerns;
|
||||
|
||||
use Laravel\Prompts\Exceptions\NonInteractiveValidationException;
|
||||
|
||||
trait Interactivity
|
||||
{
|
||||
/**
|
||||
* Whether to render the prompt interactively.
|
||||
*/
|
||||
protected static bool $interactive;
|
||||
|
||||
/**
|
||||
* Set interactive mode.
|
||||
*/
|
||||
public static function interactive(bool $interactive = true): void
|
||||
{
|
||||
static::$interactive = $interactive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the default value if it passes validation.
|
||||
*/
|
||||
protected function default(): mixed
|
||||
{
|
||||
$default = $this->value();
|
||||
|
||||
$this->validate($default);
|
||||
|
||||
if ($this->state === 'error') {
|
||||
throw new NonInteractiveValidationException($this->error);
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
}
|
115
vendor/laravel/prompts/src/Concerns/Scrolling.php
vendored
Normal file
115
vendor/laravel/prompts/src/Concerns/Scrolling.php
vendored
Normal file
@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts\Concerns;
|
||||
|
||||
use Laravel\Prompts\Themes\Contracts\Scrolling as ScrollingRenderer;
|
||||
|
||||
trait Scrolling
|
||||
{
|
||||
/**
|
||||
* The number of items to display before scrolling.
|
||||
*/
|
||||
public int $scroll;
|
||||
|
||||
/**
|
||||
* The index of the highlighted option.
|
||||
*/
|
||||
public ?int $highlighted;
|
||||
|
||||
/**
|
||||
* The index of the first visible option.
|
||||
*/
|
||||
public int $firstVisible = 0;
|
||||
|
||||
/**
|
||||
* Initialize scrolling.
|
||||
*/
|
||||
protected function initializeScrolling(?int $highlighted = null): void
|
||||
{
|
||||
$this->highlighted = $highlighted;
|
||||
|
||||
$this->reduceScrollingToFitTerminal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduce the scroll property to fit the terminal height.
|
||||
*/
|
||||
protected function reduceScrollingToFitTerminal(): void
|
||||
{
|
||||
$reservedLines = ($renderer = $this->getRenderer()) instanceof ScrollingRenderer ? $renderer->reservedLines() : 0;
|
||||
|
||||
$this->scroll = max(1, min($this->scroll, $this->terminal()->lines() - $reservedLines));
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight the given index.
|
||||
*/
|
||||
protected function highlight(?int $index): void
|
||||
{
|
||||
$this->highlighted = $index;
|
||||
|
||||
if ($this->highlighted === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->highlighted < $this->firstVisible) {
|
||||
$this->firstVisible = $this->highlighted;
|
||||
} elseif ($this->highlighted > $this->firstVisible + $this->scroll - 1) {
|
||||
$this->firstVisible = $this->highlighted - $this->scroll + 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight the previous entry, or wrap around to the last entry.
|
||||
*/
|
||||
protected function highlightPrevious(int $total, bool $allowNull = false): void
|
||||
{
|
||||
if ($total === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->highlighted === null) {
|
||||
$this->highlight($total - 1);
|
||||
} elseif ($this->highlighted === 0) {
|
||||
$this->highlight($allowNull ? null : ($total - 1));
|
||||
} else {
|
||||
$this->highlight($this->highlighted - 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight the next entry, or wrap around to the first entry.
|
||||
*/
|
||||
protected function highlightNext(int $total, bool $allowNull = false): void
|
||||
{
|
||||
if ($total === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->highlighted === $total - 1) {
|
||||
$this->highlight($allowNull ? null : 0);
|
||||
} else {
|
||||
$this->highlight(($this->highlighted ?? -1) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Center the highlighted option.
|
||||
*/
|
||||
protected function scrollToHighlighted(int $total): void
|
||||
{
|
||||
if ($this->highlighted < $this->scroll) {
|
||||
return;
|
||||
}
|
||||
|
||||
$remaining = $total - $this->highlighted - 1;
|
||||
$halfScroll = (int) floor($this->scroll / 2);
|
||||
$endOffset = max(0, $halfScroll - $remaining);
|
||||
|
||||
if ($this->scroll % 2 === 0) {
|
||||
$endOffset--;
|
||||
}
|
||||
|
||||
$this->firstVisible = $this->highlighted - $halfScroll - $endOffset;
|
||||
}
|
||||
}
|
25
vendor/laravel/prompts/src/Concerns/Termwind.php
vendored
Normal file
25
vendor/laravel/prompts/src/Concerns/Termwind.php
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts\Concerns;
|
||||
|
||||
use Laravel\Prompts\Output\BufferedConsoleOutput;
|
||||
|
||||
use function Termwind\render;
|
||||
use function Termwind\renderUsing;
|
||||
|
||||
trait Termwind
|
||||
{
|
||||
protected function termwind(string $html)
|
||||
{
|
||||
renderUsing($output = new BufferedConsoleOutput());
|
||||
|
||||
render($html);
|
||||
|
||||
return $this->restoreEscapeSequences($output->fetch());
|
||||
}
|
||||
|
||||
protected function restoreEscapeSequences(string $string)
|
||||
{
|
||||
return preg_replace('/\[(\d+)m/', "\e[".'\1m', $string);
|
||||
}
|
||||
}
|
117
vendor/laravel/prompts/src/Concerns/Themes.php
vendored
Normal file
117
vendor/laravel/prompts/src/Concerns/Themes.php
vendored
Normal file
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts\Concerns;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Laravel\Prompts\ConfirmPrompt;
|
||||
use Laravel\Prompts\MultiSearchPrompt;
|
||||
use Laravel\Prompts\MultiSelectPrompt;
|
||||
use Laravel\Prompts\Note;
|
||||
use Laravel\Prompts\PasswordPrompt;
|
||||
use Laravel\Prompts\PausePrompt;
|
||||
use Laravel\Prompts\Progress;
|
||||
use Laravel\Prompts\SearchPrompt;
|
||||
use Laravel\Prompts\SelectPrompt;
|
||||
use Laravel\Prompts\Spinner;
|
||||
use Laravel\Prompts\SuggestPrompt;
|
||||
use Laravel\Prompts\Table;
|
||||
use Laravel\Prompts\TextareaPrompt;
|
||||
use Laravel\Prompts\TextPrompt;
|
||||
use Laravel\Prompts\Themes\Default\ConfirmPromptRenderer;
|
||||
use Laravel\Prompts\Themes\Default\MultiSearchPromptRenderer;
|
||||
use Laravel\Prompts\Themes\Default\MultiSelectPromptRenderer;
|
||||
use Laravel\Prompts\Themes\Default\NoteRenderer;
|
||||
use Laravel\Prompts\Themes\Default\PasswordPromptRenderer;
|
||||
use Laravel\Prompts\Themes\Default\PausePromptRenderer;
|
||||
use Laravel\Prompts\Themes\Default\ProgressRenderer;
|
||||
use Laravel\Prompts\Themes\Default\SearchPromptRenderer;
|
||||
use Laravel\Prompts\Themes\Default\SelectPromptRenderer;
|
||||
use Laravel\Prompts\Themes\Default\SpinnerRenderer;
|
||||
use Laravel\Prompts\Themes\Default\SuggestPromptRenderer;
|
||||
use Laravel\Prompts\Themes\Default\TableRenderer;
|
||||
use Laravel\Prompts\Themes\Default\TextareaPromptRenderer;
|
||||
use Laravel\Prompts\Themes\Default\TextPromptRenderer;
|
||||
|
||||
trait Themes
|
||||
{
|
||||
/**
|
||||
* The name of the active theme.
|
||||
*/
|
||||
protected static string $theme = 'default';
|
||||
|
||||
/**
|
||||
* The available themes.
|
||||
*
|
||||
* @var array<string, array<class-string<\Laravel\Prompts\Prompt>, class-string<object&callable>>>
|
||||
*/
|
||||
protected static array $themes = [
|
||||
'default' => [
|
||||
TextPrompt::class => TextPromptRenderer::class,
|
||||
TextareaPrompt::class => TextareaPromptRenderer::class,
|
||||
PasswordPrompt::class => PasswordPromptRenderer::class,
|
||||
SelectPrompt::class => SelectPromptRenderer::class,
|
||||
MultiSelectPrompt::class => MultiSelectPromptRenderer::class,
|
||||
ConfirmPrompt::class => ConfirmPromptRenderer::class,
|
||||
PausePrompt::class => PausePromptRenderer::class,
|
||||
SearchPrompt::class => SearchPromptRenderer::class,
|
||||
MultiSearchPrompt::class => MultiSearchPromptRenderer::class,
|
||||
SuggestPrompt::class => SuggestPromptRenderer::class,
|
||||
Spinner::class => SpinnerRenderer::class,
|
||||
Note::class => NoteRenderer::class,
|
||||
Table::class => TableRenderer::class,
|
||||
Progress::class => ProgressRenderer::class,
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Get or set the active theme.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public static function theme(?string $name = null): string
|
||||
{
|
||||
if ($name === null) {
|
||||
return static::$theme;
|
||||
}
|
||||
|
||||
if (! isset(static::$themes[$name])) {
|
||||
throw new InvalidArgumentException("Prompt theme [{$name}] not found.");
|
||||
}
|
||||
|
||||
return static::$theme = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new theme.
|
||||
*
|
||||
* @param array<class-string<\Laravel\Prompts\Prompt>, class-string<object&callable>> $renderers
|
||||
*/
|
||||
public static function addTheme(string $name, array $renderers): void
|
||||
{
|
||||
if ($name === 'default') {
|
||||
throw new InvalidArgumentException('The default theme cannot be overridden.');
|
||||
}
|
||||
|
||||
static::$themes[$name] = $renderers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the renderer for the current prompt.
|
||||
*/
|
||||
protected function getRenderer(): callable
|
||||
{
|
||||
$class = get_class($this);
|
||||
|
||||
return new (static::$themes[static::$theme][$class] ?? static::$themes['default'][$class])($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the prompt using the active theme.
|
||||
*/
|
||||
protected function renderTheme(): string
|
||||
{
|
||||
$renderer = $this->getRenderer();
|
||||
|
||||
return $renderer($this);
|
||||
}
|
||||
}
|
106
vendor/laravel/prompts/src/Concerns/Truncation.php
vendored
Normal file
106
vendor/laravel/prompts/src/Concerns/Truncation.php
vendored
Normal file
@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts\Concerns;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
trait Truncation
|
||||
{
|
||||
/**
|
||||
* Truncate a value with an ellipsis if it exceeds the given width.
|
||||
*/
|
||||
protected function truncate(string $string, int $width): string
|
||||
{
|
||||
if ($width <= 0) {
|
||||
throw new InvalidArgumentException("Width [{$width}] must be greater than zero.");
|
||||
}
|
||||
|
||||
return mb_strwidth($string) <= $width ? $string : (mb_strimwidth($string, 0, $width - 1).'…');
|
||||
}
|
||||
|
||||
/**
|
||||
* Multi-byte version of wordwrap.
|
||||
*
|
||||
* @param non-empty-string $break
|
||||
*/
|
||||
protected function mbWordwrap(
|
||||
string $string,
|
||||
int $width = 75,
|
||||
string $break = "\n",
|
||||
bool $cut_long_words = false
|
||||
): string {
|
||||
$lines = explode($break, $string);
|
||||
$result = [];
|
||||
|
||||
foreach ($lines as $originalLine) {
|
||||
if (mb_strwidth($originalLine) <= $width) {
|
||||
$result[] = $originalLine;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$words = explode(' ', $originalLine);
|
||||
$line = null;
|
||||
$lineWidth = 0;
|
||||
|
||||
if ($cut_long_words) {
|
||||
foreach ($words as $index => $word) {
|
||||
$characters = mb_str_split($word);
|
||||
$strings = [];
|
||||
$str = '';
|
||||
|
||||
foreach ($characters as $character) {
|
||||
$tmp = $str.$character;
|
||||
|
||||
if (mb_strwidth($tmp) > $width) {
|
||||
$strings[] = $str;
|
||||
$str = $character;
|
||||
} else {
|
||||
$str = $tmp;
|
||||
}
|
||||
}
|
||||
|
||||
if ($str !== '') {
|
||||
$strings[] = $str;
|
||||
}
|
||||
|
||||
$words[$index] = implode(' ', $strings);
|
||||
}
|
||||
|
||||
$words = explode(' ', implode(' ', $words));
|
||||
}
|
||||
|
||||
foreach ($words as $word) {
|
||||
$tmp = ($line === null) ? $word : $line.' '.$word;
|
||||
|
||||
// Look for zero-width joiner characters (combined emojis)
|
||||
preg_match('/\p{Cf}/u', $word, $joinerMatches);
|
||||
|
||||
$wordWidth = count($joinerMatches) > 0 ? 2 : mb_strwidth($word);
|
||||
|
||||
$lineWidth += $wordWidth;
|
||||
|
||||
if ($line !== null) {
|
||||
// Space between words
|
||||
$lineWidth += 1;
|
||||
}
|
||||
|
||||
if ($lineWidth <= $width) {
|
||||
$line = $tmp;
|
||||
} else {
|
||||
$result[] = $line;
|
||||
$line = $word;
|
||||
$lineWidth = $wordWidth;
|
||||
}
|
||||
}
|
||||
|
||||
if ($line !== '') {
|
||||
$result[] = $line;
|
||||
}
|
||||
|
||||
$line = null;
|
||||
}
|
||||
|
||||
return implode($break, $result);
|
||||
}
|
||||
}
|
128
vendor/laravel/prompts/src/Concerns/TypedValue.php
vendored
Normal file
128
vendor/laravel/prompts/src/Concerns/TypedValue.php
vendored
Normal file
@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts\Concerns;
|
||||
|
||||
use Laravel\Prompts\Key;
|
||||
|
||||
trait TypedValue
|
||||
{
|
||||
/**
|
||||
* The value that has been typed.
|
||||
*/
|
||||
protected string $typedValue = '';
|
||||
|
||||
/**
|
||||
* The position of the virtual cursor.
|
||||
*/
|
||||
protected int $cursorPosition = 0;
|
||||
|
||||
/**
|
||||
* Track the value as the user types.
|
||||
*/
|
||||
protected function trackTypedValue(string $default = '', bool $submit = true, ?callable $ignore = null, bool $allowNewLine = false): void
|
||||
{
|
||||
$this->typedValue = $default;
|
||||
|
||||
if ($this->typedValue) {
|
||||
$this->cursorPosition = mb_strlen($this->typedValue);
|
||||
}
|
||||
|
||||
$this->on('key', function ($key) use ($submit, $ignore, $allowNewLine) {
|
||||
if ($key[0] === "\e" || in_array($key, [Key::CTRL_B, Key::CTRL_F, Key::CTRL_A, Key::CTRL_E])) {
|
||||
if ($ignore !== null && $ignore($key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
match ($key) {
|
||||
Key::LEFT, Key::LEFT_ARROW, Key::CTRL_B => $this->cursorPosition = max(0, $this->cursorPosition - 1),
|
||||
Key::RIGHT, Key::RIGHT_ARROW, Key::CTRL_F => $this->cursorPosition = min(mb_strlen($this->typedValue), $this->cursorPosition + 1),
|
||||
Key::oneOf([Key::HOME, Key::CTRL_A], $key) => $this->cursorPosition = 0,
|
||||
Key::oneOf([Key::END, Key::CTRL_E], $key) => $this->cursorPosition = mb_strlen($this->typedValue),
|
||||
Key::DELETE => $this->typedValue = mb_substr($this->typedValue, 0, $this->cursorPosition).mb_substr($this->typedValue, $this->cursorPosition + 1),
|
||||
default => null,
|
||||
};
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Keys may be buffered.
|
||||
foreach (mb_str_split($key) as $key) {
|
||||
if ($ignore !== null && $ignore($key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($key === Key::ENTER) {
|
||||
if ($submit) {
|
||||
$this->submit();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($allowNewLine) {
|
||||
$this->typedValue = mb_substr($this->typedValue, 0, $this->cursorPosition).PHP_EOL.mb_substr($this->typedValue, $this->cursorPosition);
|
||||
$this->cursorPosition++;
|
||||
}
|
||||
} elseif ($key === Key::BACKSPACE || $key === Key::CTRL_H) {
|
||||
if ($this->cursorPosition === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->typedValue = mb_substr($this->typedValue, 0, $this->cursorPosition - 1).mb_substr($this->typedValue, $this->cursorPosition);
|
||||
$this->cursorPosition--;
|
||||
} elseif (ord($key) >= 32) {
|
||||
$this->typedValue = mb_substr($this->typedValue, 0, $this->cursorPosition).$key.mb_substr($this->typedValue, $this->cursorPosition);
|
||||
$this->cursorPosition++;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the prompt.
|
||||
*/
|
||||
public function value(): string
|
||||
{
|
||||
return $this->typedValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a virtual cursor to the value and truncate if necessary.
|
||||
*/
|
||||
protected function addCursor(string $value, int $cursorPosition, ?int $maxWidth = null): string
|
||||
{
|
||||
$before = mb_substr($value, 0, $cursorPosition);
|
||||
$current = mb_substr($value, $cursorPosition, 1);
|
||||
$after = mb_substr($value, $cursorPosition + 1);
|
||||
|
||||
$cursor = mb_strlen($current) && $current !== PHP_EOL ? $current : ' ';
|
||||
|
||||
$spaceBefore = $maxWidth < 0 || $maxWidth === null ? mb_strwidth($before) : $maxWidth - mb_strwidth($cursor) - (mb_strwidth($after) > 0 ? 1 : 0);
|
||||
[$truncatedBefore, $wasTruncatedBefore] = mb_strwidth($before) > $spaceBefore
|
||||
? [$this->trimWidthBackwards($before, 0, $spaceBefore - 1), true]
|
||||
: [$before, false];
|
||||
|
||||
$spaceAfter = $maxWidth < 0 || $maxWidth === null ? mb_strwidth($after) : $maxWidth - ($wasTruncatedBefore ? 1 : 0) - mb_strwidth($truncatedBefore) - mb_strwidth($cursor);
|
||||
[$truncatedAfter, $wasTruncatedAfter] = mb_strwidth($after) > $spaceAfter
|
||||
? [mb_strimwidth($after, 0, $spaceAfter - 1), true]
|
||||
: [$after, false];
|
||||
|
||||
return ($wasTruncatedBefore ? $this->dim('…') : '')
|
||||
.$truncatedBefore
|
||||
.$this->inverse($cursor)
|
||||
.($current === PHP_EOL ? PHP_EOL : '')
|
||||
.$truncatedAfter
|
||||
.($wasTruncatedAfter ? $this->dim('…') : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a truncated string with the specified width from the end.
|
||||
*/
|
||||
private function trimWidthBackwards(string $string, int $start, int $width): string
|
||||
{
|
||||
$reversed = implode('', array_reverse(mb_str_split($string, 1)));
|
||||
|
||||
$trimmed = mb_strimwidth($reversed, $start, $width);
|
||||
|
||||
return implode('', array_reverse(mb_str_split($trimmed, 1)));
|
||||
}
|
||||
}
|
50
vendor/laravel/prompts/src/ConfirmPrompt.php
vendored
Normal file
50
vendor/laravel/prompts/src/ConfirmPrompt.php
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts;
|
||||
|
||||
class ConfirmPrompt extends Prompt
|
||||
{
|
||||
/**
|
||||
* Whether the prompt has been confirmed.
|
||||
*/
|
||||
public bool $confirmed;
|
||||
|
||||
/**
|
||||
* Create a new ConfirmPrompt instance.
|
||||
*/
|
||||
public function __construct(
|
||||
public string $label,
|
||||
public bool $default = true,
|
||||
public string $yes = 'Yes',
|
||||
public string $no = 'No',
|
||||
public bool|string $required = false,
|
||||
public mixed $validate = null,
|
||||
public string $hint = '',
|
||||
) {
|
||||
$this->confirmed = $default;
|
||||
|
||||
$this->on('key', fn ($key) => match ($key) {
|
||||
'y' => $this->confirmed = true,
|
||||
'n' => $this->confirmed = false,
|
||||
Key::TAB, Key::UP, Key::UP_ARROW, Key::DOWN, Key::DOWN_ARROW, Key::LEFT, Key::LEFT_ARROW, Key::RIGHT, Key::RIGHT_ARROW, Key::CTRL_P, Key::CTRL_F, Key::CTRL_N, Key::CTRL_B, 'h', 'j', 'k', 'l' => $this->confirmed = ! $this->confirmed,
|
||||
Key::ENTER => $this->submit(),
|
||||
default => null,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the prompt.
|
||||
*/
|
||||
public function value(): bool
|
||||
{
|
||||
return $this->confirmed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the label of the selected option.
|
||||
*/
|
||||
public function label(): string
|
||||
{
|
||||
return $this->confirmed ? $this->yes : $this->no;
|
||||
}
|
||||
}
|
10
vendor/laravel/prompts/src/Exceptions/FormRevertedException.php
vendored
Normal file
10
vendor/laravel/prompts/src/Exceptions/FormRevertedException.php
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts\Exceptions;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class FormRevertedException extends RuntimeException
|
||||
{
|
||||
//
|
||||
}
|
10
vendor/laravel/prompts/src/Exceptions/NonInteractiveValidationException.php
vendored
Normal file
10
vendor/laravel/prompts/src/Exceptions/NonInteractiveValidationException.php
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts\Exceptions;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class NonInteractiveValidationException extends RuntimeException
|
||||
{
|
||||
//
|
||||
}
|
279
vendor/laravel/prompts/src/FormBuilder.php
vendored
Normal file
279
vendor/laravel/prompts/src/FormBuilder.php
vendored
Normal file
@ -0,0 +1,279 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Support\Collection;
|
||||
use Laravel\Prompts\Exceptions\FormRevertedException;
|
||||
|
||||
class FormBuilder
|
||||
{
|
||||
/**
|
||||
* Each step that should be executed.
|
||||
*
|
||||
* @var array<int, \Laravel\Prompts\FormStep>
|
||||
*/
|
||||
protected array $steps = [];
|
||||
|
||||
/**
|
||||
* The responses provided by each step.
|
||||
*
|
||||
* @var array<mixed>
|
||||
*/
|
||||
protected array $responses = [];
|
||||
|
||||
/**
|
||||
* Add a new step.
|
||||
*/
|
||||
public function add(Closure $step, ?string $name = null, bool $ignoreWhenReverting = false): self
|
||||
{
|
||||
$this->steps[] = new FormStep($step, true, $name, $ignoreWhenReverting);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run all of the given steps.
|
||||
*
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public function submit(): array
|
||||
{
|
||||
$index = 0;
|
||||
$wasReverted = false;
|
||||
|
||||
while ($index < count($this->steps)) {
|
||||
$step = $this->steps[$index];
|
||||
|
||||
if ($wasReverted && $index > 0 && $step->shouldIgnoreWhenReverting($this->responses)) {
|
||||
$index--;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$wasReverted = false;
|
||||
|
||||
$index > 0
|
||||
? Prompt::revertUsing(function () use (&$wasReverted) {
|
||||
$wasReverted = true;
|
||||
}) : Prompt::preventReverting();
|
||||
|
||||
try {
|
||||
$this->responses[$step->name ?? $index] = $step->run(
|
||||
$this->responses,
|
||||
$this->responses[$step->name ?? $index] ?? null,
|
||||
);
|
||||
} catch (FormRevertedException) {
|
||||
$wasReverted = true;
|
||||
}
|
||||
|
||||
$wasReverted ? $index-- : $index++;
|
||||
}
|
||||
|
||||
Prompt::preventReverting();
|
||||
|
||||
return $this->responses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt the user for text input.
|
||||
*/
|
||||
public function text(string $label, string $placeholder = '', string $default = '', bool|string $required = false, mixed $validate = null, string $hint = '', ?string $name = null): self
|
||||
{
|
||||
return $this->runPrompt(text(...), get_defined_vars());
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt the user for multiline text input.
|
||||
*/
|
||||
public function textarea(string $label, string $placeholder = '', string $default = '', bool|string $required = false, ?Closure $validate = null, string $hint = '', int $rows = 5, ?string $name = null): self
|
||||
{
|
||||
return $this->runPrompt(textarea(...), get_defined_vars());
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt the user for input, hiding the value.
|
||||
*/
|
||||
public function password(string $label, string $placeholder = '', bool|string $required = false, mixed $validate = null, string $hint = '', ?string $name = null): self
|
||||
{
|
||||
return $this->runPrompt(password(...), get_defined_vars());
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt the user to select an option.
|
||||
*
|
||||
* @param array<int|string, string>|Collection<int|string, string> $options
|
||||
* @param true|string $required
|
||||
*/
|
||||
public function select(string $label, array|Collection $options, int|string|null $default = null, int $scroll = 5, mixed $validate = null, string $hint = '', bool|string $required = true, ?string $name = null): self
|
||||
{
|
||||
return $this->runPrompt(select(...), get_defined_vars());
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt the user to select multiple options.
|
||||
*
|
||||
* @param array<int|string, string>|Collection<int|string, string> $options
|
||||
* @param array<int|string>|Collection<int, int|string> $default
|
||||
*/
|
||||
public function multiselect(string $label, array|Collection $options, array|Collection $default = [], int $scroll = 5, bool|string $required = false, mixed $validate = null, string $hint = 'Use the space bar to select options.', ?string $name = null): self
|
||||
{
|
||||
return $this->runPrompt(multiselect(...), get_defined_vars());
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt the user to confirm an action.
|
||||
*/
|
||||
public function confirm(string $label, bool $default = true, string $yes = 'Yes', string $no = 'No', bool|string $required = false, mixed $validate = null, string $hint = '', ?string $name = null): self
|
||||
{
|
||||
return $this->runPrompt(confirm(...), get_defined_vars());
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt the user to continue or cancel after pausing.
|
||||
*/
|
||||
public function pause(string $message = 'Press enter to continue...', ?string $name = null): self
|
||||
{
|
||||
return $this->runPrompt(pause(...), get_defined_vars());
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt the user for text input with auto-completion.
|
||||
*
|
||||
* @param array<string>|Collection<int, string>|Closure(string): array<string> $options
|
||||
*/
|
||||
public function suggest(string $label, array|Collection|Closure $options, string $placeholder = '', string $default = '', int $scroll = 5, bool|string $required = false, mixed $validate = null, string $hint = '', ?string $name = null): self
|
||||
{
|
||||
return $this->runPrompt(suggest(...), get_defined_vars());
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow the user to search for an option.
|
||||
*
|
||||
* @param Closure(string): array<int|string, string> $options
|
||||
* @param true|string $required
|
||||
*/
|
||||
public function search(string $label, Closure $options, string $placeholder = '', int $scroll = 5, mixed $validate = null, string $hint = '', bool|string $required = true, ?string $name = null): self
|
||||
{
|
||||
return $this->runPrompt(search(...), get_defined_vars());
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow the user to search for multiple option.
|
||||
*
|
||||
* @param Closure(string): array<int|string, string> $options
|
||||
*/
|
||||
public function multisearch(string $label, Closure $options, string $placeholder = '', int $scroll = 5, bool|string $required = false, mixed $validate = null, string $hint = 'Use the space bar to select options.', ?string $name = null): self
|
||||
{
|
||||
return $this->runPrompt(multisearch(...), get_defined_vars());
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a spinner while the given callback is executing.
|
||||
*
|
||||
* @param \Closure(): mixed $callback
|
||||
*/
|
||||
public function spin(Closure $callback, string $message = '', ?string $name = null): self
|
||||
{
|
||||
return $this->runPrompt(spin(...), get_defined_vars(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a note.
|
||||
*/
|
||||
public function note(string $message, ?string $type = null, ?string $name = null): self
|
||||
{
|
||||
return $this->runPrompt(note(...), get_defined_vars(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display an error.
|
||||
*/
|
||||
public function error(string $message, ?string $name = null): self
|
||||
{
|
||||
return $this->runPrompt(error(...), get_defined_vars(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a warning.
|
||||
*/
|
||||
public function warning(string $message, ?string $name = null): self
|
||||
{
|
||||
return $this->runPrompt(warning(...), get_defined_vars(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display an alert.
|
||||
*/
|
||||
public function alert(string $message, ?string $name = null): self
|
||||
{
|
||||
return $this->runPrompt(alert(...), get_defined_vars(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display an informational message.
|
||||
*/
|
||||
public function info(string $message, ?string $name = null): self
|
||||
{
|
||||
return $this->runPrompt(info(...), get_defined_vars(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display an introduction.
|
||||
*/
|
||||
public function intro(string $message, ?string $name = null): self
|
||||
{
|
||||
return $this->runPrompt(intro(...), get_defined_vars(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a closing message.
|
||||
*/
|
||||
public function outro(string $message, ?string $name = null): self
|
||||
{
|
||||
return $this->runPrompt(outro(...), get_defined_vars(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a table.
|
||||
*
|
||||
* @param array<int, string|array<int, string>>|Collection<int, string|array<int, string>> $headers
|
||||
* @param array<int, array<int, string>>|Collection<int, array<int, string>> $rows
|
||||
*/
|
||||
public function table(array|Collection $headers = [], array|Collection|null $rows = null, ?string $name = null): self
|
||||
{
|
||||
return $this->runPrompt(table(...), get_defined_vars(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a progress bar.
|
||||
*
|
||||
* @template TSteps of iterable<mixed>|int
|
||||
* @template TReturn
|
||||
*
|
||||
* @param TSteps $steps
|
||||
* @param ?Closure((TSteps is int ? int : value-of<TSteps>), Progress<TSteps>): TReturn $callback
|
||||
*/
|
||||
public function progress(string $label, iterable|int $steps, ?Closure $callback = null, string $hint = '', ?string $name = null): self
|
||||
{
|
||||
return $this->runPrompt(progress(...), get_defined_vars(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given prompt passing the given arguments.
|
||||
*
|
||||
* @param array<mixed> $arguments
|
||||
*/
|
||||
protected function runPrompt(callable $prompt, array $arguments, bool $ignoreWhenReverting = false): self
|
||||
{
|
||||
return $this->add(function (array $responses, mixed $previousResponse) use ($prompt, $arguments) {
|
||||
unset($arguments['name']);
|
||||
|
||||
if (array_key_exists('default', $arguments) && $previousResponse !== null) {
|
||||
$arguments['default'] = $previousResponse;
|
||||
}
|
||||
|
||||
return $prompt(...$arguments);
|
||||
}, name: $arguments['name'], ignoreWhenReverting: $ignoreWhenReverting);
|
||||
}
|
||||
}
|
59
vendor/laravel/prompts/src/FormStep.php
vendored
Normal file
59
vendor/laravel/prompts/src/FormStep.php
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts;
|
||||
|
||||
use Closure;
|
||||
|
||||
class FormStep
|
||||
{
|
||||
protected readonly Closure $condition;
|
||||
|
||||
public function __construct(
|
||||
protected readonly Closure $step,
|
||||
bool|Closure $condition,
|
||||
public readonly ?string $name,
|
||||
protected readonly bool $ignoreWhenReverting,
|
||||
) {
|
||||
$this->condition = is_bool($condition)
|
||||
? fn () => $condition
|
||||
: $condition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute this step.
|
||||
*
|
||||
* @param array<mixed> $responses
|
||||
*/
|
||||
public function run(array $responses, mixed $previousResponse): mixed
|
||||
{
|
||||
if (! $this->shouldRun($responses)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return ($this->step)($responses, $previousResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the step should run based on the given condition.
|
||||
*
|
||||
* @param array<mixed> $responses
|
||||
*/
|
||||
protected function shouldRun(array $responses): bool
|
||||
{
|
||||
return ($this->condition)($responses);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this step should be skipped over when a subsequent step is reverted.
|
||||
*
|
||||
* @param array<mixed> $responses
|
||||
*/
|
||||
public function shouldIgnoreWhenReverting(array $responses): bool
|
||||
{
|
||||
if (! $this->shouldRun($responses)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->ignoreWhenReverting;
|
||||
}
|
||||
}
|
98
vendor/laravel/prompts/src/Key.php
vendored
Normal file
98
vendor/laravel/prompts/src/Key.php
vendored
Normal file
@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts;
|
||||
|
||||
class Key
|
||||
{
|
||||
const UP = "\e[A";
|
||||
|
||||
const DOWN = "\e[B";
|
||||
|
||||
const RIGHT = "\e[C";
|
||||
|
||||
const LEFT = "\e[D";
|
||||
|
||||
const UP_ARROW = "\eOA";
|
||||
|
||||
const DOWN_ARROW = "\eOB";
|
||||
|
||||
const RIGHT_ARROW = "\eOC";
|
||||
|
||||
const LEFT_ARROW = "\eOD";
|
||||
|
||||
const DELETE = "\e[3~";
|
||||
|
||||
const BACKSPACE = "\177";
|
||||
|
||||
const ENTER = "\n";
|
||||
|
||||
const SPACE = ' ';
|
||||
|
||||
const TAB = "\t";
|
||||
|
||||
const SHIFT_TAB = "\e[Z";
|
||||
|
||||
const HOME = ["\e[1~", "\eOH", "\e[H", "\e[7~"];
|
||||
|
||||
const END = ["\e[4~", "\eOF", "\e[F", "\e[8~"];
|
||||
|
||||
/**
|
||||
* Cancel/SIGINT
|
||||
*/
|
||||
const CTRL_C = "\x03";
|
||||
|
||||
/**
|
||||
* Previous/Up
|
||||
*/
|
||||
const CTRL_P = "\x10";
|
||||
|
||||
/**
|
||||
* Next/Down
|
||||
*/
|
||||
const CTRL_N = "\x0E";
|
||||
|
||||
/**
|
||||
* Forward/Right
|
||||
*/
|
||||
const CTRL_F = "\x06";
|
||||
|
||||
/**
|
||||
* Back/Left
|
||||
*/
|
||||
const CTRL_B = "\x02";
|
||||
|
||||
/**
|
||||
* Backspace
|
||||
*/
|
||||
const CTRL_H = "\x08";
|
||||
|
||||
/**
|
||||
* Home
|
||||
*/
|
||||
const CTRL_A = "\x01";
|
||||
|
||||
/**
|
||||
* EOF
|
||||
*/
|
||||
const CTRL_D = "\x04";
|
||||
|
||||
/**
|
||||
* End
|
||||
*/
|
||||
const CTRL_E = "\x05";
|
||||
|
||||
/**
|
||||
* Negative affirmation
|
||||
*/
|
||||
const CTRL_U = "\x15";
|
||||
|
||||
/**
|
||||
* Checks for the constant values for the given match and returns the match
|
||||
*
|
||||
* @param array<string|array<string>> $keys
|
||||
*/
|
||||
public static function oneOf(array $keys, string $match): ?string
|
||||
{
|
||||
return collect($keys)->flatten()->contains($match) ? $match : null;
|
||||
}
|
||||
}
|
190
vendor/laravel/prompts/src/MultiSearchPrompt.php
vendored
Normal file
190
vendor/laravel/prompts/src/MultiSearchPrompt.php
vendored
Normal file
@ -0,0 +1,190 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts;
|
||||
|
||||
use Closure;
|
||||
|
||||
class MultiSearchPrompt extends Prompt
|
||||
{
|
||||
use Concerns\Scrolling;
|
||||
use Concerns\Truncation;
|
||||
use Concerns\TypedValue;
|
||||
|
||||
/**
|
||||
* The cached matches.
|
||||
*
|
||||
* @var array<int|string, string>|null
|
||||
*/
|
||||
protected ?array $matches = null;
|
||||
|
||||
/**
|
||||
* Whether the matches are initially a list.
|
||||
*/
|
||||
protected bool $isList;
|
||||
|
||||
/**
|
||||
* The selected values.
|
||||
*
|
||||
* @var array<int|string, string>
|
||||
*/
|
||||
public array $values = [];
|
||||
|
||||
/**
|
||||
* Create a new MultiSearchPrompt instance.
|
||||
*
|
||||
* @param Closure(string): array<int|string, string> $options
|
||||
*/
|
||||
public function __construct(
|
||||
public string $label,
|
||||
public Closure $options,
|
||||
public string $placeholder = '',
|
||||
public int $scroll = 5,
|
||||
public bool|string $required = false,
|
||||
public mixed $validate = null,
|
||||
public string $hint = '',
|
||||
) {
|
||||
$this->trackTypedValue(submit: false, ignore: fn ($key) => Key::oneOf([Key::SPACE, Key::HOME, Key::END, Key::CTRL_A, Key::CTRL_E], $key) && $this->highlighted !== null);
|
||||
|
||||
$this->initializeScrolling(null);
|
||||
|
||||
$this->on('key', fn ($key) => match ($key) {
|
||||
Key::UP, Key::UP_ARROW, Key::SHIFT_TAB => $this->highlightPrevious(count($this->matches), true),
|
||||
Key::DOWN, Key::DOWN_ARROW, Key::TAB => $this->highlightNext(count($this->matches), true),
|
||||
Key::oneOf([Key::HOME, Key::CTRL_A], $key) => $this->highlighted !== null ? $this->highlight(0) : null,
|
||||
Key::oneOf([Key::END, Key::CTRL_E], $key) => $this->highlighted !== null ? $this->highlight(count($this->matches()) - 1) : null,
|
||||
Key::SPACE => $this->highlighted !== null ? $this->toggleHighlighted() : null,
|
||||
Key::ENTER => $this->submit(),
|
||||
Key::LEFT, Key::LEFT_ARROW, Key::RIGHT, Key::RIGHT_ARROW => $this->highlighted = null,
|
||||
default => $this->search(),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the search.
|
||||
*/
|
||||
protected function search(): void
|
||||
{
|
||||
$this->state = 'searching';
|
||||
$this->highlighted = null;
|
||||
$this->render();
|
||||
$this->matches = null;
|
||||
$this->firstVisible = 0;
|
||||
$this->state = 'active';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entered value with a virtual cursor.
|
||||
*/
|
||||
public function valueWithCursor(int $maxWidth): string
|
||||
{
|
||||
if ($this->highlighted !== null) {
|
||||
return $this->typedValue === ''
|
||||
? $this->dim($this->truncate($this->placeholder, $maxWidth))
|
||||
: $this->truncate($this->typedValue, $maxWidth);
|
||||
}
|
||||
|
||||
if ($this->typedValue === '') {
|
||||
return $this->dim($this->addCursor($this->placeholder, 0, $maxWidth));
|
||||
}
|
||||
|
||||
return $this->addCursor($this->typedValue, $this->cursorPosition, $maxWidth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get options that match the input.
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public function matches(): array
|
||||
{
|
||||
if (is_array($this->matches)) {
|
||||
return $this->matches;
|
||||
}
|
||||
|
||||
$matches = ($this->options)($this->typedValue);
|
||||
|
||||
if (! isset($this->isList) && count($matches) > 0) {
|
||||
// This needs to be captured the first time we receive matches so
|
||||
// we know what we're dealing with later if matches is empty.
|
||||
$this->isList = array_is_list($matches);
|
||||
}
|
||||
|
||||
if (! isset($this->isList)) {
|
||||
return $this->matches = [];
|
||||
}
|
||||
|
||||
if (strlen($this->typedValue) > 0) {
|
||||
return $this->matches = $matches;
|
||||
}
|
||||
|
||||
return $this->matches = $this->isList
|
||||
? [...array_diff(array_values($this->values), $matches), ...$matches]
|
||||
: array_diff($this->values, $matches) + $matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* The currently visible matches
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public function visible(): array
|
||||
{
|
||||
return array_slice($this->matches(), $this->firstVisible, $this->scroll, preserve_keys: true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the highlighted entry.
|
||||
*/
|
||||
protected function toggleHighlighted(): void
|
||||
{
|
||||
if ($this->isList()) {
|
||||
$label = $this->matches[$this->highlighted];
|
||||
$key = $label;
|
||||
} else {
|
||||
$key = array_keys($this->matches)[$this->highlighted];
|
||||
$label = $this->matches[$key];
|
||||
}
|
||||
|
||||
if (array_key_exists($key, $this->values)) {
|
||||
unset($this->values[$key]);
|
||||
} else {
|
||||
$this->values[$key] = $label;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current search query.
|
||||
*/
|
||||
public function searchValue(): string
|
||||
{
|
||||
return $this->typedValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the selected value.
|
||||
*
|
||||
* @return array<int|string>
|
||||
*/
|
||||
public function value(): array
|
||||
{
|
||||
return array_keys($this->values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the selected labels.
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public function labels(): array
|
||||
{
|
||||
return array_values($this->values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the matches are initially a list.
|
||||
*/
|
||||
public function isList(): bool
|
||||
{
|
||||
return $this->isList;
|
||||
}
|
||||
}
|
133
vendor/laravel/prompts/src/MultiSelectPrompt.php
vendored
Normal file
133
vendor/laravel/prompts/src/MultiSelectPrompt.php
vendored
Normal file
@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class MultiSelectPrompt extends Prompt
|
||||
{
|
||||
use Concerns\Scrolling;
|
||||
|
||||
/**
|
||||
* The options for the multi-select prompt.
|
||||
*
|
||||
* @var array<int|string, string>
|
||||
*/
|
||||
public array $options;
|
||||
|
||||
/**
|
||||
* The default values the multi-select prompt.
|
||||
*
|
||||
* @var array<int|string>
|
||||
*/
|
||||
public array $default;
|
||||
|
||||
/**
|
||||
* The selected values.
|
||||
*
|
||||
* @var array<int|string>
|
||||
*/
|
||||
protected array $values = [];
|
||||
|
||||
/**
|
||||
* Create a new MultiSelectPrompt instance.
|
||||
*
|
||||
* @param array<int|string, string>|Collection<int|string, string> $options
|
||||
* @param array<int|string>|Collection<int, int|string> $default
|
||||
*/
|
||||
public function __construct(
|
||||
public string $label,
|
||||
array|Collection $options,
|
||||
array|Collection $default = [],
|
||||
public int $scroll = 5,
|
||||
public bool|string $required = false,
|
||||
public mixed $validate = null,
|
||||
public string $hint = '',
|
||||
) {
|
||||
$this->options = $options instanceof Collection ? $options->all() : $options;
|
||||
$this->default = $default instanceof Collection ? $default->all() : $default;
|
||||
$this->values = $this->default;
|
||||
|
||||
$this->initializeScrolling(0);
|
||||
|
||||
$this->on('key', fn ($key) => match ($key) {
|
||||
Key::UP, Key::UP_ARROW, Key::LEFT, Key::LEFT_ARROW, Key::SHIFT_TAB, Key::CTRL_P, Key::CTRL_B, 'k', 'h' => $this->highlightPrevious(count($this->options)),
|
||||
Key::DOWN, Key::DOWN_ARROW, Key::RIGHT, Key::RIGHT_ARROW, Key::TAB, Key::CTRL_N, Key::CTRL_F, 'j', 'l' => $this->highlightNext(count($this->options)),
|
||||
Key::oneOf([Key::HOME, Key::CTRL_A], $key) => $this->highlight(0),
|
||||
Key::oneOf([Key::END, Key::CTRL_E], $key) => $this->highlight(count($this->options) - 1),
|
||||
Key::SPACE => $this->toggleHighlighted(),
|
||||
Key::ENTER => $this->submit(),
|
||||
default => null,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the selected values.
|
||||
*
|
||||
* @return array<int|string>
|
||||
*/
|
||||
public function value(): array
|
||||
{
|
||||
return array_values($this->values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the selected labels.
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public function labels(): array
|
||||
{
|
||||
if (array_is_list($this->options)) {
|
||||
return array_map(fn ($value) => (string) $value, $this->values);
|
||||
}
|
||||
|
||||
return array_values(array_intersect_key($this->options, array_flip($this->values)));
|
||||
}
|
||||
|
||||
/**
|
||||
* The currently visible options.
|
||||
*
|
||||
* @return array<int|string, string>
|
||||
*/
|
||||
public function visible(): array
|
||||
{
|
||||
return array_slice($this->options, $this->firstVisible, $this->scroll, preserve_keys: true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the value is currently highlighted.
|
||||
*/
|
||||
public function isHighlighted(string $value): bool
|
||||
{
|
||||
if (array_is_list($this->options)) {
|
||||
return $this->options[$this->highlighted] === $value;
|
||||
}
|
||||
|
||||
return array_keys($this->options)[$this->highlighted] === $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the value is currently selected.
|
||||
*/
|
||||
public function isSelected(string $value): bool
|
||||
{
|
||||
return in_array($value, $this->values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the highlighted entry.
|
||||
*/
|
||||
protected function toggleHighlighted(): void
|
||||
{
|
||||
$value = array_is_list($this->options)
|
||||
? $this->options[$this->highlighted]
|
||||
: array_keys($this->options)[$this->highlighted];
|
||||
|
||||
if (in_array($value, $this->values)) {
|
||||
$this->values = array_filter($this->values, fn ($v) => $v !== $value);
|
||||
} else {
|
||||
$this->values[] = $value;
|
||||
}
|
||||
}
|
||||
}
|
44
vendor/laravel/prompts/src/Note.php
vendored
Normal file
44
vendor/laravel/prompts/src/Note.php
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts;
|
||||
|
||||
class Note extends Prompt
|
||||
{
|
||||
/**
|
||||
* Create a new Note instance.
|
||||
*/
|
||||
public function __construct(public string $message, public ?string $type = null)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the note.
|
||||
*/
|
||||
public function display(): void
|
||||
{
|
||||
$this->prompt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the note.
|
||||
*/
|
||||
public function prompt(): bool
|
||||
{
|
||||
$this->capturePreviousNewLines();
|
||||
|
||||
$this->state = 'submit';
|
||||
|
||||
static::output()->write($this->renderTheme());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the prompt.
|
||||
*/
|
||||
public function value(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
50
vendor/laravel/prompts/src/Output/BufferedConsoleOutput.php
vendored
Normal file
50
vendor/laravel/prompts/src/Output/BufferedConsoleOutput.php
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts\Output;
|
||||
|
||||
class BufferedConsoleOutput extends ConsoleOutput
|
||||
{
|
||||
/**
|
||||
* The output buffer.
|
||||
*/
|
||||
protected string $buffer = '';
|
||||
|
||||
/**
|
||||
* Empties the buffer and returns its content.
|
||||
*/
|
||||
public function fetch(): string
|
||||
{
|
||||
$content = $this->buffer;
|
||||
$this->buffer = '';
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the content of the buffer.
|
||||
*/
|
||||
public function content(): string
|
||||
{
|
||||
return $this->buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write to the output buffer.
|
||||
*/
|
||||
protected function doWrite(string $message, bool $newline): void
|
||||
{
|
||||
$this->buffer .= $message;
|
||||
|
||||
if ($newline) {
|
||||
$this->buffer .= \PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write output directly, bypassing newline capture.
|
||||
*/
|
||||
public function writeDirectly(string $message): void
|
||||
{
|
||||
$this->doWrite($message, false);
|
||||
}
|
||||
}
|
49
vendor/laravel/prompts/src/Output/ConsoleOutput.php
vendored
Normal file
49
vendor/laravel/prompts/src/Output/ConsoleOutput.php
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts\Output;
|
||||
|
||||
use Symfony\Component\Console\Output\ConsoleOutput as SymfonyConsoleOutput;
|
||||
|
||||
class ConsoleOutput extends SymfonyConsoleOutput
|
||||
{
|
||||
/**
|
||||
* How many new lines were written by the last output.
|
||||
*/
|
||||
protected int $newLinesWritten = 1;
|
||||
|
||||
/**
|
||||
* How many new lines were written by the last output.
|
||||
*/
|
||||
public function newLinesWritten(): int
|
||||
{
|
||||
return $this->newLinesWritten;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the output and capture the number of trailing new lines.
|
||||
*/
|
||||
protected function doWrite(string $message, bool $newline): void
|
||||
{
|
||||
parent::doWrite($message, $newline);
|
||||
|
||||
if ($newline) {
|
||||
$message .= \PHP_EOL;
|
||||
}
|
||||
|
||||
$trailingNewLines = strlen($message) - strlen(rtrim($message, \PHP_EOL));
|
||||
|
||||
if (trim($message) === '') {
|
||||
$this->newLinesWritten += $trailingNewLines;
|
||||
} else {
|
||||
$this->newLinesWritten = $trailingNewLines;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write output directly, bypassing newline capture.
|
||||
*/
|
||||
public function writeDirectly(string $message): void
|
||||
{
|
||||
parent::doWrite($message, false);
|
||||
}
|
||||
}
|
41
vendor/laravel/prompts/src/PasswordPrompt.php
vendored
Normal file
41
vendor/laravel/prompts/src/PasswordPrompt.php
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts;
|
||||
|
||||
class PasswordPrompt extends Prompt
|
||||
{
|
||||
use Concerns\TypedValue;
|
||||
|
||||
/**
|
||||
* Create a new PasswordPrompt instance.
|
||||
*/
|
||||
public function __construct(
|
||||
public string $label,
|
||||
public string $placeholder = '',
|
||||
public bool|string $required = false,
|
||||
public mixed $validate = null,
|
||||
public string $hint = '',
|
||||
) {
|
||||
$this->trackTypedValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a masked version of the entered value.
|
||||
*/
|
||||
public function masked(): string
|
||||
{
|
||||
return str_repeat('•', mb_strlen($this->value()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the masked value with a virtual cursor.
|
||||
*/
|
||||
public function maskedWithCursor(int $maxWidth): string
|
||||
{
|
||||
if ($this->value() === '') {
|
||||
return $this->dim($this->addCursor($this->placeholder, 0, $maxWidth));
|
||||
}
|
||||
|
||||
return $this->addCursor($this->masked(), $this->cursorPosition, $maxWidth);
|
||||
}
|
||||
}
|
28
vendor/laravel/prompts/src/PausePrompt.php
vendored
Normal file
28
vendor/laravel/prompts/src/PausePrompt.php
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts;
|
||||
|
||||
class PausePrompt extends Prompt
|
||||
{
|
||||
/**
|
||||
* Create a new PausePrompt instance.
|
||||
*/
|
||||
public function __construct(public string $message = 'Press enter to continue...')
|
||||
{
|
||||
$this->required = false;
|
||||
$this->validate = null;
|
||||
|
||||
$this->on('key', fn ($key) => match ($key) {
|
||||
Key::ENTER => $this->submit(),
|
||||
default => null,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the prompt.
|
||||
*/
|
||||
public function value(): bool
|
||||
{
|
||||
return static::$interactive;
|
||||
}
|
||||
}
|
205
vendor/laravel/prompts/src/Progress.php
vendored
Normal file
205
vendor/laravel/prompts/src/Progress.php
vendored
Normal file
@ -0,0 +1,205 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts;
|
||||
|
||||
use Closure;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* @template TSteps of iterable<mixed>|int
|
||||
*/
|
||||
class Progress extends Prompt
|
||||
{
|
||||
/**
|
||||
* The current progress bar item count.
|
||||
*/
|
||||
public int $progress = 0;
|
||||
|
||||
/**
|
||||
* The total number of steps.
|
||||
*/
|
||||
public int $total = 0;
|
||||
|
||||
/**
|
||||
* The original value of pcntl_async_signals
|
||||
*/
|
||||
protected bool $originalAsync;
|
||||
|
||||
/**
|
||||
* Create a new ProgressBar instance.
|
||||
*
|
||||
* @param TSteps $steps
|
||||
*/
|
||||
public function __construct(public string $label, public iterable|int $steps, public string $hint = '')
|
||||
{
|
||||
$this->total = match (true) { // @phpstan-ignore assign.propertyType
|
||||
is_int($this->steps) => $this->steps,
|
||||
is_countable($this->steps) => count($this->steps),
|
||||
is_iterable($this->steps) => iterator_count($this->steps),
|
||||
default => throw new InvalidArgumentException('Unable to count steps.'),
|
||||
};
|
||||
|
||||
if ($this->total === 0) {
|
||||
throw new InvalidArgumentException('Progress bar must have at least one item.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map over the steps while rendering the progress bar.
|
||||
*
|
||||
* @template TReturn
|
||||
*
|
||||
* @param Closure((TSteps is int ? int : value-of<TSteps>), $this): TReturn $callback
|
||||
* @return array<TReturn>
|
||||
*/
|
||||
public function map(Closure $callback): array
|
||||
{
|
||||
$this->start();
|
||||
|
||||
$result = [];
|
||||
|
||||
try {
|
||||
if (is_int($this->steps)) {
|
||||
for ($i = 0; $i < $this->steps; $i++) {
|
||||
$result[] = $callback($i, $this);
|
||||
$this->advance();
|
||||
}
|
||||
} else {
|
||||
foreach ($this->steps as $step) {
|
||||
$result[] = $callback($step, $this);
|
||||
$this->advance();
|
||||
}
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
$this->state = 'error';
|
||||
$this->render();
|
||||
$this->restoreCursor();
|
||||
$this->resetSignals();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
if ($this->hint !== '') {
|
||||
// Just pause for one moment to show the final hint
|
||||
// so it doesn't look like it was skipped
|
||||
usleep(250_000);
|
||||
}
|
||||
|
||||
$this->finish();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the progress bar.
|
||||
*/
|
||||
public function start(): void
|
||||
{
|
||||
$this->capturePreviousNewLines();
|
||||
|
||||
if (function_exists('pcntl_signal')) {
|
||||
$this->originalAsync = pcntl_async_signals(true);
|
||||
pcntl_signal(SIGINT, function () {
|
||||
$this->state = 'cancel';
|
||||
$this->render();
|
||||
exit();
|
||||
});
|
||||
}
|
||||
|
||||
$this->state = 'active';
|
||||
$this->hideCursor();
|
||||
$this->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Advance the progress bar.
|
||||
*/
|
||||
public function advance(int $step = 1): void
|
||||
{
|
||||
$this->progress += $step;
|
||||
|
||||
if ($this->progress > $this->total) {
|
||||
$this->progress = $this->total;
|
||||
}
|
||||
|
||||
$this->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish the progress bar.
|
||||
*/
|
||||
public function finish(): void
|
||||
{
|
||||
$this->state = 'submit';
|
||||
$this->render();
|
||||
$this->restoreCursor();
|
||||
$this->resetSignals();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the label.
|
||||
*/
|
||||
public function label(string $label): static
|
||||
{
|
||||
$this->label = $label;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the hint.
|
||||
*/
|
||||
public function hint(string $hint): static
|
||||
{
|
||||
$this->hint = $hint;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the completion percentage.
|
||||
*/
|
||||
public function percentage(): int|float
|
||||
{
|
||||
return $this->progress / $this->total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable prompting for input.
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function prompt(): never
|
||||
{
|
||||
throw new RuntimeException('Progress Bar cannot be prompted.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the prompt.
|
||||
*/
|
||||
public function value(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the signal handling.
|
||||
*/
|
||||
protected function resetSignals(): void
|
||||
{
|
||||
if (isset($this->originalAsync)) {
|
||||
pcntl_async_signals($this->originalAsync);
|
||||
pcntl_signal(SIGINT, SIG_DFL);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the cursor.
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
$this->restoreCursor();
|
||||
}
|
||||
}
|
392
vendor/laravel/prompts/src/Prompt.php
vendored
Normal file
392
vendor/laravel/prompts/src/Prompt.php
vendored
Normal file
@ -0,0 +1,392 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts;
|
||||
|
||||
use Closure;
|
||||
use Laravel\Prompts\Exceptions\FormRevertedException;
|
||||
use Laravel\Prompts\Output\ConsoleOutput;
|
||||
use RuntimeException;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Throwable;
|
||||
|
||||
abstract class Prompt
|
||||
{
|
||||
use Concerns\Colors;
|
||||
use Concerns\Cursor;
|
||||
use Concerns\Erase;
|
||||
use Concerns\Events;
|
||||
use Concerns\FakesInputOutput;
|
||||
use Concerns\Fallback;
|
||||
use Concerns\Interactivity;
|
||||
use Concerns\Themes;
|
||||
|
||||
/**
|
||||
* The current state of the prompt.
|
||||
*/
|
||||
public string $state = 'initial';
|
||||
|
||||
/**
|
||||
* The error message from the validator.
|
||||
*/
|
||||
public string $error = '';
|
||||
|
||||
/**
|
||||
* The cancel message displayed when this prompt is cancelled.
|
||||
*/
|
||||
public string $cancelMessage = 'Cancelled.';
|
||||
|
||||
/**
|
||||
* The previously rendered frame.
|
||||
*/
|
||||
protected string $prevFrame = '';
|
||||
|
||||
/**
|
||||
* How many new lines were written by the last output.
|
||||
*/
|
||||
protected int $newLinesWritten = 1;
|
||||
|
||||
/**
|
||||
* Whether user input is required.
|
||||
*/
|
||||
public bool|string $required;
|
||||
|
||||
/**
|
||||
* The validator callback or rules.
|
||||
*/
|
||||
public mixed $validate;
|
||||
|
||||
/**
|
||||
* The cancellation callback.
|
||||
*/
|
||||
protected static ?Closure $cancelUsing;
|
||||
|
||||
/**
|
||||
* Indicates if the prompt has been validated.
|
||||
*/
|
||||
protected bool $validated = false;
|
||||
|
||||
/**
|
||||
* The custom validation callback.
|
||||
*/
|
||||
protected static ?Closure $validateUsing;
|
||||
|
||||
/**
|
||||
* The revert handler from the StepBuilder.
|
||||
*/
|
||||
protected static ?Closure $revertUsing = null;
|
||||
|
||||
/**
|
||||
* The output instance.
|
||||
*/
|
||||
protected static OutputInterface $output;
|
||||
|
||||
/**
|
||||
* The terminal instance.
|
||||
*/
|
||||
protected static Terminal $terminal;
|
||||
|
||||
/**
|
||||
* Get the value of the prompt.
|
||||
*/
|
||||
abstract public function value(): mixed;
|
||||
|
||||
/**
|
||||
* Render the prompt and listen for input.
|
||||
*/
|
||||
public function prompt(): mixed
|
||||
{
|
||||
try {
|
||||
$this->capturePreviousNewLines();
|
||||
|
||||
if (static::shouldFallback()) {
|
||||
return $this->fallback();
|
||||
}
|
||||
|
||||
static::$interactive ??= stream_isatty(STDIN);
|
||||
|
||||
if (! static::$interactive) {
|
||||
return $this->default();
|
||||
}
|
||||
|
||||
$this->checkEnvironment();
|
||||
|
||||
try {
|
||||
static::terminal()->setTty('-icanon -isig -echo');
|
||||
} catch (Throwable $e) {
|
||||
static::output()->writeln("<comment>{$e->getMessage()}</comment>");
|
||||
static::fallbackWhen(true);
|
||||
|
||||
return $this->fallback();
|
||||
}
|
||||
|
||||
$this->hideCursor();
|
||||
$this->render();
|
||||
|
||||
while (($key = static::terminal()->read()) !== null) {
|
||||
$continue = $this->handleKeyPress($key);
|
||||
|
||||
$this->render();
|
||||
|
||||
if ($continue === false || $key === Key::CTRL_C) {
|
||||
if ($key === Key::CTRL_C) {
|
||||
if (isset(static::$cancelUsing)) {
|
||||
return (static::$cancelUsing)();
|
||||
} else {
|
||||
static::terminal()->exit();
|
||||
}
|
||||
}
|
||||
|
||||
if ($key === Key::CTRL_U && self::$revertUsing) {
|
||||
throw new FormRevertedException();
|
||||
}
|
||||
|
||||
return $this->value();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
$this->clearListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback to be invoked when a user cancels a prompt.
|
||||
*/
|
||||
public static function cancelUsing(?Closure $callback): void
|
||||
{
|
||||
static::$cancelUsing = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* How many new lines were written by the last output.
|
||||
*/
|
||||
public function newLinesWritten(): int
|
||||
{
|
||||
return $this->newLinesWritten;
|
||||
}
|
||||
|
||||
/**
|
||||
* Capture the number of new lines written by the last output.
|
||||
*/
|
||||
protected function capturePreviousNewLines(): void
|
||||
{
|
||||
$this->newLinesWritten = method_exists(static::output(), 'newLinesWritten')
|
||||
? static::output()->newLinesWritten()
|
||||
: 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the output instance.
|
||||
*/
|
||||
public static function setOutput(OutputInterface $output): void
|
||||
{
|
||||
self::$output = $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current output instance.
|
||||
*/
|
||||
protected static function output(): OutputInterface
|
||||
{
|
||||
return self::$output ??= new ConsoleOutput();
|
||||
}
|
||||
|
||||
/**
|
||||
* Write output directly, bypassing newline capture.
|
||||
*/
|
||||
protected static function writeDirectly(string $message): void
|
||||
{
|
||||
match (true) {
|
||||
method_exists(static::output(), 'writeDirectly') => static::output()->writeDirectly($message),
|
||||
method_exists(static::output(), 'getOutput') => static::output()->getOutput()->write($message),
|
||||
default => static::output()->write($message),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the terminal instance.
|
||||
*/
|
||||
public static function terminal(): Terminal
|
||||
{
|
||||
return static::$terminal ??= new Terminal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the custom validation callback.
|
||||
*/
|
||||
public static function validateUsing(Closure $callback): void
|
||||
{
|
||||
static::$validateUsing = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert the prompt using the given callback.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static function revertUsing(Closure $callback): void
|
||||
{
|
||||
static::$revertUsing = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear any previous revert callback.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static function preventReverting(): void
|
||||
{
|
||||
static::$revertUsing = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the prompt.
|
||||
*/
|
||||
protected function render(): void
|
||||
{
|
||||
$this->terminal()->initDimensions();
|
||||
|
||||
$frame = $this->renderTheme();
|
||||
|
||||
if ($frame === $this->prevFrame) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->state === 'initial') {
|
||||
static::output()->write($frame);
|
||||
|
||||
$this->state = 'active';
|
||||
$this->prevFrame = $frame;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$terminalHeight = $this->terminal()->lines();
|
||||
$previousFrameHeight = count(explode(PHP_EOL, $this->prevFrame));
|
||||
$renderableLines = array_slice(explode(PHP_EOL, $frame), abs(min(0, $terminalHeight - $previousFrameHeight)));
|
||||
|
||||
$this->moveCursorToColumn(1);
|
||||
$this->moveCursorUp(min($terminalHeight, $previousFrameHeight) - 1);
|
||||
$this->eraseDown();
|
||||
$this->output()->write(implode(PHP_EOL, $renderableLines));
|
||||
|
||||
$this->prevFrame = $frame;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit the prompt.
|
||||
*/
|
||||
protected function submit(): void
|
||||
{
|
||||
$this->validate($this->value());
|
||||
|
||||
if ($this->state !== 'error') {
|
||||
$this->state = 'submit';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a key press and determine whether to continue.
|
||||
*/
|
||||
private function handleKeyPress(string $key): bool
|
||||
{
|
||||
if ($this->state === 'error') {
|
||||
$this->state = 'active';
|
||||
}
|
||||
|
||||
$this->emit('key', $key);
|
||||
|
||||
if ($this->state === 'submit') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($key === Key::CTRL_U) {
|
||||
if (! self::$revertUsing) {
|
||||
$this->state = 'error';
|
||||
$this->error = 'This cannot be reverted.';
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->state = 'cancel';
|
||||
$this->cancelMessage = 'Reverted.';
|
||||
|
||||
call_user_func(self::$revertUsing);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($key === Key::CTRL_C) {
|
||||
$this->state = 'cancel';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->validated) {
|
||||
$this->validate($this->value());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the input.
|
||||
*/
|
||||
private function validate(mixed $value): void
|
||||
{
|
||||
$this->validated = true;
|
||||
|
||||
if ($this->required !== false && $this->isInvalidWhenRequired($value)) {
|
||||
$this->state = 'error';
|
||||
$this->error = is_string($this->required) && strlen($this->required) > 0 ? $this->required : 'Required.';
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (! isset($this->validate) && ! isset(static::$validateUsing)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$error = match (true) {
|
||||
is_callable($this->validate) => ($this->validate)($value),
|
||||
isset(static::$validateUsing) => (static::$validateUsing)($this),
|
||||
default => throw new RuntimeException('The validation logic is missing.'),
|
||||
};
|
||||
|
||||
if (! is_string($error) && ! is_null($error)) {
|
||||
throw new RuntimeException('The validator must return a string or null.');
|
||||
}
|
||||
|
||||
if (is_string($error) && strlen($error) > 0) {
|
||||
$this->state = 'error';
|
||||
$this->error = $error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the given value is invalid when the prompt is required.
|
||||
*/
|
||||
protected function isInvalidWhenRequired(mixed $value): bool
|
||||
{
|
||||
return $value === '' || $value === [] || $value === false || $value === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the environment can support the prompt.
|
||||
*/
|
||||
private function checkEnvironment(): void
|
||||
{
|
||||
if (PHP_OS_FAMILY === 'Windows') {
|
||||
throw new RuntimeException('Prompts is not currently supported on Windows. Please use WSL or configure a fallback.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the cursor and terminal state.
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
$this->restoreCursor();
|
||||
|
||||
static::terminal()->restoreTty();
|
||||
}
|
||||
}
|
138
vendor/laravel/prompts/src/SearchPrompt.php
vendored
Normal file
138
vendor/laravel/prompts/src/SearchPrompt.php
vendored
Normal file
@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts;
|
||||
|
||||
use Closure;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class SearchPrompt extends Prompt
|
||||
{
|
||||
use Concerns\Scrolling;
|
||||
use Concerns\Truncation;
|
||||
use Concerns\TypedValue;
|
||||
|
||||
/**
|
||||
* The cached matches.
|
||||
*
|
||||
* @var array<int|string, string>|null
|
||||
*/
|
||||
protected ?array $matches = null;
|
||||
|
||||
/**
|
||||
* Create a new SearchPrompt instance.
|
||||
*
|
||||
* @param Closure(string): array<int|string, string> $options
|
||||
*/
|
||||
public function __construct(
|
||||
public string $label,
|
||||
public Closure $options,
|
||||
public string $placeholder = '',
|
||||
public int $scroll = 5,
|
||||
public mixed $validate = null,
|
||||
public string $hint = '',
|
||||
public bool|string $required = true,
|
||||
) {
|
||||
if ($this->required === false) {
|
||||
throw new InvalidArgumentException('Argument [required] must be true or a string.');
|
||||
}
|
||||
|
||||
$this->trackTypedValue(submit: false, ignore: fn ($key) => Key::oneOf([Key::HOME, Key::END, Key::CTRL_A, Key::CTRL_E], $key) && $this->highlighted !== null);
|
||||
|
||||
$this->initializeScrolling(null);
|
||||
|
||||
$this->on('key', fn ($key) => match ($key) {
|
||||
Key::UP, Key::UP_ARROW, Key::SHIFT_TAB, Key::CTRL_P => $this->highlightPrevious(count($this->matches), true),
|
||||
Key::DOWN, Key::DOWN_ARROW, Key::TAB, Key::CTRL_N => $this->highlightNext(count($this->matches), true),
|
||||
Key::oneOf([Key::HOME, Key::CTRL_A], $key) => $this->highlighted !== null ? $this->highlight(0) : null,
|
||||
Key::oneOf([Key::END, Key::CTRL_E], $key) => $this->highlighted !== null ? $this->highlight(count($this->matches()) - 1) : null,
|
||||
Key::ENTER => $this->highlighted !== null ? $this->submit() : $this->search(),
|
||||
Key::oneOf([Key::LEFT, Key::LEFT_ARROW, Key::RIGHT, Key::RIGHT_ARROW, Key::CTRL_B, Key::CTRL_F], $key) => $this->highlighted = null,
|
||||
default => $this->search(),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the search.
|
||||
*/
|
||||
protected function search(): void
|
||||
{
|
||||
$this->state = 'searching';
|
||||
$this->highlighted = null;
|
||||
$this->render();
|
||||
$this->matches = null;
|
||||
$this->firstVisible = 0;
|
||||
$this->state = 'active';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entered value with a virtual cursor.
|
||||
*/
|
||||
public function valueWithCursor(int $maxWidth): string
|
||||
{
|
||||
if ($this->highlighted !== null) {
|
||||
return $this->typedValue === ''
|
||||
? $this->dim($this->truncate($this->placeholder, $maxWidth))
|
||||
: $this->truncate($this->typedValue, $maxWidth);
|
||||
}
|
||||
|
||||
if ($this->typedValue === '') {
|
||||
return $this->dim($this->addCursor($this->placeholder, 0, $maxWidth));
|
||||
}
|
||||
|
||||
return $this->addCursor($this->typedValue, $this->cursorPosition, $maxWidth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get options that match the input.
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public function matches(): array
|
||||
{
|
||||
if (is_array($this->matches)) {
|
||||
return $this->matches;
|
||||
}
|
||||
|
||||
return $this->matches = ($this->options)($this->typedValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* The currently visible matches.
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public function visible(): array
|
||||
{
|
||||
return array_slice($this->matches(), $this->firstVisible, $this->scroll, preserve_keys: true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current search query.
|
||||
*/
|
||||
public function searchValue(): string
|
||||
{
|
||||
return $this->typedValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the selected value.
|
||||
*/
|
||||
public function value(): int|string|null
|
||||
{
|
||||
if ($this->matches === null || $this->highlighted === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return array_is_list($this->matches)
|
||||
? $this->matches[$this->highlighted]
|
||||
: array_keys($this->matches)[$this->highlighted];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the selected label.
|
||||
*/
|
||||
public function label(): ?string
|
||||
{
|
||||
return $this->matches[array_keys($this->matches)[$this->highlighted]] ?? null;
|
||||
}
|
||||
}
|
106
vendor/laravel/prompts/src/SelectPrompt.php
vendored
Normal file
106
vendor/laravel/prompts/src/SelectPrompt.php
vendored
Normal file
@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class SelectPrompt extends Prompt
|
||||
{
|
||||
use Concerns\Scrolling;
|
||||
|
||||
/**
|
||||
* The options for the select prompt.
|
||||
*
|
||||
* @var array<int|string, string>
|
||||
*/
|
||||
public array $options;
|
||||
|
||||
/**
|
||||
* Create a new SelectPrompt instance.
|
||||
*
|
||||
* @param array<int|string, string>|Collection<int|string, string> $options
|
||||
*/
|
||||
public function __construct(
|
||||
public string $label,
|
||||
array|Collection $options,
|
||||
public int|string|null $default = null,
|
||||
public int $scroll = 5,
|
||||
public mixed $validate = null,
|
||||
public string $hint = '',
|
||||
public bool|string $required = true,
|
||||
) {
|
||||
if ($this->required === false) {
|
||||
throw new InvalidArgumentException('Argument [required] must be true or a string.');
|
||||
}
|
||||
|
||||
$this->options = $options instanceof Collection ? $options->all() : $options;
|
||||
|
||||
if ($this->default) {
|
||||
if (array_is_list($this->options)) {
|
||||
$this->initializeScrolling(array_search($this->default, $this->options) ?: 0);
|
||||
} else {
|
||||
$this->initializeScrolling(array_search($this->default, array_keys($this->options)) ?: 0);
|
||||
}
|
||||
|
||||
$this->scrollToHighlighted(count($this->options));
|
||||
} else {
|
||||
$this->initializeScrolling(0);
|
||||
}
|
||||
|
||||
$this->on('key', fn ($key) => match ($key) {
|
||||
Key::UP, Key::UP_ARROW, Key::LEFT, Key::LEFT_ARROW, Key::SHIFT_TAB, Key::CTRL_P, Key::CTRL_B, 'k', 'h' => $this->highlightPrevious(count($this->options)),
|
||||
Key::DOWN, Key::DOWN_ARROW, Key::RIGHT, Key::RIGHT_ARROW, Key::TAB, Key::CTRL_N, Key::CTRL_F, 'j', 'l' => $this->highlightNext(count($this->options)),
|
||||
Key::oneOf([Key::HOME, Key::CTRL_A], $key) => $this->highlight(0),
|
||||
Key::oneOf([Key::END, Key::CTRL_E], $key) => $this->highlight(count($this->options) - 1),
|
||||
Key::ENTER => $this->submit(),
|
||||
default => null,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the selected value.
|
||||
*/
|
||||
public function value(): int|string|null
|
||||
{
|
||||
if (static::$interactive === false) {
|
||||
return $this->default;
|
||||
}
|
||||
|
||||
if (array_is_list($this->options)) {
|
||||
return $this->options[$this->highlighted] ?? null;
|
||||
} else {
|
||||
return array_keys($this->options)[$this->highlighted];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the selected label.
|
||||
*/
|
||||
public function label(): ?string
|
||||
{
|
||||
if (array_is_list($this->options)) {
|
||||
return $this->options[$this->highlighted] ?? null;
|
||||
} else {
|
||||
return $this->options[array_keys($this->options)[$this->highlighted]] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The currently visible options.
|
||||
*
|
||||
* @return array<int|string, string>
|
||||
*/
|
||||
public function visible(): array
|
||||
{
|
||||
return array_slice($this->options, $this->firstVisible, $this->scroll, preserve_keys: true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the given value is invalid when the prompt is required.
|
||||
*/
|
||||
protected function isInvalidWhenRequired(mixed $value): bool
|
||||
{
|
||||
return $value === null;
|
||||
}
|
||||
}
|
160
vendor/laravel/prompts/src/Spinner.php
vendored
Normal file
160
vendor/laravel/prompts/src/Spinner.php
vendored
Normal file
@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts;
|
||||
|
||||
use Closure;
|
||||
use RuntimeException;
|
||||
|
||||
class Spinner extends Prompt
|
||||
{
|
||||
/**
|
||||
* How long to wait between rendering each frame.
|
||||
*/
|
||||
public int $interval = 100;
|
||||
|
||||
/**
|
||||
* The number of times the spinner has been rendered.
|
||||
*/
|
||||
public int $count = 0;
|
||||
|
||||
/**
|
||||
* Whether the spinner can only be rendered once.
|
||||
*/
|
||||
public bool $static = false;
|
||||
|
||||
/**
|
||||
* The process ID after forking.
|
||||
*/
|
||||
protected int $pid;
|
||||
|
||||
/**
|
||||
* Create a new Spinner instance.
|
||||
*/
|
||||
public function __construct(public string $message = '')
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the spinner and execute the callback.
|
||||
*
|
||||
* @template TReturn of mixed
|
||||
*
|
||||
* @param \Closure(): TReturn $callback
|
||||
* @return TReturn
|
||||
*/
|
||||
public function spin(Closure $callback): mixed
|
||||
{
|
||||
$this->capturePreviousNewLines();
|
||||
|
||||
if (! function_exists('pcntl_fork')) {
|
||||
return $this->renderStatically($callback);
|
||||
}
|
||||
|
||||
$originalAsync = pcntl_async_signals(true);
|
||||
|
||||
pcntl_signal(SIGINT, fn () => exit());
|
||||
|
||||
try {
|
||||
$this->hideCursor();
|
||||
$this->render();
|
||||
|
||||
$this->pid = pcntl_fork();
|
||||
|
||||
if ($this->pid === 0) {
|
||||
while (true) { // @phpstan-ignore-line
|
||||
$this->render();
|
||||
|
||||
$this->count++;
|
||||
|
||||
usleep($this->interval * 1000);
|
||||
}
|
||||
} else {
|
||||
$result = $callback();
|
||||
|
||||
$this->resetTerminal($originalAsync);
|
||||
|
||||
return $result;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$this->resetTerminal($originalAsync);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the terminal.
|
||||
*/
|
||||
protected function resetTerminal(bool $originalAsync): void
|
||||
{
|
||||
pcntl_async_signals($originalAsync);
|
||||
pcntl_signal(SIGINT, SIG_DFL);
|
||||
|
||||
$this->eraseRenderedLines();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a static version of the spinner.
|
||||
*
|
||||
* @template TReturn of mixed
|
||||
*
|
||||
* @param \Closure(): TReturn $callback
|
||||
* @return TReturn
|
||||
*/
|
||||
protected function renderStatically(Closure $callback): mixed
|
||||
{
|
||||
$this->static = true;
|
||||
|
||||
try {
|
||||
$this->hideCursor();
|
||||
$this->render();
|
||||
|
||||
$result = $callback();
|
||||
} finally {
|
||||
$this->eraseRenderedLines();
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable prompting for input.
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function prompt(): never
|
||||
{
|
||||
throw new RuntimeException('Spinner cannot be prompted.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current value of the prompt.
|
||||
*/
|
||||
public function value(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the lines rendered by the spinner.
|
||||
*/
|
||||
protected function eraseRenderedLines(): void
|
||||
{
|
||||
$lines = explode(PHP_EOL, $this->prevFrame);
|
||||
$this->moveCursor(-999, -count($lines) + 1);
|
||||
$this->eraseDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up after the spinner.
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
if (! empty($this->pid)) {
|
||||
posix_kill($this->pid, SIGHUP);
|
||||
}
|
||||
|
||||
parent::__destruct();
|
||||
}
|
||||
}
|
125
vendor/laravel/prompts/src/SuggestPrompt.php
vendored
Normal file
125
vendor/laravel/prompts/src/SuggestPrompt.php
vendored
Normal file
@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class SuggestPrompt extends Prompt
|
||||
{
|
||||
use Concerns\Scrolling;
|
||||
use Concerns\Truncation;
|
||||
use Concerns\TypedValue;
|
||||
|
||||
/**
|
||||
* The options for the suggest prompt.
|
||||
*
|
||||
* @var array<string>|Closure(string): (array<string>|Collection<int, string>)
|
||||
*/
|
||||
public array|Closure $options;
|
||||
|
||||
/**
|
||||
* The cache of matches.
|
||||
*
|
||||
* @var array<string>|null
|
||||
*/
|
||||
protected ?array $matches = null;
|
||||
|
||||
/**
|
||||
* Create a new SuggestPrompt instance.
|
||||
*
|
||||
* @param array<string>|Collection<int, string>|Closure(string): (array<string>|Collection<int, string>) $options
|
||||
*/
|
||||
public function __construct(
|
||||
public string $label,
|
||||
array|Collection|Closure $options,
|
||||
public string $placeholder = '',
|
||||
public string $default = '',
|
||||
public int $scroll = 5,
|
||||
public bool|string $required = false,
|
||||
public mixed $validate = null,
|
||||
public string $hint = '',
|
||||
) {
|
||||
$this->options = $options instanceof Collection ? $options->all() : $options;
|
||||
|
||||
$this->initializeScrolling(null);
|
||||
|
||||
$this->on('key', fn ($key) => match ($key) {
|
||||
Key::UP, Key::UP_ARROW, Key::SHIFT_TAB, Key::CTRL_P => $this->highlightPrevious(count($this->matches()), true),
|
||||
Key::DOWN, Key::DOWN_ARROW, Key::TAB, Key::CTRL_N => $this->highlightNext(count($this->matches()), true),
|
||||
Key::oneOf([Key::HOME, Key::CTRL_A], $key) => $this->highlighted !== null ? $this->highlight(0) : null,
|
||||
Key::oneOf([Key::END, Key::CTRL_E], $key) => $this->highlighted !== null ? $this->highlight(count($this->matches()) - 1) : null,
|
||||
Key::ENTER => $this->selectHighlighted(),
|
||||
Key::oneOf([Key::LEFT, Key::LEFT_ARROW, Key::RIGHT, Key::RIGHT_ARROW, Key::CTRL_B, Key::CTRL_F], $key) => $this->highlighted = null,
|
||||
default => (function () {
|
||||
$this->highlighted = null;
|
||||
$this->matches = null;
|
||||
$this->firstVisible = 0;
|
||||
})(),
|
||||
});
|
||||
|
||||
$this->trackTypedValue($default, ignore: fn ($key) => Key::oneOf([Key::HOME, Key::END, Key::CTRL_A, Key::CTRL_E], $key) && $this->highlighted !== null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entered value with a virtual cursor.
|
||||
*/
|
||||
public function valueWithCursor(int $maxWidth): string
|
||||
{
|
||||
if ($this->highlighted !== null) {
|
||||
return $this->value() === ''
|
||||
? $this->dim($this->truncate($this->placeholder, $maxWidth))
|
||||
: $this->truncate($this->value(), $maxWidth);
|
||||
}
|
||||
|
||||
if ($this->value() === '') {
|
||||
return $this->dim($this->addCursor($this->placeholder, 0, $maxWidth));
|
||||
}
|
||||
|
||||
return $this->addCursor($this->value(), $this->cursorPosition, $maxWidth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get options that match the input.
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public function matches(): array
|
||||
{
|
||||
if (is_array($this->matches)) {
|
||||
return $this->matches;
|
||||
}
|
||||
|
||||
if ($this->options instanceof Closure) {
|
||||
$matches = ($this->options)($this->value());
|
||||
|
||||
return $this->matches = array_values($matches instanceof Collection ? $matches->all() : $matches);
|
||||
}
|
||||
|
||||
return $this->matches = array_values(array_filter($this->options, function ($option) {
|
||||
return str_starts_with(strtolower($option), strtolower($this->value()));
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* The current visible matches.
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public function visible(): array
|
||||
{
|
||||
return array_slice($this->matches(), $this->firstVisible, $this->scroll, preserve_keys: true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the highlighted entry.
|
||||
*/
|
||||
protected function selectHighlighted(): void
|
||||
{
|
||||
if ($this->highlighted === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->typedValue = $this->matches()[$this->highlighted];
|
||||
}
|
||||
}
|
71
vendor/laravel/prompts/src/Table.php
vendored
Normal file
71
vendor/laravel/prompts/src/Table.php
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class Table extends Prompt
|
||||
{
|
||||
/**
|
||||
* The table headers.
|
||||
*
|
||||
* @var array<int, string|array<int, string>>
|
||||
*/
|
||||
public array $headers;
|
||||
|
||||
/**
|
||||
* The table rows.
|
||||
*
|
||||
* @var array<int, array<int, string>>
|
||||
*/
|
||||
public array $rows;
|
||||
|
||||
/**
|
||||
* Create a new Table instance.
|
||||
*
|
||||
* @param array<int, string|array<int, string>>|Collection<int, string|array<int, string>> $headers
|
||||
* @param array<int, array<int, string>>|Collection<int, array<int, string>> $rows
|
||||
*
|
||||
* @phpstan-param ($rows is null ? list<list<string>>|Collection<int, list<string>> : list<string|list<string>>|Collection<int, string|list<string>>) $headers
|
||||
*/
|
||||
public function __construct(array|Collection $headers = [], array|Collection|null $rows = null)
|
||||
{
|
||||
if ($rows === null) {
|
||||
$rows = $headers;
|
||||
$headers = [];
|
||||
}
|
||||
|
||||
$this->headers = $headers instanceof Collection ? $headers->all() : $headers;
|
||||
$this->rows = $rows instanceof Collection ? $rows->all() : $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the table.
|
||||
*/
|
||||
public function display(): void
|
||||
{
|
||||
$this->prompt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the table.
|
||||
*/
|
||||
public function prompt(): bool
|
||||
{
|
||||
$this->capturePreviousNewLines();
|
||||
|
||||
$this->state = 'submit';
|
||||
|
||||
static::output()->write($this->renderTheme());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the prompt.
|
||||
*/
|
||||
public function value(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
119
vendor/laravel/prompts/src/Terminal.php
vendored
Normal file
119
vendor/laravel/prompts/src/Terminal.php
vendored
Normal file
@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts;
|
||||
|
||||
use ReflectionClass;
|
||||
use RuntimeException;
|
||||
use Symfony\Component\Console\Terminal as SymfonyTerminal;
|
||||
|
||||
class Terminal
|
||||
{
|
||||
/**
|
||||
* The initial TTY mode.
|
||||
*/
|
||||
protected ?string $initialTtyMode;
|
||||
|
||||
/**
|
||||
* The Symfony Terminal instance.
|
||||
*/
|
||||
protected SymfonyTerminal $terminal;
|
||||
|
||||
/**
|
||||
* Create a new Terminal instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->terminal = new SymfonyTerminal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a line from the terminal.
|
||||
*/
|
||||
public function read(): string
|
||||
{
|
||||
$input = fread(STDIN, 1024);
|
||||
|
||||
return $input !== false ? $input : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the TTY mode.
|
||||
*/
|
||||
public function setTty(string $mode): void
|
||||
{
|
||||
$this->initialTtyMode ??= $this->exec('stty -g');
|
||||
|
||||
$this->exec("stty $mode");
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the initial TTY mode.
|
||||
*/
|
||||
public function restoreTty(): void
|
||||
{
|
||||
if (isset($this->initialTtyMode)) {
|
||||
$this->exec("stty {$this->initialTtyMode}");
|
||||
|
||||
$this->initialTtyMode = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of columns in the terminal.
|
||||
*/
|
||||
public function cols(): int
|
||||
{
|
||||
return $this->terminal->getWidth();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of lines in the terminal.
|
||||
*/
|
||||
public function lines(): int
|
||||
{
|
||||
return $this->terminal->getHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* (Re)initialize the terminal dimensions.
|
||||
*/
|
||||
public function initDimensions(): void
|
||||
{
|
||||
(new ReflectionClass($this->terminal))
|
||||
->getMethod('initDimensions')
|
||||
->invoke($this->terminal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exit the interactive session.
|
||||
*/
|
||||
public function exit(): void
|
||||
{
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given command and return the output.
|
||||
*/
|
||||
protected function exec(string $command): string
|
||||
{
|
||||
$process = proc_open($command, [
|
||||
1 => ['pipe', 'w'],
|
||||
2 => ['pipe', 'w'],
|
||||
], $pipes);
|
||||
|
||||
if (! $process) {
|
||||
throw new RuntimeException('Failed to create process.');
|
||||
}
|
||||
|
||||
$stdout = stream_get_contents($pipes[1]);
|
||||
$stderr = stream_get_contents($pipes[2]);
|
||||
$code = proc_close($process);
|
||||
|
||||
if ($code !== 0 || $stdout === false) {
|
||||
throw new RuntimeException(trim($stderr ?: "Unknown error (code: $code)"), $code);
|
||||
}
|
||||
|
||||
return $stdout;
|
||||
}
|
||||
}
|
34
vendor/laravel/prompts/src/TextPrompt.php
vendored
Normal file
34
vendor/laravel/prompts/src/TextPrompt.php
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts;
|
||||
|
||||
class TextPrompt extends Prompt
|
||||
{
|
||||
use Concerns\TypedValue;
|
||||
|
||||
/**
|
||||
* Create a new TextPrompt instance.
|
||||
*/
|
||||
public function __construct(
|
||||
public string $label,
|
||||
public string $placeholder = '',
|
||||
public string $default = '',
|
||||
public bool|string $required = false,
|
||||
public mixed $validate = null,
|
||||
public string $hint = '',
|
||||
) {
|
||||
$this->trackTypedValue($default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entered value with a virtual cursor.
|
||||
*/
|
||||
public function valueWithCursor(int $maxWidth): string
|
||||
{
|
||||
if ($this->value() === '') {
|
||||
return $this->dim($this->addCursor($this->placeholder, 0, $maxWidth));
|
||||
}
|
||||
|
||||
return $this->addCursor($this->value(), $this->cursorPosition, $maxWidth);
|
||||
}
|
||||
}
|
245
vendor/laravel/prompts/src/TextareaPrompt.php
vendored
Normal file
245
vendor/laravel/prompts/src/TextareaPrompt.php
vendored
Normal file
@ -0,0 +1,245 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts;
|
||||
|
||||
class TextareaPrompt extends Prompt
|
||||
{
|
||||
use Concerns\Scrolling;
|
||||
use Concerns\Truncation;
|
||||
use Concerns\TypedValue;
|
||||
|
||||
/**
|
||||
* The width of the textarea.
|
||||
*/
|
||||
public int $width = 60;
|
||||
|
||||
/**
|
||||
* Create a new TextareaPrompt instance.
|
||||
*/
|
||||
public function __construct(
|
||||
public string $label,
|
||||
public string $placeholder = '',
|
||||
public string $default = '',
|
||||
public bool|string $required = false,
|
||||
public mixed $validate = null,
|
||||
public string $hint = '',
|
||||
int $rows = 5,
|
||||
) {
|
||||
$this->scroll = $rows;
|
||||
|
||||
$this->initializeScrolling();
|
||||
|
||||
$this->trackTypedValue(
|
||||
default: $default,
|
||||
submit: false,
|
||||
allowNewLine: true,
|
||||
);
|
||||
|
||||
$this->on('key', function ($key) {
|
||||
if ($key[0] === "\e") {
|
||||
match ($key) {
|
||||
Key::UP, Key::UP_ARROW, Key::CTRL_P => $this->handleUpKey(),
|
||||
Key::DOWN, Key::DOWN_ARROW, Key::CTRL_N => $this->handleDownKey(),
|
||||
default => null,
|
||||
};
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Keys may be buffered.
|
||||
foreach (mb_str_split($key) as $key) {
|
||||
if ($key === Key::CTRL_D) {
|
||||
$this->submit();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the formatted value with a virtual cursor.
|
||||
*/
|
||||
public function valueWithCursor(): string
|
||||
{
|
||||
if ($this->value() === '') {
|
||||
return $this->wrappedPlaceholderWithCursor();
|
||||
}
|
||||
|
||||
return $this->addCursor($this->wrappedValue(), $this->cursorPosition + $this->cursorOffset(), -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* The word-wrapped version of the typed value.
|
||||
*/
|
||||
public function wrappedValue(): string
|
||||
{
|
||||
return $this->mbWordwrap($this->value(), $this->width, PHP_EOL, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* The formatted lines.
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function lines(): array
|
||||
{
|
||||
return explode(PHP_EOL, $this->wrappedValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* The currently visible lines.
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function visible(): array
|
||||
{
|
||||
$this->adjustVisibleWindow();
|
||||
|
||||
$withCursor = $this->valueWithCursor();
|
||||
|
||||
return array_slice(explode(PHP_EOL, $withCursor), $this->firstVisible, $this->scroll, preserve_keys: true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the up key press.
|
||||
*/
|
||||
protected function handleUpKey(): void
|
||||
{
|
||||
if ($this->cursorPosition === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$lines = collect($this->lines());
|
||||
|
||||
// Line length + 1 for the newline character
|
||||
$lineLengths = $lines->map(fn ($line, $index) => mb_strwidth($line) + ($index === $lines->count() - 1 ? 0 : 1));
|
||||
|
||||
$currentLineIndex = $this->currentLineIndex();
|
||||
|
||||
if ($currentLineIndex === 0) {
|
||||
// They're already at the first line, jump them to the first position
|
||||
$this->cursorPosition = 0;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$currentLines = $lineLengths->slice(0, $currentLineIndex + 1);
|
||||
|
||||
$currentColumn = $currentLines->last() - ($currentLines->sum() - $this->cursorPosition);
|
||||
|
||||
$destinationLineLength = ($lineLengths->get($currentLineIndex - 1) ?? $currentLines->first()) - 1;
|
||||
|
||||
$newColumn = min($destinationLineLength, $currentColumn);
|
||||
|
||||
$fullLines = $currentLines->slice(0, -2);
|
||||
|
||||
$this->cursorPosition = $fullLines->sum() + $newColumn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the down key press.
|
||||
*/
|
||||
protected function handleDownKey(): void
|
||||
{
|
||||
$lines = collect($this->lines());
|
||||
|
||||
// Line length + 1 for the newline character
|
||||
$lineLengths = $lines->map(fn ($line, $index) => mb_strwidth($line) + ($index === $lines->count() - 1 ? 0 : 1));
|
||||
|
||||
$currentLineIndex = $this->currentLineIndex();
|
||||
|
||||
if ($currentLineIndex === $lines->count() - 1) {
|
||||
// They're already at the last line, jump them to the last position
|
||||
$this->cursorPosition = mb_strwidth($lines->implode(PHP_EOL));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Lines up to and including the current line
|
||||
$currentLines = $lineLengths->slice(0, $currentLineIndex + 1);
|
||||
|
||||
$currentColumn = $currentLines->last() - ($currentLines->sum() - $this->cursorPosition);
|
||||
|
||||
$destinationLineLength = $lineLengths->get($currentLineIndex + 1) ?? $currentLines->last();
|
||||
|
||||
if ($currentLineIndex + 1 !== $lines->count() - 1) {
|
||||
$destinationLineLength--;
|
||||
}
|
||||
|
||||
$newColumn = min(max(0, $destinationLineLength), $currentColumn);
|
||||
|
||||
$this->cursorPosition = $currentLines->sum() + $newColumn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust the visible window to ensure the cursor is always visible.
|
||||
*/
|
||||
protected function adjustVisibleWindow(): void
|
||||
{
|
||||
if (count($this->lines()) < $this->scroll) {
|
||||
return;
|
||||
}
|
||||
|
||||
$currentLineIndex = $this->currentLineIndex();
|
||||
|
||||
while ($this->firstVisible + $this->scroll <= $currentLineIndex) {
|
||||
$this->firstVisible++;
|
||||
}
|
||||
|
||||
if ($currentLineIndex === $this->firstVisible - 1) {
|
||||
$this->firstVisible = max(0, $this->firstVisible - 1);
|
||||
}
|
||||
|
||||
// Make sure there are always the scroll amount visible
|
||||
if ($this->firstVisible + $this->scroll > count($this->lines())) {
|
||||
$this->firstVisible = count($this->lines()) - $this->scroll;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the index of the current line that the cursor is on.
|
||||
*/
|
||||
protected function currentLineIndex(): int
|
||||
{
|
||||
$totalLineLength = 0;
|
||||
|
||||
return (int) collect($this->lines())->search(function ($line) use (&$totalLineLength) {
|
||||
$totalLineLength += mb_strwidth($line) + 1;
|
||||
|
||||
return $totalLineLength > $this->cursorPosition;
|
||||
}) ?: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the cursor offset considering wrapped words.
|
||||
*/
|
||||
protected function cursorOffset(): int
|
||||
{
|
||||
$cursorOffset = 0;
|
||||
|
||||
preg_match_all('/\S{'.$this->width.',}/u', $this->value(), $matches, PREG_OFFSET_CAPTURE);
|
||||
|
||||
foreach ($matches[0] as $match) {
|
||||
if ($this->cursorPosition + $cursorOffset >= $match[1] + mb_strwidth($match[0])) {
|
||||
$cursorOffset += (int) floor(mb_strwidth($match[0]) / $this->width);
|
||||
}
|
||||
}
|
||||
|
||||
return $cursorOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapped version of the placeholder with the virtual cursor.
|
||||
*/
|
||||
protected function wrappedPlaceholderWithCursor(): string
|
||||
{
|
||||
return implode(PHP_EOL, array_map(
|
||||
$this->dim(...),
|
||||
explode(PHP_EOL, $this->addCursor(
|
||||
$this->mbWordwrap($this->placeholder, $this->width, PHP_EOL, true),
|
||||
cursorPosition: 0,
|
||||
))
|
||||
));
|
||||
}
|
||||
}
|
11
vendor/laravel/prompts/src/Themes/Contracts/Scrolling.php
vendored
Normal file
11
vendor/laravel/prompts/src/Themes/Contracts/Scrolling.php
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts\Themes\Contracts;
|
||||
|
||||
interface Scrolling
|
||||
{
|
||||
/**
|
||||
* The number of lines to reserve outside of the scrollable area.
|
||||
*/
|
||||
public function reservedLines(): int;
|
||||
}
|
98
vendor/laravel/prompts/src/Themes/Default/Concerns/DrawsBoxes.php
vendored
Normal file
98
vendor/laravel/prompts/src/Themes/Default/Concerns/DrawsBoxes.php
vendored
Normal file
@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts\Themes\Default\Concerns;
|
||||
|
||||
use Laravel\Prompts\Prompt;
|
||||
|
||||
trait DrawsBoxes
|
||||
{
|
||||
protected int $minWidth = 60;
|
||||
|
||||
/**
|
||||
* Draw a box.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function box(
|
||||
string $title,
|
||||
string $body,
|
||||
string $footer = '',
|
||||
string $color = 'gray',
|
||||
string $info = '',
|
||||
): self {
|
||||
$this->minWidth = min($this->minWidth, Prompt::terminal()->cols() - 6);
|
||||
|
||||
$bodyLines = collect(explode(PHP_EOL, $body));
|
||||
$footerLines = collect(explode(PHP_EOL, $footer))->filter();
|
||||
$width = $this->longest(
|
||||
$bodyLines
|
||||
->merge($footerLines)
|
||||
->push($title)
|
||||
->toArray()
|
||||
);
|
||||
|
||||
$titleLength = mb_strwidth($this->stripEscapeSequences($title));
|
||||
$titleLabel = $titleLength > 0 ? " {$title} " : '';
|
||||
$topBorder = str_repeat('─', $width - $titleLength + ($titleLength > 0 ? 0 : 2));
|
||||
|
||||
$this->line("{$this->{$color}(' ┌')}{$titleLabel}{$this->{$color}($topBorder.'┐')}");
|
||||
|
||||
$bodyLines->each(function ($line) use ($width, $color) {
|
||||
$this->line("{$this->{$color}(' │')} {$this->pad($line, $width)} {$this->{$color}('│')}");
|
||||
});
|
||||
|
||||
if ($footerLines->isNotEmpty()) {
|
||||
$this->line($this->{$color}(' ├'.str_repeat('─', $width + 2).'┤'));
|
||||
|
||||
$footerLines->each(function ($line) use ($width, $color) {
|
||||
$this->line("{$this->{$color}(' │')} {$this->pad($line, $width)} {$this->{$color}('│')}");
|
||||
});
|
||||
}
|
||||
|
||||
$this->line($this->{$color}(' └'.str_repeat(
|
||||
'─', $info ? ($width - mb_strwidth($this->stripEscapeSequences($info))) : ($width + 2)
|
||||
).($info ? " {$info} " : '').'┘'));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the length of the longest line.
|
||||
*
|
||||
* @param array<string> $lines
|
||||
*/
|
||||
protected function longest(array $lines, int $padding = 0): int
|
||||
{
|
||||
return max(
|
||||
$this->minWidth,
|
||||
collect($lines)
|
||||
->map(fn ($line) => mb_strwidth($this->stripEscapeSequences($line)) + $padding)
|
||||
->max()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pad text ignoring ANSI escape sequences.
|
||||
*/
|
||||
protected function pad(string $text, int $length): string
|
||||
{
|
||||
$rightPadding = str_repeat(' ', max(0, $length - mb_strwidth($this->stripEscapeSequences($text))));
|
||||
|
||||
return "{$text}{$rightPadding}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip ANSI escape sequences from the given text.
|
||||
*/
|
||||
protected function stripEscapeSequences(string $text): string
|
||||
{
|
||||
// Strip ANSI escape sequences.
|
||||
$text = preg_replace("/\e[^m]*m/", '', $text);
|
||||
|
||||
// Strip Symfony named style tags.
|
||||
$text = preg_replace("/<(info|comment|question|error)>(.*?)<\/\\1>/", '$2', $text);
|
||||
|
||||
// Strip Symfony inline style tags.
|
||||
return preg_replace("/<(?:(?:[fb]g|options)=[a-z,;]+)+>(.*?)<\/>/i", '$1', $text);
|
||||
}
|
||||
}
|
55
vendor/laravel/prompts/src/Themes/Default/Concerns/DrawsScrollbars.php
vendored
Normal file
55
vendor/laravel/prompts/src/Themes/Default/Concerns/DrawsScrollbars.php
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts\Themes\Default\Concerns;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
trait DrawsScrollbars
|
||||
{
|
||||
/**
|
||||
* Render a scrollbar beside the visible items.
|
||||
*
|
||||
* @param \Illuminate\Support\Collection<int, string> $visible
|
||||
* @return \Illuminate\Support\Collection<int, string>
|
||||
*/
|
||||
protected function scrollbar(Collection $visible, int $firstVisible, int $height, int $total, int $width, string $color = 'cyan'): Collection
|
||||
{
|
||||
if ($height >= $total) {
|
||||
return $visible;
|
||||
}
|
||||
|
||||
$scrollPosition = $this->scrollPosition($firstVisible, $height, $total);
|
||||
|
||||
return $visible
|
||||
->values()
|
||||
->map(fn ($line) => $this->pad($line, $width))
|
||||
->map(fn ($line, $index) => match ($index) {
|
||||
$scrollPosition => preg_replace('/.$/', $this->{$color}('┃'), $line),
|
||||
default => preg_replace('/.$/', $this->gray('│'), $line),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the position where the scrollbar "handle" should be rendered.
|
||||
*/
|
||||
protected function scrollPosition(int $firstVisible, int $height, int $total): int
|
||||
{
|
||||
if ($firstVisible === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$maxPosition = $total - $height;
|
||||
|
||||
if ($firstVisible === $maxPosition) {
|
||||
return $height - 1;
|
||||
}
|
||||
|
||||
if ($height <= 2) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
$percent = $firstVisible / $maxPosition;
|
||||
|
||||
return (int) round($percent * ($height - 3)) + 1;
|
||||
}
|
||||
}
|
71
vendor/laravel/prompts/src/Themes/Default/ConfirmPromptRenderer.php
vendored
Normal file
71
vendor/laravel/prompts/src/Themes/Default/ConfirmPromptRenderer.php
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts\Themes\Default;
|
||||
|
||||
use Laravel\Prompts\ConfirmPrompt;
|
||||
|
||||
class ConfirmPromptRenderer extends Renderer
|
||||
{
|
||||
use Concerns\DrawsBoxes;
|
||||
|
||||
/**
|
||||
* Render the confirm prompt.
|
||||
*/
|
||||
public function __invoke(ConfirmPrompt $prompt): string
|
||||
{
|
||||
return match ($prompt->state) {
|
||||
'submit' => $this
|
||||
->box(
|
||||
$this->dim($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
|
||||
$this->truncate($prompt->label(), $prompt->terminal()->cols() - 6)
|
||||
),
|
||||
|
||||
'cancel' => $this
|
||||
->box(
|
||||
$this->truncate($prompt->label, $prompt->terminal()->cols() - 6),
|
||||
$this->renderOptions($prompt),
|
||||
color: 'red'
|
||||
)
|
||||
->error($prompt->cancelMessage),
|
||||
|
||||
'error' => $this
|
||||
->box(
|
||||
$this->truncate($prompt->label, $prompt->terminal()->cols() - 6),
|
||||
$this->renderOptions($prompt),
|
||||
color: 'yellow',
|
||||
)
|
||||
->warning($this->truncate($prompt->error, $prompt->terminal()->cols() - 5)),
|
||||
|
||||
default => $this
|
||||
->box(
|
||||
$this->cyan($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
|
||||
$this->renderOptions($prompt),
|
||||
)
|
||||
->when(
|
||||
$prompt->hint,
|
||||
fn () => $this->hint($prompt->hint),
|
||||
fn () => $this->newLine() // Space for errors
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the confirm prompt options.
|
||||
*/
|
||||
protected function renderOptions(ConfirmPrompt $prompt): string
|
||||
{
|
||||
$length = (int) floor(($prompt->terminal()->cols() - 14) / 2);
|
||||
$yes = $this->truncate($prompt->yes, $length);
|
||||
$no = $this->truncate($prompt->no, $length);
|
||||
|
||||
if ($prompt->state === 'cancel') {
|
||||
return $this->dim($prompt->confirmed
|
||||
? "● {$this->strikethrough($yes)} / ○ {$this->strikethrough($no)}"
|
||||
: "○ {$this->strikethrough($yes)} / ● {$this->strikethrough($no)}");
|
||||
}
|
||||
|
||||
return $prompt->confirmed
|
||||
? "{$this->green('●')} {$yes} {$this->dim('/ ○ '.$no)}"
|
||||
: "{$this->dim('○ '.$yes.' /')} {$this->green('●')} {$no}";
|
||||
}
|
||||
}
|
176
vendor/laravel/prompts/src/Themes/Default/MultiSearchPromptRenderer.php
vendored
Normal file
176
vendor/laravel/prompts/src/Themes/Default/MultiSearchPromptRenderer.php
vendored
Normal file
@ -0,0 +1,176 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts\Themes\Default;
|
||||
|
||||
use Laravel\Prompts\MultiSearchPrompt;
|
||||
use Laravel\Prompts\Themes\Contracts\Scrolling;
|
||||
|
||||
class MultiSearchPromptRenderer extends Renderer implements Scrolling
|
||||
{
|
||||
use Concerns\DrawsBoxes;
|
||||
use Concerns\DrawsScrollbars;
|
||||
|
||||
/**
|
||||
* Render the suggest prompt.
|
||||
*/
|
||||
public function __invoke(MultiSearchPrompt $prompt): string
|
||||
{
|
||||
$maxWidth = $prompt->terminal()->cols() - 6;
|
||||
|
||||
return match ($prompt->state) {
|
||||
'submit' => $this
|
||||
->box(
|
||||
$this->dim($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
|
||||
$this->renderSelectedOptions($prompt),
|
||||
),
|
||||
|
||||
'cancel' => $this
|
||||
->box(
|
||||
$this->dim($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
|
||||
$this->strikethrough($this->dim($this->truncate($prompt->searchValue() ?: $prompt->placeholder, $maxWidth))),
|
||||
color: 'red',
|
||||
)
|
||||
->error($prompt->cancelMessage),
|
||||
|
||||
'error' => $this
|
||||
->box(
|
||||
$this->truncate($prompt->label, $prompt->terminal()->cols() - 6),
|
||||
$prompt->valueWithCursor($maxWidth),
|
||||
$this->renderOptions($prompt),
|
||||
color: 'yellow',
|
||||
info: $this->getInfoText($prompt),
|
||||
)
|
||||
->warning($this->truncate($prompt->error, $prompt->terminal()->cols() - 5)),
|
||||
|
||||
'searching' => $this
|
||||
->box(
|
||||
$this->cyan($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
|
||||
$this->valueWithCursorAndSearchIcon($prompt, $maxWidth),
|
||||
$this->renderOptions($prompt),
|
||||
info: $this->getInfoText($prompt),
|
||||
)
|
||||
->hint($prompt->hint),
|
||||
|
||||
default => $this
|
||||
->box(
|
||||
$this->cyan($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
|
||||
$prompt->valueWithCursor($maxWidth),
|
||||
$this->renderOptions($prompt),
|
||||
info: $this->getInfoText($prompt),
|
||||
)
|
||||
->when(
|
||||
$prompt->hint,
|
||||
fn () => $this->hint($prompt->hint),
|
||||
fn () => $this->newLine() // Space for errors
|
||||
)
|
||||
->spaceForDropdown($prompt)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the value with the cursor and a search icon.
|
||||
*/
|
||||
protected function valueWithCursorAndSearchIcon(MultiSearchPrompt $prompt, int $maxWidth): string
|
||||
{
|
||||
return preg_replace(
|
||||
'/\s$/',
|
||||
$this->cyan('…'),
|
||||
$this->pad($prompt->valueWithCursor($maxWidth - 1).' ', min($this->longest($prompt->matches(), padding: 2), $maxWidth))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a spacer to prevent jumping when the suggestions are displayed.
|
||||
*/
|
||||
protected function spaceForDropdown(MultiSearchPrompt $prompt): self
|
||||
{
|
||||
if ($prompt->searchValue() !== '') {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->newLine(max(
|
||||
0,
|
||||
min($prompt->scroll, $prompt->terminal()->lines() - 7) - count($prompt->matches()),
|
||||
));
|
||||
|
||||
if ($prompt->matches() === []) {
|
||||
$this->newLine();
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the options.
|
||||
*/
|
||||
protected function renderOptions(MultiSearchPrompt $prompt): string
|
||||
{
|
||||
if ($prompt->searchValue() !== '' && empty($prompt->matches())) {
|
||||
return $this->gray(' '.($prompt->state === 'searching' ? 'Searching...' : 'No results.'));
|
||||
}
|
||||
|
||||
return $this->scrollbar(
|
||||
collect($prompt->visible())
|
||||
->map(fn ($label) => $this->truncate($label, $prompt->terminal()->cols() - 12))
|
||||
->map(function ($label, $key) use ($prompt) {
|
||||
$index = array_search($key, array_keys($prompt->matches()));
|
||||
$active = $index === $prompt->highlighted;
|
||||
$selected = $prompt->isList()
|
||||
? in_array($label, $prompt->value())
|
||||
: in_array($key, $prompt->value());
|
||||
|
||||
return match (true) {
|
||||
$active && $selected => "{$this->cyan('› ◼')} {$label} ",
|
||||
$active => "{$this->cyan('›')} ◻ {$label} ",
|
||||
$selected => " {$this->cyan('◼')} {$this->dim($label)} ",
|
||||
default => " {$this->dim('◻')} {$this->dim($label)} ",
|
||||
};
|
||||
}),
|
||||
$prompt->firstVisible,
|
||||
$prompt->scroll,
|
||||
count($prompt->matches()),
|
||||
min($this->longest($prompt->matches(), padding: 4), $prompt->terminal()->cols() - 6)
|
||||
)->implode(PHP_EOL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the selected options.
|
||||
*/
|
||||
protected function renderSelectedOptions(MultiSearchPrompt $prompt): string
|
||||
{
|
||||
if (count($prompt->labels()) === 0) {
|
||||
return $this->gray('None');
|
||||
}
|
||||
|
||||
return implode("\n", array_map(
|
||||
fn ($label) => $this->truncate($label, $prompt->terminal()->cols() - 6),
|
||||
$prompt->labels()
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the info text.
|
||||
*/
|
||||
protected function getInfoText(MultiSearchPrompt $prompt): string
|
||||
{
|
||||
$info = count($prompt->value()).' selected';
|
||||
|
||||
$hiddenCount = count($prompt->value()) - collect($prompt->matches())
|
||||
->filter(fn ($label, $key) => in_array($prompt->isList() ? $label : $key, $prompt->value()))
|
||||
->count();
|
||||
|
||||
if ($hiddenCount > 0) {
|
||||
$info .= " ($hiddenCount hidden)";
|
||||
}
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of lines to reserve outside of the scrollable area.
|
||||
*/
|
||||
public function reservedLines(): int
|
||||
{
|
||||
return 7;
|
||||
}
|
||||
}
|
121
vendor/laravel/prompts/src/Themes/Default/MultiSelectPromptRenderer.php
vendored
Normal file
121
vendor/laravel/prompts/src/Themes/Default/MultiSelectPromptRenderer.php
vendored
Normal file
@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts\Themes\Default;
|
||||
|
||||
use Laravel\Prompts\MultiSelectPrompt;
|
||||
use Laravel\Prompts\Themes\Contracts\Scrolling;
|
||||
|
||||
class MultiSelectPromptRenderer extends Renderer implements Scrolling
|
||||
{
|
||||
use Concerns\DrawsBoxes;
|
||||
use Concerns\DrawsScrollbars;
|
||||
|
||||
/**
|
||||
* Render the multiselect prompt.
|
||||
*/
|
||||
public function __invoke(MultiSelectPrompt $prompt): string
|
||||
{
|
||||
return match ($prompt->state) {
|
||||
'submit' => $this
|
||||
->box(
|
||||
$this->dim($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
|
||||
$this->renderSelectedOptions($prompt)
|
||||
),
|
||||
|
||||
'cancel' => $this
|
||||
->box(
|
||||
$this->truncate($prompt->label, $prompt->terminal()->cols() - 6),
|
||||
$this->renderOptions($prompt),
|
||||
color: 'red',
|
||||
)
|
||||
->error($prompt->cancelMessage),
|
||||
|
||||
'error' => $this
|
||||
->box(
|
||||
$this->truncate($prompt->label, $prompt->terminal()->cols() - 6),
|
||||
$this->renderOptions($prompt),
|
||||
color: 'yellow',
|
||||
info: count($prompt->options) > $prompt->scroll ? (count($prompt->value()).' selected') : '',
|
||||
)
|
||||
->warning($this->truncate($prompt->error, $prompt->terminal()->cols() - 5)),
|
||||
|
||||
default => $this
|
||||
->box(
|
||||
$this->cyan($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
|
||||
$this->renderOptions($prompt),
|
||||
info: count($prompt->options) > $prompt->scroll ? (count($prompt->value()).' selected') : '',
|
||||
)
|
||||
->when(
|
||||
$prompt->hint,
|
||||
fn () => $this->hint($prompt->hint),
|
||||
fn () => $this->newLine() // Space for errors
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the options.
|
||||
*/
|
||||
protected function renderOptions(MultiSelectPrompt $prompt): string
|
||||
{
|
||||
return $this->scrollbar(
|
||||
collect($prompt->visible())
|
||||
->map(fn ($label) => $this->truncate($label, $prompt->terminal()->cols() - 12))
|
||||
->map(function ($label, $key) use ($prompt) {
|
||||
$index = array_search($key, array_keys($prompt->options));
|
||||
$active = $index === $prompt->highlighted;
|
||||
if (array_is_list($prompt->options)) {
|
||||
$value = $prompt->options[$index];
|
||||
} else {
|
||||
$value = array_keys($prompt->options)[$index];
|
||||
}
|
||||
$selected = in_array($value, $prompt->value());
|
||||
|
||||
if ($prompt->state === 'cancel') {
|
||||
return $this->dim(match (true) {
|
||||
$active && $selected => "› ◼ {$this->strikethrough($label)} ",
|
||||
$active => "› ◻ {$this->strikethrough($label)} ",
|
||||
$selected => " ◼ {$this->strikethrough($label)} ",
|
||||
default => " ◻ {$this->strikethrough($label)} ",
|
||||
});
|
||||
}
|
||||
|
||||
return match (true) {
|
||||
$active && $selected => "{$this->cyan('› ◼')} {$label} ",
|
||||
$active => "{$this->cyan('›')} ◻ {$label} ",
|
||||
$selected => " {$this->cyan('◼')} {$this->dim($label)} ",
|
||||
default => " {$this->dim('◻')} {$this->dim($label)} ",
|
||||
};
|
||||
})
|
||||
->values(),
|
||||
$prompt->firstVisible,
|
||||
$prompt->scroll,
|
||||
count($prompt->options),
|
||||
min($this->longest($prompt->options, padding: 6), $prompt->terminal()->cols() - 6),
|
||||
$prompt->state === 'cancel' ? 'dim' : 'cyan'
|
||||
)->implode(PHP_EOL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the selected options.
|
||||
*/
|
||||
protected function renderSelectedOptions(MultiSelectPrompt $prompt): string
|
||||
{
|
||||
if (count($prompt->labels()) === 0) {
|
||||
return $this->gray('None');
|
||||
}
|
||||
|
||||
return implode("\n", array_map(
|
||||
fn ($label) => $this->truncate($label, $prompt->terminal()->cols() - 6),
|
||||
$prompt->labels()
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of lines to reserve outside of the scrollable area.
|
||||
*/
|
||||
public function reservedLines(): int
|
||||
{
|
||||
return 5;
|
||||
}
|
||||
}
|
56
vendor/laravel/prompts/src/Themes/Default/NoteRenderer.php
vendored
Normal file
56
vendor/laravel/prompts/src/Themes/Default/NoteRenderer.php
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts\Themes\Default;
|
||||
|
||||
use Laravel\Prompts\Note;
|
||||
|
||||
class NoteRenderer extends Renderer
|
||||
{
|
||||
/**
|
||||
* Render the note.
|
||||
*/
|
||||
public function __invoke(Note $note): string
|
||||
{
|
||||
$lines = collect(explode(PHP_EOL, $note->message));
|
||||
|
||||
switch ($note->type) {
|
||||
case 'intro':
|
||||
case 'outro':
|
||||
$lines = $lines->map(fn ($line) => " {$line} ");
|
||||
$longest = $lines->map(fn ($line) => strlen($line))->max();
|
||||
|
||||
$lines
|
||||
->each(function ($line) use ($longest) {
|
||||
$line = str_pad($line, $longest, ' ');
|
||||
$this->line(" {$this->bgCyan($this->black($line))}");
|
||||
});
|
||||
|
||||
return $this;
|
||||
|
||||
case 'warning':
|
||||
$lines->each(fn ($line) => $this->line($this->yellow(" {$line}")));
|
||||
|
||||
return $this;
|
||||
|
||||
case 'error':
|
||||
$lines->each(fn ($line) => $this->line($this->red(" {$line}")));
|
||||
|
||||
return $this;
|
||||
|
||||
case 'alert':
|
||||
$lines->each(fn ($line) => $this->line(" {$this->bgRed($this->white(" {$line} "))}"));
|
||||
|
||||
return $this;
|
||||
|
||||
case 'info':
|
||||
$lines->each(fn ($line) => $this->line($this->green(" {$line}")));
|
||||
|
||||
return $this;
|
||||
|
||||
default:
|
||||
$lines->each(fn ($line) => $this->line(" {$line}"));
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
}
|
53
vendor/laravel/prompts/src/Themes/Default/PasswordPromptRenderer.php
vendored
Normal file
53
vendor/laravel/prompts/src/Themes/Default/PasswordPromptRenderer.php
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts\Themes\Default;
|
||||
|
||||
use Laravel\Prompts\PasswordPrompt;
|
||||
|
||||
class PasswordPromptRenderer extends Renderer
|
||||
{
|
||||
use Concerns\DrawsBoxes;
|
||||
|
||||
/**
|
||||
* Render the password prompt.
|
||||
*/
|
||||
public function __invoke(PasswordPrompt $prompt): string
|
||||
{
|
||||
$maxWidth = $prompt->terminal()->cols() - 6;
|
||||
|
||||
return match ($prompt->state) {
|
||||
'submit' => $this
|
||||
->box(
|
||||
$this->dim($prompt->label),
|
||||
$this->truncate($prompt->masked(), $maxWidth),
|
||||
),
|
||||
|
||||
'cancel' => $this
|
||||
->box(
|
||||
$this->truncate($prompt->label, $prompt->terminal()->cols() - 6),
|
||||
$this->strikethrough($this->dim($this->truncate($prompt->masked() ?: $prompt->placeholder, $maxWidth))),
|
||||
color: 'red',
|
||||
)
|
||||
->error($prompt->cancelMessage),
|
||||
|
||||
'error' => $this
|
||||
->box(
|
||||
$this->dim($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
|
||||
$prompt->maskedWithCursor($maxWidth),
|
||||
color: 'yellow',
|
||||
)
|
||||
->warning($this->truncate($prompt->error, $prompt->terminal()->cols() - 5)),
|
||||
|
||||
default => $this
|
||||
->box(
|
||||
$this->cyan($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
|
||||
$prompt->maskedWithCursor($maxWidth),
|
||||
)
|
||||
->when(
|
||||
$prompt->hint,
|
||||
fn () => $this->hint($prompt->hint),
|
||||
fn () => $this->newLine() // Space for errors
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
25
vendor/laravel/prompts/src/Themes/Default/PausePromptRenderer.php
vendored
Normal file
25
vendor/laravel/prompts/src/Themes/Default/PausePromptRenderer.php
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts\Themes\Default;
|
||||
|
||||
use Laravel\Prompts\PausePrompt;
|
||||
|
||||
class PausePromptRenderer extends Renderer
|
||||
{
|
||||
use Concerns\DrawsBoxes;
|
||||
|
||||
/**
|
||||
* Render the pause prompt.
|
||||
*/
|
||||
public function __invoke(PausePrompt $prompt): string
|
||||
{
|
||||
match ($prompt->state) {
|
||||
'submit' => collect(explode(PHP_EOL, $prompt->message))
|
||||
->each(fn ($line) => $this->line($this->gray(" {$line}"))),
|
||||
default => collect(explode(PHP_EOL, $prompt->message))
|
||||
->each(fn ($line) => $this->line($this->green(" {$line}")))
|
||||
};
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
63
vendor/laravel/prompts/src/Themes/Default/ProgressRenderer.php
vendored
Normal file
63
vendor/laravel/prompts/src/Themes/Default/ProgressRenderer.php
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts\Themes\Default;
|
||||
|
||||
use Laravel\Prompts\Progress;
|
||||
|
||||
class ProgressRenderer extends Renderer
|
||||
{
|
||||
use Concerns\DrawsBoxes;
|
||||
|
||||
/**
|
||||
* The character to use for the progress bar.
|
||||
*/
|
||||
protected string $barCharacter = '█';
|
||||
|
||||
/**
|
||||
* Render the progress bar.
|
||||
*
|
||||
* @param Progress<int|iterable<mixed>> $progress
|
||||
*/
|
||||
public function __invoke(Progress $progress): string
|
||||
{
|
||||
$filled = str_repeat($this->barCharacter, (int) ceil($progress->percentage() * min($this->minWidth, $progress->terminal()->cols() - 6)));
|
||||
|
||||
return match ($progress->state) {
|
||||
'submit' => $this
|
||||
->box(
|
||||
$this->dim($this->truncate($progress->label, $progress->terminal()->cols() - 6)),
|
||||
$this->dim($filled),
|
||||
info: $progress->progress.'/'.$progress->total,
|
||||
),
|
||||
|
||||
'error' => $this
|
||||
->box(
|
||||
$this->truncate($progress->label, $progress->terminal()->cols() - 6),
|
||||
$this->dim($filled),
|
||||
color: 'red',
|
||||
info: $progress->progress.'/'.$progress->total,
|
||||
),
|
||||
|
||||
'cancel' => $this
|
||||
->box(
|
||||
$this->truncate($progress->label, $progress->terminal()->cols() - 6),
|
||||
$this->dim($filled),
|
||||
color: 'red',
|
||||
info: $progress->progress.'/'.$progress->total,
|
||||
)
|
||||
->error($progress->cancelMessage),
|
||||
|
||||
default => $this
|
||||
->box(
|
||||
$this->cyan($this->truncate($progress->label, $progress->terminal()->cols() - 6)),
|
||||
$this->dim($filled),
|
||||
info: $progress->progress.'/'.$progress->total,
|
||||
)
|
||||
->when(
|
||||
$progress->hint,
|
||||
fn () => $this->hint($progress->hint),
|
||||
fn () => $this->newLine() // Space for errors
|
||||
)
|
||||
};
|
||||
}
|
||||
}
|
102
vendor/laravel/prompts/src/Themes/Default/Renderer.php
vendored
Normal file
102
vendor/laravel/prompts/src/Themes/Default/Renderer.php
vendored
Normal file
@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts\Themes\Default;
|
||||
|
||||
use Laravel\Prompts\Concerns\Colors;
|
||||
use Laravel\Prompts\Concerns\Truncation;
|
||||
use Laravel\Prompts\Prompt;
|
||||
|
||||
abstract class Renderer
|
||||
{
|
||||
use Colors;
|
||||
use Truncation;
|
||||
|
||||
/**
|
||||
* The output to be rendered.
|
||||
*/
|
||||
protected string $output = '';
|
||||
|
||||
/**
|
||||
* Create a new renderer instance.
|
||||
*/
|
||||
public function __construct(protected Prompt $prompt)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a line of output.
|
||||
*/
|
||||
protected function line(string $message): self
|
||||
{
|
||||
$this->output .= $message.PHP_EOL;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a new line.
|
||||
*/
|
||||
protected function newLine(int $count = 1): self
|
||||
{
|
||||
$this->output .= str_repeat(PHP_EOL, $count);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a warning message.
|
||||
*/
|
||||
protected function warning(string $message): self
|
||||
{
|
||||
return $this->line($this->yellow(" ⚠ {$message}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Render an error message.
|
||||
*/
|
||||
protected function error(string $message): self
|
||||
{
|
||||
return $this->line($this->red(" ⚠ {$message}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Render an hint message.
|
||||
*/
|
||||
protected function hint(string $message): self
|
||||
{
|
||||
if ($message === '') {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$message = $this->truncate($message, $this->prompt->terminal()->cols() - 6);
|
||||
|
||||
return $this->line($this->gray(" {$message}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the callback if the given "value" is truthy.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function when(mixed $value, callable $callback, ?callable $default = null): self
|
||||
{
|
||||
if ($value) {
|
||||
$callback($this);
|
||||
} elseif ($default) {
|
||||
$default($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the output with a blank line above and below.
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return str_repeat(PHP_EOL, max(2 - $this->prompt->newLinesWritten(), 0))
|
||||
.$this->output
|
||||
.(in_array($this->prompt->state, ['submit', 'cancel']) ? PHP_EOL : '');
|
||||
}
|
||||
}
|
134
vendor/laravel/prompts/src/Themes/Default/SearchPromptRenderer.php
vendored
Normal file
134
vendor/laravel/prompts/src/Themes/Default/SearchPromptRenderer.php
vendored
Normal file
@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts\Themes\Default;
|
||||
|
||||
use Laravel\Prompts\SearchPrompt;
|
||||
use Laravel\Prompts\Themes\Contracts\Scrolling;
|
||||
|
||||
class SearchPromptRenderer extends Renderer implements Scrolling
|
||||
{
|
||||
use Concerns\DrawsBoxes;
|
||||
use Concerns\DrawsScrollbars;
|
||||
|
||||
/**
|
||||
* Render the suggest prompt.
|
||||
*/
|
||||
public function __invoke(SearchPrompt $prompt): string
|
||||
{
|
||||
$maxWidth = $prompt->terminal()->cols() - 6;
|
||||
|
||||
return match ($prompt->state) {
|
||||
'submit' => $this
|
||||
->box(
|
||||
$this->dim($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
|
||||
$this->truncate($prompt->label(), $maxWidth),
|
||||
),
|
||||
|
||||
'cancel' => $this
|
||||
->box(
|
||||
$this->dim($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
|
||||
$this->strikethrough($this->dim($this->truncate($prompt->searchValue() ?: $prompt->placeholder, $maxWidth))),
|
||||
color: 'red',
|
||||
)
|
||||
->error($prompt->cancelMessage),
|
||||
|
||||
'error' => $this
|
||||
->box(
|
||||
$this->truncate($prompt->label, $prompt->terminal()->cols() - 6),
|
||||
$prompt->valueWithCursor($maxWidth),
|
||||
$this->renderOptions($prompt),
|
||||
color: 'yellow',
|
||||
)
|
||||
->warning($this->truncate($prompt->error, $prompt->terminal()->cols() - 5)),
|
||||
|
||||
'searching' => $this
|
||||
->box(
|
||||
$this->cyan($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
|
||||
$this->valueWithCursorAndSearchIcon($prompt, $maxWidth),
|
||||
$this->renderOptions($prompt),
|
||||
)
|
||||
->hint($prompt->hint),
|
||||
|
||||
default => $this
|
||||
->box(
|
||||
$this->cyan($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
|
||||
$prompt->valueWithCursor($maxWidth),
|
||||
$this->renderOptions($prompt),
|
||||
)
|
||||
->when(
|
||||
$prompt->hint,
|
||||
fn () => $this->hint($prompt->hint),
|
||||
fn () => $this->newLine() // Space for errors
|
||||
)
|
||||
->spaceForDropdown($prompt)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the value with the cursor and a search icon.
|
||||
*/
|
||||
protected function valueWithCursorAndSearchIcon(SearchPrompt $prompt, int $maxWidth): string
|
||||
{
|
||||
return preg_replace(
|
||||
'/\s$/',
|
||||
$this->cyan('…'),
|
||||
$this->pad($prompt->valueWithCursor($maxWidth - 1).' ', min($this->longest($prompt->matches(), padding: 2), $maxWidth))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a spacer to prevent jumping when the suggestions are displayed.
|
||||
*/
|
||||
protected function spaceForDropdown(SearchPrompt $prompt): self
|
||||
{
|
||||
if ($prompt->searchValue() !== '') {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->newLine(max(
|
||||
0,
|
||||
min($prompt->scroll, $prompt->terminal()->lines() - 7) - count($prompt->matches()),
|
||||
));
|
||||
|
||||
if ($prompt->matches() === []) {
|
||||
$this->newLine();
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the options.
|
||||
*/
|
||||
protected function renderOptions(SearchPrompt $prompt): string
|
||||
{
|
||||
if ($prompt->searchValue() !== '' && empty($prompt->matches())) {
|
||||
return $this->gray(' '.($prompt->state === 'searching' ? 'Searching...' : 'No results.'));
|
||||
}
|
||||
|
||||
return $this->scrollbar(
|
||||
collect($prompt->visible())
|
||||
->map(fn ($label) => $this->truncate($label, $prompt->terminal()->cols() - 10))
|
||||
->map(function ($label, $key) use ($prompt) {
|
||||
$index = array_search($key, array_keys($prompt->matches()));
|
||||
|
||||
return $prompt->highlighted === $index
|
||||
? "{$this->cyan('›')} {$label} "
|
||||
: " {$this->dim($label)} ";
|
||||
})
|
||||
->values(),
|
||||
$prompt->firstVisible,
|
||||
$prompt->scroll,
|
||||
count($prompt->matches()),
|
||||
min($this->longest($prompt->matches(), padding: 4), $prompt->terminal()->cols() - 6)
|
||||
)->implode(PHP_EOL);
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of lines to reserve outside of the scrollable area.
|
||||
*/
|
||||
public function reservedLines(): int
|
||||
{
|
||||
return 7;
|
||||
}
|
||||
}
|
94
vendor/laravel/prompts/src/Themes/Default/SelectPromptRenderer.php
vendored
Normal file
94
vendor/laravel/prompts/src/Themes/Default/SelectPromptRenderer.php
vendored
Normal file
@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts\Themes\Default;
|
||||
|
||||
use Laravel\Prompts\SelectPrompt;
|
||||
use Laravel\Prompts\Themes\Contracts\Scrolling;
|
||||
|
||||
class SelectPromptRenderer extends Renderer implements Scrolling
|
||||
{
|
||||
use Concerns\DrawsBoxes;
|
||||
use Concerns\DrawsScrollbars;
|
||||
|
||||
/**
|
||||
* Render the select prompt.
|
||||
*/
|
||||
public function __invoke(SelectPrompt $prompt): string
|
||||
{
|
||||
$maxWidth = $prompt->terminal()->cols() - 6;
|
||||
|
||||
return match ($prompt->state) {
|
||||
'submit' => $this
|
||||
->box(
|
||||
$this->dim($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
|
||||
$this->truncate($prompt->label(), $maxWidth),
|
||||
),
|
||||
|
||||
'cancel' => $this
|
||||
->box(
|
||||
$this->truncate($prompt->label, $prompt->terminal()->cols() - 6),
|
||||
$this->renderOptions($prompt),
|
||||
color: 'red',
|
||||
)
|
||||
->error($prompt->cancelMessage),
|
||||
|
||||
'error' => $this
|
||||
->box(
|
||||
$this->truncate($prompt->label, $prompt->terminal()->cols() - 6),
|
||||
$this->renderOptions($prompt),
|
||||
color: 'yellow',
|
||||
)
|
||||
->warning($this->truncate($prompt->error, $prompt->terminal()->cols() - 5)),
|
||||
|
||||
default => $this
|
||||
->box(
|
||||
$this->cyan($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
|
||||
$this->renderOptions($prompt),
|
||||
)
|
||||
->when(
|
||||
$prompt->hint,
|
||||
fn () => $this->hint($prompt->hint),
|
||||
fn () => $this->newLine() // Space for errors
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the options.
|
||||
*/
|
||||
protected function renderOptions(SelectPrompt $prompt): string
|
||||
{
|
||||
return $this->scrollbar(
|
||||
collect($prompt->visible())
|
||||
->map(fn ($label) => $this->truncate($label, $prompt->terminal()->cols() - 12))
|
||||
->map(function ($label, $key) use ($prompt) {
|
||||
$index = array_search($key, array_keys($prompt->options));
|
||||
|
||||
if ($prompt->state === 'cancel') {
|
||||
return $this->dim($prompt->highlighted === $index
|
||||
? "› ● {$this->strikethrough($label)} "
|
||||
: " ○ {$this->strikethrough($label)} "
|
||||
);
|
||||
}
|
||||
|
||||
return $prompt->highlighted === $index
|
||||
? "{$this->cyan('›')} {$this->cyan('●')} {$label} "
|
||||
: " {$this->dim('○')} {$this->dim($label)} ";
|
||||
})
|
||||
->values(),
|
||||
$prompt->firstVisible,
|
||||
$prompt->scroll,
|
||||
count($prompt->options),
|
||||
min($this->longest($prompt->options, padding: 6), $prompt->terminal()->cols() - 6),
|
||||
$prompt->state === 'cancel' ? 'dim' : 'cyan'
|
||||
)->implode(PHP_EOL);
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of lines to reserve outside of the scrollable area.
|
||||
*/
|
||||
public function reservedLines(): int
|
||||
{
|
||||
return 5;
|
||||
}
|
||||
}
|
41
vendor/laravel/prompts/src/Themes/Default/SpinnerRenderer.php
vendored
Normal file
41
vendor/laravel/prompts/src/Themes/Default/SpinnerRenderer.php
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts\Themes\Default;
|
||||
|
||||
use Laravel\Prompts\Spinner;
|
||||
|
||||
class SpinnerRenderer extends Renderer
|
||||
{
|
||||
/**
|
||||
* The frames of the spinner.
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
protected array $frames = ['⠂', '⠒', '⠐', '⠰', '⠠', '⠤', '⠄', '⠆'];
|
||||
|
||||
/**
|
||||
* The frame to render when the spinner is static.
|
||||
*/
|
||||
protected string $staticFrame = '⠶';
|
||||
|
||||
/**
|
||||
* The interval between frames.
|
||||
*/
|
||||
protected int $interval = 75;
|
||||
|
||||
/**
|
||||
* Render the spinner.
|
||||
*/
|
||||
public function __invoke(Spinner $spinner): string
|
||||
{
|
||||
if ($spinner->static) {
|
||||
return $this->line(" {$this->cyan($this->staticFrame)} {$spinner->message}");
|
||||
}
|
||||
|
||||
$spinner->interval = $this->interval;
|
||||
|
||||
$frame = $this->frames[$spinner->count % count($this->frames)];
|
||||
|
||||
return $this->line(" {$this->cyan($frame)} {$spinner->message}");
|
||||
}
|
||||
}
|
122
vendor/laravel/prompts/src/Themes/Default/SuggestPromptRenderer.php
vendored
Normal file
122
vendor/laravel/prompts/src/Themes/Default/SuggestPromptRenderer.php
vendored
Normal file
@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts\Themes\Default;
|
||||
|
||||
use Laravel\Prompts\SuggestPrompt;
|
||||
use Laravel\Prompts\Themes\Contracts\Scrolling;
|
||||
|
||||
class SuggestPromptRenderer extends Renderer implements Scrolling
|
||||
{
|
||||
use Concerns\DrawsBoxes;
|
||||
use Concerns\DrawsScrollbars;
|
||||
|
||||
/**
|
||||
* Render the suggest prompt.
|
||||
*/
|
||||
public function __invoke(SuggestPrompt $prompt): string
|
||||
{
|
||||
$maxWidth = $prompt->terminal()->cols() - 6;
|
||||
|
||||
return match ($prompt->state) {
|
||||
'submit' => $this
|
||||
->box(
|
||||
$this->dim($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
|
||||
$this->truncate($prompt->value(), $maxWidth),
|
||||
),
|
||||
|
||||
'cancel' => $this
|
||||
->box(
|
||||
$this->dim($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
|
||||
$this->strikethrough($this->dim($this->truncate($prompt->value() ?: $prompt->placeholder, $maxWidth))),
|
||||
color: 'red',
|
||||
)
|
||||
->error($prompt->cancelMessage),
|
||||
|
||||
'error' => $this
|
||||
->box(
|
||||
$this->truncate($prompt->label, $prompt->terminal()->cols() - 6),
|
||||
$this->valueWithCursorAndArrow($prompt, $maxWidth),
|
||||
$this->renderOptions($prompt),
|
||||
color: 'yellow',
|
||||
)
|
||||
->warning($this->truncate($prompt->error, $prompt->terminal()->cols() - 5)),
|
||||
|
||||
default => $this
|
||||
->box(
|
||||
$this->cyan($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
|
||||
$this->valueWithCursorAndArrow($prompt, $maxWidth),
|
||||
$this->renderOptions($prompt),
|
||||
)
|
||||
->when(
|
||||
$prompt->hint,
|
||||
fn () => $this->hint($prompt->hint),
|
||||
fn () => $this->newLine() // Space for errors
|
||||
)
|
||||
->spaceForDropdown($prompt),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the value with the cursor and an arrow.
|
||||
*/
|
||||
protected function valueWithCursorAndArrow(SuggestPrompt $prompt, int $maxWidth): string
|
||||
{
|
||||
if ($prompt->highlighted !== null || $prompt->value() !== '' || count($prompt->matches()) === 0) {
|
||||
return $prompt->valueWithCursor($maxWidth);
|
||||
}
|
||||
|
||||
return preg_replace(
|
||||
'/\s$/',
|
||||
$this->cyan('⌄'),
|
||||
$this->pad($prompt->valueWithCursor($maxWidth - 1).' ', min($this->longest($prompt->matches(), padding: 2), $maxWidth))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a spacer to prevent jumping when the suggestions are displayed.
|
||||
*/
|
||||
protected function spaceForDropdown(SuggestPrompt $prompt): self
|
||||
{
|
||||
if ($prompt->value() === '' && $prompt->highlighted === null) {
|
||||
$this->newLine(min(
|
||||
count($prompt->matches()),
|
||||
$prompt->scroll,
|
||||
$prompt->terminal()->lines() - 7
|
||||
) + 1);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the options.
|
||||
*/
|
||||
protected function renderOptions(SuggestPrompt $prompt): string
|
||||
{
|
||||
if (empty($prompt->matches()) || ($prompt->value() === '' && $prompt->highlighted === null)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->scrollbar(
|
||||
collect($prompt->visible())
|
||||
->map(fn ($label) => $this->truncate($label, $prompt->terminal()->cols() - 10))
|
||||
->map(fn ($label, $key) => $prompt->highlighted === $key
|
||||
? "{$this->cyan('›')} {$label} "
|
||||
: " {$this->dim($label)} "
|
||||
),
|
||||
$prompt->firstVisible,
|
||||
$prompt->scroll,
|
||||
count($prompt->matches()),
|
||||
min($this->longest($prompt->matches(), padding: 4), $prompt->terminal()->cols() - 6),
|
||||
$prompt->state === 'cancel' ? 'dim' : 'cyan'
|
||||
)->implode(PHP_EOL);
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of lines to reserve outside of the scrollable area.
|
||||
*/
|
||||
public function reservedLines(): int
|
||||
{
|
||||
return 7;
|
||||
}
|
||||
}
|
42
vendor/laravel/prompts/src/Themes/Default/TableRenderer.php
vendored
Normal file
42
vendor/laravel/prompts/src/Themes/Default/TableRenderer.php
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts\Themes\Default;
|
||||
|
||||
use Laravel\Prompts\Output\BufferedConsoleOutput;
|
||||
use Laravel\Prompts\Table;
|
||||
use Symfony\Component\Console\Helper\Table as SymfonyTable;
|
||||
use Symfony\Component\Console\Helper\TableStyle;
|
||||
|
||||
class TableRenderer extends Renderer
|
||||
{
|
||||
/**
|
||||
* Render the table.
|
||||
*/
|
||||
public function __invoke(Table $table): string
|
||||
{
|
||||
$tableStyle = (new TableStyle())
|
||||
->setHorizontalBorderChars('─')
|
||||
->setVerticalBorderChars('│', '│')
|
||||
->setCellHeaderFormat($this->dim('<fg=default>%s</>'))
|
||||
->setCellRowFormat('<fg=default>%s</>');
|
||||
|
||||
if (empty($table->headers)) {
|
||||
$tableStyle->setCrossingChars('┼', '', '', '', '┤', '┘</>', '┴', '└', '├', '<fg=gray>┌', '┬', '┐');
|
||||
} else {
|
||||
$tableStyle->setCrossingChars('┼', '<fg=gray>┌', '┬', '┐', '┤', '┘</>', '┴', '└', '├');
|
||||
}
|
||||
|
||||
$buffered = new BufferedConsoleOutput();
|
||||
|
||||
(new SymfonyTable($buffered))
|
||||
->setHeaders($table->headers)
|
||||
->setRows($table->rows)
|
||||
->setStyle($tableStyle)
|
||||
->render();
|
||||
|
||||
collect(explode(PHP_EOL, trim($buffered->content(), PHP_EOL)))
|
||||
->each(fn ($line) => $this->line(' '.$line));
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
53
vendor/laravel/prompts/src/Themes/Default/TextPromptRenderer.php
vendored
Normal file
53
vendor/laravel/prompts/src/Themes/Default/TextPromptRenderer.php
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts\Themes\Default;
|
||||
|
||||
use Laravel\Prompts\TextPrompt;
|
||||
|
||||
class TextPromptRenderer extends Renderer
|
||||
{
|
||||
use Concerns\DrawsBoxes;
|
||||
|
||||
/**
|
||||
* Render the text prompt.
|
||||
*/
|
||||
public function __invoke(TextPrompt $prompt): string
|
||||
{
|
||||
$maxWidth = $prompt->terminal()->cols() - 6;
|
||||
|
||||
return match ($prompt->state) {
|
||||
'submit' => $this
|
||||
->box(
|
||||
$this->dim($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
|
||||
$this->truncate($prompt->value(), $maxWidth),
|
||||
),
|
||||
|
||||
'cancel' => $this
|
||||
->box(
|
||||
$this->truncate($prompt->label, $prompt->terminal()->cols() - 6),
|
||||
$this->strikethrough($this->dim($this->truncate($prompt->value() ?: $prompt->placeholder, $maxWidth))),
|
||||
color: 'red',
|
||||
)
|
||||
->error($prompt->cancelMessage),
|
||||
|
||||
'error' => $this
|
||||
->box(
|
||||
$this->truncate($prompt->label, $prompt->terminal()->cols() - 6),
|
||||
$prompt->valueWithCursor($maxWidth),
|
||||
color: 'yellow',
|
||||
)
|
||||
->warning($this->truncate($prompt->error, $prompt->terminal()->cols() - 5)),
|
||||
|
||||
default => $this
|
||||
->box(
|
||||
$this->cyan($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
|
||||
$prompt->valueWithCursor($maxWidth),
|
||||
)
|
||||
->when(
|
||||
$prompt->hint,
|
||||
fn () => $this->hint($prompt->hint),
|
||||
fn () => $this->newLine() // Space for errors
|
||||
)
|
||||
};
|
||||
}
|
||||
}
|
87
vendor/laravel/prompts/src/Themes/Default/TextareaPromptRenderer.php
vendored
Normal file
87
vendor/laravel/prompts/src/Themes/Default/TextareaPromptRenderer.php
vendored
Normal file
@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts\Themes\Default;
|
||||
|
||||
use Laravel\Prompts\TextareaPrompt;
|
||||
use Laravel\Prompts\Themes\Contracts\Scrolling;
|
||||
|
||||
class TextareaPromptRenderer extends Renderer implements Scrolling
|
||||
{
|
||||
use Concerns\DrawsBoxes;
|
||||
use Concerns\DrawsScrollbars;
|
||||
|
||||
/**
|
||||
* Render the textarea prompt.
|
||||
*/
|
||||
public function __invoke(TextareaPrompt $prompt): string
|
||||
{
|
||||
$prompt->width = $prompt->terminal()->cols() - 8;
|
||||
|
||||
return match ($prompt->state) {
|
||||
'submit' => $this
|
||||
->box(
|
||||
$this->dim($this->truncate($prompt->label, $prompt->width)),
|
||||
collect($prompt->lines())->implode(PHP_EOL),
|
||||
),
|
||||
|
||||
'cancel' => $this
|
||||
->box(
|
||||
$this->truncate($prompt->label, $prompt->width),
|
||||
collect($prompt->lines())->map(fn ($line) => $this->strikethrough($this->dim($line)))->implode(PHP_EOL),
|
||||
color: 'red',
|
||||
)
|
||||
->error($prompt->cancelMessage),
|
||||
|
||||
'error' => $this
|
||||
->box(
|
||||
$this->truncate($prompt->label, $prompt->width),
|
||||
$this->renderText($prompt),
|
||||
color: 'yellow',
|
||||
info: 'Ctrl+D to submit'
|
||||
)
|
||||
->warning($this->truncate($prompt->error, $prompt->terminal()->cols() - 5)),
|
||||
|
||||
default => $this
|
||||
->box(
|
||||
$this->cyan($this->truncate($prompt->label, $prompt->width)),
|
||||
$this->renderText($prompt),
|
||||
info: 'Ctrl+D to submit'
|
||||
)
|
||||
->when(
|
||||
$prompt->hint,
|
||||
fn () => $this->hint($prompt->hint),
|
||||
fn () => $this->newLine() // Space for errors
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the text in the prompt.
|
||||
*/
|
||||
protected function renderText(TextareaPrompt $prompt): string
|
||||
{
|
||||
$visible = collect($prompt->visible());
|
||||
|
||||
while ($visible->count() < $prompt->scroll) {
|
||||
$visible->push('');
|
||||
}
|
||||
|
||||
$longest = $this->longest($prompt->lines()) + 2;
|
||||
|
||||
return $this->scrollbar(
|
||||
$visible,
|
||||
$prompt->firstVisible,
|
||||
$prompt->scroll,
|
||||
count($prompt->lines()),
|
||||
min($longest, $prompt->width + 2),
|
||||
)->implode(PHP_EOL);
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of lines to reserve outside of the scrollable area.
|
||||
*/
|
||||
public function reservedLines(): int
|
||||
{
|
||||
return 5;
|
||||
}
|
||||
}
|
207
vendor/laravel/prompts/src/helpers.php
vendored
Normal file
207
vendor/laravel/prompts/src/helpers.php
vendored
Normal file
@ -0,0 +1,207 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Prompt the user for text input.
|
||||
*/
|
||||
function text(string $label, string $placeholder = '', string $default = '', bool|string $required = false, mixed $validate = null, string $hint = ''): string
|
||||
{
|
||||
return (new TextPrompt(...func_get_args()))->prompt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt the user for multiline text input.
|
||||
*/
|
||||
function textarea(string $label, string $placeholder = '', string $default = '', bool|string $required = false, ?Closure $validate = null, string $hint = '', int $rows = 5): string
|
||||
{
|
||||
return (new TextareaPrompt($label, $placeholder, $default, $required, $validate, $hint, $rows))->prompt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt the user for input, hiding the value.
|
||||
*/
|
||||
function password(string $label, string $placeholder = '', bool|string $required = false, mixed $validate = null, string $hint = ''): string
|
||||
{
|
||||
return (new PasswordPrompt(...func_get_args()))->prompt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt the user to select an option.
|
||||
*
|
||||
* @param array<int|string, string>|Collection<int|string, string> $options
|
||||
* @param true|string $required
|
||||
*/
|
||||
function select(string $label, array|Collection $options, int|string|null $default = null, int $scroll = 5, mixed $validate = null, string $hint = '', bool|string $required = true): int|string
|
||||
{
|
||||
return (new SelectPrompt(...func_get_args()))->prompt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt the user to select multiple options.
|
||||
*
|
||||
* @param array<int|string, string>|Collection<int|string, string> $options
|
||||
* @param array<int|string>|Collection<int, int|string> $default
|
||||
* @return array<int|string>
|
||||
*/
|
||||
function multiselect(string $label, array|Collection $options, array|Collection $default = [], int $scroll = 5, bool|string $required = false, mixed $validate = null, string $hint = 'Use the space bar to select options.'): array
|
||||
{
|
||||
return (new MultiSelectPrompt(...func_get_args()))->prompt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt the user to confirm an action.
|
||||
*/
|
||||
function confirm(string $label, bool $default = true, string $yes = 'Yes', string $no = 'No', bool|string $required = false, mixed $validate = null, string $hint = ''): bool
|
||||
{
|
||||
return (new ConfirmPrompt(...func_get_args()))->prompt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt the user to continue or cancel after pausing.
|
||||
*/
|
||||
function pause(string $message = 'Press enter to continue...'): bool
|
||||
{
|
||||
return (new PausePrompt(...func_get_args()))->prompt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt the user for text input with auto-completion.
|
||||
*
|
||||
* @param array<string>|Collection<int, string>|Closure(string): array<string> $options
|
||||
*/
|
||||
function suggest(string $label, array|Collection|Closure $options, string $placeholder = '', string $default = '', int $scroll = 5, bool|string $required = false, mixed $validate = null, string $hint = ''): string
|
||||
{
|
||||
return (new SuggestPrompt(...func_get_args()))->prompt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow the user to search for an option.
|
||||
*
|
||||
* @param Closure(string): array<int|string, string> $options
|
||||
* @param true|string $required
|
||||
*/
|
||||
function search(string $label, Closure $options, string $placeholder = '', int $scroll = 5, mixed $validate = null, string $hint = '', bool|string $required = true): int|string
|
||||
{
|
||||
return (new SearchPrompt(...func_get_args()))->prompt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow the user to search for multiple option.
|
||||
*
|
||||
* @param Closure(string): array<int|string, string> $options
|
||||
* @return array<int|string>
|
||||
*/
|
||||
function multisearch(string $label, Closure $options, string $placeholder = '', int $scroll = 5, bool|string $required = false, mixed $validate = null, string $hint = 'Use the space bar to select options.'): array
|
||||
{
|
||||
return (new MultiSearchPrompt(...func_get_args()))->prompt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a spinner while the given callback is executing.
|
||||
*
|
||||
* @template TReturn of mixed
|
||||
*
|
||||
* @param \Closure(): TReturn $callback
|
||||
* @return TReturn
|
||||
*/
|
||||
function spin(Closure $callback, string $message = ''): mixed
|
||||
{
|
||||
return (new Spinner($message))->spin($callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a note.
|
||||
*/
|
||||
function note(string $message, ?string $type = null): void
|
||||
{
|
||||
(new Note($message, $type))->display();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display an error.
|
||||
*/
|
||||
function error(string $message): void
|
||||
{
|
||||
(new Note($message, 'error'))->display();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a warning.
|
||||
*/
|
||||
function warning(string $message): void
|
||||
{
|
||||
(new Note($message, 'warning'))->display();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display an alert.
|
||||
*/
|
||||
function alert(string $message): void
|
||||
{
|
||||
(new Note($message, 'alert'))->display();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display an informational message.
|
||||
*/
|
||||
function info(string $message): void
|
||||
{
|
||||
(new Note($message, 'info'))->display();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display an introduction.
|
||||
*/
|
||||
function intro(string $message): void
|
||||
{
|
||||
(new Note($message, 'intro'))->display();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a closing message.
|
||||
*/
|
||||
function outro(string $message): void
|
||||
{
|
||||
(new Note($message, 'outro'))->display();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a table.
|
||||
*
|
||||
* @param array<int, string|array<int, string>>|Collection<int, string|array<int, string>> $headers
|
||||
* @param array<int, array<int, string>>|Collection<int, array<int, string>> $rows
|
||||
*/
|
||||
function table(array|Collection $headers = [], array|Collection|null $rows = null): void
|
||||
{
|
||||
(new Table($headers, $rows))->display();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a progress bar.
|
||||
*
|
||||
* @template TSteps of iterable<mixed>|int
|
||||
* @template TReturn
|
||||
*
|
||||
* @param TSteps $steps
|
||||
* @param ?Closure((TSteps is int ? int : value-of<TSteps>), Progress<TSteps>): TReturn $callback
|
||||
* @return ($callback is null ? Progress<TSteps> : array<TReturn>)
|
||||
*/
|
||||
function progress(string $label, iterable|int $steps, ?Closure $callback = null, string $hint = ''): array|Progress
|
||||
{
|
||||
$progress = new Progress($label, $steps, $hint);
|
||||
|
||||
if ($callback !== null) {
|
||||
return $progress->map($callback);
|
||||
}
|
||||
|
||||
return $progress;
|
||||
}
|
||||
|
||||
function form(): FormBuilder
|
||||
{
|
||||
return new FormBuilder();
|
||||
}
|
Reference in New Issue
Block a user