Yaffs site version 1.1
[yaffs-website] / vendor / psy / psysh / src / Psy / Command / TraceCommand.php
1 <?php
2
3 /*
4  * This file is part of Psy Shell.
5  *
6  * (c) 2012-2017 Justin Hileman
7  *
8  * For the full copyright and license information, please view the LICENSE
9  * file that was distributed with this source code.
10  */
11
12 namespace Psy\Command;
13
14 use Psy\Input\FilterOptions;
15 use Psy\Output\ShellOutput;
16 use Symfony\Component\Console\Formatter\OutputFormatter;
17 use Symfony\Component\Console\Input\InputInterface;
18 use Symfony\Component\Console\Input\InputOption;
19 use Symfony\Component\Console\Output\OutputInterface;
20
21 /**
22  * Show the current stack trace.
23  */
24 class TraceCommand extends Command
25 {
26     protected $filter;
27
28     /**
29      * {@inheritdoc}
30      */
31     public function __construct($name = null)
32     {
33         $this->filter = new FilterOptions();
34
35         parent::__construct($name);
36     }
37
38     /**
39      * {@inheritdoc}
40      */
41     protected function configure()
42     {
43         list($grep, $insensitive, $invert) = FilterOptions::getOptions();
44
45         $this
46             ->setName('trace')
47             ->setDefinition(array(
48                 new InputOption('include-psy', 'p', InputOption::VALUE_NONE,     'Include Psy in the call stack.'),
49                 new InputOption('num',         'n', InputOption::VALUE_REQUIRED, 'Only include NUM lines.'),
50
51                 $grep,
52                 $insensitive,
53                 $invert,
54             ))
55             ->setDescription('Show the current call stack.')
56             ->setHelp(
57                 <<<'HELP'
58 Show the current call stack.
59
60 Optionally, include PsySH in the call stack by passing the <info>--include-psy</info> option.
61
62 e.g.
63 <return>> trace -n10</return>
64 <return>> trace --include-psy</return>
65 HELP
66             );
67     }
68
69     /**
70      * {@inheritdoc}
71      */
72     protected function execute(InputInterface $input, OutputInterface $output)
73     {
74         $this->filter->bind($input);
75         $trace = $this->getBacktrace(new \Exception(), $input->getOption('num'), $input->getOption('include-psy'));
76         $output->page($trace, ShellOutput::NUMBER_LINES);
77     }
78
79     /**
80      * Get a backtrace for an exception.
81      *
82      * Optionally limit the number of rows to include with $count, and exclude
83      * Psy from the trace.
84      *
85      * @param \Exception $e          The exception with a backtrace
86      * @param int        $count      (default: PHP_INT_MAX)
87      * @param bool       $includePsy (default: true)
88      *
89      * @return array Formatted stacktrace lines
90      */
91     protected function getBacktrace(\Exception $e, $count = null, $includePsy = true)
92     {
93         if ($cwd = getcwd()) {
94             $cwd = rtrim($cwd, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
95         }
96
97         if ($count === null) {
98             $count = PHP_INT_MAX;
99         }
100
101         $lines = array();
102
103         $trace = $e->getTrace();
104         array_unshift($trace, array(
105             'function' => '',
106             'file'     => $e->getFile() !== null ? $e->getFile() : 'n/a',
107             'line'     => $e->getLine() !== null ? $e->getLine() : 'n/a',
108             'args'     => array(),
109         ));
110
111         if (!$includePsy) {
112             for ($i = count($trace) - 1; $i >= 0; $i--) {
113                 $thing = isset($trace[$i]['class']) ? $trace[$i]['class'] : $trace[$i]['function'];
114                 if (preg_match('/\\\\?Psy\\\\/', $thing)) {
115                     $trace = array_slice($trace, $i + 1);
116                     break;
117                 }
118             }
119         }
120
121         for ($i = 0, $count = min($count, count($trace)); $i < $count; $i++) {
122             $class    = isset($trace[$i]['class']) ? $trace[$i]['class'] : '';
123             $type     = isset($trace[$i]['type']) ? $trace[$i]['type'] : '';
124             $function = $trace[$i]['function'];
125             $file     = isset($trace[$i]['file']) ? $this->replaceCwd($cwd, $trace[$i]['file']) : 'n/a';
126             $line     = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a';
127
128             // Leave execution loop out of the `eval()'d code` lines
129             if (preg_match("#/Psy/ExecutionLoop/Loop.php\(\d+\) : eval\(\)'d code$#", $file)) {
130                 $file = "eval()'d code";
131             }
132
133             // Skip any lines that don't match our filter options
134             if (!$this->filter->match(sprintf('%s%s%s() at %s:%s', $class, $type, $function, $file, $line))) {
135                 continue;
136             }
137
138             $lines[] = sprintf(
139                 ' <class>%s</class>%s%s() at <info>%s:%s</info>',
140                 OutputFormatter::escape($class),
141                 OutputFormatter::escape($type),
142                 OutputFormatter::escape($function),
143                 OutputFormatter::escape($file),
144                 OutputFormatter::escape($line)
145             );
146         }
147
148         return $lines;
149     }
150
151     /**
152      * Replace the given directory from the start of a filepath.
153      *
154      * @param string $cwd
155      * @param string $file
156      *
157      * @return string
158      */
159     private function replaceCwd($cwd, $file)
160     {
161         if ($cwd === false) {
162             return $file;
163         } else {
164             return preg_replace('/^' . preg_quote($cwd, '/') . '/', '', $file);
165         }
166     }
167 }