diff --git a/src/ncc/ThirdParty/php-school/terminal/Exception/NotInteractiveTerminal.php b/src/ncc/ThirdParty/php-school/terminal/Exception/NotInteractiveTerminal.php new file mode 100644 index 0000000..764cbf1 --- /dev/null +++ b/src/ncc/ThirdParty/php-school/terminal/Exception/NotInteractiveTerminal.php @@ -0,0 +1,19 @@ + + */ +class NotInteractiveTerminal extends \RuntimeException +{ + public static function inputNotInteractive() : self + { + return new self('Input stream is not interactive (non TTY)'); + } + + public static function outputNotInteractive() : self + { + return new self('Output stream is not interactive (non TTY)'); + } +} diff --git a/src/ncc/ThirdParty/php-school/terminal/IO/BufferedOutput.php b/src/ncc/ThirdParty/php-school/terminal/IO/BufferedOutput.php new file mode 100644 index 0000000..ad00297 --- /dev/null +++ b/src/ncc/ThirdParty/php-school/terminal/IO/BufferedOutput.php @@ -0,0 +1,40 @@ + + */ +class BufferedOutput implements OutputStream +{ + private $buffer = ''; + + public function write(string $buffer): void + { + $this->buffer .= $buffer; + } + + public function fetch(bool $clean = true) : string + { + $buffer = $this->buffer; + + if ($clean) { + $this->buffer = ''; + } + + return $buffer; + } + + public function __toString() : string + { + return $this->fetch(); + } + + /** + * Whether the stream is connected to an interactive terminal + */ + public function isInteractive() : bool + { + return false; + } +} diff --git a/src/ncc/ThirdParty/php-school/terminal/IO/InputStream.php b/src/ncc/ThirdParty/php-school/terminal/IO/InputStream.php new file mode 100644 index 0000000..8fcd82a --- /dev/null +++ b/src/ncc/ThirdParty/php-school/terminal/IO/InputStream.php @@ -0,0 +1,20 @@ + + */ +interface InputStream +{ + /** + * Callback should be called with the number of bytes requested + * when ready. + */ + public function read(int $numBytes, callable $callback) : void; + + /** + * Whether the stream is connected to an interactive terminal + */ + public function isInteractive() : bool; +} diff --git a/src/ncc/ThirdParty/php-school/terminal/IO/OutputStream.php b/src/ncc/ThirdParty/php-school/terminal/IO/OutputStream.php new file mode 100644 index 0000000..9314cd5 --- /dev/null +++ b/src/ncc/ThirdParty/php-school/terminal/IO/OutputStream.php @@ -0,0 +1,19 @@ + + */ +interface OutputStream +{ + /** + * Write the buffer to the stream + */ + public function write(string $buffer) : void; + + /** + * Whether the stream is connected to an interactive terminal + */ + public function isInteractive() : bool; +} diff --git a/src/ncc/ThirdParty/php-school/terminal/IO/ResourceInputStream.php b/src/ncc/ThirdParty/php-school/terminal/IO/ResourceInputStream.php new file mode 100644 index 0000000..d72cbd0 --- /dev/null +++ b/src/ncc/ThirdParty/php-school/terminal/IO/ResourceInputStream.php @@ -0,0 +1,47 @@ + + */ +class ResourceInputStream implements InputStream +{ + /** + * @var resource + */ + private $stream; + + public function __construct($stream = STDIN) + { + if (!is_resource($stream) || get_resource_type($stream) !== 'stream') { + throw new \InvalidArgumentException('Expected a valid stream'); + } + + $meta = stream_get_meta_data($stream); + if (strpos($meta['mode'], 'r') === false && strpos($meta['mode'], '+') === false) { + throw new \InvalidArgumentException('Expected a readable stream'); + } + + $this->stream = $stream; + } + + public function read(int $numBytes, callable $callback) : void + { + $buffer = fread($this->stream, $numBytes); + $callback($buffer); + } + + /** + * Whether the stream is connected to an interactive terminal + */ + public function isInteractive() : bool + { + return posix_isatty($this->stream); + } +} diff --git a/src/ncc/ThirdParty/php-school/terminal/IO/ResourceOutputStream.php b/src/ncc/ThirdParty/php-school/terminal/IO/ResourceOutputStream.php new file mode 100644 index 0000000..005ed22 --- /dev/null +++ b/src/ncc/ThirdParty/php-school/terminal/IO/ResourceOutputStream.php @@ -0,0 +1,46 @@ + + */ +class ResourceOutputStream implements OutputStream +{ + /** + * @var resource + */ + private $stream; + + public function __construct($stream = STDOUT) + { + if (!is_resource($stream) || get_resource_type($stream) !== 'stream') { + throw new \InvalidArgumentException('Expected a valid stream'); + } + + $meta = stream_get_meta_data($stream); + if (strpos($meta['mode'], 'r') !== false && strpos($meta['mode'], '+') === false) { + throw new \InvalidArgumentException('Expected a writable stream'); + } + + $this->stream = $stream; + } + + public function write(string $buffer): void + { + fwrite($this->stream, $buffer); + } + + /** + * Whether the stream is connected to an interactive terminal + */ + public function isInteractive() : bool + { + return posix_isatty($this->stream); + } +} diff --git a/src/ncc/ThirdParty/php-school/terminal/InputCharacter.php b/src/ncc/ThirdParty/php-school/terminal/InputCharacter.php new file mode 100644 index 0000000..7829512 --- /dev/null +++ b/src/ncc/ThirdParty/php-school/terminal/InputCharacter.php @@ -0,0 +1,137 @@ + + */ +class InputCharacter +{ + /** + * @var string + */ + private $data; + + public const UP = 'UP'; + public const DOWN = 'DOWN'; + public const RIGHT = 'RIGHT'; + public const LEFT = 'LEFT'; + public const CTRLA = 'CTRLA'; + public const CTRLB = 'CTRLB'; + public const CTRLE = 'CTRLE'; + public const CTRLF = 'CTRLF'; + public const BACKSPACE = 'BACKSPACE'; + public const CTRLW = 'CTRLW'; + public const ENTER = 'ENTER'; + public const TAB = 'TAB'; + public const ESC = 'ESC'; + + private static $controls = [ + "\033[A" => self::UP, + "\033[B" => self::DOWN, + "\033[C" => self::RIGHT, + "\033[D" => self::LEFT, + "\033OA" => self::UP, + "\033OB" => self::DOWN, + "\033OC" => self::RIGHT, + "\033OD" => self::LEFT, + "\001" => self::CTRLA, + "\002" => self::CTRLB, + "\005" => self::CTRLE, + "\006" => self::CTRLF, + "\010" => self::BACKSPACE, + "\177" => self::BACKSPACE, + "\027" => self::CTRLW, + "\n" => self::ENTER, + "\t" => self::TAB, + "\e" => self::ESC, + ]; + + public function __construct(string $data) + { + $this->data = $data; + } + + public function isHandledControl() : bool + { + return isset(static::$controls[$this->data]); + } + + /** + * Is this character a control sequence? + */ + public function isControl() : bool + { + return preg_match('/[\x00-\x1F\x7F]/', $this->data); + } + + /** + * Is this character a normal character? + */ + public function isNotControl() : bool + { + return ! $this->isControl(); + } + + /** + * Get the raw character or control sequence + */ + public function get() : string + { + return $this->data; + } + + /** + * Get the actual control name that this sequence represents. + * One of the class constants. Eg. self::UP. + * + * Throws an exception if the character is not actually a control sequence + */ + public function getControl() : string + { + if (!isset(static::$controls[$this->data])) { + throw new \RuntimeException(sprintf('Character "%s" is not a control', $this->data)); + } + + return static::$controls[$this->data]; + } + + /** + * Get the raw character or control sequence + */ + public function __toString() : string + { + return $this->get(); + } + + /** + * Does the given control name exist? eg self::UP. + */ + public static function controlExists(string $controlName) : bool + { + return in_array($controlName, static::$controls, true); + } + + /** + * Get all of the available control names + */ + public static function getControls() : array + { + return array_values(array_unique(static::$controls)); + } + + /** + * Create a instance from a given control name. Throws an exception if the + * control name does not exist. + */ + public static function fromControlName(string $controlName) : self + { + if (!static::controlExists($controlName)) { + throw new \InvalidArgumentException(sprintf('Control "%s" does not exist', $controlName)); + } + + return new static(array_search($controlName, static::$controls, true)); + } +} diff --git a/src/ncc/ThirdParty/php-school/terminal/NonCanonicalReader.php b/src/ncc/ThirdParty/php-school/terminal/NonCanonicalReader.php new file mode 100644 index 0000000..6d26666 --- /dev/null +++ b/src/ncc/ThirdParty/php-school/terminal/NonCanonicalReader.php @@ -0,0 +1,79 @@ + + */ +class NonCanonicalReader +{ + /** + * @var Terminal + */ + private $terminal; + + /** + * @var bool + */ + private $wasCanonicalModeEnabled; + + /** + * Map of characters to controls. + * Eg map 'w' to the up control. + * + * @var array + */ + private $mappings = []; + + public function __construct(Terminal $terminal) + { + $this->terminal = $terminal; + $this->wasCanonicalModeEnabled = $terminal->isCanonicalMode(); + $this->terminal->disableCanonicalMode(); + } + + public function addControlMapping(string $character, string $mapToControl) : void + { + if (!InputCharacter::controlExists($mapToControl)) { + throw new \InvalidArgumentException(sprintf('Control "%s" does not exist', $mapToControl)); + } + + $this->mappings[$character] = $mapToControl; + } + + public function addControlMappings(array $mappings) : void + { + foreach ($mappings as $character => $mapToControl) { + $this->addControlMapping($character, $mapToControl); + } + } + + /** + * This should be ran with the terminal canonical mode disabled. + * + * @return InputCharacter + */ + public function readCharacter() : InputCharacter + { + $char = $this->terminal->read(4); + + if (isset($this->mappings[$char])) { + return InputCharacter::fromControlName($this->mappings[$char]); + } + + return new InputCharacter($char); + } + + public function __destruct() + { + if ($this->wasCanonicalModeEnabled) { + $this->terminal->enableCanonicalMode(); + } + } +} diff --git a/src/ncc/ThirdParty/php-school/terminal/README.md b/src/ncc/ThirdParty/php-school/terminal/README.md new file mode 100644 index 0000000..c96b02a --- /dev/null +++ b/src/ncc/ThirdParty/php-school/terminal/README.md @@ -0,0 +1,32 @@ +
+ Small utility to help provide a simple, consise API for terminal interaction +
+ + + +--- + +## Install + +```bash +composer require php-school/terminal +``` + +## TODO + +- [ ] Docs diff --git a/src/ncc/ThirdParty/php-school/terminal/Terminal.php b/src/ncc/ThirdParty/php-school/terminal/Terminal.php new file mode 100644 index 0000000..96d1603 --- /dev/null +++ b/src/ncc/ThirdParty/php-school/terminal/Terminal.php @@ -0,0 +1,133 @@ + + * @author Aydin Hassan