Skip to content

Commit c3a87b9

Browse files
authored
Merge pull request #1 from php-school/rewrite
Rewrite
2 parents dd19551 + 90e8de2 commit c3a87b9

22 files changed

+1378
-240
lines changed

.travis.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
language: php
2+
3+
php:
4+
- 7.1
5+
- 7.2
6+
7+
install:
8+
- composer self-update
9+
- composer install
10+
11+
before_script:
12+
- mkdir -p build/logs
13+
14+
script:
15+
- ./vendor/bin/phpunit --coverage-clover ./build/logs/clover.xml
16+
- composer cs
17+
- composer static
18+
19+
after_script:
20+
- bash <(curl -s https://codecov.io/bash)
21+
- wget https://scrutinizer-ci.com/ocular.phar
22+
- php ocular.phar code-coverage:upload --format=php-clover ./build/logs/clover.xml

composer.json

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@
1313
"email": "[email protected]"
1414
}
1515
],
16-
"require-dev": {
17-
"phpunit/phpunit": "^6.0",
18-
"squizlabs/php_codesniffer": "^3.0"
19-
},
2016
"require": {
21-
"php" : ">=7.0",
22-
"ext-posix": "*",
23-
"myclabs/php-enum": "^1.5"
17+
"php" : ">=7.1",
18+
"ext-posix": "*"
19+
},
20+
"require-dev": {
21+
"phpunit/phpunit": "^7.1",
22+
"squizlabs/php_codesniffer": "^3.2",
23+
"phpstan/phpstan": "^0.9.2"
2424
},
2525
"autoload" : {
2626
"psr-4" : {
@@ -34,6 +34,9 @@
3434
"cs" : [
3535
"phpcs src --standard=PSR2",
3636
"phpcs test --standard=PSR2"
37+
],
38+
"static" : [
39+
"phpstan analyse src --level=7"
3740
]
3841
}
3942
}

phpunit.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<phpunit colors="true">
4+
<testsuite name="Terminal Test Suite">
5+
<directory>./test</directory>
6+
</testsuite>
7+
<filter>
8+
<whitelist addUncoveredFilesFromWhitelist="true">
9+
<directory suffix=".php">./src</directory>
10+
</whitelist>
11+
</filter>
12+
</phpunit>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace PhpSchool\Terminal\Exception;
4+
5+
/**
6+
* @author Aydin Hassan <[email protected]>
7+
*/
8+
class NotInteractiveTerminal extends \RuntimeException
9+
{
10+
public static function inputNotInteractive() : self
11+
{
12+
return new self('Input stream is not interactive (non TTY)');
13+
}
14+
15+
public static function outputNotInteractive() : self
16+
{
17+
return new self('Output stream is not interactive (non TTY)');
18+
}
19+
}

src/IO/BufferedOutput.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
namespace PhpSchool\Terminal\IO;
4+
5+
/**
6+
* @author Aydin Hassan <[email protected]>
7+
*/
8+
class BufferedOutput implements OutputStream
9+
{
10+
private $buffer = '';
11+
12+
public function write(string $buffer): void
13+
{
14+
$this->buffer .= $buffer;
15+
}
16+
17+
public function fetch(bool $clean = true) : string
18+
{
19+
$buffer = $this->buffer;
20+
21+
if ($clean) {
22+
$this->buffer = '';
23+
}
24+
25+
return $buffer;
26+
}
27+
28+
public function __toString() : string
29+
{
30+
return $this->fetch();
31+
}
32+
33+
/**
34+
* Whether the stream is connected to an interactive terminal
35+
*/
36+
public function isInteractive() : bool
37+
{
38+
return false;
39+
}
40+
}

src/IO/InputStream.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace PhpSchool\Terminal\IO;
4+
5+
/**
6+
* @author Aydin Hassan <[email protected]>
7+
*/
8+
interface InputStream
9+
{
10+
/**
11+
* Callback should be called with the number of bytes requested
12+
* when ready.
13+
*/
14+
public function read(int $numBytes, callable $callback) : void;
15+
16+
/**
17+
* Whether the stream is connected to an interactive terminal
18+
*/
19+
public function isInteractive() : bool;
20+
}

src/IO/OutputStream.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace PhpSchool\Terminal\IO;
4+
5+
/**
6+
* @author Aydin Hassan <[email protected]>
7+
*/
8+
interface OutputStream
9+
{
10+
/**
11+
* Write the buffer to the stream
12+
*/
13+
public function write(string $buffer) : void;
14+
15+
/**
16+
* Whether the stream is connected to an interactive terminal
17+
*/
18+
public function isInteractive() : bool;
19+
}

src/IO/ResourceInputStream.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
namespace PhpSchool\Terminal\IO;
4+
5+
use function is_resource;
6+
use function get_resource_type;
7+
use function stream_get_meta_data;
8+
use function strpos;
9+
10+
/**
11+
* @author Aydin Hassan <[email protected]>
12+
*/
13+
class ResourceInputStream implements InputStream
14+
{
15+
/**
16+
* @var resource
17+
*/
18+
private $stream;
19+
20+
public function __construct($stream = STDIN)
21+
{
22+
if (!is_resource($stream) || get_resource_type($stream) !== 'stream') {
23+
throw new \InvalidArgumentException('Expected a valid stream');
24+
}
25+
26+
$meta = stream_get_meta_data($stream);
27+
if (strpos($meta['mode'], 'r') === false && strpos($meta['mode'], '+') === false) {
28+
throw new \InvalidArgumentException('Expected a readable stream');
29+
}
30+
31+
$this->stream = $stream;
32+
}
33+
34+
public function read(int $numBytes, callable $callback) : void
35+
{
36+
$buffer = fread($this->stream, $numBytes);
37+
$callback($buffer);
38+
}
39+
40+
/**
41+
* Whether the stream is connected to an interactive terminal
42+
*/
43+
public function isInteractive() : bool
44+
{
45+
return posix_isatty($this->stream);
46+
}
47+
}

src/IO/ResourceOutputStream.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
namespace PhpSchool\Terminal\IO;
4+
5+
use function is_resource;
6+
use function get_resource_type;
7+
use function stream_get_meta_data;
8+
use function strpos;
9+
10+
/**
11+
* @author Aydin Hassan <[email protected]>
12+
*/
13+
class ResourceOutputStream implements OutputStream
14+
{
15+
/**
16+
* @var resource
17+
*/
18+
private $stream;
19+
20+
public function __construct($stream = STDOUT)
21+
{
22+
if (!is_resource($stream) || get_resource_type($stream) !== 'stream') {
23+
throw new \InvalidArgumentException('Expected a valid stream');
24+
}
25+
26+
$meta = stream_get_meta_data($stream);
27+
if (strpos($meta['mode'], 'r') !== false && strpos($meta['mode'], '+') === false) {
28+
throw new \InvalidArgumentException('Expected a writable stream');
29+
}
30+
31+
$this->stream = $stream;
32+
}
33+
34+
public function write(string $buffer): void
35+
{
36+
fwrite($this->stream, $buffer);
37+
}
38+
39+
/**
40+
* Whether the stream is connected to an interactive terminal
41+
*/
42+
public function isInteractive() : bool
43+
{
44+
return posix_isatty($this->stream);
45+
}
46+
}

src/InputCharacter.php

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
<?php
2+
3+
namespace PhpSchool\Terminal;
4+
5+
use function in_array;
6+
7+
/**
8+
* @author Aydin Hassan <[email protected]>
9+
*/
10+
class InputCharacter
11+
{
12+
/**
13+
* @var string
14+
*/
15+
private $data;
16+
17+
public const UP = 'UP';
18+
public const DOWN = 'DOWN';
19+
public const RIGHT = 'RIGHT';
20+
public const LEFT = 'LEFT';
21+
public const CTRLA = 'CTRLA';
22+
public const CTRLB = 'CTRLB';
23+
public const CTRLE = 'CTRLE';
24+
public const CTRLF = 'CTRLF';
25+
public const BACKSPACE = 'BACKSPACE';
26+
public const CTRLW = 'CTRLW';
27+
public const ENTER = 'ENTER';
28+
public const TAB = 'TAB';
29+
30+
private static $controls = [
31+
"\033[A" => self::UP,
32+
"\033[B" => self::DOWN,
33+
"\033[C" => self::RIGHT,
34+
"\033[D" => self::LEFT,
35+
"\001" => self::CTRLA,
36+
"\002" => self::CTRLB,
37+
"\005" => self::CTRLE,
38+
"\006" => self::CTRLF,
39+
"\010" => self::BACKSPACE,
40+
"\177" => self::BACKSPACE,
41+
"\027" => self::CTRLW,
42+
"\n" => self::ENTER,
43+
"\t" => self::TAB,
44+
];
45+
46+
public function __construct(string $data)
47+
{
48+
$this->data = $data;
49+
}
50+
51+
/**
52+
* Is this character a control sequence?
53+
*/
54+
public function isControl() : bool
55+
{
56+
return isset(static::$controls[$this->data]);
57+
}
58+
59+
/**
60+
* Is this character a normal character?
61+
*/
62+
public function isNotControl() : bool
63+
{
64+
return ! $this->isControl();
65+
}
66+
67+
/**
68+
* Get the raw character or control sequence
69+
*/
70+
public function get() : string
71+
{
72+
return $this->data;
73+
}
74+
75+
/**
76+
* Get the actual control name that this sequence represents.
77+
* One of the class constants. Eg. self::UP.
78+
*
79+
* Throws an exception if the character is not actually a control sequence
80+
*/
81+
public function getControl() : string
82+
{
83+
if (!isset(static::$controls[$this->data])) {
84+
throw new \RuntimeException(sprintf('Character "%s" is not a control', $this->data));
85+
}
86+
87+
return static::$controls[$this->data];
88+
}
89+
90+
/**
91+
* Get the raw character or control sequence
92+
*/
93+
public function __toString() : string
94+
{
95+
return $this->get();
96+
}
97+
98+
/**
99+
* Does the given control name exist? eg self::UP.
100+
*/
101+
public static function controlExists(string $controlName) : bool
102+
{
103+
return in_array($controlName, static::$controls, true);
104+
}
105+
106+
/**
107+
* Get all of the available control names
108+
*/
109+
public static function getControls() : array
110+
{
111+
return array_values(array_unique(static::$controls));
112+
}
113+
114+
/**
115+
* Create a instance from a given control name. Throws an exception if the
116+
* control name does not exist.
117+
*/
118+
public static function fromControlName(string $controlName) : self
119+
{
120+
if (!static::controlExists($controlName)) {
121+
throw new \InvalidArgumentException(sprintf('Control "%s" does not exist', $controlName));
122+
}
123+
124+
return new static(array_search($controlName, static::$controls, true));
125+
}
126+
}

0 commit comments

Comments
 (0)