Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature signals #12

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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" : {
Expand Down
2 changes: 2 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
parameters:
checkMissingIterableValueType: false
3 changes: 3 additions & 0 deletions src/IO/BufferedOutput.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
*/
class BufferedOutput implements OutputStream
{
/**
* @var string
*/
private $buffer = '';

public function write(string $buffer): void
Expand Down
38 changes: 38 additions & 0 deletions src/IO/NonBlockingResourceInputStream.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace PhpSchool\Terminal\IO;

class NonBlockingResourceInputStream implements InputStream
{
/**
* @var InputStream
*/
private $innerStream;

/**
* @param resource $stream
*/
public function __construct($stream = null)
{
$this->innerStream = new ResourceInputStream($stream ?? STDIN);
stream_set_blocking($stream, false);
}

/**
* @inheritDoc
*/
public function read(int $numBytes, callable $callback): void
{
$this->innerStream->read($numBytes, $callback);
}

/**
* @inheritDoc
*/
public function isInteractive(): bool
{
return $this->innerStream->isInteractive();
}
}
7 changes: 6 additions & 1 deletion src/IO/ResourceInputStream.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
Expand Down
7 changes: 6 additions & 1 deletion src/IO/ResourceOutputStream.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
Expand Down
7 changes: 5 additions & 2 deletions src/InputCharacter.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
}

/**
Expand Down Expand Up @@ -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));
}
}
10 changes: 6 additions & 4 deletions src/NonCanonicalReader.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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]);
}
Expand Down
47 changes: 42 additions & 5 deletions src/UnixTerminal.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?php
<?php declare(ticks=1);

namespace PhpSchool\Terminal;

Expand Down Expand Up @@ -45,6 +45,11 @@ class UnixTerminal implements Terminal
*/
private $originalConfiguration;

/**
* @var array
*/
private $signalHandlers = [];

/**
* @var InputStream
*/
Expand All @@ -57,18 +62,33 @@ class UnixTerminal implements Terminal

public function __construct(InputStream $input, OutputStream $output)
{
$this->getOriginalConfiguration();
$this->getOriginalCanonicalMode();
$this->initTerminal();

$this->input = $input;
$this->output = $output;
}

private function initTerminal() : void
{
$this->getOriginalConfiguration();
$this->getOriginalCanonicalMode();

pcntl_async_signals(true);

$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');
Expand All @@ -84,9 +104,26 @@ 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 (!isset($this->signalHandlers[$signo])) {
$this->signalHandlers[$signo] = [];
pcntl_signal($signo, [$this, 'handleSignal']);
}
$this->signalHandlers[$signo][] = $handler;
}

public function handleSignal(int $signo) : void
{
foreach ($this->signalHandlers[$signo] as $signalHandler) {
$signalHandler();
}
}

/**
Expand Down