From 3926f9e50656c8e72a9943e73e7c5d5e66adf0fc Mon Sep 17 00:00:00 2001 From: Allan Mariucci Carvalho Date: Thu, 8 Feb 2024 14:17:55 -0300 Subject: [PATCH 1/4] Added pause prompt --- playground/pause.php | 38 +++++++++++ src/Concerns/Themes.php | 3 + src/PausePrompt.php | 46 +++++++++++++ src/Themes/Default/PausePromptRenderer.php | 75 ++++++++++++++++++++++ src/helpers.php | 8 +++ tests/Feature/PausePromptTest.php | 63 ++++++++++++++++++ 6 files changed, 233 insertions(+) create mode 100644 playground/pause.php create mode 100644 src/PausePrompt.php create mode 100644 src/Themes/Default/PausePromptRenderer.php create mode 100644 tests/Feature/PausePromptTest.php diff --git a/playground/pause.php b/playground/pause.php new file mode 100644 index 00000000..c899823d --- /dev/null +++ b/playground/pause.php @@ -0,0 +1,38 @@ + 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, diff --git a/src/PausePrompt.php b/src/PausePrompt.php new file mode 100644 index 00000000..7fa4e3cb --- /dev/null +++ b/src/PausePrompt.php @@ -0,0 +1,46 @@ +validate = null; + $this->on('key', fn ($key) => match ($key) { + default => $this->onKey($key), + }); + } + + /** + * Check key pressed to allow to continue case it's enter + */ + public function onKey(string $key): void + { + if ($key === Key::ENTER) { + $this->confirmed = true; + } + $this->submit(); + } + + /** + * Get the value of the prompt. + */ + public function value(): bool + { + return $this->confirmed; + } +} diff --git a/src/Themes/Default/PausePromptRenderer.php b/src/Themes/Default/PausePromptRenderer.php new file mode 100644 index 00000000..f386d247 --- /dev/null +++ b/src/Themes/Default/PausePromptRenderer.php @@ -0,0 +1,75 @@ +state) { + 'submit' => $this + ->box( + $this->dim($this->truncate($prompt->title, $prompt->terminal()->cols() - 6)), + $this->renderBody($prompt), + ), + + 'cancel' => $this + ->box( + $this->truncate($prompt->title, $prompt->terminal()->cols() - 6), + $this->renderBody($prompt), + color: 'red', + ) + ->error('Cancelled.'), + + 'error' => $this + ->box( + $this->truncate($prompt->title, $prompt->terminal()->cols() - 6), + $this->renderBody($prompt), + color: 'yellow', + info: $this->truncate($prompt->info, $prompt->terminal()->cols() - 15), + ) + ->warning($this->truncate($prompt->error, $prompt->terminal()->cols() - 5)), + + default => $this + ->box( + $this->cyan($this->truncate($prompt->title, $prompt->terminal()->cols() - 6)), + $this->renderBody($prompt), + info: $this->magenta($this->truncate($prompt->info, $prompt->terminal()->cols() - 15)), + ) + ->when( + $prompt->hint, + fn () => $this->hint($prompt->hint), + fn () => $this->newLine() // Space for errors + ), + }; + } + + /** + * Render the pause body. + */ + protected function renderBody(PausePrompt $prompt): string + { + $maxRowCols = $prompt->terminal()->cols() - 6; + $rows = []; + $currentRow = ''; + $wordsArray = explode(' ', $prompt->body); + while (count($wordsArray) > 0) { + $word = ' ' . $wordsArray[array_key_first($wordsArray)]; + if (mb_strlen($currentRow) + mb_strlen($word) <= $maxRowCols) { + $currentRow .= $word; + unset($wordsArray[array_key_first($wordsArray)]); + } else { + $rows[] = $currentRow; + $currentRow = ''; + } + } + return implode("\n", $rows); + } +} diff --git a/src/helpers.php b/src/helpers.php index d5e60b68..2b2a63b1 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -52,6 +52,14 @@ function confirm(string $label, bool $default = true, string $yes = 'Yes', strin return (new ConfirmPrompt(...func_get_args()))->prompt(); } +/** + * Prompt the user to continue or cancel after pause. + */ +function pause(string $body, string $title = '', string $info = 'ENTER to continue or Ctrl+C to cancel', bool|string $required = 'Please, press ENTER to continue or Ctrl+C to cancel.', string $hint = ''): bool +{ + return (new PausePrompt(...func_get_args()))->prompt(); +} + /** * Prompt the user for text input with auto-completion. * diff --git a/tests/Feature/PausePromptTest.php b/tests/Feature/PausePromptTest.php new file mode 100644 index 00000000..cc01c546 --- /dev/null +++ b/tests/Feature/PausePromptTest.php @@ -0,0 +1,63 @@ +toBeTrue(); +}); + +it('allows the title to be changed', function () { + Prompt::fake([Key::ENTER]); + + $result = pause( + 'teste', + 'Leia com atenção!', + ); + + expect($result)->toBeTrue(); + + Prompt::assertOutputContains('Leia com atenção!'); +}); + + +it('can fall back', function () { + Prompt::fallbackWhen(true); + + PausePrompt::fallbackUsing(function (PausePrompt $prompt) { + expect($prompt->body)->toBe('This is a fake content.') + ->and($prompt->title) + ->toBe('Warning...'); + + return true; + }); + + $result = pause('This is a fake content.', 'Warning...', false); + + expect($result)->toBeTrue(); +}); + +it('returns the default value when non-interactive', function () { + Prompt::interactive(false); + pause('This is a fake content.'); +})->throws(NonInteractiveValidationException::class, 'Please, press ENTER to continue or Ctrl+C to cancel.'); + +it('validates the default value when non-interactive', function () { + Prompt::interactive(false); + + pause( + 'This is a fake content.', + required: true, + ); +})->throws(NonInteractiveValidationException::class, 'Required.'); + From 9011b9986c28c5a3189bd8ef1de8bc2ff63c3a90 Mon Sep 17 00:00:00 2001 From: Allan Mariucci Carvalho Date: Mon, 19 Feb 2024 16:10:55 -0300 Subject: [PATCH 2/4] refactoring --- playground/pause.php | 25 +-------- src/PausePrompt.php | 19 +++---- src/Themes/Default/PausePromptRenderer.php | 63 +++------------------- src/helpers.php | 2 +- tests/Feature/PausePromptTest.php | 35 +++++------- 5 files changed, 27 insertions(+), 117 deletions(-) diff --git a/playground/pause.php b/playground/pause.php index c899823d..50ebdc43 100644 --- a/playground/pause.php +++ b/playground/pause.php @@ -7,31 +7,8 @@ Prompt::fallbackWhen(true); -$license = "Copyright (c) 2023 Laravel LCC -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."; - -$continued = pause( - $license, - 'MIT License', - hint: 'By pressing ENTER you accept the license!' -); +$continued = pause(); var_dump($continued); diff --git a/src/PausePrompt.php b/src/PausePrompt.php index 7fa4e3cb..77864ca1 100644 --- a/src/PausePrompt.php +++ b/src/PausePrompt.php @@ -5,24 +5,19 @@ class PausePrompt extends Prompt { /** - * Whether the prompt has been confirmed. + * Whether enter key has been pressed. */ - public bool $confirmed = false; + public bool $enterPressed = false; /** * Create a new PausePrompt instance. */ public function __construct( - public string $body, - public string $title = '', - public string $info = 'ENTER to continue or Ctrl+C to cancel', - public bool|string $required = 'Please, press ENTER to continue or Ctrl+C to cancel.', - public string $hint = '', + public string $message = 'Press enter to continue...', ) { + $this->required = $this->message; $this->validate = null; - $this->on('key', fn ($key) => match ($key) { - default => $this->onKey($key), - }); + $this->on('key', fn ($key) => $this->onKey($key)); } /** @@ -31,7 +26,7 @@ public function __construct( public function onKey(string $key): void { if ($key === Key::ENTER) { - $this->confirmed = true; + $this->enterPressed = true; } $this->submit(); } @@ -41,6 +36,6 @@ public function onKey(string $key): void */ public function value(): bool { - return $this->confirmed; + return $this->enterPressed; } } diff --git a/src/Themes/Default/PausePromptRenderer.php b/src/Themes/Default/PausePromptRenderer.php index f386d247..242be178 100644 --- a/src/Themes/Default/PausePromptRenderer.php +++ b/src/Themes/Default/PausePromptRenderer.php @@ -13,63 +13,12 @@ class PausePromptRenderer extends Renderer */ public function __invoke(PausePrompt $prompt): string { - return match ($prompt->state) { - 'submit' => $this - ->box( - $this->dim($this->truncate($prompt->title, $prompt->terminal()->cols() - 6)), - $this->renderBody($prompt), - ), - - 'cancel' => $this - ->box( - $this->truncate($prompt->title, $prompt->terminal()->cols() - 6), - $this->renderBody($prompt), - color: 'red', - ) - ->error('Cancelled.'), - - 'error' => $this - ->box( - $this->truncate($prompt->title, $prompt->terminal()->cols() - 6), - $this->renderBody($prompt), - color: 'yellow', - info: $this->truncate($prompt->info, $prompt->terminal()->cols() - 15), - ) - ->warning($this->truncate($prompt->error, $prompt->terminal()->cols() - 5)), - - default => $this - ->box( - $this->cyan($this->truncate($prompt->title, $prompt->terminal()->cols() - 6)), - $this->renderBody($prompt), - info: $this->magenta($this->truncate($prompt->info, $prompt->terminal()->cols() - 15)), - ) - ->when( - $prompt->hint, - fn () => $this->hint($prompt->hint), - fn () => $this->newLine() // Space for errors - ), + 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}"))) }; - } - - /** - * Render the pause body. - */ - protected function renderBody(PausePrompt $prompt): string - { - $maxRowCols = $prompt->terminal()->cols() - 6; - $rows = []; - $currentRow = ''; - $wordsArray = explode(' ', $prompt->body); - while (count($wordsArray) > 0) { - $word = ' ' . $wordsArray[array_key_first($wordsArray)]; - if (mb_strlen($currentRow) + mb_strlen($word) <= $maxRowCols) { - $currentRow .= $word; - unset($wordsArray[array_key_first($wordsArray)]); - } else { - $rows[] = $currentRow; - $currentRow = ''; - } - } - return implode("\n", $rows); + return $this; } } diff --git a/src/helpers.php b/src/helpers.php index 2b2a63b1..4203a9de 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -55,7 +55,7 @@ function confirm(string $label, bool $default = true, string $yes = 'Yes', strin /** * Prompt the user to continue or cancel after pause. */ -function pause(string $body, string $title = '', string $info = 'ENTER to continue or Ctrl+C to cancel', bool|string $required = 'Please, press ENTER to continue or Ctrl+C to cancel.', string $hint = ''): bool +function pause(string $message = 'Press enter to continue...'): bool { return (new PausePrompt(...func_get_args()))->prompt(); } diff --git a/tests/Feature/PausePromptTest.php b/tests/Feature/PausePromptTest.php index cc01c546..95fde41f 100644 --- a/tests/Feature/PausePromptTest.php +++ b/tests/Feature/PausePromptTest.php @@ -9,25 +9,24 @@ use function Laravel\Prompts\confirm; use function Laravel\Prompts\pause; -it('continues', function () { + +it('continues after enter', function () { Prompt::fake([Key::ENTER]); - $result = pause('This is a fake content.'); + $result = pause(); expect($result)->toBeTrue(); + Prompt::assertOutputContains('Press enter to continue...'); }); -it('allows the title to be changed', function () { +it('allows the message to be changed', function () { Prompt::fake([Key::ENTER]); - $result = pause( - 'teste', - 'Leia com atenção!', - ); + $result = pause('Read and then press enter...'); expect($result)->toBeTrue(); - Prompt::assertOutputContains('Leia com atenção!'); + Prompt::assertOutputContains('Read and then press enter...'); }); @@ -35,29 +34,19 @@ Prompt::fallbackWhen(true); PausePrompt::fallbackUsing(function (PausePrompt $prompt) { - expect($prompt->body)->toBe('This is a fake content.') - ->and($prompt->title) - ->toBe('Warning...'); + expect($prompt->message)->toBe('Press enter to continue...'); return true; }); - $result = pause('This is a fake content.', 'Warning...', false); + $result = pause(); expect($result)->toBeTrue(); }); -it('returns the default value when non-interactive', function () { - Prompt::interactive(false); - pause('This is a fake content.'); -})->throws(NonInteractiveValidationException::class, 'Please, press ENTER to continue or Ctrl+C to cancel.'); - -it('validates the default value when non-interactive', function () { +it('returns the message value when non-interactive', function () { Prompt::interactive(false); + pause('This is a fake message.'); +})->throws(NonInteractiveValidationException::class, 'This is a fake message.'); - pause( - 'This is a fake content.', - required: true, - ); -})->throws(NonInteractiveValidationException::class, 'Required.'); From cd9d4a1860118212b570f357a307247daa9765da Mon Sep 17 00:00:00 2001 From: Jess Archer Date: Wed, 21 Feb 2024 09:06:15 +1000 Subject: [PATCH 3/4] Ignore when non-interactive --- playground/pause.php | 6 ------ src/PausePrompt.php | 28 +++++++--------------------- tests/Feature/PausePromptTest.php | 16 ++++++++-------- 3 files changed, 15 insertions(+), 35 deletions(-) diff --git a/playground/pause.php b/playground/pause.php index 50ebdc43..d10c1c17 100644 --- a/playground/pause.php +++ b/playground/pause.php @@ -1,15 +1,9 @@ required = $this->message; + public function __construct(public string $message = 'Press enter to continue...') { + $this->required = false; $this->validate = null; - $this->on('key', fn ($key) => $this->onKey($key)); - } - /** - * Check key pressed to allow to continue case it's enter - */ - public function onKey(string $key): void - { - if ($key === Key::ENTER) { - $this->enterPressed = true; - } - $this->submit(); + $this->on('key', fn ($key) => match($key) { + Key::ENTER => $this->submit(), + default => null, + }); } /** @@ -36,6 +22,6 @@ public function onKey(string $key): void */ public function value(): bool { - return $this->enterPressed; + return static::$interactive; } } diff --git a/tests/Feature/PausePromptTest.php b/tests/Feature/PausePromptTest.php index 95fde41f..66b1bd7b 100644 --- a/tests/Feature/PausePromptTest.php +++ b/tests/Feature/PausePromptTest.php @@ -1,21 +1,18 @@ toBeTrue(); + Prompt::assertOutputContains('Press enter to continue...'); }); @@ -29,7 +26,6 @@ Prompt::assertOutputContains('Read and then press enter...'); }); - it('can fall back', function () { Prompt::fallbackWhen(true); @@ -44,9 +40,13 @@ expect($result)->toBeTrue(); }); -it('returns the message value when non-interactive', function () { +it('does not render when non-interactive', function () { + Prompt::fake(); Prompt::interactive(false); - pause('This is a fake message.'); -})->throws(NonInteractiveValidationException::class, 'This is a fake message.'); + $result = pause('This should not be rendered'); + expect($result)->toBeFalse(); + + Prompt::assertOutputDoesntContain('This should not be rendered'); +}); From 0cb494a44865c8140eec414325cd22ac0645b559 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 21 Feb 2024 13:24:55 -0600 Subject: [PATCH 4/4] Update helpers.php --- src/helpers.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers.php b/src/helpers.php index 4203a9de..8c05b784 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -53,7 +53,7 @@ function confirm(string $label, bool $default = true, string $yes = 'Yes', strin } /** - * Prompt the user to continue or cancel after pause. + * Prompt the user to continue or cancel after pausing. */ function pause(string $message = 'Press enter to continue...'): bool {