1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330: 331: 332: 333: 334: 335: 336: 337: 338: 339: 340: 341: 342: 343: 344: 345: 346: 347: 348: 349: 350: 351: 352: 353: 354: 355: 356: 357: 358: 359: 360: 361: 362: 363: 364: 365: 366: 367: 368: 369: 370: 371: 372: 373: 374: 375: 376: 377: 378: 379: 380: 381: 382: 383: 384: 385: 386: 387: 388: 389: 390: 391: 392: 393: 394: 395: 396: 397: 398: 399: 400: 401: 402: 403: 404: 405: 406: 407: 408: 409: 410: 411: 412: 413: 414: 415: 416: 417: 418: 419: 420: 421: 422: 423: 424: 425: 426: 427: 428: 429: 430: 431: 432: 433: 434: 435: 436: 437: 438: 439: 440: 441: 442: 443: 444: 445: 446: 447: 448: 449: 450: 451: 452: 453: 454:
<?php
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Question\ChoiceQuestion;
class QuestionHelper extends Helper
{
private $inputStream;
private static $shell;
private static $stty;
public function ask(InputInterface $input, OutputInterface $output, Question $question)
{
if ($output instanceof ConsoleOutputInterface) {
$output = $output->getErrorOutput();
}
if (!$input->isInteractive()) {
return $question->getDefault();
}
if (!$question->getValidator()) {
return $this->doAsk($output, $question);
}
$that = $this;
$interviewer = function () use ($output, $question, $that) {
return $that->doAsk($output, $question);
};
return $this->validateAttempts($interviewer, $output, $question);
}
public function setInputStream($stream)
{
if (!is_resource($stream)) {
throw new InvalidArgumentException('Input stream must be a valid resource.');
}
$this->inputStream = $stream;
}
public function getInputStream()
{
return $this->inputStream;
}
public function getName()
{
return 'question';
}
public function doAsk(OutputInterface $output, Question $question)
{
$this->writePrompt($output, $question);
$inputStream = $this->inputStream ?: STDIN;
$autocomplete = $question->getAutocompleterValues();
if (null === $autocomplete || !$this->hasSttyAvailable()) {
$ret = false;
if ($question->isHidden()) {
try {
$ret = trim($this->getHiddenResponse($output, $inputStream));
} catch (\RuntimeException $e) {
if (!$question->isHiddenFallback()) {
throw $e;
}
}
}
if (false === $ret) {
$ret = fgets($inputStream, 4096);
if (false === $ret) {
throw new RuntimeException('Aborted');
}
$ret = trim($ret);
}
} else {
$ret = trim($this->autocomplete($output, $question, $inputStream));
}
$ret = strlen($ret) > 0 ? $ret : $question->getDefault();
if ($normalizer = $question->getNormalizer()) {
return $normalizer($ret);
}
return $ret;
}
protected function writePrompt(OutputInterface $output, Question $question)
{
$message = $question->getQuestion();
if ($question instanceof ChoiceQuestion) {
$maxWidth = max(array_map(array($this, 'strlen'), array_keys($question->getChoices())));
$messages = (array) $question->getQuestion();
foreach ($question->getChoices() as $key => $value) {
$width = $maxWidth - $this->strlen($key);
$messages[] = ' [<info>'.$key.str_repeat(' ', $width).'</info>] '.$value;
}
$output->writeln($messages);
$message = $question->getPrompt();
}
$output->write($message);
}
protected function writeError(OutputInterface $output, \Exception $error)
{
if (null !== $this->getHelperSet() && $this->getHelperSet()->has('formatter')) {
$message = $this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error');
} else {
$message = '<error>'.$error->getMessage().'</error>';
}
$output->writeln($message);
}
private function autocomplete(OutputInterface $output, Question $question, $inputStream)
{
$autocomplete = $question->getAutocompleterValues();
$ret = '';
$i = 0;
$ofs = -1;
$matches = $autocomplete;
$numMatches = count($matches);
$sttyMode = shell_exec('stty -g');
shell_exec('stty -icanon -echo');
$output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white'));
while (!feof($inputStream)) {
$c = fread($inputStream, 1);
if ("\177" === $c) {
if (0 === $numMatches && 0 !== $i) {
--$i;
$output->write("\033[1D");
}
if ($i === 0) {
$ofs = -1;
$matches = $autocomplete;
$numMatches = count($matches);
} else {
$numMatches = 0;
}
$ret = substr($ret, 0, $i);
} elseif ("\033" === $c) {
$c .= fread($inputStream, 2);
if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) {
if ('A' === $c[2] && -1 === $ofs) {
$ofs = 0;
}
if (0 === $numMatches) {
continue;
}
$ofs += ('A' === $c[2]) ? -1 : 1;
$ofs = ($numMatches + $ofs) % $numMatches;
}
} elseif (ord($c) < 32) {
if ("\t" === $c || "\n" === $c) {
if ($numMatches > 0 && -1 !== $ofs) {
$ret = $matches[$ofs];
$output->write(substr($ret, $i));
$i = strlen($ret);
}
if ("\n" === $c) {
$output->write($c);
break;
}
$numMatches = 0;
}
continue;
} else {
$output->write($c);
$ret .= $c;
++$i;
$numMatches = 0;
$ofs = 0;
foreach ($autocomplete as $value) {
if (0 === strpos($value, $ret) && $i !== strlen($value)) {
$matches[$numMatches++] = $value;
}
}
}
$output->write("\033[K");
if ($numMatches > 0 && -1 !== $ofs) {
$output->write("\0337");
$output->write('<hl>'.substr($matches[$ofs], $i).'</hl>');
$output->write("\0338");
}
}
shell_exec(sprintf('stty %s', $sttyMode));
return $ret;
}
private function getHiddenResponse(OutputInterface $output, $inputStream)
{
if ('\\' === DIRECTORY_SEPARATOR) {
$exe = __DIR__.'/../Resources/bin/hiddeninput.exe';
if ('phar:' === substr(__FILE__, 0, 5)) {
$tmpExe = sys_get_temp_dir().'/hiddeninput.exe';
copy($exe, $tmpExe);
$exe = $tmpExe;
}
$value = rtrim(shell_exec($exe));
$output->writeln('');
if (isset($tmpExe)) {
unlink($tmpExe);
}
return $value;
}
if ($this->hasSttyAvailable()) {
$sttyMode = shell_exec('stty -g');
shell_exec('stty -echo');
$value = fgets($inputStream, 4096);
shell_exec(sprintf('stty %s', $sttyMode));
if (false === $value) {
throw new RuntimeException('Aborted');
}
$value = trim($value);
$output->writeln('');
return $value;
}
if (false !== $shell = $this->getShell()) {
$readCmd = $shell === 'csh' ? '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));
$output->writeln('');
return $value;
}
throw new RuntimeException('Unable to hide the response.');
}
private function validateAttempts($interviewer, OutputInterface $output, Question $question)
{
$error = null;
$attempts = $question->getMaxAttempts();
while (null === $attempts || $attempts--) {
if (null !== $error) {
$this->writeError($output, $error);
}
try {
return call_user_func($question->getValidator(), $interviewer());
} catch (RuntimeException $e) {
throw $e;
} catch (\Exception $error) {
}
}
throw $error;
}
private function getShell()
{
if (null !== self::$shell) {
return self::$shell;
}
self::$shell = false;
if (file_exists('/usr/bin/env')) {
$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('stty 2>&1', $output, $exitcode);
return self::$stty = $exitcode === 0;
}
}