Completed
Push — master ( 7db43c...e90e77 )
by Todd
01:54
created

LogFormatter::setOutputHandle()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 7
ccs 0
cts 5
cp 0
rs 9.4285
cc 2
eloc 5
nc 2
nop 1
crap 6
1
<?php
2
/**
3
 * @author Todd Burry <[email protected]>
4
 * @copyright 2009-2015 Vanilla Forums Inc.
5
 * @license MIT
6
 */
7
8
namespace Garden\Cli;
9
10
/**
11
 * A helper class to format CLI output in a log-style format.
12
 */
13
class LogFormatter {
14
    /**
15
     * @var string The date format as passed to {@link strftime()}.
16
     */
17
    protected $dateFormat = '[%F %T]';
18
19
    /**
20
     * @var string The end of line string to use.
21
     */
22
    protected $eol = PHP_EOL;
23
24
    /**
25
     * @var bool Whether or not to format output.
26
     */
27
    protected $formatOutput;
28
29
    /**
30
     * @var resource The output file handle.
31
     */
32
    protected $outputHandle;
33
34
    /**
35
     * @var bool Whether or not the console is on a new line.
36
     */
37
    protected $isNewline = true;
38
39
    /**
40
     * @var int The maximum level deep to output.
41
     */
42
    protected $maxLevel = 2;
43
44
    /**
45
     * @var bool Whether or not to show durations for tasks.
46
     */
47
    protected $showDurations = true;
48
49
    /**
50
     * @var array An array of currently running tasks.
51
     */
52
    protected $taskStack = [];
53
54
    /**
55
     * LogFormatter constructor.
56
     */
57 13
    public function __construct() {
58 13
        $this->formatOutput = Cli::guessFormatOutput();
59 13
        $this->outputHandle = fopen('php://output', 'w');
60 13
    }
61
62
    /**
63
     * Output an error message.
64
     *
65
     * When formatting is turned on, error messages are displayed in red. Error messages are always output, even if they
66
     * are past the maximum display level.
67
     *
68
     * @param string $str The message to output.
69
     * @return LogFormatter Returns `$this` for fluent calls.
70
     */
71 3
    public function error($str) {
72 3
        return $this->message($this->formatString($str, ["\033[1;31m", "\033[0m"]), true);
73
    }
74
75
    /**
76
     * Output a success message.
77
     *
78
     * When formatting is turned on, success messages are displayed in green.
79
     *
80
     * @param string $str The message to output.
81
     * @return LogFormatter Returns `$this` for fluent calls.
82
     */
83 1
    public function success($str) {
84 1
        return $this->message($this->formatString($str, ["\033[1;32m", "\033[0m"]));
85
    }
86
87
    /**
88
     * Get the current depth of tasks.
89
     *
90
     * @return int Returns the current level.
91
     */
92 11
    protected function currentLevel() {
93 11
        return count($this->taskStack) + 1;
94
    }
95
96
    /**
97
     * Output a message that designates the beginning of a task.
98
     *
99
     * @param string $str The message to output.
100
     * @return $this Returns `$this` for fluent calls.
101
     */
102 8
    public function begin($str) {
103 8
        $output = $this->currentLevel() <= $this->getMaxLevel();
104 8
        $task = [$str, microtime(true), $output];
105
106 8
        if ($output) {
107 8
            if (!$this->isNewline) {
108 2
                $this->write($this->getEol());
109 2
                $this->isNewline = true;
110 2
            }
111
112 8
            $this->write($this->messageStr($str, false));
113 8
            $this->isNewline = false;
114 8
        }
115
116 8
        array_push($this->taskStack, $task);
117
118 8
        return $this;
119
    }
120
121
    /**
122
     * Output a message that designates a task being completed.
123
     *
124
     * @param string $str The message to output.
125
     * @param bool $force Whether or not to always output the message even if the task is past the max depth.
126
     * @return $this Returns `$this` for fluent calls.
127
     */
128 8
    public function end($str = '', $force = false) {
129
        // Find the task we are finishing.
130 8
        $task = array_pop($this->taskStack);
131 8
        if ($task !== null) {
132 8
            list($taskStr, $taskTimestamp, $taskOutput) = $task;
133 8
            $timespan = microtime(true) - $taskTimestamp;
134 8
        } else {
135 1
            trigger_error('Called LogFormatter::end() without calling LogFormatter::begin()', E_USER_NOTICE);
136
        }
137
138 8
        $pastMaxLevel = $this->currentLevel() > $this->getMaxLevel();
139 8
        if ($pastMaxLevel) {
140 3
            if ($force && isset($taskStr) && isset($taskOutput)) {
141 1
                if (!$taskOutput) {
142
                    // Output the full task string if it hasn't already been output.
143 1
                    $str = trim($taskStr.' '.$str);
144 1
                }
145 1
                if (!$this->isNewline) {
146 1
                    $this->write($this->getEol());
147 1
                    $this->isNewline = true;
148 1
                }
149 1
            } else {
150 2
                return $this;
151
            }
152 1
        }
153
154 8
        if (!empty($timespan) && $this->getShowDurations()) {
155
            $str = trim($str.' '.$this->formatString($this->formatDuration($timespan), ["\033[1;34m", "\033[0m"]));
156
        }
157
158 8
        if ($this->isNewline) {
159
            // Output the end message as a normal message.
160 6
            $this->message($str, $force);
161 6
        } else {
162
            // Output the end message on the same line.
163 4
            $this->write(' '.$str.$this->getEol());
164 4
            $this->isNewline = true;
165
        }
166
167 8
        return $this;
168
    }
169
170
    /**
171
     * Output a message that represents a task being completed in success.
172
     *
173
     * When formatting is turned on, success messages are output in green.
174
     *
175
     * @param string $str The message to output.
176
     * @param bool $force Whether or not to force a message past the max level to be output.
177
     * @return LogFormatter Returns `$this` for fluent calls.
178
     */
179
    public function endSuccess($str, $force = false) {
180
        return $this->end($this->formatString($str, ["\033[1;32m", "\033[0m"]), $force);
181
    }
182
183
    /**
184
     * Output a message that represents a task being completed in an error.
185
     *
186
     * When formatting is turned on, error messages are output in red. Error messages are always output even if they are
187
     * past the maximum depth.
188
     *
189
     * @param string $str The message to output.
190
     * @return LogFormatter Returns `$this` for fluent calls.
191
     */
192 1
    public function endError($str) {
193 1
        return $this->end($this->formatString($str, ["\033[1;31m", "\033[0m"]), true);
194
    }
195
196
    /**
197
     * Output a message that ends a task with an HTTP status code.
198
     *
199
     * This method is useful if you are making a call to an external API as a task. You can end the task by passing the
200
     * response code to this message.
201
     *
202
     * @param int $httpStatus The HTTP status code that represents the completion of a task.
203
     * @param bool $force Whether or not to force message output.
204
     * @return $this Returns `$this` for fluent calls.
205
     * @see LogFormatter::endSuccess(), LogFormatter::endError().
206
     */
207
    public function endHttpStatus($httpStatus, $force = false) {
208
        $statusStr = sprintf('%03d', $httpStatus);
209
210
        if ($httpStatus == 0 || $httpStatus >= 400) {
211
            $this->endError($statusStr);
212
        } elseif ($httpStatus >= 200 && $httpStatus < 300) {
213
            $this->endSuccess($statusStr, $force);
214
        } else {
215
            $this->end($statusStr, $force);
216
        }
217
218
        return $this;
219
    }
220
221
    /**
222
     * Format a time duration.
223
     *
224
     * @param float $duration The duration in seconds and fractions of a second.
225
     * @return string Returns the duration formatted for humans.
226
     * @see microtime()
227
     */
228 1
    public function formatDuration($duration) {
229 1
        $suffixes = [0.000001 => 'μs', 1 => 'ms', 60 => 's', 3600 => 'm'];
0 ignored issues
show
Unused Code introduced by
$suffixes is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
230
231 1
        if ($duration < 1.0e-3) {
232 1
            $n = number_format($duration * 1.0e6, 0);
233 1
            $sx = 'μs';
234 1
        } elseif ($duration < 1) {
235 1
            $n = number_format($duration * 1000, 0);
236 1
            $sx = 'ms';
237 1
        } elseif ($duration < 60) {
238 1
            $n = number_format($duration, 2);
239 1
            $sx = 's';
240 1
        } elseif ($duration < 3600) {
241 1
            $n = number_format($duration / 60, 1);
242 1
            $sx = 'm';
243 1
        } elseif ($duration < 86400) {
244 1
            $n = number_format($duration / 3600, 1);
245 1
            $sx = 'h';
246 1
        } else {
247 1
            $n = number_format($duration / 86400, 1);
248 1
            $sx = 'd';
249
        }
250
251 1
        $result = rtrim($n, '0.').$sx;
252 1
        return $result;
253
    }
254
255
    /**
256
     * Output a message.
257
     *
258
     * @param string $str The message to output.
259
     * @param bool $force Whether or not to force output of the message even if it's past the max depth.
260
     * @return $this Returns `$this` for fluent calls.
261
     */
262 10
    public function message($str, $force = false) {
263 10
        $pastMaxLevel = $this->currentLevel() > $this->getMaxLevel();
264
265 10
        if ($pastMaxLevel) {
266 5
            if ($force) {
267
                // Trace down the task list and output everything that hasn't already been output.
268 3
                foreach ($this->taskStack as $indent => $task) {
269 3
                    list($taskStr, $taskTimestamp, $taskOutput) = $this->taskStack[$indent];
270 3
                    if (!$taskOutput) {
271 1
                        if (!$this->isNewline) {
272 1
                            $this->write($this->eol);
273 1
                            $this->isNewline = true;
274 1
                        }
275 1
                        $this->write($this->fullMessageStr($taskTimestamp, $taskStr, $indent, true));
276 1
                        $this->taskStack[$indent][2] = true;
277 1
                    } else {
278 3
                        continue;
279
                    }
280 3
                }
281 3
            } else {
282 5
                return $this;
283
            }
284 3
        }
285
286 9
        if (!$this->isNewline) {
287 2
            $this->write($this->eol);
288 2
            $this->isNewline = true;
289 2
        }
290 9
        $this->write($this->messageStr($str, true));
291 9
        return $this;
292
    }
293
294
    /**
295
     * Get whether or not output should be formatted.
296
     *
297
     * @return boolean Returns **true** if output should be formatted or **false** otherwise.
298
     */
299
    public function getFormatOutput() {
300
        return $this->formatOutput;
301
    }
302
303
    /**
304
     * Set whether or not output should be formatted.
305
     *
306
     * @param boolean $formatOutput Whether or not to format output.
307
     * @return LogFormatter Returns `$this` for fluent calls.
308
     */
309 11
    public function setFormatOutput($formatOutput) {
310 11
        $this->formatOutput = $formatOutput;
311 11
        return $this;
312
    }
313
314 11
    protected function fullMessageStr($timestamp, $str, $indent = null, $eol = true) {
315 11
        if ($indent === null) {
316 11
            $indent = $this->currentLevel() - 1;
317 11
        }
318
319 11
        if ($indent <= 0) {
320 11
            $indentStr = '';
321 11
        } elseif ($indent === 1) {
322 5
            $indentStr = '- ';
323 5
        } else {
324 2
            $indentStr = str_repeat('  ', $indent - 1).'- ';
325
        }
326
327 11
        $result = $indentStr.$str;
328
329 11
        if ($this->getDateFormat()) {
330 9
            $result = strftime($this->getDateFormat(), $timestamp).' '.$result;
331 9
        }
332
333 11
        if ($eol) {
334 9
            $result .= $this->eol;
335 9
        }
336 11
        return $result;
337
    }
338
339
    /**
340
     * Create a message string.
341
     *
342
     * @param string $str The message to output.
343
     * @param bool $eol Whether or not to add an EOL.
344
     * @return string Returns the message.
345
     */
346 11
    protected function messageStr($str, $eol = true) {
347 11
        return $this->fullMessageStr(time(), $str, null, $eol);
348
    }
349
350
    /**
351
     * Format some text for the console.
352
     *
353
     * @param string $text The text to format.
354
     * @param string[] $wrap The format to wrap in the form ['before', 'after'].
355
     * @return string Returns the string formatted according to {@link Cli::$format}.
356
     */
357 4
    protected function formatString($text, array $wrap) {
358 4
        if ($this->formatOutput) {
359 1
            return "{$wrap[0]}$text{$wrap[1]}";
360
        } else {
361 3
            return $text;
362
        }
363
    }
364
365
    /**
366
     * Get the maxLevel.
367
     *
368
     * @return int Returns the maxLevel.
369
     */
370 11
    public function getMaxLevel() {
371 11
        return $this->maxLevel;
372
    }
373
374
    /**
375
     * @param int $maxLevel
376
     * @return LogFormatter
377
     */
378 12
    public function setMaxLevel($maxLevel) {
379 12
        if ($maxLevel < 0) {
380 1
            throw new \InvalidArgumentException("The max level must be greater than zero.", 416);
381
        }
382
383 11
        $this->maxLevel = $maxLevel;
384 11
        return $this;
385
    }
386
387
    /**
388
     * Get the date format as passed to {@link strftime()}.
389
     *
390
     * @return string Returns the date format.
391
     * @see strftime()
392
     */
393 11
    public function getDateFormat() {
394 11
        return $this->dateFormat;
395
    }
396
397
    /**
398
     * Set the date format as passed to {@link strftime()}.
399
     *
400
     * @param string $dateFormat
401
     * @return LogFormatter Returns `$this` for fluent calls.
402
     * @see strftime()
403
     */
404 11
    public function setDateFormat($dateFormat) {
405 11
        $this->dateFormat = $dateFormat;
406 11
        return $this;
407
    }
408
409
    /**
410
     * Get the end of line string to use.
411
     *
412
     * @return string Returns the eol string.
413
     */
414 6
    public function getEol() {
415 6
        return $this->eol;
416
    }
417
418
    /**
419
     * Set the end of line string.
420
     *
421
     * @param string $eol The end of line string to use.
422
     * @return LogFormatter Returns `$this` for fluent calls.
423
     */
424 11
    public function setEol($eol) {
425 11
        $this->eol = $eol;
426 11
        return $this;
427
    }
428
429
    /**
430
     * Get the showDurations.
431
     *
432
     * @return boolean Returns the showDurations.
433
     */
434 8
    public function getShowDurations() {
435 8
        return $this->showDurations;
436
    }
437
438
    /**
439
     * Set the showDurations.
440
     *
441
     * @param boolean $showDurations
442
     * @return LogFormatter Returns `$this` for fluent calls.
443
     */
444 11
    public function setShowDurations($showDurations) {
445 11
        $this->showDurations = $showDurations;
446 11
        return $this;
447
    }
448
449
    /**
450
     * Set the output file handle.
451
     *
452
     * @param resource $handle
453
     * @return LogFormatter Returns `$this` for fluent calls.
454
     */
455
    public function setOutputHandle($handle) {
456
        if (feof($handle)) {
457
            throw new \InvalidArgumentException("The provided file handle must be open.", 416);
458
        }
459
        $this->outputHandle = $handle;
460
        return $this;
461
    }
462
463
    /**
464
     * Write a string to the CLI.
465
     *
466
     * This method is intended to centralize the echoing of output in case the class is subclassed and the behaviour
467
     * needs to change.
468
     *
469
     * @param string $str The string to write.
470
     */
471 11
    public function write($str) {
472 11
        if (feof($this->outputHandle)) {
473
            trigger_error('Called LogFormatter::write() but file handle was closed.', E_USER_WARNING);
474
            return;
475
        }
476 11
        fwrite($this->outputHandle, $str);
477 11
    }
478
}
479