Skip to content

Refactor IO #80

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

Closed
wants to merge 2 commits into from
Closed
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 src/CliMenu.php
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ protected function draw() : void
$frame->newLine(2);

foreach ($frame->getRows() as $row) {
echo $row;
$this->terminal->getOutput()->write($row);
}

$this->currentFrame = $frame;
Expand Down
2 changes: 1 addition & 1 deletion src/Dialogue/Dialogue.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ protected function emptyRow() : void
protected function write(string $text, int $column = null) : void
{
$this->terminal->moveCursorToColumn($column ?: $this->x);
echo $text;
$this->terminal->getOutput()->write($text);
}

public function getStyle() : MenuStyle
Expand Down
32 changes: 32 additions & 0 deletions src/IO/BufferedOutput.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace PhpSchool\CliMenu\IO;

/**
* @author Aydin Hassan <[email protected]>
*/
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();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like this would always return an empty string ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope - it will return whatever has been written so far and clean the buffer

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh my dayz... obvs .. I must have been tired not to see that 😂

}
}
15 changes: 15 additions & 0 deletions src/IO/InputStream.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace PhpSchool\CliMenu\IO;

/**
* @author Aydin Hassan <[email protected]>
*/
interface InputStream
{
/**
* Callback should be called with the number of bytes requested
* when ready.
*/
public function read(int $numBytes, callable $callback) : void;
}
11 changes: 11 additions & 0 deletions src/IO/OutputStream.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace PhpSchool\CliMenu\IO;

/**
* @author Aydin Hassan <[email protected]>
*/
interface OutputStream
{
public function write(string $buffer) : void;
}
43 changes: 43 additions & 0 deletions src/IO/ResourceInputStream.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

namespace PhpSchool\CliMenu\IO;

use function is_resource;
use function get_resource_type;
use function stream_get_meta_data;
use function strpos;

/**
* @author Aydin Hassan <[email protected]>
*/
class ResourceInputStream implements InputStream
{
/**
* @var resource
*/
private $stream;

public function __construct($stream = null)
{
if ($stream === null) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe I'm being silly, can't you have STDIN as the default value?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's give it a whirl

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed we can!

$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);
}
}
42 changes: 42 additions & 0 deletions src/IO/ResourceOutputStream.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace PhpSchool\CliMenu\IO;

use function is_resource;
use function get_resource_type;
use function stream_get_meta_data;
use function strpos;

/**
* @author Aydin Hassan <[email protected]>
*/
class ResourceOutputStream implements OutputStream
{
/**
* @var resource
*/
private $stream;

public function __construct($stream = null)
{
if ($stream === null) {
$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);
}
}
5 changes: 4 additions & 1 deletion src/Terminal/TerminalFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@

namespace PhpSchool\CliMenu\Terminal;

use PhpSchool\CliMenu\IO\ResourceInputStream;
use PhpSchool\CliMenu\IO\ResourceOutputStream;

/**
* @author Michael Woodward <[email protected]>
*/
class TerminalFactory
{
public static function fromSystem() : TerminalInterface
{
return new UnixTerminal();
return new UnixTerminal(new ResourceInputStream, new ResourceOutputStream);
}
}
9 changes: 8 additions & 1 deletion src/Terminal/TerminalInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace PhpSchool\CliMenu\Terminal;

use PhpSchool\CliMenu\IO\OutputStream;

/**
* @author Michael Woodward <[email protected]>
*/
Expand Down Expand Up @@ -85,5 +87,10 @@ public function disableCursor() : void;
/**
* @return string
*/
public function getKeyedInput() : string;
public function getKeyedInput(array $map = []) : ?string;

/**
* Get the output stream
*/
public function getOutput() : OutputStream;
}
44 changes: 34 additions & 10 deletions src/Terminal/UnixTerminal.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

namespace PhpSchool\CliMenu\Terminal;

use PhpSchool\CliMenu\IO\InputStream;
use PhpSchool\CliMenu\IO\OutputStream;

/**
* @author Michael Woodward <[email protected]>
*/
Expand Down Expand Up @@ -37,13 +40,25 @@ class UnixTerminal implements TerminalInterface
*/
private $originalConfiguration;

/**
* @var InputStream
*/
private $input;

/**
* @var OutputStream
*/
private $output;

/**
* Initialise the terminal from resource
*
*/
public function __construct()
public function __construct(InputStream $input, OutputStream $output)
{
$this->getOriginalConfiguration();
$this->input = $input;
$this->output = $output;
}

/**
Expand Down Expand Up @@ -129,7 +144,7 @@ public function supportsColour() : bool
return $this->isTTY();
}

public function getKeyedInput() : string
public function getKeyedInput(array $map = []) : ?string
{
// TODO: Move to class var?
// TODO: up, down, enter etc in Abstract CONSTs
Expand All @@ -145,7 +160,11 @@ public function getKeyedInput() : string
" " => 'enter',
];

$input = fread(STDIN, 4);
$input = '';
$this->input->read(4, function ($buffer) use (&$input) {
$input .= $buffer;
});

$this->clearLine();

return array_key_exists($input, $map)
Expand All @@ -158,23 +177,23 @@ public function getKeyedInput() : string
*/
public function clear() : void
{
echo "\033[2J";
$this->output->write("\033[2J");
}

/**
* Enable cursor
*/
public function enableCursor() : void
{
echo "\033[?25h";
$this->output->write("\033[?25h");
}

/**
* Disable cursor
*/
public function disableCursor() : void
{
echo "\033[?25l";
$this->output->write("\033[?25l");
}

/**
Expand All @@ -184,31 +203,31 @@ public function disableCursor() : void
*/
public function moveCursorToTop() : void
{
echo "\033[H";
$this->output->write("\033[H");
}

/**
* Move the cursor to the start of a specific row
*/
public function moveCursorToRow(int $rowNumber) : void
{
echo sprintf("\033[%d;0H", $rowNumber);
$this->output->write(sprintf("\033[%d;0H", $rowNumber));
}

/**
* Move the cursor to the start of a specific column
*/
public function moveCursorToColumn(int $column) : void
{
echo sprintf("\033[%dC", $column);
$this->output->write(sprintf("\033[%dC", $column));
}

/**
* Clear the current cursors line
*/
public function clearLine() : void
{
echo sprintf("\033[%dD\033[K", $this->getWidth());
$this->output->write(sprintf("\033[%dD\033[K", $this->getWidth()));
}

/**
Expand All @@ -221,4 +240,9 @@ public function clean() : void
$this->clearLine();
}
}

public function getOutput() : OutputStream
{
return $this->output;
}
}
Loading