From c16a162f51694554982e643cf9ed4af0c5231726 Mon Sep 17 00:00:00 2001 From: Len Woodward Date: Tue, 11 Jun 2024 02:24:14 -0700 Subject: [PATCH 1/9] extract looping mechanism in prompt --- src/Prompt.php | 31 +++++++++++++++++++++++++++++-- src/Support/Nothing.php | 14 ++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 src/Support/Nothing.php diff --git a/src/Prompt.php b/src/Prompt.php index 560bc15..2730970 100644 --- a/src/Prompt.php +++ b/src/Prompt.php @@ -5,6 +5,7 @@ use Closure; use Laravel\Prompts\Exceptions\FormRevertedException; use Laravel\Prompts\Output\ConsoleOutput; +use Laravel\Prompts\Support\Nothing; use RuntimeException; use Symfony\Component\Console\Output\OutputInterface; use Throwable; @@ -127,7 +128,7 @@ public function prompt(): mixed $this->hideCursor(); $this->render(); - while (($key = static::terminal()->read()) !== null) { + $result = $this->runLoop(function (string $key): mixed { $continue = $this->handleKeyPress($key); $this->render(); @@ -147,12 +148,38 @@ public function prompt(): mixed return $this->transformedValue(); } - } + + // `null` is a valid return value for this loop + // so we'll return an instance of Nothing to + // indicate that the loop should continue. + return new Nothing; + }); + + return $result; } finally { $this->clearListeners(); } } + public function runLoop(callable $callable): mixed + { + while(($key = static::terminal()->read()) !== null) { + $result = $callable($key); + + if (! $this->is_nothing($result)) { + return $result; + } + } + } + + /** + * Check if the provided item is an instance of Nothing. + */ + public function is_nothing(mixed $item): bool + { + return is_object($item) && is_a($item, Nothing::class); + } + /** * Register a callback to be invoked when a user cancels a prompt. */ diff --git a/src/Support/Nothing.php b/src/Support/Nothing.php new file mode 100644 index 0000000..8601ba7 --- /dev/null +++ b/src/Support/Nothing.php @@ -0,0 +1,14 @@ + Date: Tue, 11 Jun 2024 02:26:47 -0700 Subject: [PATCH 2/9] extract keypress looping mechanism in FakesInputOutput --- src/Concerns/FakesInputOutput.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Concerns/FakesInputOutput.php b/src/Concerns/FakesInputOutput.php index 5bbf53f..cc99a3e 100644 --- a/src/Concerns/FakesInputOutput.php +++ b/src/Concerns/FakesInputOutput.php @@ -2,6 +2,7 @@ namespace Laravel\Prompts\Concerns; +use Closure; use Laravel\Prompts\Output\BufferedConsoleOutput; use Laravel\Prompts\Terminal; use PHPUnit\Framework\Assert; @@ -29,15 +30,22 @@ public static function fake(array $keys = []): void $mock->shouldReceive('lines')->byDefault()->andReturn(24); $mock->shouldReceive('initDimensions')->byDefault(); - foreach ($keys as $key) { + static::fakeKeyPresses($keys, function (string $key) use ($mock) { $mock->shouldReceive('read')->once()->andReturn($key); - } + }); static::$terminal = $mock; self::setOutput(new BufferedConsoleOutput); } + public static function fakeKeyPresses(array $keys, Closure $closure): void + { + foreach ($keys as $key) { + $closure($key); + } + } + /** * Assert that the output contains the given string. */ From 39b5a2a4a5f664ec7cb027c2781611644165f348 Mon Sep 17 00:00:00 2001 From: Len Woodward Date: Tue, 11 Jun 2024 03:07:44 -0700 Subject: [PATCH 3/9] add docblock with type --- src/Concerns/FakesInputOutput.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Concerns/FakesInputOutput.php b/src/Concerns/FakesInputOutput.php index cc99a3e..d7d9a48 100644 --- a/src/Concerns/FakesInputOutput.php +++ b/src/Concerns/FakesInputOutput.php @@ -39,6 +39,11 @@ public static function fake(array $keys = []): void self::setOutput(new BufferedConsoleOutput); } + /** + * Implementation of the looping mechanism for simulating key presses. + * + * @param array $keys + */ public static function fakeKeyPresses(array $keys, Closure $closure): void { foreach ($keys as $key) { From af7bd73cac6c73a5a422ca10f0f8ac6703d11202 Mon Sep 17 00:00:00 2001 From: Len Woodward Date: Sun, 8 Sep 2024 13:01:37 -0700 Subject: [PATCH 4/9] swap out `Nothing` class for proper `Result` sentinel class --- src/Prompt.php | 30 ++++++++++++------------------ src/Support/Nothing.php | 14 -------------- src/Support/Result.php | 22 ++++++++++++++++++++++ 3 files changed, 34 insertions(+), 32 deletions(-) delete mode 100644 src/Support/Nothing.php create mode 100644 src/Support/Result.php diff --git a/src/Prompt.php b/src/Prompt.php index 2730970..b525431 100644 --- a/src/Prompt.php +++ b/src/Prompt.php @@ -5,7 +5,7 @@ use Closure; use Laravel\Prompts\Exceptions\FormRevertedException; use Laravel\Prompts\Output\ConsoleOutput; -use Laravel\Prompts\Support\Nothing; +use Laravel\Prompts\Support\Result; use RuntimeException; use Symfony\Component\Console\Output\OutputInterface; use Throwable; @@ -128,7 +128,7 @@ public function prompt(): mixed $this->hideCursor(); $this->render(); - $result = $this->runLoop(function (string $key): mixed { + $result = $this->runLoop(function (string $key): ?Result { $continue = $this->handleKeyPress($key); $this->render(); @@ -136,7 +136,7 @@ public function prompt(): mixed if ($continue === false || $key === Key::CTRL_C) { if ($key === Key::CTRL_C) { if (isset(static::$cancelUsing)) { - return (static::$cancelUsing)(); + return Result::from((static::$cancelUsing)()); } else { static::terminal()->exit(); } @@ -146,13 +146,10 @@ public function prompt(): mixed throw new FormRevertedException; } - return $this->transformedValue(); + return Result::from($this->transformedValue()); } - // `null` is a valid return value for this loop - // so we'll return an instance of Nothing to - // indicate that the loop should continue. - return new Nothing; + return null; }); return $result; @@ -161,25 +158,22 @@ public function prompt(): mixed } } + /** + * Implementation of the prompt looping mechanism. + * + * @param callable(string $key):?Result $callable + */ public function runLoop(callable $callable): mixed { while(($key = static::terminal()->read()) !== null) { $result = $callable($key); - if (! $this->is_nothing($result)) { - return $result; + if ($result instanceof Result) { + return $result->value; } } } - /** - * Check if the provided item is an instance of Nothing. - */ - public function is_nothing(mixed $item): bool - { - return is_object($item) && is_a($item, Nothing::class); - } - /** * Register a callback to be invoked when a user cancels a prompt. */ diff --git a/src/Support/Nothing.php b/src/Support/Nothing.php deleted file mode 100644 index 8601ba7..0000000 --- a/src/Support/Nothing.php +++ /dev/null @@ -1,14 +0,0 @@ - Date: Sun, 8 Sep 2024 13:08:31 -0700 Subject: [PATCH 5/9] formatting --- src/Prompt.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Prompt.php b/src/Prompt.php index b525431..eb1e661 100644 --- a/src/Prompt.php +++ b/src/Prompt.php @@ -161,7 +161,7 @@ public function prompt(): mixed /** * Implementation of the prompt looping mechanism. * - * @param callable(string $key):?Result $callable + * @param callable(string $key): ?Result $callable */ public function runLoop(callable $callable): mixed { From ea2aaca29a5218940e7639a9d4d40c9144fbce8f Mon Sep 17 00:00:00 2001 From: Len Woodward Date: Sun, 8 Sep 2024 13:20:53 -0700 Subject: [PATCH 6/9] swap out Closure for callable, add types --- src/Concerns/FakesInputOutput.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Concerns/FakesInputOutput.php b/src/Concerns/FakesInputOutput.php index d7d9a48..0dd196e 100644 --- a/src/Concerns/FakesInputOutput.php +++ b/src/Concerns/FakesInputOutput.php @@ -2,7 +2,6 @@ namespace Laravel\Prompts\Concerns; -use Closure; use Laravel\Prompts\Output\BufferedConsoleOutput; use Laravel\Prompts\Terminal; use PHPUnit\Framework\Assert; @@ -30,7 +29,7 @@ public static function fake(array $keys = []): void $mock->shouldReceive('lines')->byDefault()->andReturn(24); $mock->shouldReceive('initDimensions')->byDefault(); - static::fakeKeyPresses($keys, function (string $key) use ($mock) { + static::fakeKeyPresses($keys, function (string $key) use ($mock): void { $mock->shouldReceive('read')->once()->andReturn($key); }); @@ -43,11 +42,12 @@ public static function fake(array $keys = []): void * Implementation of the looping mechanism for simulating key presses. * * @param array $keys + * @param callable(string $key): void $callable */ - public static function fakeKeyPresses(array $keys, Closure $closure): void + public static function fakeKeyPresses(array $keys, callable $callable): void { foreach ($keys as $key) { - $closure($key); + $callable($key); } } From 34ae1cfe2f32032ceb90f6b201c1e048a5f4aacc Mon Sep 17 00:00:00 2001 From: Len Woodward Date: Sun, 8 Sep 2024 13:45:08 -0700 Subject: [PATCH 7/9] add documentation --- src/Concerns/FakesInputOutput.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Concerns/FakesInputOutput.php b/src/Concerns/FakesInputOutput.php index 0dd196e..ebee895 100644 --- a/src/Concerns/FakesInputOutput.php +++ b/src/Concerns/FakesInputOutput.php @@ -40,6 +40,10 @@ public static function fake(array $keys = []): void /** * Implementation of the looping mechanism for simulating key presses. + * + * By ignoring the `$callable` parameter which contains the default logic + * for simulating fake key presses, we can use a custom implementation + * to emit key presses instead, allowing us to use different inputs. * * @param array $keys * @param callable(string $key): void $callable From 9ec2bfb35f8beac83b9dcbb5443ac16b28ef8ded Mon Sep 17 00:00:00 2001 From: Len Woodward Date: Sun, 8 Sep 2024 13:47:35 -0700 Subject: [PATCH 8/9] add comment --- src/Prompt.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Prompt.php b/src/Prompt.php index eb1e661..11a7960 100644 --- a/src/Prompt.php +++ b/src/Prompt.php @@ -149,6 +149,7 @@ public function prompt(): mixed return Result::from($this->transformedValue()); } + // Continue looping. return null; }); From 5e5023107822a24b6d4685f1e4facf2e02c6a6e0 Mon Sep 17 00:00:00 2001 From: Len Woodward Date: Tue, 10 Sep 2024 10:40:36 -0700 Subject: [PATCH 9/9] move readonly keyword to property for php8.1 support --- src/Support/Result.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Support/Result.php b/src/Support/Result.php index 7d0ce31..c08273b 100644 --- a/src/Support/Result.php +++ b/src/Support/Result.php @@ -9,10 +9,10 @@ * allow us to differentiate between a `null` return value and * a `null` return value that's intended to continue a loop. */ -final readonly class Result +final class Result { public function __construct( - public mixed $value, + public readonly mixed $value, ) {} public static function from(mixed $value): self