Skip to content

Commit 21bf3c0

Browse files
authored
Merge pull request #65 from clue-labs/data
Consistently emit incoming "data" event with trailing newline
2 parents 11363e3 + f07a554 commit 21bf3c0

File tree

5 files changed

+59
-18
lines changed

5 files changed

+59
-18
lines changed

README.md

+33-5
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ $stdio = new Stdio($loop);
6464
See below for waiting for user input and writing output.
6565
The `Stdio` class is a well-behaving duplex stream
6666
(implementing ReactPHP's `DuplexStreamInterface`) that emits each complete
67-
line as a `data` event (including the trailing newline).
67+
line as a `data` event, including the trailing newline.
6868

6969
#### Output
7070

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

82-
Alternatively, you can also use the `Stdio` as a writable stream.
83-
You can `pipe()` any readable stream into this stream.
82+
Because the `Stdio` is a well-behaving writable stream,
83+
you can also `pipe()` any readable stream into this stream.
84+
85+
```php
86+
$logger->pipe($stdio);
87+
```
8488

8589
#### Input
8690

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

102-
Because the `Stdio` is a well-behaving redable stream that will emit incoming
106+
Note that this class takes care of buffering incomplete lines and will only emit
107+
complete lines.
108+
This means that the line will usually end with the trailing newline character.
109+
If the stream ends without a trailing newline character, it will not be present
110+
in the `data` event.
111+
As such, it's usually recommended to remove the trailing newline character
112+
before processing command line input like this:
113+
114+
```php
115+
$stdio->on('data', function ($line) {
116+
$line = rtrim($line, "\r\n");
117+
if ($line === "start") {
118+
doSomething();
119+
}
120+
});
121+
```
122+
123+
Similarly, if you copy and paste a larger chunk of text, it will properly emit
124+
multiple complete lines with a separate `data` event for each line.
125+
126+
Because the `Stdio` is a well-behaving readable stream that will emit incoming
103127
data as-is, you can also use this to `pipe()` this stream into other writable
104128
streams.
105129

130+
```
131+
$stdio->pipe($logger);
132+
```
133+
106134
You can control various aspects of the console input through the [`Readline`](#readline),
107135
so read on..
108136

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

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

130158
#### Prompt

src/Readline.php

+4-4
Original file line numberDiff line numberDiff line change
@@ -628,7 +628,7 @@ public function onKeyEnter()
628628
if ($this->echo !== false) {
629629
$this->output->write("\n");
630630
}
631-
$this->processLine();
631+
$this->processLine("\n");
632632
}
633633

634634
/** @internal */
@@ -741,7 +741,7 @@ public function deleteChar($n)
741741
*
742742
* @uses self::setInput()
743743
*/
744-
protected function processLine()
744+
protected function processLine($eol)
745745
{
746746
// reset history cycle position
747747
$this->historyPosition = null;
@@ -758,7 +758,7 @@ protected function processLine()
758758
}
759759

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

764764
private function strlen($str)
@@ -818,7 +818,7 @@ public function strwidth($str)
818818
public function handleEnd()
819819
{
820820
if ($this->linebuffer !== '') {
821-
$this->processLine();
821+
$this->processLine('');
822822
}
823823

824824
if (!$this->closed) {

src/Stdio.php

+1-3
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,7 @@ public function __construct(LoopInterface $loop, ReadableStreamInterface $input
4646
$this->readline->on('data', function($line) use ($that, &$incomplete) {
4747
// readline emits a new line on enter, so start with a blank line
4848
$incomplete = '';
49-
50-
// emit data with trailing newline in order to preserve readable API
51-
$that->emit('data', array($line . PHP_EOL));
49+
$that->emit('data', array($line));
5250
});
5351

5452
// handle all input events (readline forwards all input events)

tests/ReadlineTest.php

+5-5
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ public function testMovingCursorWithoutEchoDoesNotNeedToRedraw()
193193

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

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

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

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

@@ -218,7 +218,7 @@ public function testDataEventWillBeEmittedForCompleteLineAndRemainingWillStayInI
218218

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

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

906906
public function testEmitEmptyInputOnEnter()
907907
{
908-
$this->readline->on('data', $this->expectCallableOnceWith(''));
908+
$this->readline->on('data', $this->expectCallableOnceWith("\n"));
909909
$this->readline->onKeyEnter();
910910
}
911911

912912
public function testEmitInputOnEnterAndClearsInput()
913913
{
914914
$this->readline->setInput('test');
915-
$this->readline->on('data', $this->expectCallableOnceWith('test'));
915+
$this->readline->on('data', $this->expectCallableOnceWith("test\n"));
916916
$this->readline->onKeyEnter();
917917

918918
$this->assertEquals('', $this->readline->getInput());

tests/StdioTest.php

+16-1
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,22 @@ public function testDataEventWillBeForwarded()
383383

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

386-
$readline->emit('data', array('hello'));
386+
$readline->emit('data', array("hello\n"));
387+
}
388+
389+
public function testDataEventWithoutNewlineWillBeForwardedAsIs()
390+
{
391+
$input = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
392+
$output = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
393+
394+
//$readline = $this->getMockBuilder('Clue\React\Stdio\Readline')->disableOriginalConstructor()->getMock();
395+
$readline = new Readline($input, $output);
396+
397+
$stdio = new Stdio($this->loop, $input, $output, $readline);
398+
399+
$stdio->on('data', $this->expectCallableOnceWith("hello"));
400+
401+
$readline->emit('data', array("hello"));
387402
}
388403

389404
public function testEndEventWillBeForwarded()

0 commit comments

Comments
 (0)