Skip to content

[Console] allow answer to be trimmed by adding a flag #32707

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 1 commit 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
5 changes: 5 additions & 0 deletions src/Symfony/Component/Console/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
CHANGELOG
=========

3.4.30
------

* added `Question::setTrimmable` default to true to allow the answer to be trimmed or not

3.4.0
-----

Expand Down
26 changes: 17 additions & 9 deletions src/Symfony/Component/Console/Helper/QuestionHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand Down Expand Up @@ -161,7 +161,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;
Expand All @@ -174,10 +175,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, \is_array($autocomplete) ? $autocomplete : iterator_to_array($autocomplete, false)));
$autocomplete = $this->autocomplete($output, $question, $inputStream, \is_array($autocomplete) ? $autocomplete : iterator_to_array($autocomplete, false));
$ret = $question->isTrimmable() ? trim($autocomplete) : $autocomplete;
}

$ret = \strlen($ret) > 0 ? $ret : $question->getDefault();
Expand Down Expand Up @@ -385,12 +389,13 @@ private function mostRecentlyEnteredValue($entered)
*
* @param OutputInterface $output An Output instance
* @param resource $inputStream The handler resource
* @param bool $trimmable Is the answer trimmable
*
* @return string The answer
*
* @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
*/
private function getHiddenResponse(OutputInterface $output, $inputStream)
private function getHiddenResponse(OutputInterface $output, $inputStream, $trimmable = true)
{
if ('\\' === \DIRECTORY_SEPARATOR) {
$exe = __DIR__.'/../Resources/bin/hiddeninput.exe';
Expand All @@ -402,7 +407,8 @@ private function getHiddenResponse(OutputInterface $output, $inputStream)
$exe = $tmpExe;
}

$value = rtrim(shell_exec($exe));
$sExec = shell_exec($exe);
$value = $trimmable ? rtrim($sExec) : $sExec;
$output->writeln('');

if (isset($tmpExe)) {
Expand All @@ -422,8 +428,9 @@ private function getHiddenResponse(OutputInterface $output, $inputStream)
if (false === $value) {
throw new RuntimeException('Aborted.');
}

$value = trim($value);
if ($trimmable) {
$value = trim($value);
}
$output->writeln('');

return $value;
Expand All @@ -432,7 +439,8 @@ private function getHiddenResponse(OutputInterface $output, $inputStream)
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;
Expand Down
16 changes: 16 additions & 0 deletions src/Symfony/Component/Console/Question/Question.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -243,4 +244,19 @@ protected function isAssoc($array)
{
return (bool) \count(array_filter(array_keys($array), 'is_string'));
}

public function isTrimmable()
{
return $this->trimmable;
}

/**
* @return $this
*/
public function setTrimmable(bool $trimmable)
{
$this->trimmable = $trimmable;

return $this;
}
}
63 changes: 63 additions & 0 deletions src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down Expand Up @@ -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<NEWLINE>
// Ac<BACKSPACE><BACKSPACE>s<TAB>Test<NEWLINE>
// <NEWLINE>
// <UP ARROW><UP ARROW><NEWLINE>
// <UP ARROW><UP ARROW><UP ARROW><UP ARROW><UP ARROW><TAB>Test<NEWLINE>
// <DOWN ARROW><NEWLINE>
// S<BACKSPACE><BACKSPACE><DOWN ARROW><DOWN ARROW><NEWLINE>
// F00<BACKSPACE><BACKSPACE>oo<TAB><NEWLINE>
$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 testAskWithAutocompleteWithNonSequentialKeys()
{
if (!$this->hasSttyAvailable()) {
Expand Down Expand Up @@ -330,6 +378,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
*/
Expand Down