Browse files

[Console] Add DialogHelper::askHiddenResponse method

  • Loading branch information...
1 parent ccba363 commit e396edbdc88dd1c42915ac523bbec431e6950245 @romainneutron committed Oct 10, 2012
View
179 src/Symfony/Component/Console/Helper/DialogHelper.php
@@ -21,6 +21,8 @@
class DialogHelper extends Helper
{
private $inputStream;
+ private static $shell;
+ private static $stty;
/**
* Asks a question to the user.
@@ -72,6 +74,72 @@ public function askConfirmation(OutputInterface $output, $question, $default = t
}
/**
+ * Ask a question to the user, the response is hidden
+ *
+ * @param OutputInterface $output An Output instance
+ * @param string|array $question The question
+ * @param Boolean $fallback In case the response can not be hidden, whether to fallback on non-hidden question or not
+ *
+ * @return string The answer
+ *
+ * @throws \RuntimeException In case the fallback is disactivated and the response can not be hidden
+ */
+ public function askHiddenResponse(OutputInterface $output, $question, $fallback = true)
+ {
+ if (defined('PHP_WINDOWS_VERSION_BUILD')) {
+ $exe = __DIR__ . '\\hiddeninput.exe';
+
+ // handle code running from a phar
+ if ('phar:' === substr(__FILE__, 0, 5)) {
+ $tmpExe = sys_get_temp_dir() . '/hiddeninput.exe';
+ copy($exe, $tmpExe);
+ $exe = $tmpExe;
+ }
+
+ $output->write($question);
+ $value = rtrim(shell_exec($exe));
+ $output->writeln('');
+
+ if (isset($tmpExe)) {
+ unlink($tmpExe);
+ }
+
+ return $value;
+ } elseif ($this->hasSttyAvailable()) {
+
+ $output->write($question);
+
+ $sttyMode = shell_exec('/usr/bin/env stty -g');
+
+ shell_exec('/usr/bin/env stty -echo');
+ $value = fgets($this->inputStream ?: STDIN, 4096);
+ shell_exec(sprintf('/usr/bin/env stty %s', escapeshellarg($sttyMode)));
+
+ if (false === $value) {
+ throw new \RuntimeException('Aborted');
+ }
+
+ $value = trim($value);
+ $output->writeln('');
+
+ return $value;
+ } elseif (false !== $shell = $this->getShell()) {
+
+ $output->write($question);
+ $readCmd = $shell === 'csh' ? 'set mypassword = $<' : 'read mypassword';
+ $command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd);
+ $value = rtrim(shell_exec($command));
+ $output->writeln('');
+
+ return $value;
+ } elseif ($fallback) {
+ return $this->ask($output, $question);
+ }
+
+ throw new \RuntimeException('Unable to hide the response');
+ }
+
+ /**
* Asks for a value and validates the response.
*
* The validator receives the data to validate. It must return the
@@ -80,7 +148,7 @@ public function askConfirmation(OutputInterface $output, $question, $default = t
*
* @param OutputInterface $output An Output instance
* @param string|array $question The question to ask
- * @param callback $validator A PHP callback
+ * @param callable $validator A PHP callback
* @param integer $attempts Max number of times to ask before giving up (false by default, which means infinite)
* @param string $default The default answer if none is given by the user
*
@@ -90,21 +158,39 @@ public function askConfirmation(OutputInterface $output, $question, $default = t
*/
public function askAndValidate(OutputInterface $output, $question, $validator, $attempts = false, $default = null)
{
- $error = null;
- while (false === $attempts || $attempts--) {
- if (null !== $error) {
- $output->writeln($this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error'));
- }
+ $interviewer = function() use ($output, $question, $default) {
+ return $this->ask($output, $question, $default);
+ };
- $value = $this->ask($output, $question, $default);
+ return $this->validateAttempts($interviewer, $output, $validator, $attempts);
+ }
- try {
- return call_user_func($validator, $value);
- } catch (\Exception $error) {
- }
- }
+ /**
+ * Asks for a value, hide and validates the response.
+ *
+ * The validator receives the data to validate. It must return the
+ * validated data when the data is valid and throw an exception
+ * otherwise.
+ *
+ * @param OutputInterface $output An Output instance
+ * @param string|array $question The question to ask
+ * @param callable $validator A PHP callback
+ * @param integer $attempts Max number of times to ask before giving up (false by default, which means infinite)
+ * @param Boolean $fallback In case the response can not be hidden, whether to fallback on non-hidden question or not
+ *
+ * @return string The response
+ *
+ * @throws \Exception When any of the validators return an error
+ * @throws \RuntimeException In case the fallback is disactivated and the response can not be hidden
+ *
+ */
+ public function askHiddenResponseAndValidate(OutputInterface $output, $question, $validator, $attempts = false, $fallback = true)
+ {
+ $interviewer = function() use ($output, $question, $fallback) {
+ return $this->askHiddenResponse($output, $question, $fallback);
+ };
- throw $error;
+ return $this->validateAttempts($interviewer, $output, $validator, $attempts);
}
/**
@@ -136,4 +222,71 @@ public function getName()
{
return 'dialog';
}
+
+ /**
+ * Return a valid unix shell
+ *
+ * @return string|false The valid shell name, false in case no valid shell is found
+ */
+ private function getShell()
+ {
+ if (null !== self::$shell) {
+ return self::$shell;
+ }
+
+ self::$shell = false;
+
+ if (file_exists('/usr/bin/env')) {
+ // handle other OSs with bash/zsh/ksh/csh if available to hide the answer
+ $test = "/usr/bin/env %s -c 'echo OK' 2> /dev/null";
+ foreach (array('bash', 'zsh', 'ksh', 'csh') as $sh) {
+ if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) {
+ self::$shell = $sh;
+ break;
+ }
+ }
+ }
+
+ return self::$shell;
+ }
+
+ private function hasSttyAvailable()
+ {
+ if (null !== self::$stty) {
+ return self::$stty;
+ }
+
+ exec('/usr/bin/env stty', $output, $exicode);
+
+ return self::$stty = $exicode === 0;
+ }
+
+ /**
+ * Validate an attempt
+ *
+ * @param callable $interviewer A callable that will ask for a question and return the result
+ * @param OutputInterface $output An Output instance
+ * @param callable $validator A PHP callback
+ * @param integer $attempts Max number of times to ask before giving up ; false will ask infinitely
+ *
+ * @return string The validated response
+ *
+ * @throws \Exception In case the max number of attempts has been reached and no valid response has been given
+ */
+ private function validateAttempts($interviewer, OutputInterface $output, $validator, $attempts)
+ {
+ $error = null;
+ while (false === $attempts || $attempts--) {
+ if (null !== $error) {
+ $output->writeln($this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error'));
+ }
+
+ try {
+ return call_user_func($validator, $interviewer());
+ } catch (\Exception $error) {
+ }
+ }
+
+ throw $error;
+ }
}
View
BIN src/Symfony/Component/Console/Helper/hiddeninput.exe
Binary file not shown.
View
9 src/Symfony/Component/Console/Tests/Helper/DialogHelperTest.php
@@ -31,6 +31,15 @@ public function testAsk()
$this->assertEquals('What time is it?', stream_get_contents($output->getStream()));
}
+ public function testAskHiddenResponse()
+ {
+ $dialog = new DialogHelper();
+
+ $dialog->setInputStream($this->getInputStream("8AM\n"));
+
+ $this->assertEquals('8AM', $dialog->askHiddenResponse($this->getOutputStream(), 'What time is it?'));
+ }
+
public function testAskConfirmation()
{
$dialog = new DialogHelper();

0 comments on commit e396edb

Please sign in to comment.