Passed
Push — stable ( 3bae5b...a6d1c3 )
by Nuno
14:40
created

Writer::renderEditor()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 16
ccs 9
cts 9
cp 1
rs 9.2
c 0
b 0
f 0
cc 4
eloc 9
nc 2
nop 1
crap 4
1
<?php
2
3
/**
4
 * This file is part of Collision.
5
 *
6
 * (c) Nuno Maduro <[email protected]>
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 NunoMaduro\Collision;
13
14
use Whoops\Exception\Frame;
15
use Whoops\Exception\Inspector;
16
use Symfony\Component\Console\Output\ConsoleOutput;
17
use Symfony\Component\Console\Output\OutputInterface;
18
use NunoMaduro\Collision\Contracts\Writer as WriterContract;
19
use NunoMaduro\Collision\Contracts\ArgumentFormatter as ArgumentFormatterContract;
20
21
/**
22
 * This is an Collision Writer implementation.
23
 *
24
 * @author Nuno Maduro <[email protected]>
25
 */
26
class Writer implements WriterContract
27
{
28
    /**
29
     * The number of frames if no verbosity is specified.
30
     */
31
    const VERBOSITY_NORMAL_FRAMES = 1;
32
33
    /**
34
     * Holds an instance of the Output.
35
     *
36
     * @var \Symfony\Component\Console\Output\OutputInterface
37
     */
38
    protected $output;
39
40
    /**
41
     * Holds an instance of the Argument Formatter.
42
     *
43
     * @var \NunoMaduro\Collision\Contracts\ArgumentFormatter
44
     */
45
    protected $argumentFormatter;
46
47
    /**
48
     * Ignores traces where the file string matches one
49
     * of the provided regex expressions.
50
     *
51
     * @var string[]
52
     */
53
    protected $ignore = [];
54
55
    /**
56
     * Declares whether or not the trace should appear.
57
     *
58
     * @var bool
59
     */
60
    protected $showTrace = true;
61
62
    /**
63
     * Declares whether or not the editor should appear.
64
     *
65
     * @var bool
66
     */
67
    protected $showEditor = true;
68
69
    /**
70
     * Creates an instance of the writer.
71
     *
72
     * @param \Symfony\Component\Console\Output\OutputInterface|null $output
73
     * @param \NunoMaduro\Collision\Contracts\ArgumentFormatter|null $argumentFormatter
74
     */
75 14
    public function __construct(OutputInterface $output = null, ArgumentFormatterContract $argumentFormatter = null)
76
    {
77 14
        $this->output = $output ?: new ConsoleOutput;
78 14
        $this->argumentFormatter = $argumentFormatter ?: new ArgumentFormatter;
79 14
    }
80
81
    /**
82
     * {@inheritdoc}
83
     */
84 5
    public function write(Inspector $inspector): void
85
    {
86 5
        $this->renderTitle($inspector);
87
88 5
        $frames = $this->getFrames($inspector);
89
90 5
        $editorFrame = array_shift($frames);
91 5
        if ($this->showEditor && $editorFrame !== null) {
92 4
            $this->renderEditor($editorFrame);
93
        }
94
95 5
        if ($this->showTrace && ! empty($frames)) {
96 4
            $this->renderTrace($frames);
97
        } else {
98 1
            $this->output->writeln('');
99
        }
100 5
    }
101
102
    /**
103
     * {@inheritdoc}
104
     */
105 1
    public function ignoreFilesIn(array $ignore): WriterContract
106
    {
107 1
        $this->ignore = $ignore;
108
109 1
        return $this;
110
    }
111
112
    /**
113
     * {@inheritdoc}
114
     */
115 1
    public function showTrace(bool $show): WriterContract
116
    {
117 1
        $this->showTrace = $show;
118
119 1
        return $this;
120
    }
121
122
    /**
123
     * {@inheritdoc}
124
     */
125 1
    public function showEditor(bool $show): WriterContract
126
    {
127 1
        $this->showEditor = $show;
128
129 1
        return $this;
130
    }
131
132
    /**
133
     * {@inheritdoc}
134
     */
135 2
    public function setOutput(OutputInterface $output): WriterContract
136
    {
137 2
        $this->output = $output;
138
139 2
        return $this;
140
    }
141
142
    /**
143
     * {@inheritdoc}
144
     */
145 2
    public function getOutput(): OutputInterface
146
    {
147 2
        return $this->output;
148
    }
149
150
    /**
151
     * Returns pertinent frames.
152
     *
153
     * @param  \Whoops\Exception\Inspector $inspector
154
     *
155
     * @return array
156
     */
157 5
    protected function getFrames(Inspector $inspector): array
158
    {
159 5
        return $inspector->getFrames()
160 5
            ->filter(
161 5
                function ($frame) {
162 5
                    foreach ($this->ignore as $ignore) {
163 1
                        if (preg_match($ignore, $frame->getFile())) {
164 1
                            return false;
165
                        }
166
                    }
167
168 5
                    return true;
169 5
                }
170
            )
171 5
            ->getArray();
172
    }
173
174
    /**
175
     * Renders the title of the exception.
176
     *
177
     * @param \Whoops\Exception\Inspector $inspector
178
     *
179
     * @return \NunoMaduro\Collision\Contracts\Writer
180
     */
181 5
    protected function renderTitle(Inspector $inspector): WriterContract
182
    {
183 5
        $exception = $inspector->getException();
184 5
        $message = $exception->getMessage();
185 5
        $class = $inspector->getExceptionName();
186
187 5
        $this->render("<bg=red;options=bold> $class </> : <comment>$message</>");
188
189 5
        return $this;
190
    }
191
192
    /**
193
     * Renders the editor containing the code that was the
194
     * origin of the exception.
195
     *
196
     * @param \Whoops\Exception\Frame $frame
197
     *
198
     * @return \NunoMaduro\Collision\Contracts\Writer
199
     */
200 4
    protected function renderEditor(Frame $frame): WriterContract
201
    {
202 4
        $this->render('at <fg=green>'.$frame->getFile().'</>'.': <fg=green>'.$frame->getLine().'</>');
203
204 4
        $range = $frame->getFileLines($frame->getLine() - 5, 10);
205
206 4
        if (! empty($range)) {
207 4
            foreach ($range as $k => $code) {
208 4
                $line = $k + 1;
209 4
                $code = $line === $frame->getLine() ? "<bg=red>$code</>" : $code;
210 4
                $this->render("$line: $code", false);
211
            }
212
        }
213
214 4
        return $this;
215
    }
216
217
    /**
218
     * Renders the trace of the exception.
219
     *
220
     * @param  array $frames
221
     *
222
     * @return \NunoMaduro\Collision\Contracts\Writer
223
     */
224 4
    protected function renderTrace(array $frames): WriterContract
225
    {
226 4
        $this->render('<comment>Exception trace:</comment>');
227 4
        foreach ($frames as $i => $frame) {
228 4
            if ($i > static::VERBOSITY_NORMAL_FRAMES && $this->output->getVerbosity(
229 4
                ) < OutputInterface::VERBOSITY_VERBOSE) {
230 3
                $this->render('<info>Please use the argument <fg=red>-v</> to see more details.</info>');
231 3
                break;
232
            }
233
234 4
            $file = $frame->getFile();
235 4
            $line = $frame->getLine();
236 4
            $class = empty($frame->getClass()) ? '' : $frame->getClass().'::';
237 4
            $function = $frame->getFunction();
238 4
            $args = $this->argumentFormatter->format($frame->getArgs());
239 4
            $pos = str_pad($i + 1, 4, ' ');
240
241 4
            $this->render("<comment><fg=cyan>$pos</>$class$function($args)</comment>");
242 4
            $this->render("    <fg=green>$file</> : <fg=green>$line</>", false);
243
        }
244
245 4
        return $this;
246
    }
247
248
    /**
249
     * Renders an message into the console.
250
     *
251
     * @param  string $message
252
     * @param  bool $break
253
     *
254
     * @return $this
255
     */
256 5
    protected function render(string $message, bool $break = true): WriterContract
257
    {
258 5
        if ($break) {
259 5
            $this->output->writeln('');
260
        }
261
262 5
        $this->output->writeln("  $message");
263
264 5
        return $this;
265
    }
266
}
267