Chris@0: filter = new FilterOptions(); Chris@0: Chris@0: parent::__construct($name); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Set the Shell's Readline service. Chris@0: * Chris@0: * @param Readline $readline Chris@0: */ Chris@0: public function setReadline(Readline $readline) Chris@0: { Chris@0: $this->readline = $readline; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: protected function configure() Chris@0: { Chris@0: list($grep, $insensitive, $invert) = FilterOptions::getOptions(); Chris@0: Chris@0: $this Chris@0: ->setName('history') Chris@0: ->setAliases(['hist']) Chris@0: ->setDefinition([ Chris@0: new InputOption('show', 's', InputOption::VALUE_REQUIRED, 'Show the given range of lines.'), Chris@0: new InputOption('head', 'H', InputOption::VALUE_REQUIRED, 'Display the first N items.'), Chris@0: new InputOption('tail', 'T', InputOption::VALUE_REQUIRED, 'Display the last N items.'), Chris@0: Chris@0: $grep, Chris@0: $insensitive, Chris@0: $invert, Chris@0: Chris@0: new InputOption('no-numbers', 'N', InputOption::VALUE_NONE, 'Omit line numbers.'), Chris@0: Chris@0: new InputOption('save', '', InputOption::VALUE_REQUIRED, 'Save history to a file.'), Chris@0: new InputOption('replay', '', InputOption::VALUE_NONE, 'Replay.'), Chris@0: new InputOption('clear', '', InputOption::VALUE_NONE, 'Clear the history.'), Chris@0: ]) Chris@0: ->setDescription('Show the Psy Shell history.') Chris@0: ->setHelp( Chris@0: <<<'HELP' Chris@0: Show, search, save or replay the Psy Shell history. Chris@0: Chris@0: e.g. Chris@0: >>> history --grep /[bB]acon/ Chris@0: >>> history --show 0..10 --replay Chris@0: >>> history --clear Chris@0: >>> history --tail 1000 --save somefile.txt Chris@0: HELP Chris@0: ); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: protected function execute(InputInterface $input, OutputInterface $output) Chris@0: { Chris@0: $this->validateOnlyOne($input, ['show', 'head', 'tail']); Chris@0: $this->validateOnlyOne($input, ['save', 'replay', 'clear']); Chris@0: Chris@0: $history = $this->getHistorySlice( Chris@0: $input->getOption('show'), Chris@0: $input->getOption('head'), Chris@0: $input->getOption('tail') Chris@0: ); Chris@0: $highlighted = false; Chris@0: Chris@0: $this->filter->bind($input); Chris@0: if ($this->filter->hasFilter()) { Chris@0: $matches = []; Chris@0: $highlighted = []; Chris@0: foreach ($history as $i => $line) { Chris@0: if ($this->filter->match($line, $matches)) { Chris@0: if (isset($matches[0])) { Chris@4: $chunks = \explode($matches[0], $history[$i]); Chris@4: $chunks = \array_map([__CLASS__, 'escape'], $chunks); Chris@4: $glue = \sprintf('%s', self::escape($matches[0])); Chris@0: Chris@4: $highlighted[$i] = \implode($glue, $chunks); Chris@0: } Chris@0: } else { Chris@0: unset($history[$i]); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: if ($save = $input->getOption('save')) { Chris@4: $output->writeln(\sprintf('Saving history in %s...', $save)); Chris@4: \file_put_contents($save, \implode(PHP_EOL, $history) . PHP_EOL); Chris@0: $output->writeln('History saved.'); Chris@0: } elseif ($input->getOption('replay')) { Chris@0: if (!($input->getOption('show') || $input->getOption('head') || $input->getOption('tail'))) { Chris@0: throw new \InvalidArgumentException('You must limit history via --head, --tail or --show before replaying'); Chris@0: } Chris@0: Chris@4: $count = \count($history); Chris@4: $output->writeln(\sprintf('Replaying %d line%s of history', $count, ($count !== 1) ? 's' : '')); Chris@0: $this->getApplication()->addInput($history); Chris@0: } elseif ($input->getOption('clear')) { Chris@0: $this->clearHistory(); Chris@0: $output->writeln('History cleared.'); Chris@0: } else { Chris@0: $type = $input->getOption('no-numbers') ? 0 : ShellOutput::NUMBER_LINES; Chris@0: if (!$highlighted) { Chris@0: $type = $type | ShellOutput::OUTPUT_RAW; Chris@0: } Chris@0: Chris@0: $output->page($highlighted ?: $history, $type); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Extract a range from a string. Chris@0: * Chris@0: * @param string $range Chris@0: * Chris@0: * @return array [ start, end ] Chris@0: */ Chris@0: private function extractRange($range) Chris@0: { Chris@4: if (\preg_match('/^\d+$/', $range)) { Chris@0: return [$range, $range + 1]; Chris@0: } Chris@0: Chris@0: $matches = []; Chris@4: if ($range !== '..' && \preg_match('/^(\d*)\.\.(\d*)$/', $range, $matches)) { Chris@4: $start = $matches[1] ? \intval($matches[1]) : 0; Chris@4: $end = $matches[2] ? \intval($matches[2]) + 1 : PHP_INT_MAX; Chris@0: Chris@0: return [$start, $end]; Chris@0: } Chris@0: Chris@0: throw new \InvalidArgumentException('Unexpected range: ' . $range); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Retrieve a slice of the readline history. Chris@0: * Chris@0: * @param string $show Chris@0: * @param string $head Chris@0: * @param string $tail Chris@0: * Chris@0: * @return array A slilce of history Chris@0: */ Chris@0: private function getHistorySlice($show, $head, $tail) Chris@0: { Chris@0: $history = $this->readline->listHistory(); Chris@0: Chris@0: // don't show the current `history` invocation Chris@4: \array_pop($history); Chris@0: Chris@0: if ($show) { Chris@0: list($start, $end) = $this->extractRange($show); Chris@0: $length = $end - $start; Chris@0: } elseif ($head) { Chris@4: if (!\preg_match('/^\d+$/', $head)) { Chris@0: throw new \InvalidArgumentException('Please specify an integer argument for --head'); Chris@0: } Chris@0: Chris@0: $start = 0; Chris@4: $length = \intval($head); Chris@0: } elseif ($tail) { Chris@4: if (!\preg_match('/^\d+$/', $tail)) { Chris@0: throw new \InvalidArgumentException('Please specify an integer argument for --tail'); Chris@0: } Chris@0: Chris@4: $start = \count($history) - $tail; Chris@4: $length = \intval($tail) + 1; Chris@0: } else { Chris@0: return $history; Chris@0: } Chris@0: Chris@4: return \array_slice($history, $start, $length, true); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Validate that only one of the given $options is set. Chris@0: * Chris@0: * @param InputInterface $input Chris@0: * @param array $options Chris@0: */ Chris@0: private function validateOnlyOne(InputInterface $input, array $options) Chris@0: { Chris@0: $count = 0; Chris@0: foreach ($options as $opt) { Chris@0: if ($input->getOption($opt)) { Chris@0: $count++; Chris@0: } Chris@0: } Chris@0: Chris@0: if ($count > 1) { Chris@4: throw new \InvalidArgumentException('Please specify only one of --' . \implode(', --', $options)); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Clear the readline history. Chris@0: */ Chris@0: private function clearHistory() Chris@0: { Chris@0: $this->readline->clearHistory(); Chris@0: } Chris@0: Chris@0: public static function escape($string) Chris@0: { Chris@0: return OutputFormatter::escape($string); Chris@0: } Chris@0: }