Skip to content

Commit 12a0a3d

Browse files
committed
Migration from CLI Menu
* Simplified to single Terminal class * Removed TerminalFactory * Added Enum for Keypress input
0 parents  commit 12a0a3d

File tree

6 files changed

+340
-0
lines changed

6 files changed

+340
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
vendor/
2+
.idea
3+
/composer.lock

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<h1 align="center">Terminal Utility</h1>
2+
3+
<p align="center">
4+
Small utility to help provide a simple, consise API for terminal interaction
5+
</p>
6+
7+
<p align="center">
8+
<a href="https://phpschool-team.slack.com/messages">
9+
<img src="https://phpschool.herokuapp.com/badge.svg">
10+
</a>
11+
</p>
12+
13+
---
14+
15+
## Install
16+
17+
```bash
18+
composer require php-school/terminal
19+
```
20+
21+
## TODO
22+
23+
- [ ] Tests
24+
- [ ] I/O separation

composer.json

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"name": "php-school/terminal",
3+
"description": "A command line terminal utility in PHP",
4+
"keywords": ["cli", "console", "terminal", "phpschool", "php-school"],
5+
"license": "MIT",
6+
"authors": [
7+
{
8+
"name": "Michael Woodward",
9+
"email": "[email protected]"
10+
},
11+
{
12+
"name": "Aydin Hassan",
13+
"email": "[email protected]"
14+
}
15+
],
16+
"require-dev": {
17+
"phpunit/phpunit": "^6.0",
18+
"squizlabs/php_codesniffer": "^3.0"
19+
},
20+
"require": {
21+
"php" : ">=7.0",
22+
"ext-posix": "*",
23+
"myclabs/php-enum": "^1.5"
24+
},
25+
"autoload" : {
26+
"psr-4" : {
27+
"PhpSchool\\Terminal\\": "src"
28+
}
29+
},
30+
"autoload-dev": {
31+
"psr-4": { "PhpSchool\\TerminalTest\\": "test" }
32+
},
33+
"scripts" : {
34+
"cs" : [
35+
"phpcs src --standard=PSR2",
36+
"phpcs test --standard=PSR2"
37+
]
38+
}
39+
}

src/KeypressInput.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;
4+
5+
use MyCLabs\Enum\Enum;
6+
7+
/**
8+
* @author Michael Woodward <[email protected]>
9+
*
10+
* @method static UP()
11+
* @method static DOWN()
12+
* @method static ENTER()
13+
*/
14+
class KeypressInput extends Enum
15+
{
16+
const UP = 'up';
17+
const DOWN = 'down';
18+
const ENTER = 'enter';
19+
}

src/Terminal.php

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
<?php
2+
3+
namespace PhpSchool\Terminal;
4+
5+
/**
6+
* @author Michael Woodward <[email protected]>
7+
*/
8+
class Terminal implements TerminalInterface
9+
{
10+
/**
11+
* @var bool
12+
*/
13+
private $isTTY;
14+
15+
/**
16+
* @var bool
17+
*/
18+
private $isCanonical = false;
19+
20+
/**
21+
* @var int
22+
*/
23+
private $width;
24+
25+
/**
26+
* @var int
27+
*/
28+
private $height;
29+
30+
/**
31+
* @var string
32+
*/
33+
private $details;
34+
35+
/**
36+
* @var string
37+
*/
38+
private $originalConfiguration;
39+
40+
public function __construct()
41+
{
42+
$this->getOriginalConfiguration();
43+
}
44+
45+
public function getWidth(): int
46+
{
47+
return $this->width ?: $this->width = (int) exec('tput cols');
48+
}
49+
50+
public function getHeight(): int
51+
{
52+
return $this->height ?: $this->height = (int) exec('tput lines');
53+
}
54+
55+
public function getDetails(): string
56+
{
57+
if (!$this->details) {
58+
$this->details = function_exists('posix_ttyname')
59+
? @posix_ttyname(STDOUT)
60+
: "Can't retrieve terminal details";
61+
}
62+
63+
return $this->details;
64+
}
65+
66+
private function getOriginalConfiguration(): string
67+
{
68+
return $this->originalConfiguration ?: $this->originalConfiguration = exec('stty -g');
69+
}
70+
71+
public function setCanonicalMode(bool $useCanonicalMode = true)
72+
{
73+
if ($useCanonicalMode) {
74+
exec('stty -icanon');
75+
$this->isCanonical = true;
76+
} else {
77+
exec('stty ' . $this->getOriginalConfiguration());
78+
$this->isCanonical = false;
79+
}
80+
}
81+
82+
public function isCanonical(): bool
83+
{
84+
return $this->isCanonical;
85+
}
86+
87+
public function isTTY(): bool
88+
{
89+
return $this->isTTY ?: $this->isTTY = function_exists('posix_isatty') && @posix_isatty(STDOUT);
90+
}
91+
92+
/**
93+
* @see https://github.com/symfony/Console/blob/master/Output/StreamOutput.php#L95-L102
94+
*/
95+
public function supportsColour(): bool
96+
{
97+
if (DIRECTORY_SEPARATOR === '\\') {
98+
return false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI') || 'xterm' === getenv('TERM');
99+
}
100+
101+
return $this->isTTY();
102+
}
103+
104+
public function getKeyedInput(): KeypressInput
105+
{
106+
$map = [
107+
"\033[A" => KeypressInput::UP(),
108+
"k" => KeypressInput::UP(),
109+
"\033[B" => KeypressInput::DOWN(),
110+
"j" => KeypressInput::DOWN(),
111+
"\n" => KeypressInput::ENTER(),
112+
"\r" => KeypressInput::ENTER(),
113+
" " => KeypressInput::ENTER(),
114+
];
115+
116+
$input = fread(STDIN, 4);
117+
$this->clearLine();
118+
119+
return array_key_exists($input, $map)
120+
? $map[$input]
121+
: $input;
122+
}
123+
124+
public function clear()
125+
{
126+
echo "\033[2J";
127+
}
128+
129+
public function enableCursor()
130+
{
131+
echo "\033[?25h";
132+
}
133+
134+
public function disableCursor()
135+
{
136+
echo "\033[?25l";
137+
}
138+
139+
public function moveCursorToTop()
140+
{
141+
echo "\033[H";
142+
}
143+
144+
public function moveCursorToRow(int $rowNumber)
145+
{
146+
echo sprintf("\033[%d;0H", $rowNumber);
147+
}
148+
149+
public function moveCursorToColumn(int $column)
150+
{
151+
echo sprintf("\033[%dC", $column);
152+
}
153+
154+
public function clearLine()
155+
{
156+
echo sprintf("\033[%dD\033[K", $this->getWidth());
157+
}
158+
159+
public function clean()
160+
{
161+
foreach (range(0, $this->getHeight()) as $rowNum) {
162+
$this->moveCursorToRow($rowNum);
163+
$this->clearLine();
164+
}
165+
}
166+
}

src/TerminalInterface.php

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php
2+
3+
namespace PhpSchool\Terminal;
4+
5+
/**
6+
* @author Michael Woodward <[email protected]>
7+
*/
8+
interface TerminalInterface
9+
{
10+
/**
11+
* Get terminal details which can be used to identify
12+
*/
13+
public function getDetails(): string;
14+
15+
/**
16+
* Get the available width of the terminal
17+
*/
18+
public function getWidth(): int;
19+
20+
/**
21+
* Get the available height of the terminal
22+
*/
23+
public function getHeight(): int;
24+
25+
/**
26+
* Toggle canonical mode on TTY
27+
*/
28+
public function setCanonicalMode(bool $useCanonicalMode = true);
29+
30+
/**
31+
* Check if TTY is in canonical mode
32+
*/
33+
public function isCanonical(): bool;
34+
35+
/**
36+
* Test whether terminal is valid TTY
37+
*/
38+
public function isTTY(): bool;
39+
40+
/**
41+
* Test whether terminal supports colour output
42+
*/
43+
public function supportsColour(): bool;
44+
45+
/**
46+
* Clear the terminal window
47+
*/
48+
public function clear();
49+
50+
/**
51+
* Clear the current cursors line
52+
*/
53+
public function clearLine();
54+
55+
/**
56+
* Move the cursor to the top left of the window
57+
*/
58+
public function moveCursorToTop();
59+
60+
/**
61+
* Move the cursor to the start of a specific row
62+
*/
63+
public function moveCursorToRow(int $rowNumber);
64+
65+
/**
66+
* Move the cursor to a specific column
67+
*/
68+
public function moveCursorToColumn(int $columnNumber);
69+
70+
/**
71+
* Clean the whole console without jumping the window
72+
*/
73+
public function clean();
74+
75+
/**
76+
* Enable cursor display
77+
*/
78+
public function enableCursor();
79+
80+
/**
81+
* Disable cursor display
82+
*/
83+
public function disableCursor();
84+
85+
/**
86+
* Read keypress from terminal input
87+
*/
88+
public function getKeyedInput(): KeypressInput;
89+
}

0 commit comments

Comments
 (0)