diff --git a/src/Symfony/Component/Console/CHANGELOG.md b/src/Symfony/Component/Console/CHANGELOG.md index 67decd30beae3..cda9e1a9efc92 100644 --- a/src/Symfony/Component/Console/CHANGELOG.md +++ b/src/Symfony/Component/Console/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +4.4.0 +----- + +* added `Question::setTrimmable` default to true to allow the answer to be trimmed or not + 4.3.0 ----- diff --git a/src/Symfony/Component/Console/Helper/QuestionHelper.php b/src/Symfony/Component/Console/Helper/QuestionHelper.php index e6a700aa45db0..a20d141433ed0 100644 --- a/src/Symfony/Component/Console/Helper/QuestionHelper.php +++ b/src/Symfony/Component/Console/Helper/QuestionHelper.php @@ -64,7 +64,7 @@ public function ask(InputInterface $input, OutputInterface $output, Question $qu $default = explode(',', $default); foreach ($default as $k => $v) { - $v = trim($v); + $v = $question->isTrimmable() ? trim($v) : $v; $default[$k] = isset($choices[$v]) ? $choices[$v] : $v; } } @@ -121,7 +121,8 @@ private function doAsk(OutputInterface $output, Question $question) $ret = false; if ($question->isHidden()) { try { - $ret = trim($this->getHiddenResponse($output, $inputStream)); + $hiddenResponse = $this->getHiddenResponse($output, $inputStream, $question->isTrimmable()); + $ret = $question->isTrimmable() ? trim($hiddenResponse) : $hiddenResponse; } catch (RuntimeException $e) { if (!$question->isHiddenFallback()) { throw $e; @@ -134,10 +135,13 @@ private function doAsk(OutputInterface $output, Question $question) if (false === $ret) { throw new RuntimeException('Aborted.'); } - $ret = trim($ret); + if ($question->isTrimmable()) { + $ret = trim($ret); + } } } else { - $ret = trim($this->autocomplete($output, $question, $inputStream, $autocomplete)); + $autocomplete = $this->autocomplete($output, $question, $inputStream, $autocomplete); + $ret = $question->isTrimmable() ? trim($autocomplete) : $autocomplete; } if ($output instanceof ConsoleSectionOutput) { @@ -351,10 +355,11 @@ private function mostRecentlyEnteredValue($entered) * * @param OutputInterface $output An Output instance * @param resource $inputStream The handler resource + * @param bool $trimmable Is the answer trimmable * * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden */ - private function getHiddenResponse(OutputInterface $output, $inputStream): string + private function getHiddenResponse(OutputInterface $output, $inputStream, $trimmable = true): string { if ('\\' === \DIRECTORY_SEPARATOR) { $exe = __DIR__.'/../Resources/bin/hiddeninput.exe'; @@ -366,7 +371,8 @@ private function getHiddenResponse(OutputInterface $output, $inputStream): strin $exe = $tmpExe; } - $value = rtrim(shell_exec($exe)); + $sExec = shell_exec($exe); + $value = $trimmable ? rtrim($sExec) : $sExec; $output->writeln(''); if (isset($tmpExe)) { @@ -386,8 +392,9 @@ private function getHiddenResponse(OutputInterface $output, $inputStream): strin if (false === $value) { throw new RuntimeException('Aborted.'); } - - $value = trim($value); + if ($trimmable) { + $value = trim($value); + } $output->writeln(''); return $value; @@ -396,7 +403,8 @@ private function getHiddenResponse(OutputInterface $output, $inputStream): strin if (false !== $shell = $this->getShell()) { $readCmd = 'csh' === $shell ? 'set mypassword = $<' : 'read -r mypassword'; $command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd); - $value = rtrim(shell_exec($command)); + $sCommand = shell_exec($command); + $value = $trimmable ? rtrim($sCommand) : $sCommand; $output->writeln(''); return $value; diff --git a/src/Symfony/Component/Console/Question/Question.php b/src/Symfony/Component/Console/Question/Question.php index 9201af2fd5d82..43ad8b0917cd0 100644 --- a/src/Symfony/Component/Console/Question/Question.php +++ b/src/Symfony/Component/Console/Question/Question.php @@ -29,6 +29,7 @@ class Question private $validator; private $default; private $normalizer; + private $trimmable = true; /** * @param string $question The question to ask to the user @@ -274,4 +275,19 @@ protected function isAssoc($array) { return (bool) \count(array_filter(array_keys($array), 'is_string')); } + + public function isTrimmable(): bool + { + return $this->trimmable; + } + + /** + * @return $this + */ + public function setTrimmable(bool $trimmable): self + { + $this->trimmable = $trimmable; + + return $this; + } } diff --git a/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php b/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php index eca929fd306b4..19e9f355d41bd 100644 --- a/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php @@ -165,6 +165,20 @@ public function testAsk() $this->assertEquals('What time is it?', stream_get_contents($output->getStream())); } + public function testAskNonTrimmed() + { + $dialog = new QuestionHelper(); + + $inputStream = $this->getInputStream(' 8AM '); + + $question = new Question('What time is it?', '2PM'); + $question->setTrimmable(false); + $this->assertEquals(' 8AM ', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $output = $this->createOutputInterface(), $question)); + + rewind($output->getStream()); + $this->assertEquals('What time is it?', stream_get_contents($output->getStream())); + } + public function testAskWithAutocomplete() { if (!$this->hasSttyAvailable()) { @@ -198,6 +212,40 @@ public function testAskWithAutocomplete() $this->assertEquals('FooBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); } + public function testAskWithAutocompleteTrimmable() + { + if (!$this->hasSttyAvailable()) { + $this->markTestSkipped('`stty` is required to test autocomplete functionality'); + } + + // Acm + // AcsTest + // + // + // Test + // + // S + // F00oo + $inputStream = $this->getInputStream("Acm\nAc\177\177s\tTest\n\n\033[A\033[A\n\033[A\033[A\033[A\033[A\033[A\tTest\n\033[B\nS\177\177\033[B\033[B\nF00\177\177oo\t\n"); + + $dialog = new QuestionHelper(); + $helperSet = new HelperSet([new FormatterHelper()]); + $dialog->setHelperSet($helperSet); + + $question = new Question('Please select a bundle', 'FrameworkBundle'); + $question->setAutocompleterValues(['AcmeDemoBundle ', 'AsseticBundle', ' SecurityBundle ', 'FooBundle']); + $question->setTrimmable(false); + + $this->assertEquals('AcmeDemoBundle ', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); + $this->assertEquals('AsseticBundleTest', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); + $this->assertEquals('FrameworkBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); + $this->assertEquals(' SecurityBundle ', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); + $this->assertEquals('FooBundleTest', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); + $this->assertEquals('AcmeDemoBundle ', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); + $this->assertEquals('AsseticBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); + $this->assertEquals('FooBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); + } + public function testAskWithAutocompleteCallback() { if (!$this->hasSttyAvailable()) { @@ -373,6 +421,21 @@ public function testAskHiddenResponse() $this->assertEquals('8AM', $dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream("8AM\n")), $this->createOutputInterface(), $question)); } + public function testAskHiddenResponseTrimmed() + { + if ('\\' === \DIRECTORY_SEPARATOR) { + $this->markTestSkipped('This test is not supported on Windows'); + } + + $dialog = new QuestionHelper(); + + $question = new Question('What time is it?'); + $question->setHidden(true); + $question->setTrimmable(false); + + $this->assertEquals(' 8AM', $dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream(' 8AM')), $this->createOutputInterface(), $question)); + } + /** * @dataProvider getAskConfirmationData */