Chris@13: colorMode = $colorMode ?: Configuration::COLOR_MODE_AUTO;
Chris@17: $this->backtrace = \debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
Chris@13:
Chris@13: parent::__construct();
Chris@13: }
Chris@13:
Chris@13: /**
Chris@13: * {@inheritdoc}
Chris@13: */
Chris@13: protected function configure()
Chris@13: {
Chris@13: $this
Chris@13: ->setName('whereami')
Chris@13: ->setDefinition([
Chris@13: new InputOption('num', 'n', InputOption::VALUE_OPTIONAL, 'Number of lines before and after.', '5'),
Chris@13: ])
Chris@13: ->setDescription('Show where you are in the code.')
Chris@13: ->setHelp(
Chris@13: <<<'HELP'
Chris@13: Show where you are in the code.
Chris@13:
Chris@13: Optionally, include how many lines before and after you want to display.
Chris@13:
Chris@13: e.g.
Chris@13: > whereami
Chris@13: > whereami -n10
Chris@13: HELP
Chris@13: );
Chris@13: }
Chris@13:
Chris@13: /**
Chris@13: * Obtains the correct stack frame in the full backtrace.
Chris@13: *
Chris@13: * @return array
Chris@13: */
Chris@13: protected function trace()
Chris@13: {
Chris@17: foreach (\array_reverse($this->backtrace) as $stackFrame) {
Chris@13: if ($this->isDebugCall($stackFrame)) {
Chris@13: return $stackFrame;
Chris@13: }
Chris@13: }
Chris@13:
Chris@17: return \end($this->backtrace);
Chris@13: }
Chris@13:
Chris@13: private static function isDebugCall(array $stackFrame)
Chris@13: {
Chris@13: $class = isset($stackFrame['class']) ? $stackFrame['class'] : null;
Chris@13: $function = isset($stackFrame['function']) ? $stackFrame['function'] : null;
Chris@13:
Chris@13: return ($class === null && $function === 'Psy\debug') ||
Chris@17: ($class === 'Psy\Shell' && \in_array($function, ['__construct', 'debug']));
Chris@13: }
Chris@13:
Chris@13: /**
Chris@13: * Determine the file and line based on the specific backtrace.
Chris@13: *
Chris@13: * @return array
Chris@13: */
Chris@13: protected function fileInfo()
Chris@13: {
Chris@13: $stackFrame = $this->trace();
Chris@17: if (\preg_match('/eval\(/', $stackFrame['file'])) {
Chris@17: \preg_match_all('/([^\(]+)\((\d+)/', $stackFrame['file'], $matches);
Chris@13: $file = $matches[1][0];
Chris@13: $line = (int) $matches[2][0];
Chris@13: } else {
Chris@13: $file = $stackFrame['file'];
Chris@13: $line = $stackFrame['line'];
Chris@13: }
Chris@13:
Chris@17: return \compact('file', 'line');
Chris@13: }
Chris@13:
Chris@13: /**
Chris@13: * {@inheritdoc}
Chris@13: */
Chris@13: protected function execute(InputInterface $input, OutputInterface $output)
Chris@13: {
Chris@13: $info = $this->fileInfo();
Chris@13: $num = $input->getOption('num');
Chris@13: $factory = new ConsoleColorFactory($this->colorMode);
Chris@13: $colors = $factory->getConsoleColor();
Chris@13: $highlighter = new Highlighter($colors);
Chris@17: $contents = \file_get_contents($info['file']);
Chris@13:
Chris@13: $output->startPaging();
Chris@13: $output->writeln('');
Chris@17: $output->writeln(\sprintf('From %s:%s:', $this->replaceCwd($info['file']), $info['line']));
Chris@13: $output->writeln('');
Chris@13: $output->write($highlighter->getCodeSnippet($contents, $info['line'], $num, $num), ShellOutput::OUTPUT_RAW);
Chris@13: $output->stopPaging();
Chris@13: }
Chris@13:
Chris@13: /**
Chris@13: * Replace the given directory from the start of a filepath.
Chris@13: *
Chris@13: * @param string $file
Chris@13: *
Chris@13: * @return string
Chris@13: */
Chris@13: private function replaceCwd($file)
Chris@13: {
Chris@17: $cwd = \getcwd();
Chris@13: if ($cwd === false) {
Chris@13: return $file;
Chris@13: }
Chris@13:
Chris@17: $cwd = \rtrim($cwd, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
Chris@13:
Chris@17: return \preg_replace('/^' . \preg_quote($cwd, '/') . '/', '', $file);
Chris@13: }
Chris@13: }