From 3b783cb23861fc9d3cbe465a0d8882d2086182f0 Mon Sep 17 00:00:00 2001 From: Michael Woodward Date: Wed, 2 May 2018 12:33:03 +0100 Subject: [PATCH 1/3] Add signal handling functionality --- src/UnixTerminal.php | 49 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/src/UnixTerminal.php b/src/UnixTerminal.php index 879b1a7..436874a 100644 --- a/src/UnixTerminal.php +++ b/src/UnixTerminal.php @@ -1,4 +1,4 @@ -getOriginalConfiguration(); - $this->getOriginalCanonicalMode(); + $this->initTerminal(); + $this->input = $input; $this->output = $output; } + private function initTerminal() : void + { + $this->getOriginalConfiguration(); + $this->getOriginalCanonicalMode(); + + register_tick_function('pcntl_signal_dispatch'); + + $this->onSignal(SIGWINCH, [$this, 'refreshDimensions']); + } + private function getOriginalCanonicalMode() : void { exec('stty -a', $output); $this->isCanonical = (strpos(implode("\n", $output), ' icanon') !== false); } + private function getOriginalConfiguration() : string + { + return $this->originalConfiguration ?: $this->originalConfiguration = exec('stty -g'); + } + public function getWidth() : int { return $this->width ?: $this->width = (int) exec('tput cols'); @@ -84,9 +104,28 @@ public function getColourSupport() : int return $this->colourSupport ?: $this->colourSupport = (int) exec('tput colors'); } - private function getOriginalConfiguration() : string + private function refreshDimensions() : void { - return $this->originalConfiguration ?: $this->originalConfiguration = exec('stty -g'); + $this->width = (int) exec('tput cols'); + $this->height = (int) exec('tput lines'); + } + + public function onSignal(int $signo, callable $handler) : void + { + if (!\is_callable($handler)) { + throw new \InvalidArgumentException('Handler for signal not callable'); + } + + pcntl_signal($signo, [$this, 'handleSignal']); + + $this->signalHandlers[$signo][] = $handler; + } + + public function handleSignal(int $signo) : void + { + foreach ($this->signalHandlers[$signo] as $signalHandler) { + $signalHandler(); + } } /** From 265645b7ad1cd297d32bc784f7f5974d5e13cd10 Mon Sep 17 00:00:00 2001 From: Aydin Hassan Date: Sun, 22 Dec 2019 18:19:05 +0100 Subject: [PATCH 2/3] Add non blocking stream impl --- src/IO/NonBlockingResourceInputStream.php | 35 +++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/IO/NonBlockingResourceInputStream.php diff --git a/src/IO/NonBlockingResourceInputStream.php b/src/IO/NonBlockingResourceInputStream.php new file mode 100644 index 0000000..ea46922 --- /dev/null +++ b/src/IO/NonBlockingResourceInputStream.php @@ -0,0 +1,35 @@ +innerStream = new ResourceInputStream($stream); + stream_set_blocking($stream, false); + } + + /** + * @inheritDoc + */ + public function read(int $numBytes, callable $callback): void + { + $this->innerStream->read($numBytes, $callback); + } + + /** + * @inheritDoc + */ + public function isInteractive(): bool + { + $this->innerStream->isInteractive(); + } +} From 30e5a50fc9edd57979b65627ac29e4b4dababdc1 Mon Sep 17 00:00:00 2001 From: Aydin Hassan Date: Sun, 22 Dec 2019 19:35:03 +0100 Subject: [PATCH 3/3] Implement non-blocking stream reader + upgrade and fix phpstan issues --- composer.json | 2 +- phpstan.neon | 2 ++ src/IO/BufferedOutput.php | 3 +++ src/IO/NonBlockingResourceInputStream.php | 9 ++++++--- src/IO/ResourceInputStream.php | 7 ++++++- src/IO/ResourceOutputStream.php | 7 ++++++- src/InputCharacter.php | 7 +++++-- src/NonCanonicalReader.php | 10 ++++++---- src/UnixTerminal.php | 10 ++++------ 9 files changed, 39 insertions(+), 18 deletions(-) create mode 100644 phpstan.neon diff --git a/composer.json b/composer.json index 0869497..afb132a 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "require-dev": { "phpunit/phpunit": "^7.1", "squizlabs/php_codesniffer": "^3.2", - "phpstan/phpstan": "^0.9.2" + "phpstan/phpstan": "^0.12" }, "autoload" : { "psr-4" : { diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..9d52fd9 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,2 @@ +parameters: + checkMissingIterableValueType: false diff --git a/src/IO/BufferedOutput.php b/src/IO/BufferedOutput.php index ad00297..de4e98d 100644 --- a/src/IO/BufferedOutput.php +++ b/src/IO/BufferedOutput.php @@ -7,6 +7,9 @@ */ class BufferedOutput implements OutputStream { + /** + * @var string + */ private $buffer = ''; public function write(string $buffer): void diff --git a/src/IO/NonBlockingResourceInputStream.php b/src/IO/NonBlockingResourceInputStream.php index ea46922..5da4a11 100644 --- a/src/IO/NonBlockingResourceInputStream.php +++ b/src/IO/NonBlockingResourceInputStream.php @@ -11,9 +11,12 @@ class NonBlockingResourceInputStream implements InputStream */ private $innerStream; - public function __construct($stream = STDIN) + /** + * @param resource $stream + */ + public function __construct($stream = null) { - $this->innerStream = new ResourceInputStream($stream); + $this->innerStream = new ResourceInputStream($stream ?? STDIN); stream_set_blocking($stream, false); } @@ -30,6 +33,6 @@ public function read(int $numBytes, callable $callback): void */ public function isInteractive(): bool { - $this->innerStream->isInteractive(); + return $this->innerStream->isInteractive(); } } diff --git a/src/IO/ResourceInputStream.php b/src/IO/ResourceInputStream.php index d72cbd0..b334c37 100644 --- a/src/IO/ResourceInputStream.php +++ b/src/IO/ResourceInputStream.php @@ -17,8 +17,13 @@ class ResourceInputStream implements InputStream */ private $stream; - public function __construct($stream = STDIN) + /** + * @param resource $stream + */ + public function __construct($stream = null) { + $stream = $stream ? $stream : STDIN; + if (!is_resource($stream) || get_resource_type($stream) !== 'stream') { throw new \InvalidArgumentException('Expected a valid stream'); } diff --git a/src/IO/ResourceOutputStream.php b/src/IO/ResourceOutputStream.php index 005ed22..c4f5fcf 100644 --- a/src/IO/ResourceOutputStream.php +++ b/src/IO/ResourceOutputStream.php @@ -17,8 +17,13 @@ class ResourceOutputStream implements OutputStream */ private $stream; - public function __construct($stream = STDOUT) + /** + * @param resource $stream + */ + public function __construct($stream = null) { + $stream = $stream ? $stream : STDOUT; + if (!is_resource($stream) || get_resource_type($stream) !== 'stream') { throw new \InvalidArgumentException('Expected a valid stream'); } diff --git a/src/InputCharacter.php b/src/InputCharacter.php index 4aec960..57d6f82 100644 --- a/src/InputCharacter.php +++ b/src/InputCharacter.php @@ -28,6 +28,9 @@ class InputCharacter public const TAB = 'TAB'; public const ESC = 'ESC'; + /** + * @var array + */ private static $controls = [ "\033[A" => self::UP, "\033[B" => self::DOWN, @@ -60,7 +63,7 @@ public function isHandledControl() : bool */ public function isControl() : bool { - return preg_match('/[\x00-\x1F\x7F]/', $this->data); + return (bool) preg_match('/[\x00-\x1F\x7F]/', $this->data); } /** @@ -128,6 +131,6 @@ public static function fromControlName(string $controlName) : self throw new \InvalidArgumentException(sprintf('Control "%s" does not exist', $controlName)); } - return new static(array_search($controlName, static::$controls, true)); + return new self(array_search($controlName, static::$controls, true)); } } diff --git a/src/NonCanonicalReader.php b/src/NonCanonicalReader.php index 6d26666..7838879 100644 --- a/src/NonCanonicalReader.php +++ b/src/NonCanonicalReader.php @@ -3,7 +3,7 @@ namespace PhpSchool\Terminal; /** - * This class takes a terminal and disabled canonical mode. It reads the input + * This class takes a terminal and disables canonical mode. It reads the input * and returns characters and control sequences as `InputCharacters` as soon * as they are read - character by character. * @@ -56,13 +56,15 @@ public function addControlMappings(array $mappings) : void /** * This should be ran with the terminal canonical mode disabled. - * - * @return InputCharacter */ - public function readCharacter() : InputCharacter + public function readCharacter() : ?InputCharacter { $char = $this->terminal->read(4); + if ($char === '') { + return null; + } + if (isset($this->mappings[$char])) { return InputCharacter::fromControlName($this->mappings[$char]); } diff --git a/src/UnixTerminal.php b/src/UnixTerminal.php index 436874a..b4dc47d 100644 --- a/src/UnixTerminal.php +++ b/src/UnixTerminal.php @@ -73,7 +73,7 @@ private function initTerminal() : void $this->getOriginalConfiguration(); $this->getOriginalCanonicalMode(); - register_tick_function('pcntl_signal_dispatch'); + pcntl_async_signals(true); $this->onSignal(SIGWINCH, [$this, 'refreshDimensions']); } @@ -112,12 +112,10 @@ private function refreshDimensions() : void public function onSignal(int $signo, callable $handler) : void { - if (!\is_callable($handler)) { - throw new \InvalidArgumentException('Handler for signal not callable'); + if (!isset($this->signalHandlers[$signo])) { + $this->signalHandlers[$signo] = []; + pcntl_signal($signo, [$this, 'handleSignal']); } - - pcntl_signal($signo, [$this, 'handleSignal']); - $this->signalHandlers[$signo][] = $handler; }