10000 Consistently emit incoming "data" event with trailing newline by clue · Pull Request #65 · clue/reactphp-stdio · GitHub
[go: up one dir, main page]

Skip to content

Consistently emit incoming "data" event with trailing newline #65

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

Merged
merged 1 commit into from
Dec 18, 2017
Merged
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
38 changes: 33 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ $stdio = new Stdio($loop);
See below for waiting for user input and writing output.
The `Stdio` class is a well-behaving duplex stream
(implementing ReactPHP's `DuplexStreamInterface`) that emits each complete
line as a `data` event (including the trailing newline).
line as a `data` event, including the trailing newline.

#### Output

Expand All @@ -79,8 +79,12 @@ $stdio->write('hello');
$stdio->write(" world\n");
```

Alternatively, you can also use the `Stdio` as a writable stream.
You can `pipe()` any readable stream into this stream.
Because the `Stdio` is a well-behaving writable stream,
you can also `pipe()` any readable stream into this stream.

```php
$logger->pipe($stdio);
```

#### Input

Expand All @@ -99,10 +103,34 @@ $stdio->on('data', function ($line) {
});
```

Because the `Stdio` is a well-behaving redable stream that will emit incoming
Note that this class takes care of buffering incomplete lines and will only emit
complete lines.
This means that the line will usually end with the trailing newline character.
If the stream ends without a trailing newline character, it will not be present
in the `data` event.
As such, it's usually recommended to remove the trailing newline character
before processing command line input like this:

```php
$stdio->on('data', function ($line) {
$line = rtrim($line, "\r\n");
if ($line === "start") {
doSomething();
}
});
```

Similarly, if you copy and paste a larger chunk of text, it will properly emit
multiple complete lines with a separate `data` event for each line.

Because the `Stdio` is a well-behaving readable stream that will emit incoming
data as-is, you can also use this to `pipe()` this stream into other writable
streams.

```
$stdio->pipe($logger);
```

You can control various aspects of the console input through the [`Readline`](#readline),
so read on..

Expand All @@ -124,7 +152,7 @@ See above for waiting for user input.

Alternatively, the `Readline` is also a well-behaving readable stream
(implementing ReactPHP's `ReadableStreamInterface`) that emits each complete
line as a `data` event (without the trailing newline).
line as a `data` event, including the trailing newline.
This is considered advanced usage.

#### Prompt
Expand Down
8 changes: 4 additions & 4 deletions src/Readline.php
Original file line number Diff line number Diff line change
Expand Up @@ -628,7 +628,7 @@ public function onKeyEnter()
if ($this->echo !== false) {
$this->output->write("\n");
}
$this->processLine();
$this->processLine("\n");
}

/** @internal */
Expand Down Expand Up @@ -741,7 +741,7 @@ public function deleteChar($n)
*
* @uses self::setInput()
*/
protected function processLine()
protected function processLine($eol)
{
// reset history cycle position
$this->historyPosition = null;
Expand All @@ -758,7 +758,7 @@ protected function processLine()
}

// process stored input buffer
$this->emit('data', array($line));
$this->emit('data', array($line . $eol));
}

private function strlen($str)
Expand Down Expand Up @@ -818,7 +818,7 @@ public function strwidth($str)
public function handleEnd()
{
if ($this->linebuffer !== '') {
$this->processLine();
$this->processLine('');
}

if (!$this->closed) {
Expand Down
4 changes: 1 addition & 3 deletions src/Stdio.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,7 @@ public function __construct(LoopInterface $loop, ReadableStreamInterface $input
$this->readline->on('data', function($line) use ($that, &$incomplete) {
// readline emits a new line on enter, so start with a blank line
$incomplete = '';

// emit data with trailing newline in order to preserve readable API
$that->emit('data', array($line . PHP_EOL));
$that->emit('data', array($line));
});

// handle all input events (readline forwards all input events)
Expand Down
10 changes: 5 additions & 5 deletions tests/ReadlineTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ public function testMovingCursorWithoutEchoDoesNotNeedToRedraw()

public function testDataEventWillBeEmittedForCompleteLine()
{
$this->readline->on('data', $this->expectCallableOnceWith('hello'));
$this->readline->on('data', $this->expectCallableOnceWith("hello\n"));

$this->input->emit('data', array("hello\n"));
}
Expand All @@ -209,7 +209,7 @@ public function testDataEventWillNotBeEmittedForIncompleteLineButWillStayInInput

public function testDataEventWillBeEmittedForCompleteLineAndRemainingWillStayInInputBuffer()
{
$this->readline->on('data', $this->expectCallableOnceWith('hello'));
$this->readline->on('data', $this->expectCallableOnceWith("hello\n"));

$this->input->emit('data', array("hello\nworld"));

Expand All @@ -218,7 +218,7 @@ public function testDataEventWillBeEmittedForCompleteLineAndRemainingWillStayInI

public function testDataEventWillBeEmittedForEmptyLine()
{
$this->readline->on('data', $this->expectCallableOnceWith(''));
$this->readline->on('data', $this->expectCallableOnceWith("\n"));

$this->input->emit('data', array("\n"));
}
Expand Down Expand Up @@ -905,14 +905,14 @@ public function testAutocompleteShowsLimitedNumberOfAvailableOptionsWhenMultiple

public function testEmitEmptyInputOnEnter()
{
$this->readline->on('data', $this->expectCallableOnceWith(''));
$this->readline->on('data', $this->expectCallableOnceWith("\n"));
$this->readline->onKeyEnter();
}

public function testEmitInputOnEnterAndClearsInput()
{
$this->readline->setInput('test');
$this->readline->on('data', $this->expectCallableOnceWith('test'));
$this->readline->on('data', $this->expectCallableOnceWith("test\n"));
$this->readline->onKeyEnter();

$this->assertEquals('', $this->readline->getInput());
Expand Down
17 changes: 16 additions & 1 deletion tests/StdioTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,22 @@ public function testDataEventWillBeForwarded()

$stdio->on('data', $this->expectCallableOnceWith("hello\n"));

$readline->emit('data', array('hello'));
$readline->emit('data', array("hello\n"));
}

public function testDataEventWithoutNewlineWillBeForwardedAsIs()
{
$input = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
$output = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();

//$readline = $this->getMockBuilder('Clue\React\Stdio\Readline')->disableOriginalConstructor()->getMock();
$readline = new Readline($input, $output);

$stdio = new Stdio($this->loop, $input, $output, $readline);

$stdio->on('data', $this->expectCallableOnceWith("hello"));

$readline->emit('data', array("hello"));
}

public function testEndEventWillBeForwarded()
Expand Down
0