Completed
Push — master ( 84ee08...7db43c )
by Todd
02:25
created

LogFormatter::write()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 3
ccs 3
cts 3
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 1
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 bool Whether or not the console is on a new line.
31
     */
32
    protected $isNewline = true;
33
34
    /**
35
     * @var int The maximum level deep to output.
36
     */
37
    protected $maxLevel = 2;
38
39
    /**
40
     * @var bool Whether or not to show durations for tasks.
41
     */
42
    protected $showDurations = true;
43
44
    /**
45
     * @var array An array of currently running tasks.
46
     */
47
    protected $taskStack = [];
48
49
    /**
50
     * LogFormatter constructor.
51
     */
52 13
    public function __construct() {
53 13
        $this->formatOutput = Cli::guessFormatOutput();
54 13
    }
55
56
    /**
57
     * Output an error message.
58
     *
59
     * When formatting is turned on, error messages are displayed in red. Error messages are always output, even if they
60
     * are past the maximum display level.
61
     *
62
     * @param string $str The message to output.
63
     * @return LogFormatter Returns `$this` for fluent calls.
64
     */
65 3
    public function error($str) {
66 3
        return $this->message($this->formatString($str, ["\033[1;31m", "\033[0m"]), true);
67
    }
68
69
    /**
70
     * Output a success message.
71
     *
72
     * When formatting is turned on, success messages are displayed in green.
73
     *
74
     * @param string $str The message to output.
75
     * @return LogFormatter Returns `$this` for fluent calls.
76
     */
77 1
    public function success($str) {
78 1
        return $this->message($this->formatString($str, ["\033[1;32m", "\033[0m"]));
79
    }
80
81
    /**
82
     * Get the current depth of tasks.
83
     *
84
     * @return int Returns the current level.
85
     */
86 11
    protected function currentLevel() {
87 11
        return count($this->taskStack) + 1;
88
    }
89
90
    /**
91
     * Output a message that designates the beginning of a task.
92
     *
93
     * @param string $str The message to output.
94
     * @return $this Returns `$this` for fluent calls.
95
     */
96 8
    public function begin($str) {
97 8
        $output = $this->currentLevel() <= $this->getMaxLevel();
98 8
        $task = [$str, microtime(true), $output];
99
100 8
        if ($output) {
101 8
            if (!$this->isNewline) {
102 2
                $this->write($this->getEol());
103 2
                $this->isNewline = true;
104 2
            }
105
106 8
            $this->write($this->messageStr($str, false));
107 8
            $this->isNewline = false;
108 8
        }
109
110 8
        array_push($this->taskStack, $task);
111
112 8
        return $this;
113
    }
114
115
    /**
116
     * Output a message that designates a task being completed.
117
     *
118
     * @param string $str The message to output.
119
     * @param bool $force Whether or not to always output the message even if the task is past the max depth.
120
     * @return $this Returns `$this` for fluent calls.
121
     */
122 8
    public function end($str = '', $force = false) {
123
        // Find the task we are finishing.
124 8
        $task = array_pop($this->taskStack);
125 8
        if ($task !== null) {
126 8
            list($taskStr, $taskTimestamp, $taskOutput) = $task;
127 8
            $timespan = microtime(true) - $taskTimestamp;
128 8
        } else {
129 1
            trigger_error('Called LogFormatter::end() without calling LogFormatter::begin()', E_USER_NOTICE);
130
        }
131
132 8
        $pastMaxLevel = $this->currentLevel() > $this->getMaxLevel();
133 8
        if ($pastMaxLevel) {
134 3
            if ($force && isset($taskStr) && isset($taskOutput)) {
135 1
                if (!$taskOutput) {
136
                    // Output the full task string if it hasn't already been output.
137 1
                    $str = trim($taskStr.' '.$str);
138 1
                }
139 1
                if (!$this->isNewline) {
140 1
                    $this->write($this->getEol());
141 1
                    $this->isNewline = true;
142 1
                }
143 1
            } else {
144 2
                return $this;
145
            }
146 1
        }
147
148 8
        if (!empty($timespan) && $this->getShowDurations()) {
149
            $str = trim($str.' '.$this->formatString($this->formatDuration($timespan), ["\033[1;34m", "\033[0m"]));
150
        }
151
152 8
        if ($this->isNewline) {
153
            // Output the end message as a normal message.
154 6
            $this->message($str, $force);
155 6
        } else {
156
            // Output the end message on the same line.
157 4
            $this->write(' '.$str.$this->getEol());
158 4
            $this->isNewline = true;
159
        }
160
161 8
        return $this;
162
    }
163
164
    /**
165
     * Output a message that represents a task being completed in success.
166
     *
167
     * When formatting is turned on, success messages are output in green.
168
     *
169
     * @param string $str The message to output.
170
     * @param bool $force Whether or not to force a message past the max level to be output.
171
     * @return LogFormatter Returns `$this` for fluent calls.
172
     */
173
    public function endSuccess($str, $force = false) {
174
        return $this->end($this->formatString($str, ["\033[1;32m", "\033[0m"]), $force);
175
    }
176
177
    /**
178
     * Output a message that represents a task being completed in an error.
179
     *
180
     * When formatting is turned on, error messages are output in red. Error messages are always output even if they are
181
     * past the maximum depth.
182
     *
183
     * @param string $str The message to output.
184
     * @return LogFormatter Returns `$this` for fluent calls.
185
     */
186 1
    public function endError($str) {
187 1
        return $this->end($this->formatString($str, ["\033[1;31m", "\033[0m"]), true);
188
    }
189
190
    /**
191
     * Output a message that ends a task with an HTTP status code.
192
     *
193
     * 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
194
     * response code to this message.
195
     *
196
     * @param int $httpStatus The HTTP status code that represents the completion of a task.
197
     * @param bool $force Whether or not to force message output.
198
     * @return $this Returns `$this` for fluent calls.
199
     * @see LogFormatter::endSuccess(), LogFormatter::endError().
200
     */
201
    public function endHttpStatus($httpStatus, $force = false) {
202
        $statusStr = sprintf('%03d', $httpStatus);
203
204
        if ($httpStatus == 0 || $httpStatus >= 400) {
205
            $this->endError($statusStr);
206
        } elseif ($httpStatus >= 200 && $httpStatus < 300) {
207
            $this->endSuccess($statusStr, $force);
208
        } else {
209
            $this->end($statusStr, $force);
210
        }
211
212
        return $this;
213
    }
214
215
    /**
216
     * Format a time duration.
217
     *
218
     * @param float $duration The duration in seconds and fractions of a second.
219
     * @return string Returns the duration formatted for humans.
220
     * @see microtime()
221
     */
222 1
    public function formatDuration($duration) {
223 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...
224
225 1
        if ($duration < 1.0e-3) {
226 1
            $n = number_format($duration * 1.0e6, 0);
227 1
            $sx = 'μs';
228 1
        } elseif ($duration < 1) {
229 1
            $n = number_format($duration * 1000, 0);
230 1
            $sx = 'ms';
231 1
        } elseif ($duration < 60) {
232 1
            $n = number_format($duration, 2);
233 1
            $sx = 's';
234 1
        } elseif ($duration < 3600) {
235 1
            $n = number_format($duration / 60, 1);
236 1
            $sx = 'm';
237 1
        } elseif ($duration < 86400) {
238 1
            $n = number_format($duration / 3600, 1);
239 1
            $sx = 'h';
240 1
        } else {
241 1
            $n = number_format($duration / 86400, 1);
242 1
            $sx = 'd';
243
        }
244
245 1
        $result = rtrim($n, '0.').$sx;
246 1
        return $result;
247
    }
248
249
    /**
250
     * Output a message.
251
     *
252
     * @param string $str The message to output.
253
     * @param bool $force Whether or not to force output of the message even if it's past the max depth.
254
     * @return $this Returns `$this` for fluent calls.
255
     */
256 10
    public function message($str, $force = false) {
257 10
        $pastMaxLevel = $this->currentLevel() > $this->getMaxLevel();
258
259 10
        if ($pastMaxLevel) {
260 5
            if ($force) {
261
                // Trace down the task list and output everything that hasn't already been output.
262 3
                foreach ($this->taskStack as $indent => $task) {
263 3
                    list($taskStr, $taskTimestamp, $taskOutput) = $this->taskStack[$indent];
264 3
                    if (!$taskOutput) {
265 1
                        if (!$this->isNewline) {
266 1
                            $this->write($this->eol);
267 1
                            $this->isNewline = true;
268 1
                        }
269 1
                        $this->write($this->fullMessageStr($taskTimestamp, $taskStr, $indent, true));
270 1
                        $this->taskStack[$indent][2] = true;
271 1
                    } else {
272 3
                        continue;
273
                    }
274 3
                }
275 3
            } else {
276 5
                return $this;
277
            }
278 3
        }
279
280 9
        if (!$this->isNewline) {
281 2
            $this->write($this->eol);
282 2
            $this->isNewline = true;
283 2
        }
284 9
        $this->write($this->messageStr($str, true));
285 9
        return $this;
286
    }
287
288
    /**
289
     * Get whether or not output should be formatted.
290
     *
291
     * @return boolean Returns **true** if output should be formatted or **false** otherwise.
292
     */
293
    public function getFormatOutput() {
294
        return $this->formatOutput;
295
    }
296
297
    /**
298
     * Set whether or not output should be formatted.
299
     *
300
     * @param boolean $formatOutput Whether or not to format output.
301
     * @return LogFormatter Returns `$this` for fluent calls.
302
     */
303 11
    public function setFormatOutput($formatOutput) {
304 11
        $this->formatOutput = $formatOutput;
305 11
        return $this;
306
    }
307
308 11
    protected function fullMessageStr($timestamp, $str, $indent = null, $eol = true) {
309 11
        if ($indent === null) {
310 11
            $indent = $this->currentLevel() - 1;
311 11
        }
312
313 11
        if ($indent <= 0) {
314 11
            $indentStr = '';
315 11
        } elseif ($indent === 1) {
316 5
            $indentStr = '- ';
317 5
        } else {
318 2
            $indentStr = str_repeat('  ', $indent - 1).'- ';
319
        }
320
321 11
        $result = $indentStr.$str;
322
323 11
        if ($this->getDateFormat()) {
324 9
            $result = strftime($this->getDateFormat(), $timestamp).' '.$result;
325 9
        }
326
327 11
        if ($eol) {
328 9
            $result .= $this->eol;
329 9
        }
330 11
        return $result;
331
    }
332
333
    /**
334
     * Create a message string.
335
     *
336
     * @param string $str The message to output.
337
     * @param bool $eol Whether or not to add an EOL.
338
     * @return string Returns the message.
339
     */
340 11
    protected function messageStr($str, $eol = true) {
341 11
        return $this->fullMessageStr(time(), $str, null, $eol);
342
    }
343
344
    /**
345
     * Format some text for the console.
346
     *
347
     * @param string $text The text to format.
348
     * @param string[] $wrap The format to wrap in the form ['before', 'after'].
349
     * @return string Returns the string formatted according to {@link Cli::$format}.
350
     */
351 4
    protected function formatString($text, array $wrap) {
352 4
        if ($this->formatOutput) {
353 1
            return "{$wrap[0]}$text{$wrap[1]}";
354
        } else {
355 3
            return $text;
356
        }
357
    }
358
359
    /**
360
     * Get the maxLevel.
361
     *
362
     * @return int Returns the maxLevel.
363
     */
364 11
    public function getMaxLevel() {
365 11
        return $this->maxLevel;
366
    }
367
368
    /**
369
     * @param int $maxLevel
370
     * @return LogFormatter
371
     */
372 12
    public function setMaxLevel($maxLevel) {
373 12
        if ($maxLevel < 0) {
374 1
            throw new \InvalidArgumentException("The max level must be greater than zero.", 416);
375
        }
376
377 11
        $this->maxLevel = $maxLevel;
378 11
        return $this;
379
    }
380
381
    /**
382
     * Get the date format as passed to {@link strftime()}.
383
     *
384
     * @return string Returns the date format.
385
     * @see strftime()
386
     */
387 11
    public function getDateFormat() {
388 11
        return $this->dateFormat;
389
    }
390
391
    /**
392
     * Set the date format as passed to {@link strftime()}.
393
     *
394
     * @param string $dateFormat
395
     * @return LogFormatter Returns `$this` for fluent calls.
396
     * @see strftime()
397
     */
398 11
    public function setDateFormat($dateFormat) {
399 11
        $this->dateFormat = $dateFormat;
400 11
        return $this;
401
    }
402
403
    /**
404
     * Get the end of line string to use.
405
     *
406
     * @return string Returns the eol string.
407
     */
408 6
    public function getEol() {
409 6
        return $this->eol;
410
    }
411
412
    /**
413
     * Set the end of line string.
414
     *
415
     * @param string $eol The end of line string to use.
416
     * @return LogFormatter Returns `$this` for fluent calls.
417
     */
418 11
    public function setEol($eol) {
419 11
        $this->eol = $eol;
420 11
        return $this;
421
    }
422
423
    /**
424
     * Get the showDurations.
425
     *
426
     * @return boolean Returns the showDurations.
427
     */
428 8
    public function getShowDurations() {
429 8
        return $this->showDurations;
430
    }
431
432
    /**
433
     * Set the showDurations.
434
     *
435
     * @param boolean $showDurations
436
     * @return LogFormatter Returns `$this` for fluent calls.
437
     */
438 11
    public function setShowDurations($showDurations) {
439 11
        $this->showDurations = $showDurations;
440 11
        return $this;
441
    }
442
443
    /**
444
     * Write a string to the CLI.
445
     *
446
     * This method is intended to centralize the echoing of output in case the class is subclassed and the behaviour
447
     * needs to change.
448
     *
449
     * @param string $str The string to write.
450
     */
451 11
    public function write($str) {
452 11
        echo $str;
453 11
    }
454
}
455