PageRenderTime 51ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/vendor/symfony/console/Helper/DialogHelper.php

https://gitlab.com/Pasantias/pasantiasASLG
PHP | 500 lines | 339 code | 58 blank | 103 comment | 54 complexity | 923eb5ab6f88798c5d4ca35e37948b57 MD5 | raw file
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Console\Helper;
  11. use Symfony\Component\Console\Output\ConsoleOutputInterface;
  12. use Symfony\Component\Console\Output\OutputInterface;
  13. use Symfony\Component\Console\Formatter\OutputFormatterStyle;
  14. /**
  15. * The Dialog class provides helpers to interact with the user.
  16. *
  17. * @author Fabien Potencier <fabien@symfony.com>
  18. *
  19. * @deprecated since version 2.5, to be removed in 3.0.
  20. * Use {@link \Symfony\Component\Console\Helper\QuestionHelper} instead.
  21. */
  22. class DialogHelper extends InputAwareHelper
  23. {
  24. private $inputStream;
  25. private static $shell;
  26. private static $stty;
  27. public function __construct($triggerDeprecationError = true)
  28. {
  29. if ($triggerDeprecationError) {
  30. @trigger_error('"Symfony\Component\Console\Helper\DialogHelper" is deprecated since version 2.5 and will be removed in 3.0. Use "Symfony\Component\Console\Helper\QuestionHelper" instead.', E_USER_DEPRECATED);
  31. }
  32. }
  33. /**
  34. * Asks the user to select a value.
  35. *
  36. * @param OutputInterface $output An Output instance
  37. * @param string|array $question The question to ask
  38. * @param array $choices List of choices to pick from
  39. * @param bool|string $default The default answer if the user enters nothing
  40. * @param bool|int $attempts Max number of times to ask before giving up (false by default, which means infinite)
  41. * @param string $errorMessage Message which will be shown if invalid value from choice list would be picked
  42. * @param bool $multiselect Select more than one value separated by comma
  43. *
  44. * @return int|string|array The selected value or values (the key of the choices array)
  45. *
  46. * @throws \InvalidArgumentException
  47. */
  48. public function select(OutputInterface $output, $question, $choices, $default = null, $attempts = false, $errorMessage = 'Value "%s" is invalid', $multiselect = false)
  49. {
  50. if ($output instanceof ConsoleOutputInterface) {
  51. $output = $output->getErrorOutput();
  52. }
  53. $width = max(array_map('strlen', array_keys($choices)));
  54. $messages = (array) $question;
  55. foreach ($choices as $key => $value) {
  56. $messages[] = sprintf(" [<info>%-{$width}s</info>] %s", $key, $value);
  57. }
  58. $output->writeln($messages);
  59. $result = $this->askAndValidate($output, '> ', function ($picked) use ($choices, $errorMessage, $multiselect) {
  60. // Collapse all spaces.
  61. $selectedChoices = str_replace(' ', '', $picked);
  62. if ($multiselect) {
  63. // Check for a separated comma values
  64. if (!preg_match('/^[a-zA-Z0-9_-]+(?:,[a-zA-Z0-9_-]+)*$/', $selectedChoices, $matches)) {
  65. throw new \InvalidArgumentException(sprintf($errorMessage, $picked));
  66. }
  67. $selectedChoices = explode(',', $selectedChoices);
  68. } else {
  69. $selectedChoices = array($picked);
  70. }
  71. $multiselectChoices = array();
  72. foreach ($selectedChoices as $value) {
  73. if (empty($choices[$value])) {
  74. throw new \InvalidArgumentException(sprintf($errorMessage, $value));
  75. }
  76. $multiselectChoices[] = $value;
  77. }
  78. if ($multiselect) {
  79. return $multiselectChoices;
  80. }
  81. return $picked;
  82. }, $attempts, $default);
  83. return $result;
  84. }
  85. /**
  86. * Asks a question to the user.
  87. *
  88. * @param OutputInterface $output An Output instance
  89. * @param string|array $question The question to ask
  90. * @param string $default The default answer if none is given by the user
  91. * @param array $autocomplete List of values to autocomplete
  92. *
  93. * @return string The user answer
  94. *
  95. * @throws \RuntimeException If there is no data to read in the input stream
  96. */
  97. public function ask(OutputInterface $output, $question, $default = null, array $autocomplete = null)
  98. {
  99. if ($this->input && !$this->input->isInteractive()) {
  100. return $default;
  101. }
  102. if ($output instanceof ConsoleOutputInterface) {
  103. $output = $output->getErrorOutput();
  104. }
  105. $output->write($question);
  106. $inputStream = $this->inputStream ?: STDIN;
  107. if (null === $autocomplete || !$this->hasSttyAvailable()) {
  108. $ret = fgets($inputStream, 4096);
  109. if (false === $ret) {
  110. throw new \RuntimeException('Aborted');
  111. }
  112. $ret = trim($ret);
  113. } else {
  114. $ret = '';
  115. $i = 0;
  116. $ofs = -1;
  117. $matches = $autocomplete;
  118. $numMatches = count($matches);
  119. $sttyMode = shell_exec('stty -g');
  120. // Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead)
  121. shell_exec('stty -icanon -echo');
  122. // Add highlighted text style
  123. $output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white'));
  124. // Read a keypress
  125. while (!feof($inputStream)) {
  126. $c = fread($inputStream, 1);
  127. // Backspace Character
  128. if ("\177" === $c) {
  129. if (0 === $numMatches && 0 !== $i) {
  130. --$i;
  131. // Move cursor backwards
  132. $output->write("\033[1D");
  133. }
  134. if ($i === 0) {
  135. $ofs = -1;
  136. $matches = $autocomplete;
  137. $numMatches = count($matches);
  138. } else {
  139. $numMatches = 0;
  140. }
  141. // Pop the last character off the end of our string
  142. $ret = substr($ret, 0, $i);
  143. } elseif ("\033" === $c) {
  144. // Did we read an escape sequence?
  145. $c .= fread($inputStream, 2);
  146. // A = Up Arrow. B = Down Arrow
  147. if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) {
  148. if ('A' === $c[2] && -1 === $ofs) {
  149. $ofs = 0;
  150. }
  151. if (0 === $numMatches) {
  152. continue;
  153. }
  154. $ofs += ('A' === $c[2]) ? -1 : 1;
  155. $ofs = ($numMatches + $ofs) % $numMatches;
  156. }
  157. } elseif (ord($c) < 32) {
  158. if ("\t" === $c || "\n" === $c) {
  159. if ($numMatches > 0 && -1 !== $ofs) {
  160. $ret = $matches[$ofs];
  161. // Echo out remaining chars for current match
  162. $output->write(substr($ret, $i));
  163. $i = strlen($ret);
  164. }
  165. if ("\n" === $c) {
  166. $output->write($c);
  167. break;
  168. }
  169. $numMatches = 0;
  170. }
  171. continue;
  172. } else {
  173. $output->write($c);
  174. $ret .= $c;
  175. ++$i;
  176. $numMatches = 0;
  177. $ofs = 0;
  178. foreach ($autocomplete as $value) {
  179. // If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle)
  180. if (0 === strpos($value, $ret) && $i !== strlen($value)) {
  181. $matches[$numMatches++] = $value;
  182. }
  183. }
  184. }
  185. // Erase characters from cursor to end of line
  186. $output->write("\033[K");
  187. if ($numMatches > 0 && -1 !== $ofs) {
  188. // Save cursor position
  189. $output->write("\0337");
  190. // Write highlighted text
  191. $output->write('<hl>'.substr($matches[$ofs], $i).'</hl>');
  192. // Restore cursor position
  193. $output->write("\0338");
  194. }
  195. }
  196. // Reset stty so it behaves normally again
  197. shell_exec(sprintf('stty %s', $sttyMode));
  198. }
  199. return strlen($ret) > 0 ? $ret : $default;
  200. }
  201. /**
  202. * Asks a confirmation to the user.
  203. *
  204. * The question will be asked until the user answers by nothing, yes, or no.
  205. *
  206. * @param OutputInterface $output An Output instance
  207. * @param string|array $question The question to ask
  208. * @param bool $default The default answer if the user enters nothing
  209. *
  210. * @return bool true if the user has confirmed, false otherwise
  211. */
  212. public function askConfirmation(OutputInterface $output, $question, $default = true)
  213. {
  214. $answer = 'z';
  215. while ($answer && !in_array(strtolower($answer[0]), array('y', 'n'))) {
  216. $answer = $this->ask($output, $question);
  217. }
  218. if (false === $default) {
  219. return $answer && 'y' == strtolower($answer[0]);
  220. }
  221. return !$answer || 'y' == strtolower($answer[0]);
  222. }
  223. /**
  224. * Asks a question to the user, the response is hidden.
  225. *
  226. * @param OutputInterface $output An Output instance
  227. * @param string|array $question The question
  228. * @param bool $fallback In case the response can not be hidden, whether to fallback on non-hidden question or not
  229. *
  230. * @return string The answer
  231. *
  232. * @throws \RuntimeException In case the fallback is deactivated and the response can not be hidden
  233. */
  234. public function askHiddenResponse(OutputInterface $output, $question, $fallback = true)
  235. {
  236. if ($output instanceof ConsoleOutputInterface) {
  237. $output = $output->getErrorOutput();
  238. }
  239. if ('\\' === DIRECTORY_SEPARATOR) {
  240. $exe = __DIR__.'/../Resources/bin/hiddeninput.exe';
  241. // handle code running from a phar
  242. if ('phar:' === substr(__FILE__, 0, 5)) {
  243. $tmpExe = sys_get_temp_dir().'/hiddeninput.exe';
  244. copy($exe, $tmpExe);
  245. $exe = $tmpExe;
  246. }
  247. $output->write($question);
  248. $value = rtrim(shell_exec($exe));
  249. $output->writeln('');
  250. if (isset($tmpExe)) {
  251. unlink($tmpExe);
  252. }
  253. return $value;
  254. }
  255. if ($this->hasSttyAvailable()) {
  256. $output->write($question);
  257. $sttyMode = shell_exec('stty -g');
  258. shell_exec('stty -echo');
  259. $value = fgets($this->inputStream ?: STDIN, 4096);
  260. shell_exec(sprintf('stty %s', $sttyMode));
  261. if (false === $value) {
  262. throw new \RuntimeException('Aborted');
  263. }
  264. $value = trim($value);
  265. $output->writeln('');
  266. return $value;
  267. }
  268. if (false !== $shell = $this->getShell()) {
  269. $output->write($question);
  270. $readCmd = $shell === 'csh' ? 'set mypassword = $<' : 'read -r mypassword';
  271. $command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd);
  272. $value = rtrim(shell_exec($command));
  273. $output->writeln('');
  274. return $value;
  275. }
  276. if ($fallback) {
  277. return $this->ask($output, $question);
  278. }
  279. throw new \RuntimeException('Unable to hide the response');
  280. }
  281. /**
  282. * Asks for a value and validates the response.
  283. *
  284. * The validator receives the data to validate. It must return the
  285. * validated data when the data is valid and throw an exception
  286. * otherwise.
  287. *
  288. * @param OutputInterface $output An Output instance
  289. * @param string|array $question The question to ask
  290. * @param callable $validator A PHP callback
  291. * @param int|false $attempts Max number of times to ask before giving up (false by default, which means infinite)
  292. * @param string $default The default answer if none is given by the user
  293. * @param array $autocomplete List of values to autocomplete
  294. *
  295. * @return mixed
  296. *
  297. * @throws \Exception When any of the validators return an error
  298. */
  299. public function askAndValidate(OutputInterface $output, $question, $validator, $attempts = false, $default = null, array $autocomplete = null)
  300. {
  301. $that = $this;
  302. $interviewer = function () use ($output, $question, $default, $autocomplete, $that) {
  303. return $that->ask($output, $question, $default, $autocomplete);
  304. };
  305. return $this->validateAttempts($interviewer, $output, $validator, $attempts);
  306. }
  307. /**
  308. * Asks for a value, hide and validates the response.
  309. *
  310. * The validator receives the data to validate. It must return the
  311. * validated data when the data is valid and throw an exception
  312. * otherwise.
  313. *
  314. * @param OutputInterface $output An Output instance
  315. * @param string|array $question The question to ask
  316. * @param callable $validator A PHP callback
  317. * @param int|false $attempts Max number of times to ask before giving up (false by default, which means infinite)
  318. * @param bool $fallback In case the response can not be hidden, whether to fallback on non-hidden question or not
  319. *
  320. * @return string The response
  321. *
  322. * @throws \Exception When any of the validators return an error
  323. * @throws \RuntimeException In case the fallback is deactivated and the response can not be hidden
  324. */
  325. public function askHiddenResponseAndValidate(OutputInterface $output, $question, $validator, $attempts = false, $fallback = true)
  326. {
  327. $that = $this;
  328. $interviewer = function () use ($output, $question, $fallback, $that) {
  329. return $that->askHiddenResponse($output, $question, $fallback);
  330. };
  331. return $this->validateAttempts($interviewer, $output, $validator, $attempts);
  332. }
  333. /**
  334. * Sets the input stream to read from when interacting with the user.
  335. *
  336. * This is mainly useful for testing purpose.
  337. *
  338. * @param resource $stream The input stream
  339. */
  340. public function setInputStream($stream)
  341. {
  342. $this->inputStream = $stream;
  343. }
  344. /**
  345. * Returns the helper's input stream.
  346. *
  347. * @return resource|null The input stream or null if the default STDIN is used
  348. */
  349. public function getInputStream()
  350. {
  351. return $this->inputStream;
  352. }
  353. /**
  354. * {@inheritdoc}
  355. */
  356. public function getName()
  357. {
  358. return 'dialog';
  359. }
  360. /**
  361. * Return a valid Unix shell.
  362. *
  363. * @return string|bool The valid shell name, false in case no valid shell is found
  364. */
  365. private function getShell()
  366. {
  367. if (null !== self::$shell) {
  368. return self::$shell;
  369. }
  370. self::$shell = false;
  371. if (file_exists('/usr/bin/env')) {
  372. // handle other OSs with bash/zsh/ksh/csh if available to hide the answer
  373. $test = "/usr/bin/env %s -c 'echo OK' 2> /dev/null";
  374. foreach (array('bash', 'zsh', 'ksh', 'csh') as $sh) {
  375. if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) {
  376. self::$shell = $sh;
  377. break;
  378. }
  379. }
  380. }
  381. return self::$shell;
  382. }
  383. private function hasSttyAvailable()
  384. {
  385. if (null !== self::$stty) {
  386. return self::$stty;
  387. }
  388. exec('stty 2>&1', $output, $exitcode);
  389. return self::$stty = $exitcode === 0;
  390. }
  391. /**
  392. * Validate an attempt.
  393. *
  394. * @param callable $interviewer A callable that will ask for a question and return the result
  395. * @param OutputInterface $output An Output instance
  396. * @param callable $validator A PHP callback
  397. * @param int|false $attempts Max number of times to ask before giving up; false will ask infinitely
  398. *
  399. * @return string The validated response
  400. *
  401. * @throws \Exception In case the max number of attempts has been reached and no valid response has been given
  402. */
  403. private function validateAttempts($interviewer, OutputInterface $output, $validator, $attempts)
  404. {
  405. if ($output instanceof ConsoleOutputInterface) {
  406. $output = $output->getErrorOutput();
  407. }
  408. $e = null;
  409. while (false === $attempts || $attempts--) {
  410. if (null !== $e) {
  411. $output->writeln($this->getHelperSet()->get('formatter')->formatBlock($e->getMessage(), 'error'));
  412. }
  413. try {
  414. return call_user_func($validator, $interviewer());
  415. } catch (\Exception $e) {
  416. }
  417. }
  418. throw $e;
  419. }
  420. }