PageRenderTime 27ms CodeModel.GetById 0ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/psy/psysh/src/Psy/Command/HistoryCommand.php

https://gitlab.com/techniconline/kmc
PHP | 260 lines | 171 code | 33 blank | 56 comment | 23 complexity | f5eb73a428c2238f2d03b5b276965766 MD5 | raw file
  1. <?php
  2. /*
  3. * This file is part of Psy Shell
  4. *
  5. * (c) 2012-2014 Justin Hileman
  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 Psy\Command;
  11. use Psy\Output\ShellOutput;
  12. use Psy\Readline\Readline;
  13. use Symfony\Component\Console\Formatter\OutputFormatter;
  14. use Symfony\Component\Console\Input\InputInterface;
  15. use Symfony\Component\Console\Input\InputOption;
  16. use Symfony\Component\Console\Output\OutputInterface;
  17. /**
  18. * Psy Shell history command.
  19. *
  20. * Shows, searches and replays readline history. Not too shabby.
  21. */
  22. class HistoryCommand extends Command
  23. {
  24. /**
  25. * Set the Shell's Readline service.
  26. *
  27. * @param Readline $readline
  28. */
  29. public function setReadline(Readline $readline)
  30. {
  31. $this->readline = $readline;
  32. }
  33. /**
  34. * {@inheritdoc}
  35. */
  36. protected function configure()
  37. {
  38. $this
  39. ->setName('history')
  40. ->setAliases(array('hist'))
  41. ->setDefinition(array(
  42. new InputOption('show', 's', InputOption::VALUE_REQUIRED, 'Show the given range of lines'),
  43. new InputOption('head', 'H', InputOption::VALUE_REQUIRED, 'Display the first N items.'),
  44. new InputOption('tail', 'T', InputOption::VALUE_REQUIRED, 'Display the last N items.'),
  45. new InputOption('grep', 'G', InputOption::VALUE_REQUIRED, 'Show lines matching the given pattern (string or regex).'),
  46. new InputOption('insensitive', 'i', InputOption::VALUE_NONE, 'Case insensitive search (requires --grep).'),
  47. new InputOption('invert', 'v', InputOption::VALUE_NONE, 'Inverted search (requires --grep).'),
  48. new InputOption('no-numbers', 'N', InputOption::VALUE_NONE, 'Omit line numbers.'),
  49. new InputOption('save', '', InputOption::VALUE_REQUIRED, 'Save history to a file.'),
  50. new InputOption('replay', '', InputOption::VALUE_NONE, 'Replay'),
  51. new InputOption('clear', '', InputOption::VALUE_NONE, 'Clear the history.'),
  52. ))
  53. ->setDescription('Show the Psy Shell history.')
  54. ->setHelp(
  55. <<<HELP
  56. Show, search, save or replay the Psy Shell history.
  57. e.g.
  58. <return>>>> history --grep /[bB]acon/</return>
  59. <return>>>> history --show 0..10 --replay</return>
  60. <return>>>> history --clear</return>
  61. <return>>>> history --tail 1000 --save somefile.txt</return>
  62. HELP
  63. );
  64. }
  65. /**
  66. * {@inheritdoc}
  67. */
  68. protected function execute(InputInterface $input, OutputInterface $output)
  69. {
  70. $this->validateOnlyOne($input, array('show', 'head', 'tail'));
  71. $this->validateOnlyOne($input, array('save', 'replay', 'clear'));
  72. $history = $this->getHistorySlice(
  73. $input->getOption('show'),
  74. $input->getOption('head'),
  75. $input->getOption('tail')
  76. );
  77. $highlighted = false;
  78. $invert = $input->getOption('invert');
  79. $insensitive = $input->getOption('insensitive');
  80. if ($pattern = $input->getOption('grep')) {
  81. if (substr($pattern, 0, 1) !== '/' || substr($pattern, -1) !== '/' || strlen($pattern) < 3) {
  82. $pattern = '/' . preg_quote($pattern, '/') . '/';
  83. }
  84. if ($insensitive) {
  85. $pattern .= 'i';
  86. }
  87. $this->validateRegex($pattern);
  88. $matches = array();
  89. $highlighted = array();
  90. foreach ($history as $i => $line) {
  91. if (preg_match($pattern, $line, $matches) xor $invert) {
  92. if (!$invert) {
  93. $chunks = explode($matches[0], $history[$i]);
  94. $chunks = array_map(array(__CLASS__, 'escape'), $chunks);
  95. $glue = sprintf('<urgent>%s</urgent>', self::escape($matches[0]));
  96. $highlighted[$i] = implode($glue, $chunks);
  97. }
  98. } else {
  99. unset($history[$i]);
  100. }
  101. }
  102. } elseif ($invert) {
  103. throw new \InvalidArgumentException('Cannot use -v without --grep.');
  104. } elseif ($insensitive) {
  105. throw new \InvalidArgumentException('Cannot use -i without --grep.');
  106. }
  107. if ($save = $input->getOption('save')) {
  108. $output->writeln(sprintf('Saving history in %s...', $save));
  109. file_put_contents($save, implode(PHP_EOL, $history) . PHP_EOL);
  110. $output->writeln('<info>History saved.</info>');
  111. } elseif ($input->getOption('replay')) {
  112. if (!($input->getOption('show') || $input->getOption('head') || $input->getOption('tail'))) {
  113. throw new \InvalidArgumentException('You must limit history via --head, --tail or --show before replaying.');
  114. }
  115. $count = count($history);
  116. $output->writeln(sprintf('Replaying %d line%s of history', $count, ($count !== 1) ? 's' : ''));
  117. $this->getApplication()->addInput($history);
  118. } elseif ($input->getOption('clear')) {
  119. $this->clearHistory();
  120. $output->writeln('<info>History cleared.</info>');
  121. } else {
  122. $type = $input->getOption('no-numbers') ? 0 : ShellOutput::NUMBER_LINES;
  123. if (!$highlighted) {
  124. $type = $type | ShellOutput::OUTPUT_RAW;
  125. }
  126. $output->page($highlighted ?: $history, $type);
  127. }
  128. }
  129. /**
  130. * Extract a range from a string.
  131. *
  132. * @param string $range
  133. *
  134. * @return array [ start, end ]
  135. */
  136. private function extractRange($range)
  137. {
  138. if (preg_match('/^\d+$/', $range)) {
  139. return array($range, $range + 1);
  140. }
  141. $matches = array();
  142. if ($range !== '..' && preg_match('/^(\d*)\.\.(\d*)$/', $range, $matches)) {
  143. $start = $matches[1] ? intval($matches[1]) : 0;
  144. $end = $matches[2] ? intval($matches[2]) + 1 : PHP_INT_MAX;
  145. return array($start, $end);
  146. }
  147. throw new \InvalidArgumentException('Unexpected range: ' . $range);
  148. }
  149. /**
  150. * Retrieve a slice of the readline history.
  151. *
  152. * @param string $show
  153. * @param string $head
  154. * @param string $tail
  155. *
  156. * @return array A slilce of history.
  157. */
  158. private function getHistorySlice($show, $head, $tail)
  159. {
  160. $history = $this->readline->listHistory();
  161. if ($show) {
  162. list($start, $end) = $this->extractRange($show);
  163. $length = $end - $start;
  164. } elseif ($head) {
  165. if (!preg_match('/^\d+$/', $head)) {
  166. throw new \InvalidArgumentException('Please specify an integer argument for --head.');
  167. }
  168. $start = 0;
  169. $length = intval($head);
  170. } elseif ($tail) {
  171. if (!preg_match('/^\d+$/', $tail)) {
  172. throw new \InvalidArgumentException('Please specify an integer argument for --tail.');
  173. }
  174. $start = count($history) - $tail;
  175. $length = intval($tail) + 1;
  176. } else {
  177. return $history;
  178. }
  179. return array_slice($history, $start, $length, true);
  180. }
  181. /**
  182. * Validate that $pattern is a valid regular expression.
  183. *
  184. * @param string $pattern
  185. *
  186. * @return boolean
  187. */
  188. private function validateRegex($pattern)
  189. {
  190. set_error_handler(array('Psy\Exception\ErrorException', 'throwException'));
  191. try {
  192. preg_match($pattern, '');
  193. } catch (ErrorException $e) {
  194. throw new RuntimeException(str_replace('preg_match(): ', 'Invalid regular expression: ', $e->getRawMessage()));
  195. }
  196. restore_error_handler();
  197. }
  198. /**
  199. * Validate that only one of the given $options is set.
  200. *
  201. * @param InputInterface $input
  202. * @param array $options
  203. */
  204. private function validateOnlyOne(InputInterface $input, array $options)
  205. {
  206. $count = 0;
  207. foreach ($options as $opt) {
  208. if ($input->getOption($opt)) {
  209. $count++;
  210. }
  211. }
  212. if ($count > 1) {
  213. throw new \InvalidArgumentException('Please specify only one of --' . implode(', --', $options));
  214. }
  215. }
  216. /**
  217. * Clear the readline history.
  218. */
  219. private function clearHistory()
  220. {
  221. $this->readline->clearHistory();
  222. }
  223. public static function escape($string)
  224. {
  225. return OutputFormatter::escape($string);
  226. }
  227. }