8000 [Console] clone stream on multiline questions so EOT doesn't close input · symfony/symfony@ec688a3 · GitHub
[go: up one dir, main page]

Skip to content

Commit ec688a3

Browse files
ramseychalasr
authored andcommitted
[Console] clone stream on multiline questions so EOT doesn't close input
1 parent af8ad34 commit ec688a3

File tree

2 files changed

+91
-4
lines changed

2 files changed

+91
-4
lines changed

src/Symfony/Component/Console/Helper/QuestionHelper.php

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -517,11 +517,53 @@ private function readInput($inputStream, Question $question)
517517
return fgets($inputStream, 4096);
518518
}
519519

520+
$multiLineStreamReader = $this->cloneInputStream($inputStream);
521+
if (null === $multiLineStreamReader) {
522+
return false;
523+
}
524+
520525
$ret = '';
521-
while (false !== ($char = fgetc($inputStream))) {
526+
while (false !== ($char = fgetc($multiLineStreamReader))) {
527+
if (\PHP_EOL === "{$ret}{$char}") {
528+
break;
529+
}
522530
$ret .= $char;
523531
}
524532

525533
return $ret;
526534
}
535+
536+
/**
537+
* Clones an input stream in order to act on one instance of the same
538+
* stream without affecting the other instance.
539+
*
540+
* @param resource $inputStream The handler resource
541+
*
542+
* @return resource|null The cloned resource, null in case it could not be cloned
543+
*/
544+
private function cloneInputStream($inputStream)
545+
{
546+
$streamMetaData = stream_get_meta_data($inputStream);
547+
$seekable = $streamMetaData['seekable'] ?? false;
548+
$mode = $streamMetaData['mode'] ?? 'rb';
549+
$uri = $streamMetaData['uri'] ?? null;
550+
551+
if (null === $uri) {
552+
return null;
553+
}
554+
555+
$cloneStream = fopen($uri, $mode);
556+
557+
// For seekable and writable streams, add all the same data to the
558+
// cloned stream and then seek to the same offset.
559+
if (true === $seekable && !\in_array($mode, ['r', 'rb', 'rt'])) {
560+
$offset = ftell($inputStream);
561+
rewind($inputStream);
562+
stream_copy_to_stream($inputStream, $cloneStream);
563+
fseek($inputStream, $offset);
564+
fseek($cloneStream, $offset);
565+
}
566+
567+
return $cloneStream;
568+
}
527569
}

src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -461,19 +461,64 @@ public function testAskMultilineResponseWithEOF()
461461
$question = new Question('Write an essay');
462462
$question->setMultiline(true);
463463

464-
$this->assertEquals($essay, $dialog->ask($this->createStreamableInputInterfaceMock($response), $this->createOutputInterface(), $question));
464+
$this->assertSame($essay, $dialog->ask($this->createStreamableInputInterfaceMock($response), $this->createOutputInterface(), $question));
465465
}
466466

467467
public function testAskMultilineResponseWithSingleNewline()
468468
{
469-
$response = $this->getInputStream("\n");
469+
$response = $this->getInputStream(\PHP_EOL);
470470

471471
$dialog = new QuestionHelper();
472472

473473
$question = new Question('Write an essay');
474474
$question->setMultiline(true);
475475

476-
$this->assertEquals('', $dialog->ask($this->createStreamableInputInterfaceMock($response), $this->createOutputInterface(), $question));
476+
$this->assertNull($dialog->ask($this->createStreamableInputInterfaceMock($response), $this->createOutputInterface(), $question));
477+
}
478+
479+
public function testAskMultilineResponseWithDataAfterNewline()
480+
{
481+
$response = $this->getInputStream(\PHP_EOL.'this is text');
482+
483+
$dialog = new QuestionHelper();
484+
485+
$question = new Question('Write an essay');
486+
$question->setMultiline(true);
487+
488+
$this->assertNull($dialog->ask($this->createStreamableInputInterfaceMock($response), $this->createOutputInterface(), $question));
489+
}
490+
491+
public function testAskMultilineResponseWithMultipleNewlinesAtEnd()
492+
{
493+
$typedText = 'This is a body'.\PHP_EOL.\PHP_EOL;
494+
$response = $this->getInputStream($typedText);
495+
496+
$dialog = new QuestionHelper();
497+
498+
$question = new Question('Write an essay');
499+
$question->setMultiline(true);
500+
501+
$this->assertSame('This is a body', $dialog->ask($this->createStreamableInputInterfaceMock($response), $this->createOutputInterface(), $question));
502+
}
503+
504+
public function testAskMultilineResponseWithWithCursorInMiddleOfSeekableInputStream()
505+
{
506+
$input = <<<EOD
507+
This
508+
is
509+
some
510+
input
511+
EOD;
512+
$response = $this->getInputStream($input);
513+
fseek($response, 8);
514+
515+
$dialog = new QuestionHelper();
516+
517+
$question = new Question('Write an essay');
518+
$question->setMultiline(true);
519+
520+
$this->assertSame("some\ninput", $dialog->ask($this->createStreamableInputInterfaceMock($response), $this->createOutputInterface(), $question));
521+
$this->assertSame(8, ftell($response));
477522
}
478523

479524
/**

0 commit comments

Comments
 (0)
0