Passed
Push — main ( 484639...0ca30d )
by Siad
05:19
created

DefaultLogger::throwableMessage()   B

Complexity

Conditions 10
Paths 24

Size

Total Lines 34
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 15.9914

Importance

Changes 0
Metric Value
eloc 25
dl 0
loc 34
ccs 14
cts 23
cp 0.6087
rs 7.6666
c 0
b 0
f 0
cc 10
nc 24
nop 3
crap 15.9914

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the LGPL. For more information please see
17
 * <http://phing.info>.
18
 */
19
20
namespace Phing\Listener;
21
22
use Phing\Exception\BuildException;
23
use Phing\Io\IOException;
24
use Phing\Io\OutputStream;
25
use Phing\Project;
26
use Phing\Util\Clock;
27
use Phing\Util\DefaultClock;
28
use Phing\Util\ProjectTimer;
29
use Phing\Util\ProjectTimerMap;
30
use Phing\Util\StringHelper;
31
use function end;
32
use function fmod;
33
use function intdiv;
34
use function vsprintf;
35
36
/**
37
 * Writes a build event to the console.
38
 *
39
 * Currently, it only writes which targets are being executed, and
40
 * any messages that get logged.
41
 *
42
 * @author    Andreas Aderhold <[email protected]>
43
 * @copyright 2001,2002 THYRELL. All rights reserved
44
 * @see       BuildEvent
45
 */
46
class DefaultLogger implements StreamRequiredBuildLogger
47
{
48
    /**
49
     *  Size of the left column in output. The default char width is 12.
50
     *
51
     * @var int
52
     */
53
    public const LEFT_COLUMN_SIZE = 12;
54
55
    /**
56
     * A day in seconds.
57
     */
58
    protected const A_DAY = 86400;
59
60
    /**
61
     * An hour in seconds.
62
     */
63
    protected const AN_HOUR = 3600;
64
65
    /**
66
     * A minute in seconds.
67
     */
68
    protected const A_MINUTE = 60;
69
70
    /**
71
     *  The message output level that should be used. The default is
72
     *  <code>Project::MSG_VERBOSE</code>.
73
     *
74
     * @var int
75
     */
76
    protected $msgOutputLevel = Project::MSG_ERR;
77
78
    /**
79
     *  Time that the build started
80
     *
81
     * @var int
82
     */
83
    protected $startTime;
84
85
    /**
86
     * @var OutputStream Stream to use for standard output.
87
     */
88
    protected $out;
89
90
    /**
91
     * @var OutputStream Stream to use for error output.
92
     */
93
    protected $err;
94
95
    protected $emacsMode = false;
96
    /**
97
     * @var Clock|DefaultClock
98
     */
99
    protected $clock;
100
    /**
101
     * @var ProjectTimerMap
102
     */
103
    protected $projectTimerMap;
104
105
    /**
106
     *  Construct a new default logger.
107
     */
108 5
    public function __construct(Clock $clock = null)
109
    {
110 5
        $this->projectTimerMap = new ProjectTimerMap();
111 5
        if ($clock === null) {
112 5
            $this->clock = new DefaultClock();
113
        } else {
114
            $this->clock = $clock;
115
        }
116 5
    }
117
118
    /**
119
     *  Set the msgOutputLevel this logger is to respond to.
120
     *
121
     *  Only messages with a message level lower than or equal to the given
122
     *  level are output to the log.
123
     *
124
     *  <p> Constants for the message levels are in Project.php. The order of
125
     *  the levels, from least to most verbose, is:
126
     *
127
     *  <ul>
128
     *    <li>Project::MSG_ERR</li>
129
     *    <li>Project::MSG_WARN</li>
130
     *    <li>Project::MSG_INFO</li>
131
     *    <li>Project::MSG_VERBOSE</li>
132
     *    <li>Project::MSG_DEBUG</li>
133
     *  </ul>
134
     *
135
     *  The default message level for DefaultLogger is Project::MSG_ERR.
136
     *
137
     * @param int $level The logging level for the logger.
138
     * @see   BuildLogger#setMessageOutputLevel()
139
     */
140 1
    public function setMessageOutputLevel($level)
141
    {
142 1
        $this->msgOutputLevel = (int) $level;
143 1
    }
144
145
    /**
146
     * Sets the output stream.
147
     *
148
     * @see   BuildLogger#setOutputStream()
149
     */
150 1
    public function setOutputStream(OutputStream $output)
151
    {
152 1
        $this->out = $output;
153 1
    }
154
155
    /**
156
     * Sets the error stream.
157
     *
158
     * @see   BuildLogger#setErrorStream()
159
     */
160 1
    public function setErrorStream(OutputStream $err)
161
    {
162 1
        $this->err = $err;
163 1
    }
164
165
    /**
166
     * Sets this logger to produce emacs (and other editor) friendly output.
167
     *
168
     * @param bool $emacsMode <code>true</code> if output is to be unadorned so that
169
     *                        emacs and other editors can parse files names, etc.
170
     */
171
    public function setEmacsMode($emacsMode)
172
    {
173
        $this->emacsMode = $emacsMode;
174
    }
175
176
    /**
177
     *  Sets the start-time when the build started. Used for calculating
178
     *  the build-time.
179
     *
180
     */
181
    public function buildStarted(BuildEvent $event)
182
    {
183
        $this->findInitialProjectTimer()->start();
184
        if ($this->msgOutputLevel >= Project::MSG_INFO) {
185
            $this->printMessage(
186
                "Buildfile: " . $event->getProject()->getProperty("phing.file"),
187
                $this->out,
188
                Project::MSG_INFO
189
            );
190
        }
191
    }
192
193
    /**
194
     *  Prints whether the build succeeded or failed, and any errors that
195
     *  occurred during the build. Also outputs the total build-time.
196
     *
197
     * @see   BuildEvent::getException()
198
     */
199 3
    public function buildFinished(BuildEvent $event)
200
    {
201 3
        $projectTimer = $this->findProjectTimer($event);
202 3
        $this->updateDurationWithInitialProjectTimer($projectTimer);
203 3
        $projectTimer->finish();
204 3
        $msg = PHP_EOL . $this->getBuildSuccessfulMessage() . PHP_EOL;
205 3
        $error = $event->getException();
206
207 3
        if ($error !== null) {
208 1
            $msg = PHP_EOL . $this->getBuildFailedMessage() . PHP_EOL;
209
210 1
            self::throwableMessage($msg, $error, Project::MSG_VERBOSE <= $this->msgOutputLevel);
211
        }
212 3
        $msg .= PHP_EOL . "Total time: "
213 3
            . static::formatTime($projectTimer->getTime()) . PHP_EOL;
214
215 3
        $error === null
216 2
            ? $this->printMessage($msg, $this->out, Project::MSG_VERBOSE)
217 1
            : $this->printMessage($msg, $this->err, Project::MSG_ERR);
218 3
    }
219
220 4
    public static function throwableMessage(&$msg, $error, $verbose)
221
    {
222 4
        while ($error instanceof BuildException) {
223 3
            $cause = $error->getPrevious();
224 3
            if ($cause === null) {
225 3
                break;
226
            }
227 2
            $msg1 = trim($error);
228 2
            $msg2 = trim($cause);
229 2
            if (StringHelper::endsWith($msg2, $msg1)) {
230 2
                $msg .= StringHelper::substring($msg1, 0, strlen($msg1) - strlen($msg2) - 1);
231 2
                $error = $cause;
232
            } else {
233
                break;
234
            }
235
        }
236
237 4
        if ($verbose) {
238
            if ($error instanceof BuildException) {
239
                $msg .= $error->getLocation() . PHP_EOL;
240
            }
241
            $msg .= '[' . get_class($error) . '] ' . $error->getMessage() . PHP_EOL
242
                . $error->getTraceAsString() . PHP_EOL;
243
        } else {
244 4
            $msg .= ($error instanceof BuildException ? $error->getLocation() . " " : "")
245 4
                . $error->getMessage() . PHP_EOL;
246
        }
247
248 4
        if ($error->getPrevious() && $verbose) {
249
            $error = $error->getPrevious();
250
            do {
251
                $msg .= '[Caused by ' . get_class($error) . '] ' . $error->getMessage() . PHP_EOL
252
                    . $error->getTraceAsString() . PHP_EOL;
253
            } while ($error = $error->getPrevious());
254
        }
255 4
    }
256
257
    /**
258
     * Get the message to return when a build failed.
259
     *
260
     * @return string The classic "BUILD FAILED"
261
     */
262 1
    protected function getBuildFailedMessage()
263
    {
264 1
        return "BUILD FAILED";
265
    }
266
267
    /**
268
     * Get the message to return when a build succeeded.
269
     *
270
     * @return string The classic "BUILD FINISHED"
271
     */
272 3
    protected function getBuildSuccessfulMessage()
273
    {
274 3
        return "BUILD FINISHED";
275
    }
276
277
    /**
278
     *  Prints the current target name
279
     *
280
     * @see   BuildEvent::getTarget()
281
     */
282 1
    public function targetStarted(BuildEvent $event)
283
    {
284
        if (
285 1
            Project::MSG_INFO <= $this->msgOutputLevel
286 1
            && $event->getTarget()->getName() != ''
287
        ) {
288 1
            $showLongTargets = $event->getProject()->getProperty("phing.showlongtargets");
289 1
            $msg = PHP_EOL . $event->getProject()->getName() . ' > ' . $event->getTarget()->getName()
290 1
                . ($showLongTargets ? ' [' . $event->getTarget()->getDescription() . ']' : '') . ':' . PHP_EOL;
291 1
            $this->printMessage($msg, $this->out, $event->getPriority());
292
        }
293 1
    }
294
295
    /**
296
     *  Fired when a target has finished. We don't need specific action on this
297
     *  event. So the methods are empty.
298
     *
299
     * @see   BuildEvent::getException()
300
     */
301 1
    public function targetFinished(BuildEvent $event)
302
    {
303 1
    }
304
305
    /**
306
     *  Fired when a task is started. We don't need specific action on this
307
     *  event. So the methods are empty.
308
     *
309
     * @see   BuildEvent::getTask()
310
     */
311 1
    public function taskStarted(BuildEvent $event)
312
    {
313 1
    }
314
315
    /**
316
     *  Fired when a task has finished. We don't need specific action on this
317
     *  event. So the methods are empty.
318
     *
319
     * @param BuildEvent $event The BuildEvent
320
     * @see   BuildEvent::getException()
321
     */
322 1
    public function taskFinished(BuildEvent $event)
323
    {
324 1
    }
325
326
    /**
327
     *  Print a message to the stdout.
328
     *
329
     * @see   BuildEvent::getMessage()
330
     */
331 2
    public function messageLogged(BuildEvent $event)
332
    {
333 2
        $priority = $event->getPriority();
334 2
        if ($priority <= $this->msgOutputLevel) {
335 2
            $msg = "";
336 2
            if ($event->getTask() !== null && !$this->emacsMode) {
337 1
                $name = $event->getTask();
338 1
                $name = $name->getTaskName();
339 1
                $msg = str_pad("[$name] ", self::LEFT_COLUMN_SIZE, " ", STR_PAD_LEFT);
340
            }
341
342 2
            $msg .= $event->getMessage();
343
344 2
            if ($priority != Project::MSG_ERR) {
345 2
                $this->printMessage($msg, $this->out, $priority);
346
            } else {
347
                $this->printMessage($msg, $this->err, $priority);
348
            }
349
        }
350 2
    }
351
352
    /**
353
     * Formats time (expressed in seconds) to a human readable format.
354
     *
355
     * @param float $seconds Time to convert, can have decimals.
356
     * @return string
357
     * @noinspection PhpMissingBreakStatementInspection
358
     */
359 21
    public static function formatTime(float $seconds): string
360
    {
361
        /** @var int|float $number */
362 21
        $getPlural = function ($number): string {
363 21
            return $number == 1 ? '' : 's';
364 21
        };
365 21
        $chunks    = [];
366 21
        $format    = '';
367 21
        $precision = 4;
368
        switch (true) {
369
            // Days
370 21
            case ($seconds >= self::A_DAY):
371 5
                $chunks[] = intdiv($seconds, self::A_DAY);
0 ignored issues
show
Bug introduced by
$seconds of type double is incompatible with the type integer expected by parameter $num1 of intdiv(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

371
                $chunks[] = intdiv(/** @scrutinizer ignore-type */ $seconds, self::A_DAY);
Loading history...
372 5
                $chunks[] = $getPlural(end($chunks));
373 5
                $seconds  = fmod($seconds, self::A_DAY);
374 5
                $format   .= '%u day%s  ';
375
            // Hours
376 16
            case ($seconds >= self::AN_HOUR):
377 10
                $chunks[] = intdiv($seconds, self::AN_HOUR);
378 10
                $chunks[] = $getPlural(end($chunks));
379 10
                $seconds  = fmod($seconds, self::AN_HOUR);
380 10
                $format   .= '%u hour%s  ';
381
            // Minutes
382 11
            case ($seconds >= self::A_MINUTE):
383 15
                $chunks[]  = intdiv($seconds, self::A_MINUTE);
384 15
                $chunks[]  = $getPlural(end($chunks));
385 15
                $seconds   = fmod($seconds, self::A_MINUTE);
386 15
                $format    .= '%u minute%s  ';
387 15
                $precision = 2;
388
            // Seconds
389
            default:
390 21
                $chunks[] = $seconds;
391 21
                $chunks[] = $getPlural(end($chunks));
392 21
                $format   .= "%.{$precision}F second%s";
393 21
                break;
394
        }
395
396 21
        return vsprintf($format, $chunks);
397
    }
398
399
    /**
400
     * Prints a message to console.
401
     *
402
     * @param  string                $message  The message to print.
403
     *                                         Should not be
404
     *                                         <code>null</code>.
405
     * @param  OutputStream|resource $stream   The stream to use for message printing.
406
     * @param  int                   $priority The priority of the message.
407
     *                                         (Ignored in this
408
     *                                         implementation.)
409
     * @throws IOException
410
     */
411 2
    protected function printMessage($message, OutputStream $stream, $priority)
0 ignored issues
show
Unused Code introduced by
The parameter $priority is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

411
    protected function printMessage($message, OutputStream $stream, /** @scrutinizer ignore-unused */ $priority)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
412
    {
413 2
        $stream->write($message . PHP_EOL);
414 2
    }
415
416 3
    private function findProjectTimer(BuildEvent $buildEvent)
417
    {
418 3
        $project = $buildEvent->getProject();
419 3
        return $this->projectTimerMap->find($project, $this->clock);
420
    }
421
422 3
    protected function findInitialProjectTimer()
423
    {
424 3
        return $this->projectTimerMap->find('', $this->clock);
425
    }
426
427 3
    private function updateDurationWithInitialProjectTimer(ProjectTimer $projectTimer)
428
    {
429 3
        $rootProjectTimer = $this->findInitialProjectTimer();
430 3
        $duration = $rootProjectTimer->getSeries()->current();
431 3
        $projectTimer->getSeries()->add($duration);
432 3
    }
433
}
434