From c3b3a8be843f1f93a8e285460152ff58b3acd1a8 Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Sat, 21 Oct 2023 15:08:52 -0400 Subject: [PATCH 1/2] allow for a final message when rendering spinner --- playground/spin.php | 26 +++++++++++-- src/Prompt.php | 8 ++-- src/Spinner.php | 54 ++++++++++++++++++++++---- src/Themes/Default/SpinnerRenderer.php | 11 ++++++ src/helpers.php | 5 ++- tests/Feature/SpinnerTest.php | 18 +++++++++ 6 files changed, 106 insertions(+), 16 deletions(-) diff --git a/playground/spin.php b/playground/spin.php index 331c6563..0a978988 100644 --- a/playground/spin.php +++ b/playground/spin.php @@ -2,9 +2,9 @@ use function Laravel\Prompts\spin; -require __DIR__.'/../vendor/autoload.php'; +require __DIR__ . '/../vendor/autoload.php'; -$result = spin( +$result1 = spin( function () { sleep(4); @@ -13,8 +13,28 @@ function () { 'Installing dependencies...', ); +$result2 = spin( + function () { + sleep(4); + + return 'A-OK'; + }, + 'Checking system...', + 'System looks good!', +); + +$result3 = spin( + function () { + sleep(4); + + return '8.2'; + }, + 'Detecting PHP Version...', + fn ($result) => "PHP Version: {$result}", +); + echo PHP_EOL; -var_dump($result); +var_dump($result1, $result2, $result3); echo str_repeat(PHP_EOL, 6); diff --git a/src/Prompt.php b/src/Prompt.php index dfa0657e..15cbd38f 100644 --- a/src/Prompt.php +++ b/src/Prompt.php @@ -83,7 +83,7 @@ public function prompt(): mixed static::$interactive ??= stream_isatty(STDIN); - if (! static::$interactive) { + if (!static::$interactive) { return $this->default(); } @@ -264,7 +264,7 @@ private function diffLines(string $a, string $b): array $diff = []; for ($i = 0; $i < max(count($aLines), count($bLines)); $i++) { - if (! isset($aLines[$i]) || ! isset($bLines[$i]) || $aLines[$i] !== $bLines[$i]) { + if (!isset($aLines[$i]) || !isset($bLines[$i]) || $aLines[$i] !== $bLines[$i]) { $diff[] = $i; } } @@ -314,13 +314,13 @@ private function validate(mixed $value): void return; } - if (! isset($this->validate)) { + if (!isset($this->validate)) { return; } $error = ($this->validate)($value); - if (! is_string($error) && ! is_null($error)) { + if (!is_string($error) && !is_null($error)) { throw new \RuntimeException('The validator must return a string or null.'); } diff --git a/src/Spinner.php b/src/Spinner.php index fce1facd..fcd61144 100644 --- a/src/Spinner.php +++ b/src/Spinner.php @@ -27,12 +27,28 @@ class Spinner extends Prompt */ protected int $pid; + /** + * The final message to display. + * + * @var string + */ + public string $finalMessage = ''; + + /** + * A callback to generate the final message. + * + * @var string|\Closure(mixed): string + */ + protected $finalMessageHandler; + /** * Create a new Spinner instance. + * + * @param string|\Closure(mixed): string $finalMessageHandler */ - public function __construct(public string $message = '') + public function __construct(public string $message = '', string|Closure $finalMessageHandler = '') { - // + $this->finalMessageHandler = $finalMessageHandler; } /** @@ -47,7 +63,7 @@ public function spin(Closure $callback): mixed { $this->capturePreviousNewLines(); - if (! function_exists('pcntl_fork')) { + if (!function_exists('pcntl_fork')) { return $this->renderStatically($callback); } @@ -72,6 +88,8 @@ public function spin(Closure $callback): mixed } else { $result = $callback(); + $this->finalMessage = $this->getFinalMessage($result); + $this->resetTerminal($originalAsync); return $result; @@ -83,6 +101,22 @@ public function spin(Closure $callback): mixed } } + /** + * Get the final message to display. + */ + protected function getFinalMessage(mixed $result): string + { + if ($this->finalMessageHandler === '') { + return ''; + } + + if (is_callable($this->finalMessageHandler)) { + return ($this->finalMessageHandler)($result) ?? ''; + } + + return $this->finalMessageHandler; + } + /** * Reset the terminal. */ @@ -111,6 +145,8 @@ protected function renderStatically(Closure $callback): mixed $this->render(); $result = $callback(); + + $this->finalMessage = $this->getFinalMessage($result); } finally { $this->eraseRenderedLines(); } @@ -141,9 +177,13 @@ public function value(): bool */ protected function eraseRenderedLines(): void { - $lines = explode(PHP_EOL, $this->prevFrame); - $this->moveCursor(-999, -count($lines) + 1); - $this->eraseDown(); + if ($this->finalMessage !== '') { + $this->render(); + } else { + $lines = explode(PHP_EOL, $this->prevFrame); + $this->moveCursor(-999, -count($lines) + 1); + $this->eraseDown(); + } } /** @@ -151,7 +191,7 @@ protected function eraseRenderedLines(): void */ public function __destruct() { - if (! empty($this->pid)) { + if (!empty($this->pid)) { posix_kill($this->pid, SIGHUP); } diff --git a/src/Themes/Default/SpinnerRenderer.php b/src/Themes/Default/SpinnerRenderer.php index c68aef4f..99d23cd4 100644 --- a/src/Themes/Default/SpinnerRenderer.php +++ b/src/Themes/Default/SpinnerRenderer.php @@ -28,6 +28,17 @@ class SpinnerRenderer extends Renderer */ public function __invoke(Spinner $spinner): string { + if ($spinner->finalMessage !== '') { + $finalMessage = wordwrap($spinner->finalMessage, $spinner->terminal()->cols() - 6); + + collect(explode(PHP_EOL, $finalMessage))->each(fn ($line) => $this->line(' ' . $line)); + + // Avoid partial line indicator on re-render + $this->line(''); + + return $this; + } + if ($spinner->static) { return $this->line(" {$this->cyan($this->staticFrame)} {$spinner->message}"); } diff --git a/src/helpers.php b/src/helpers.php index 178cb22f..99231252 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -90,11 +90,12 @@ function multisearch(string $label, Closure $options, string $placeholder = '', * @template TReturn of mixed * * @param \Closure(): TReturn $callback + * @param string|\Closure(TReturn): string $finalMessage * @return TReturn */ -function spin(Closure $callback, string $message = ''): mixed +function spin(Closure $callback, string $message = '', string|Closure $finalMessage = ''): mixed { - return (new Spinner($message))->spin($callback); + return (new Spinner($message, $finalMessage))->spin($callback); } /** diff --git a/tests/Feature/SpinnerTest.php b/tests/Feature/SpinnerTest.php index 92078d64..b897b0db 100644 --- a/tests/Feature/SpinnerTest.php +++ b/tests/Feature/SpinnerTest.php @@ -17,3 +17,21 @@ Prompt::assertOutputContains('Running...'); }); + +it('renders a spinner and displays a final message', function ($finalMessageHandler, $expectedMessage) { + Prompt::fake(); + + $result = spin(function () { + usleep(1000); + + return 'result!'; + }, 'Running...', $finalMessageHandler); + + expect($result)->toBe('result!'); + + Prompt::assertOutputContains('Running...'); + Prompt::assertOutputContains($expectedMessage); +})->with([ + 'string' => ['All done!', 'All done!'], + 'closure' => [fn ($result) => "All done: {$result}", 'All done: result!'], +]); From 816cf8f03b6b55ae10ba32216042e4ece3e759d2 Mon Sep 17 00:00:00 2001 From: joetannenbaum Date: Sat, 21 Oct 2023 19:09:21 +0000 Subject: [PATCH 2/2] Fix code styling --- playground/spin.php | 2 +- src/Prompt.php | 8 ++++---- src/Spinner.php | 6 ++---- src/Themes/Default/SpinnerRenderer.php | 2 +- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/playground/spin.php b/playground/spin.php index 0a978988..fdc4cb6e 100644 --- a/playground/spin.php +++ b/playground/spin.php @@ -2,7 +2,7 @@ use function Laravel\Prompts\spin; -require __DIR__ . '/../vendor/autoload.php'; +require __DIR__.'/../vendor/autoload.php'; $result1 = spin( function () { diff --git a/src/Prompt.php b/src/Prompt.php index 15cbd38f..dfa0657e 100644 --- a/src/Prompt.php +++ b/src/Prompt.php @@ -83,7 +83,7 @@ public function prompt(): mixed static::$interactive ??= stream_isatty(STDIN); - if (!static::$interactive) { + if (! static::$interactive) { return $this->default(); } @@ -264,7 +264,7 @@ private function diffLines(string $a, string $b): array $diff = []; for ($i = 0; $i < max(count($aLines), count($bLines)); $i++) { - if (!isset($aLines[$i]) || !isset($bLines[$i]) || $aLines[$i] !== $bLines[$i]) { + if (! isset($aLines[$i]) || ! isset($bLines[$i]) || $aLines[$i] !== $bLines[$i]) { $diff[] = $i; } } @@ -314,13 +314,13 @@ private function validate(mixed $value): void return; } - if (!isset($this->validate)) { + if (! isset($this->validate)) { return; } $error = ($this->validate)($value); - if (!is_string($error) && !is_null($error)) { + if (! is_string($error) && ! is_null($error)) { throw new \RuntimeException('The validator must return a string or null.'); } diff --git a/src/Spinner.php b/src/Spinner.php index fcd61144..06f59294 100644 --- a/src/Spinner.php +++ b/src/Spinner.php @@ -29,8 +29,6 @@ class Spinner extends Prompt /** * The final message to display. - * - * @var string */ public string $finalMessage = ''; @@ -63,7 +61,7 @@ public function spin(Closure $callback): mixed { $this->capturePreviousNewLines(); - if (!function_exists('pcntl_fork')) { + if (! function_exists('pcntl_fork')) { return $this->renderStatically($callback); } @@ -191,7 +189,7 @@ protected function eraseRenderedLines(): void */ public function __destruct() { - if (!empty($this->pid)) { + if (! empty($this->pid)) { posix_kill($this->pid, SIGHUP); } diff --git a/src/Themes/Default/SpinnerRenderer.php b/src/Themes/Default/SpinnerRenderer.php index 99d23cd4..b5256257 100644 --- a/src/Themes/Default/SpinnerRenderer.php +++ b/src/Themes/Default/SpinnerRenderer.php @@ -31,7 +31,7 @@ public function __invoke(Spinner $spinner): string if ($spinner->finalMessage !== '') { $finalMessage = wordwrap($spinner->finalMessage, $spinner->terminal()->cols() - 6); - collect(explode(PHP_EOL, $finalMessage))->each(fn ($line) => $this->line(' ' . $line)); + collect(explode(PHP_EOL, $finalMessage))->each(fn ($line) => $this->line(' '.$line)); // Avoid partial line indicator on re-render $this->line('');